# HG changeset patch
# User paulb
# Date 1276038771 25200
# Node ID dd7d3426144c5598686b8e8cf6d86bdb6708c575
# Parent 1f666f57e7776fd4b34326ae2ac72134cf83d81a# Parent 8e561bc07a21b26593c3743fc1480703900de4ee
merged heads
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/plugin.properties
--- a/org.chromium.debug.core/plugin.properties Tue Jun 08 16:06:40 2010 -0700
+++ b/org.chromium.debug.core/plugin.properties Tue Jun 08 16:12:51 2010 -0700
@@ -4,4 +4,7 @@
providerName = The Chromium Authors
-pluginName = Chromium JavaScript Remote Debugger Core
\ No newline at end of file
+pluginName = Chromium JavaScript Remote Debugger Core
+
+SourceNameMapperContainer.name = JavaScript Source Name Mapper
+SourceNameMapperContainer.description = Transforms name of source and delegates lookup to another source container.
\ No newline at end of file
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/plugin.xml
--- a/org.chromium.debug.core/plugin.xml Tue Jun 08 16:06:40 2010 -0700
+++ b/org.chromium.debug.core/plugin.xml Tue Jun 08 16:12:51 2010 -0700
@@ -41,4 +41,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java
--- a/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java Tue Jun 08 16:06:40 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumDebugPlugin.java Tue Jun 08 16:12:51 2010 -0700
@@ -6,6 +6,7 @@
import java.text.MessageFormat;
+import org.chromium.debug.core.model.BreakpointMap;
import org.chromium.debug.core.model.ChromiumBreakpointWBAFactory;
import org.chromium.debug.core.model.ChromiumLineBreakpoint;
import org.eclipse.core.runtime.CoreException;
@@ -30,6 +31,8 @@
/** The shared instance. */
private static ChromiumDebugPlugin plugin;
+ private final BreakpointMap breakpointMap = new BreakpointMap();
+
private ChromiumBreakpointWBAFactory breakpointWorkbenchAdapterFactory;
public ChromiumDebugPlugin() {
@@ -52,6 +55,10 @@
super.stop(context);
}
+ public BreakpointMap getBreakpointMap() {
+ return breakpointMap;
+ }
+
/**
* @return the shared instance
*/
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceComputer.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceComputer.java Tue Jun 08 16:12:51 2010 -0700
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourcePathComputerDelegate;
+
+/**
+ * A source path computer implementation that provides {@link VProjectSourceContainer} as
+ * a default source files container for V8/Chrome debug sessions.
+ */
+public class ChromiumSourceComputer implements ISourcePathComputerDelegate {
+ public ISourceContainer[] computeSourceContainers(ILaunchConfiguration configuration,
+ IProgressMonitor monitor) throws CoreException {
+ return new ISourceContainer[] { new VProjectSourceContainer() };
+ }
+}
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceDirector.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/ChromiumSourceDirector.java Tue Jun 08 16:12:51 2010 -0700
@@ -0,0 +1,68 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core;
+
+import org.chromium.debug.core.model.ResourceManager;
+import org.chromium.debug.core.model.StackFrame;
+import org.chromium.debug.core.model.VmResourceId;
+import org.chromium.sdk.Breakpoint;
+import org.chromium.sdk.Script;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupDirector;
+import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupParticipant;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant;
+
+/**
+ * A source lookup director implementation that provides a simple participant and
+ * accepts instance of virtual project once it is created.
+ */
+public class ChromiumSourceDirector extends AbstractSourceLookupDirector {
+ private volatile ResourceManager resourceManager = null;
+ private volatile IProject project = null;
+ private volatile ReverseSourceLookup reverseSourceLookup = null;
+
+
+ public void initializeParticipants() {
+ ISourceLookupParticipant participant = new AbstractSourceLookupParticipant() {
+ public String getSourceName(Object object) throws CoreException {
+ Script script = null;
+ if (object instanceof Script) {
+ script = (Script) object;
+ } else if (object instanceof StackFrame) {
+ StackFrame jsStackFrame = (StackFrame) object;
+ script = jsStackFrame.getCallFrame().getScript();
+ } else if (object instanceof Breakpoint) {
+ Breakpoint breakpoint = (Breakpoint) object;
+ return breakpoint.getScriptName();
+ }
+ if (script == null) {
+ return null;
+ }
+
+ return VmResourceId.forScript(script).getEclipseSourceName();
+ }
+ };
+ addParticipants(new ISourceLookupParticipant[] { participant } );
+ }
+
+ public void initializeVProjectContainers(IProject project, ResourceManager resourceManager) {
+ this.resourceManager = resourceManager;
+ this.project = project;
+ this.reverseSourceLookup = new ReverseSourceLookup(this);
+ }
+
+ public ReverseSourceLookup getReverseSourceLookup() {
+ return reverseSourceLookup;
+ }
+
+ public ResourceManager getResourceManager() {
+ return resourceManager;
+ }
+
+ IProject getProject() {
+ return project;
+ }
+}
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/Messages.java
--- a/org.chromium.debug.core/src/org/chromium/debug/core/Messages.java Tue Jun 08 16:06:40 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/Messages.java Tue Jun 08 16:12:51 2010 -0700
@@ -15,6 +15,10 @@
public static String ChromiumDebugPlugin_InternalError;
+ public static String SourceNameMapperContainer_NAME;
+
+ public static String VProjectSourceContainer_DEFAULT_TYPE_NAME;
+
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/ReverseSourceLookup.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/ReverseSourceLookup.java Tue Jun 08 16:12:51 2010 -0700
@@ -0,0 +1,100 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core;
+
+import org.chromium.debug.core.model.VmResourceId;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+import org.eclipse.debug.core.sourcelookup.containers.ContainerSourceContainer;
+import org.eclipse.debug.core.sourcelookup.containers.DefaultSourceContainer;
+
+/**
+ * Eclipse has a standard facility for looking up source file for a debug artifact.
+ * LiveEdit feature has an opposite problem: find script in remote VM for a particular js file.
+ * This class implements some approach to this problem. An instance of this class corresponds
+ * to a particular debug launch.
+ */
+public class ReverseSourceLookup {
+ private final ISourceLookupDirector sourceDirector;
+
+ public ReverseSourceLookup(ISourceLookupDirector sourceDirector) {
+ this.sourceDirector = sourceDirector;
+ }
+
+ /**
+ * Tries to find a corresponding script for a file from a user workspace. The method uses
+ * the file name and current source lookup rules to retrieve a resource id, regardless of
+ * whether the resource has actually been loaded into the VM (you may want to set a breakpoint
+ * on resource before it is actually loaded).
+ */
+ public VmResourceId findVmResource(IFile sourceFile) throws CoreException {
+ for (ISourceContainer container : sourceDirector.getSourceContainers()) {
+ VmResourceId scriptName = tryForContainer(sourceFile, container);
+ if (scriptName != null) {
+ return scriptName;
+ }
+ }
+ return null;
+ }
+
+ private VmResourceId tryForContainer(IFile sourceFile, ISourceContainer container)
+ throws CoreException {
+ if (container.isComposite() && isSupportedCompositeContainer(container)) {
+ ISourceContainer[] subContainers = container.getSourceContainers();
+ for (ISourceContainer subContainer : subContainers) {
+ VmResourceId res = tryForContainer(sourceFile, subContainer);
+ if (res != null) {
+ return res;
+ }
+ }
+ return null;
+ } else if (container instanceof VProjectSourceContainer) {
+ VProjectSourceContainer projectSourceContainer = (VProjectSourceContainer) container;
+ return projectSourceContainer.findScriptId(sourceFile);
+ } else {
+ String name = tryForNonVirtualContainer(sourceFile, container);
+ if (name == null) {
+ return null;
+ }
+ return VmResourceId.forName(name);
+ }
+ }
+
+ /**
+ * We use {@link ISourceContainer#getSourceContainers()} method to unwrap internal containers.
+ * However it doesn't make sense for all composite containers (some of them may return their
+ * subdirectories as containers, which is not what we need).
+ */
+ private boolean isSupportedCompositeContainer(ISourceContainer container) {
+ return container instanceof DefaultSourceContainer;
+ }
+
+ /**
+ * @param container that may not wrap VProjectSourceContainer
+ */
+ private String tryForNonVirtualContainer(IFile resource, ISourceContainer container) {
+ if (container instanceof ContainerSourceContainer) {
+ ContainerSourceContainer containerSourceContainer = (ContainerSourceContainer) container;
+ IContainer resourceContainer = containerSourceContainer.getContainer();
+ if (resourceContainer.getFullPath().isPrefixOf(resource.getFullPath())) {
+ String name = resource.getFullPath().makeRelativeTo(
+ resourceContainer.getFullPath()).toPortableString();
+ return name;
+ }
+ } else if (container instanceof SourceNameMapperContainer) {
+ SourceNameMapperContainer mappingContainer =
+ (SourceNameMapperContainer) container;
+ String subResult = tryForNonVirtualContainer(resource, mappingContainer.getTargetContainer());
+ if (subResult != null) {
+ return mappingContainer.getPrefix() + subResult;
+ }
+ }
+
+ return null;
+ }
+}
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/SourceNameMapperContainer.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/SourceNameMapperContainer.java Tue Jun 08 16:12:51 2010 -0700
@@ -0,0 +1,188 @@
+package org.chromium.debug.core;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerType;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerTypeDelegate;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * A special type of container that translates names of the source and delegates lookup
+ * to another source container.
+ * This could be useful when JS resource name is like "http://localhost/scripts/util.js"; such
+ * source name could be converted into "scripts/util.js".
+ * Currently container supports only prefix-based translation: if source name starts with a prefix,
+ * the prefix is truncated; otherwise the source name is discarded.
+ */
+public class SourceNameMapperContainer implements ISourceContainer {
+
+ private static final String TYPE_ID =
+ "org.chromium.debug.core.SourceNameMapperContainer.type"; //$NON-NLS-1$
+
+ private final ISourceContainer targetContainer;
+ private final String prefix;
+
+ public SourceNameMapperContainer(String prefix, ISourceContainer targetContainer) {
+ this.targetContainer = targetContainer;
+ this.prefix = prefix;
+ }
+
+ public void dispose() {
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public ISourceContainer getTargetContainer() {
+ return targetContainer;
+ }
+
+ public Object[] findSourceElements(String name) throws CoreException {
+ if (!name.startsWith(prefix)) {
+ return new Object[0];
+ }
+ String shortName = name.substring(prefix.length());
+ return targetContainer.findSourceElements(shortName);
+ }
+
+
+ public String getName() {
+ return NLS.bind(Messages.SourceNameMapperContainer_NAME, prefix);
+ }
+
+
+ public ISourceContainer[] getSourceContainers() {
+ return new ISourceContainer[] { targetContainer };
+ }
+
+
+ public ISourceContainerType getType() {
+ return DebugPlugin.getDefault().getLaunchManager().getSourceContainerType(TYPE_ID);
+ }
+
+
+ public void init(ISourceLookupDirector director) {
+ }
+
+
+ public boolean isComposite() {
+ return true;
+ }
+
+ private String getMemento() throws CoreException {
+ StringBuilder builder = new StringBuilder();
+ MementoFormat.encodeComponent(prefix, builder);
+ MementoFormat.encodeComponent(targetContainer.getType().getId(), builder);
+ MementoFormat.encodeComponent(targetContainer.getType().getMemento(targetContainer), builder);
+ return builder.toString();
+ }
+
+
+ public Object getAdapter(Class adapter) {
+ return null;
+ }
+
+ /**
+ * A type delegate that serializes/deserializes container instances into/from memento.
+ */
+ public static class TypeDelegate implements ISourceContainerTypeDelegate {
+ public ISourceContainer createSourceContainer(String memento) throws CoreException {
+ MementoFormat.Parser parser = new MementoFormat.Parser(memento);
+ String prefix;
+ String typeId;
+ String subContainerMemento;
+ try {
+ prefix = parser.nextComponent();
+ typeId = parser.nextComponent();
+ subContainerMemento = parser.nextComponent();
+ } catch (MementoFormat.ParserException e) {
+ throw new CoreException(new Status(IStatus.ERROR,
+ ChromiumDebugPlugin.PLUGIN_ID, "Failed to parse memento", e)); //$NON-NLS-1$
+ }
+ ISourceContainerType subContainerType =
+ DebugPlugin.getDefault().getLaunchManager().getSourceContainerType(typeId);
+ ISourceContainer subContainer = subContainerType.createSourceContainer(subContainerMemento);
+ return new SourceNameMapperContainer(prefix, subContainer);
+ }
+
+ public String getMemento(ISourceContainer container) throws CoreException {
+ SourceNameMapperContainer chromeContainer = (SourceNameMapperContainer) container;
+ return chromeContainer.getMemento();
+ }
+ }
+
+ /**
+ * Handles memento string format. The format is just a sequence of strings that are preceded
+ * with their lengths and decorated with parentheses to make it more human-readable.
+ */
+ private static class MementoFormat {
+
+ static void encodeComponent(String component, StringBuilder output) {
+ output.append(component.length());
+ output.append('(').append(component).append(')');
+ }
+
+ /**
+ * A simple parser that reads char sequence as a sequence of strings.
+ */
+ static class Parser {
+ private final CharSequence charSequence;
+ private int pos = 0;
+ Parser(CharSequence charSequence) {
+ this.charSequence = charSequence;
+ }
+ String nextComponent() throws ParserException {
+ if (pos >= charSequence.length()) {
+ throw new ParserException("Unexpected end of line"); //$NON-NLS-1$
+ }
+ char ch = charSequence.charAt(pos);
+ pos++;
+ int num = Character.digit(ch, 10);
+ if (num == -1) {
+ throw new ParserException("Digit expected"); //$NON-NLS-1$
+ }
+ int len = num;
+ while (true) {
+ if (pos >= charSequence.length()) {
+ throw new ParserException("Unexpected end of line"); //$NON-NLS-1$
+ }
+ ch = charSequence.charAt(pos);
+ if (!Character.isDigit(ch)) {
+ break;
+ }
+ pos++;
+ num = Character.digit(ch, 10);
+ if (num == -1) {
+ throw new ParserException("Digit expected"); //$NON-NLS-1$
+ }
+ len = len * 10 + num;
+ }
+ pos++;
+ if (pos + len + 1 > charSequence.length()) {
+ throw new ParserException("Unexpected end of line"); //$NON-NLS-1$
+ }
+ String result = charSequence.subSequence(pos, pos + len).toString();
+ pos += len + 1;
+ return result;
+ }
+ }
+ private static class ParserException extends Exception {
+ ParserException() {
+ }
+ ParserException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ ParserException(String message) {
+ super(message);
+ }
+ ParserException(Throwable cause) {
+ super(cause);
+ }
+ }
+ }
+}
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/VProjectSourceContainer.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/VProjectSourceContainer.java Tue Jun 08 16:12:51 2010 -0700
@@ -0,0 +1,107 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core;
+
+import org.chromium.debug.core.model.ResourceManager;
+import org.chromium.debug.core.model.VmResourceId;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.sourcelookup.ISourceContainer;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerType;
+import org.eclipse.debug.core.sourcelookup.ISourceContainerTypeDelegate;
+import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
+
+/**
+ * A source container implementation that wraps V8 virtual project. Currently virtual project
+ * has a flat file structure, so the container is accordingly one-level.
+ *
+ * Unlike other implementation of {@link ISourceContainer} this class initially gets instantiated
+ * with no data. In this state it serves as an empty container because actual VM scripts are
+ * not available yet. Launch configuration UI will use it in this state more as a symbolic
+ * place-holder in sources tab. Later when VM is connected, method
+ * {@link #init(ISourceLookupDirector)} will be called and the actual content will be set.
+ */
+public class VProjectSourceContainer implements ISourceContainer {
+
+ private static final String TYPE_ID =
+ "org.chromium.debug.core.VProjectSourceContainer.type"; //$NON-NLS-1$
+
+ private ChromiumSourceDirector chromiumSourceDirector = null;
+
+ VProjectSourceContainer() {
+ }
+
+ public void init(ISourceLookupDirector director) {
+ if (director instanceof ChromiumSourceDirector) {
+ chromiumSourceDirector = (ChromiumSourceDirector) director;
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public Object[] findSourceElements(String name) {
+ if (chromiumSourceDirector == null) {
+ return new Object[0];
+ }
+ ResourceManager resourceManager = chromiumSourceDirector.getResourceManager();
+ IFile file = resourceManager.getFile(name);
+ if (file == null) {
+ return new Object[0];
+ }
+ return new Object[] { file };
+ }
+
+ public String getName() {
+ IProject project = null;
+ if (chromiumSourceDirector != null) {
+ project = chromiumSourceDirector.getProject();
+ }
+ if (project == null) {
+ return Messages.VProjectSourceContainer_DEFAULT_TYPE_NAME;
+ } else {
+ return project.getName();
+ }
+ }
+
+ public ISourceContainer[] getSourceContainers() {
+ return null;
+ }
+
+ public ISourceContainerType getType() {
+ return DebugPlugin.getDefault().getLaunchManager().getSourceContainerType(TYPE_ID);
+ }
+
+ public boolean isComposite() {
+ return false;
+ }
+
+ public VmResourceId findScriptId(IFile resource) {
+ if (chromiumSourceDirector == null) {
+ throw new IllegalStateException();
+ }
+ return chromiumSourceDirector.getResourceManager().getResourceId(resource);
+ }
+
+ public Object getAdapter(Class adapter) {
+ return null;
+ }
+
+ /**
+ * A type delegate that implements a trivial memento. We do not save any actual data here,
+ * because it all should be derived from a current VM launch.
+ */
+ public static class TypeDelegate implements ISourceContainerTypeDelegate {
+ public ISourceContainer createSourceContainer(String memento) throws CoreException {
+ return new VProjectSourceContainer();
+ }
+
+ public String getMemento(ISourceContainer container) throws CoreException {
+ return "VProjectSourceContainer.memento.stub"; //$NON-NLS-1$
+ }
+ }
+}
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/messages.properties
--- a/org.chromium.debug.core/src/org/chromium/debug/core/messages.properties Tue Jun 08 16:06:40 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/messages.properties Tue Jun 08 16:12:51 2010 -0700
@@ -3,3 +3,5 @@
# found in the LICENSE file.
ChromiumDebugPlugin_InternalError=Internal Error
+SourceNameMapperContainer_NAME=Source mapper with prefix {0}
+VProjectSourceContainer_DEFAULT_TYPE_NAME=Remote V8/Chrome Scripts
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java Tue Jun 08 16:06:40 2010 -0700
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointAdapterFactory.java Tue Jun 08 16:12:51 2010 -0700
@@ -23,7 +23,7 @@
(IResource) editorPart.getEditorInput().getAdapter(IResource.class);
if (resource != null) {
String extension = resource.getFileExtension();
- if (extension != null && ChromiumDebugPluginUtil.CHROMIUM_EXTENSION.equals(extension)) {
+ if (extension != null && ChromiumDebugPluginUtil.SUPPORTED_EXTENSIONS.contains(extension)) {
return new LineBreakpointAdapter.ForVirtualProject();
}
}
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointMap.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointMap.java Tue Jun 08 16:12:51 2010 -0700
@@ -0,0 +1,59 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.debug.core.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.chromium.sdk.Breakpoint;
+
+/**
+ * TODO(peter.rybin): this class is obsolete, it only holds useful inner class;
+ * consider removing this class.
+ */
+public class BreakpointMap {
+
+ /**
+ * A one-to-one map between SDK and UI breakpoints inside one debug target.
+ */
+ public static class InTargetMap {
+ private final Map sdkToUiMap =
+ new HashMap();
+ private final Map uiToSdkMap =
+ new HashMap();
+
+ public InTargetMap() {
+ }
+
+ public synchronized Breakpoint getSdkBreakpoint(ChromiumLineBreakpoint chromiumLineBreakpoint) {
+ return uiToSdkMap.get(chromiumLineBreakpoint);
+ }
+
+ public synchronized ChromiumLineBreakpoint getUiBreakpoint(Breakpoint sdkBreakpoint) {
+ return sdkToUiMap.get(sdkBreakpoint);
+ }
+
+ public synchronized void add(Breakpoint sdkBreakpoint, ChromiumLineBreakpoint uiBreakpoint) {
+ Object conflict1 = uiToSdkMap.put(uiBreakpoint, sdkBreakpoint);
+ Object conflict2 = sdkToUiMap.put(sdkBreakpoint, uiBreakpoint);
+ if (conflict1 != null || conflict2 != null) {
+ throw new RuntimeException();
+ }
+ }
+
+ public synchronized void remove(ChromiumLineBreakpoint lineBreakpoint) {
+ Breakpoint sdkBreakpoint = uiToSdkMap.remove(lineBreakpoint);
+ if (sdkBreakpoint == null) {
+ throw new RuntimeException();
+ }
+ sdkToUiMap.remove(sdkBreakpoint);
+ }
+
+ public synchronized void clear() {
+ sdkToUiMap.clear();
+ uiToSdkMap.clear();
+ }
+ }
+}
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointRegistry.java
--- a/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointRegistry.java Tue Jun 08 16:06:40 2010 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,227 +0,0 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.debug.core.model;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.Map;
-
-import org.chromium.sdk.Breakpoint;
-import org.chromium.sdk.Script;
-
-/**
- * A registry of existing breakpoints associated with their script locations. It
- * is used to restore
- */
-public class BreakpointRegistry {
-
- /**
- * Script identifier for a breakpoint location.
- */
- public static class ScriptIdentifier {
- private final String name;
-
- private final long id;
-
- private final int startLine;
-
- private final int endLine;
-
- public static ScriptIdentifier forScript(Script script) {
- String name = script.getName();
- return new ScriptIdentifier(
- name,
- name != null ? -1 : script.getId(),
- script.getStartLine(),
- script.getEndLine());
- }
-
- private ScriptIdentifier(String name, long id, int startLine, int endLine) {
- this.name = name;
- this.id = id;
- this.startLine = startLine;
- this.endLine = endLine;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + (int) (id ^ (id >>> 32));
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- result = prime * result + 17 * startLine + 19 * endLine;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof ScriptIdentifier)) {
- return false;
- }
- ScriptIdentifier that = (ScriptIdentifier) obj;
- if (this.startLine != that.startLine || this.endLine != that.endLine) {
- return false;
- }
- if (name == null) {
- // an unnamed script, only id is known
- return that.name == null && this.id == that.id;
- }
- // a named script
- return this.name.equals(that.name);
- }
- }
-
- static class BreakpointLocation {
- private final ScriptIdentifier scriptIdentifier;
-
- private final int line;
-
- public BreakpointLocation(ScriptIdentifier scriptIdentifier, int line) {
- this.scriptIdentifier = scriptIdentifier;
- this.line = line;
- }
-
- public ScriptIdentifier getScriptIdentifier() {
- return scriptIdentifier;
- }
-
- public int getLine() {
- return line;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + line;
- result = prime * result + ((scriptIdentifier == null)
- ? 0
- : scriptIdentifier.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof BreakpointLocation)) {
- return false;
- }
- BreakpointLocation that = (BreakpointLocation) obj;
- return (this.line == that.line && eq(this.scriptIdentifier, that.scriptIdentifier));
- }
- }
-
- /**
- * A breakpoint accompanied by its line number in the corresponding enclosing
- * resource.
- */
- public static class BreakpointEntry {
- public final Breakpoint breakpoint;
-
- public final int line;
-
- private BreakpointEntry(Breakpoint breakpoint, int line) {
- this.breakpoint = breakpoint;
- this.line = line;
- }
-
- boolean isWithinScriptRange(Script script) {
- return line >= script.getStartLine() && line <= script.getEndLine();
- }
-
- @Override
- public int hashCode() {
- return 31 * line + 17 * breakpoint.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof BreakpointEntry)) {
- return false;
- }
- BreakpointEntry that = (BreakpointEntry) obj;
- return this.line == that.line && this.breakpoint.equals(that.breakpoint);
- }
- }
-
- private final Map> scriptIdToBreakpointEntries =
- new HashMap>();
-
- /**
- * Adds the given line breakpoint.
- *
- * @param script where the breakpoint is set
- * @param line (0-based, like in V8) in the script
- * @param breakpoint
- */
- public void add(Script script, int line, Breakpoint breakpoint) {
- ScriptIdentifier scriptId = ScriptIdentifier.forScript(script);
- Collection entries = scriptIdToBreakpointEntries.get(scriptId);
- if (entries == null) {
- entries = new HashSet();
- scriptIdToBreakpointEntries.put(scriptId, entries);
- }
- entries.add(new BreakpointEntry(breakpoint, line));
- }
-
- /**
- * Gets breakpoint entries for the given script. An empty collection for a
- * {@code null} script.
- *
- * @param script to extract the breakpoints for
- * @return the breakpoints that fall within the given script line range
- */
- public Collection extends BreakpointEntry> getBreakpointEntries(Script script) {
- if (script == null) {
- return Collections.emptySet();
- }
- Collection entries =
- scriptIdToBreakpointEntries.get(ScriptIdentifier.forScript(script));
- if (entries == null) {
- return Collections.emptySet();
- }
- Collection scriptBreakpoints = new LinkedList();
- // Linear search should work fairly well for a reasonable number of
- // breakpoints per script
- for (BreakpointEntry entry : entries) {
- if (entry.isWithinScriptRange(script)) {
- scriptBreakpoints.add(entry);
- }
- }
- return scriptBreakpoints;
- }
-
- /**
- * Removes the given line breakpoint in the given script. Does nothing for a
- * {@code null} script (which may be the case after a navigation + disconnect
- * when the resource referenced by the breakpoint marker is absent.)
- *
- * @param script where the breakpoint is set
- * @param line (0-based, like in V8) in the script
- * @param breakpoint
- */
- public void remove(Script script, int line, Breakpoint breakpoint) {
- if (script == null) {
- return;
- }
- ScriptIdentifier scriptId = ScriptIdentifier.forScript(script);
- Collection entries = scriptIdToBreakpointEntries.get(scriptId);
- if (entries == null) {
- return;
- }
- if (entries.size() == 1) {
- scriptIdToBreakpointEntries.remove(scriptId);
- } else {
- entries.remove(new BreakpointEntry(breakpoint, line));
- }
- }
-
- protected static boolean eq(Object left, Object right) {
- return left == right || (left != null && left.equals(right));
- }
-
-}
diff -r 1f666f57e777 -r dd7d3426144c org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointSynchronizer.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointSynchronizer.java Tue Jun 08 16:12:51 2010 -0700
@@ -0,0 +1,631 @@
+package org.chromium.debug.core.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.chromium.debug.core.ChromiumDebugPlugin;
+import org.chromium.debug.core.ChromiumSourceDirector;
+import org.chromium.sdk.Breakpoint;
+import org.chromium.sdk.CallbackSemaphore;
+import org.chromium.sdk.JavascriptVm;
+import org.chromium.sdk.SyncCallback;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.IBreakpointManager;
+import org.eclipse.debug.core.model.IBreakpoint;
+
+/**
+ * A class responsible for comparing breakpoints in workspace and on remote VM and synchronizing
+ * them in both directions. {@link Direction#RESET_REMOTE} allows several synchronization
+ * jobs to different VMs.
+ */
+public class BreakpointSynchronizer {
+ private final JavascriptVm javascriptVm;
+ private final BreakpointMap.InTargetMap breakpointInTargetMap;
+ private final ChromiumSourceDirector sourceDirector;
+ private final BreakpointHelper breakpointHelper;
+ private final String debugModelId;
+
+ public BreakpointSynchronizer(JavascriptVm javascriptVm,
+ BreakpointMap.InTargetMap breakpointInTargetMap,
+ ChromiumSourceDirector sourceDirector, BreakpointHelper breakpointHelper,
+ String debugModelId) {
+ this.javascriptVm = javascriptVm;
+ this.breakpointInTargetMap = breakpointInTargetMap;
+ this.sourceDirector = sourceDirector;
+ this.breakpointHelper = breakpointHelper;
+ this.debugModelId = debugModelId;
+ }
+
+ /**
+ * Describes a direction the breakpoint synchronization should be performed in.
+ */
+ public enum Direction {
+
+ /**
+ * All breakpoints in remote VM/VMs are cleared/updated/created to conform to breakpoints in
+ * Eclipse workspace.
+ */
+ RESET_REMOTE,
+
+ /**
+ * All breakpoints in local workspace are cleared/updated/created to conform to breakpoints in
+ * remote VM (not applicable for multiple VMs).
+ */
+ RESET_LOCAL,
+
+ /**
+ * Breakpoints are created locally or remotely or tied together so that every breakpoint
+ * has a counterpart on other side.
+ */
+ MERGE
+ }
+
+ /**
+ * Additional interface used by {@link BreakpointSynchronizer}.
+ */
+ public interface BreakpointHelper {
+ /**
+ * Create breakpoint on remote VM (asynchronously) and link it to uiBreakpoint.
+ */
+ void createBreakpointOnRemote(ChromiumLineBreakpoint uiBreakpoint,
+ VmResourceId vmResourceId,
+ CreateCallback createCallback, SyncCallback syncCallback);
+
+ interface CreateCallback {
+ void failure(Exception ex);
+ void success();
+ }
+ }
+
+ public interface Callback {
+ void onDone(IStatus status);
+ }
+
+ /**
+ * The main entry method of the class. Asynchronously performs synchronization job.
+ * TODO(peter.rybin): consider some end-of-job notification for this method and possibly locks
+ */
+ public void syncBreakpoints(Direction direction, Callback callback) {
+ ReportBuilder reportBuilder = new ReportBuilder(direction);
+ StatusBuilder statusBuilder = new StatusBuilder(callback, reportBuilder);
+
+ statusBuilder.plan();
+ Exception ex = null;
+ try {
+ syncBreakpointsImpl(direction, statusBuilder);
+ } catch (RuntimeException e) {
+ ex = e;
+ } finally {
+ statusBuilder.done(ex);
+ }
+ }
+
+ private void syncBreakpointsImpl(final Direction direction, final StatusBuilder statusBuilder) {
+ // Collect the remote breakpoints.
+ Collection extends Breakpoint> sdkBreakpoints = readSdkBreakpoints(javascriptVm);
+ // Collect all local breakpoints.
+ Set uiBreakpoints = getUiBreakpoints();
+
+ List sdkBreakpoints2 = new ArrayList(sdkBreakpoints.size());
+
+ if (direction != Direction.MERGE) {
+ breakpointInTargetMap.clear();
+ }
+
+ // Throw away all already linked breakpoints and put remaining into sdkBreakpoints2 list.
+ for (Breakpoint sdkBreakpoint : sdkBreakpoints) {
+ ChromiumLineBreakpoint uiBreakpoint = breakpointInTargetMap.getUiBreakpoint(sdkBreakpoint);
+ if (uiBreakpoint == null) {
+ // No mapping. Schedule for further processing.
+ sdkBreakpoints2.add(sdkBreakpoint);
+ } else {
+ // There is a live mapping. This set should also contain this breakpoint.
+ uiBreakpoints.remove(uiBreakpoint);
+ statusBuilder.getReportBuilder().increment(ReportBuilder.Property.LINKED);
+ }
+ }
+
+ // Sort all breakpoints by (script_name, line_number).
+ SortedBreakpoints sortedUiBreakpoints =
+ sortBreakpoints(uiBreakpoints, uiBreakpointHandler);
+ SortedBreakpoints sortedSdkBreakpoints =
+ sortBreakpoints(sdkBreakpoints2, sdkBreakpointHandler);
+
+ BreakpointMerger breakpointMerger = new BreakpointMerger(direction, breakpointInTargetMap);
+
+ // Find all unlinked breakpoints on both sides.
+ mergeBreakpoints(breakpointMerger, sortedUiBreakpoints, sortedSdkBreakpoints);
+
+ List sdkBreakpointsToDelete;
+ List sdkBreakpointsToCreate;
+ List uiBreakpointsToDelete;
+ List uiBreakpointsToCreate;
+
+ // Plan actions for all breakpoints without pair.
+ if (direction == Direction.RESET_REMOTE) {
+ sdkBreakpointsToDelete = breakpointMerger.getMissingSdk();
+ sdkBreakpointsToCreate = Collections.emptyList();
+ } else {
+ sdkBreakpointsToCreate = breakpointMerger.getMissingSdk();
+ sdkBreakpointsToDelete = Collections.emptyList();
+ }
+
+ if (direction == Direction.RESET_LOCAL) {
+ uiBreakpointsToDelete = breakpointMerger.getMissingUi();
+ uiBreakpointsToCreate = Collections.emptyList();
+ } else {
+ uiBreakpointsToCreate = breakpointMerger.getMissingUi();
+ uiBreakpointsToDelete = Collections.emptyList();
+ }
+
+ // First delete everything, then create (we may need to re-create some breakpoints, so order
+ // is significant).
+ deteleBreakpoints(sdkBreakpointsToDelete, uiBreakpointsToDelete, statusBuilder);
+ createBreakpoints(sdkBreakpointsToCreate, uiBreakpointsToCreate, statusBuilder);
+ }
+
+ private void deteleBreakpoints(List sdkBreakpointsToDelete,
+ List uiBreakpointsToDelete, final StatusBuilder statusBuilder) {
+ for (Breakpoint sdkBreakpoint : sdkBreakpointsToDelete) {
+ final PlannedTaskHelper deleteTaskHelper = new PlannedTaskHelper(statusBuilder);
+ JavascriptVm.BreakpointCallback callback = new JavascriptVm.BreakpointCallback() {
+ public void failure(String errorMessage) {
+ deleteTaskHelper.setException(new Exception(errorMessage));
+ }
+ public void success(Breakpoint breakpoint) {
+ statusBuilder.getReportBuilder().increment(ReportBuilder.Property.DELETED_ON_REMOTE);
+ }
+ };
+ sdkBreakpoint.clear(callback, deleteTaskHelper);
+ }
+ for (ChromiumLineBreakpoint uiBreakpoint : uiBreakpointsToDelete) {
+ ChromiumLineBreakpoint.getIgnoreList().add(uiBreakpoint);
+ try {
+ try {
+ uiBreakpoint.delete();
+ } catch (CoreException e) {
+ throw new RuntimeException(e);
+ }
+ } finally {
+ ChromiumLineBreakpoint.getIgnoreList().remove(uiBreakpoint);
+ }
+ statusBuilder.getReportBuilder().increment(ReportBuilder.Property.DELETED_LOCALLY);
+ }
+ }
+
+ private void createBreakpoints(List sdkBreakpointsToCreate,
+ List uiBreakpointsToCreate, final StatusBuilder statusBuilder) {
+ IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
+ for (Breakpoint sdkBreakpoint : sdkBreakpointsToCreate) {
+ Object sourceElement = sourceDirector.getSourceElement(sdkBreakpoint);
+ if (sourceElement instanceof IFile == false) {
+ continue;
+ }
+ // We do not actually support working files for scripts with offset.
+ int script_line_offset = 0;
+ IFile resource = (IFile) sourceElement;
+ ChromiumLineBreakpoint uiBreakpoint;
+ try {
+ uiBreakpoint = ChromiumLineBreakpoint.Helper.createLocal(sdkBreakpoint, breakpointManager,
+ resource, script_line_offset, debugModelId);
+ breakpointInTargetMap.add(sdkBreakpoint, uiBreakpoint);
+ } catch (CoreException e) {
+ throw new RuntimeException(e);
+ }
+ statusBuilder.getReportBuilder().increment(ReportBuilder.Property.CREATED_LOCALLY);
+ }
+ for (ChromiumLineBreakpoint uiBreakpoint : uiBreakpointsToCreate) {
+ VmResourceId vmResourceId = uiBreakpointHandler.getVmResourceId(uiBreakpoint);
+ if (vmResourceId == null) {
+ // Actually we should not get here, because getScript call succeeded before.
+ continue;
+ }
+
+ final PlannedTaskHelper createTaskHelper = new PlannedTaskHelper(statusBuilder);
+ BreakpointHelper.CreateCallback createCallback = new BreakpointHelper.CreateCallback() {
+ public void success() {
+ statusBuilder.getReportBuilder().increment(ReportBuilder.Property.CREATED_ON_REMOTE);
+ }
+ public void failure(Exception ex) {
+ createTaskHelper.setException(ex);
+ }
+ };
+ breakpointHelper.createBreakpointOnRemote(uiBreakpoint, vmResourceId, createCallback,
+ createTaskHelper);
+ }
+ }
+
+ private static class BreakpointMerger extends Merger {
+ private final Direction direction;
+ private final List missingUi = new ArrayList();
+ private final List missingSdk = new ArrayList();
+ private final BreakpointMap.InTargetMap breakpointMap;
+
+ BreakpointMerger(Direction direction, BreakpointMap.InTargetMap breakpointMap) {
+ this.direction = direction;
+ this.breakpointMap = breakpointMap;
+ }
+ @Override
+ void both(ChromiumLineBreakpoint v1, Breakpoint v2) {
+ if (direction == Direction.MERGE) {
+ breakpointMap.add(v2, v1);
+ } else {
+ onlyFirst(v1);
+ onlySecond(v2);
+ }
+ }
+ @Override
+ void onlyFirst(ChromiumLineBreakpoint v1) {
+ missingUi.add(v1);
+ }
+ @Override
+ void onlySecond(Breakpoint v2) {
+ missingSdk.add(v2);
+ }
+ List getMissingUi() {
+ return missingUi;
+ }
+ List getMissingSdk() {
+ return missingSdk;
+ }
+ }
+
+ /**
+ * A class responsible for creating a summary status of synchronization operation. The status
+ * is created once all asynchronous jobs have finished. Each job first registers itself
+ * via {@link #plan()} method and
+ * later reports its result via {@link #done(Exception)} method.
+ * When the last job is reporting its finishing, the status gets built and sent to
+ * {@link #callback}. If no exceptions were registered,
+ * status contains text report from {@link ReportBuilder}.
+ */
+ private static class StatusBuilder {
+ private final Callback callback;
+ private int plannedNumber = 0;
+ private final List exceptions = new ArrayList(0);
+ private boolean alreadyReported = false;
+ private final ReportBuilder reportBuilder;
+
+ StatusBuilder(Callback callback, ReportBuilder reportBuilder) {
+ this.callback = callback;
+ this.reportBuilder = reportBuilder;
+ }
+
+ ReportBuilder getReportBuilder() {
+ return reportBuilder;
+ }
+
+ public synchronized void plan() {
+ if (alreadyReported) {
+ throw new IllegalStateException();
+ }
+ plannedNumber++;
+ }
+
+ public void done(Exception ex) {
+ boolean timeToReport = doneImpl(ex);
+ if (timeToReport) {
+ reportResult();
+ }
+ }
+
+ private synchronized boolean doneImpl(Exception ex) {
+ if (ex != null) {
+ exceptions.add(ex);
+ }
+ plannedNumber--;
+ if (plannedNumber == 0) {
+ if (!alreadyReported) {
+ alreadyReported = true;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void reportResult() {
+ IStatus status;
+ if (exceptions.isEmpty()) {
+ status = new Status(IStatus.OK, ChromiumDebugPlugin.PLUGIN_ID,
+ "Breakpoint synchronization done: " + reportBuilder.build(), null); //$NON-NLS-1$
+ } else {
+ IStatus[] subStatuses = new IStatus[exceptions.size()];
+ for (int i = 0 ; i < subStatuses.length; i++) {
+ subStatuses[i] = new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
+ exceptions.get(i).getMessage(), exceptions.get(i));
+ }
+ status = new MultiStatus(ChromiumDebugPlugin.PLUGIN_ID, IStatus.ERROR, subStatuses,
+ "Breakpoint synchronization errors", null); //$NON-NLS-1$
+ }
+ if (callback != null) {
+ callback.onDone(status);
+ }
+ }
+ }
+
+ private static class PlannedTaskHelper implements SyncCallback {
+ private final StatusBuilder statusBuilder;
+ private volatile Exception exception = null;
+ PlannedTaskHelper(StatusBuilder statusBuilder) {
+ this.statusBuilder = statusBuilder;
+ statusBuilder.plan();
+ }
+ public void callbackDone(RuntimeException e) {
+ if (e != null) {
+ exception = e;
+ }
+ statusBuilder.done(exception);
+ }
+ void setException(Exception ex) {
+ exception = ex;
+ }
+ }
+
+ /**
+ * A class that contains several conunters.
+ */
+ private static class ReportBuilder {
+ enum Property {
+ LINKED,
+ CREATED_LOCALLY,
+ DELETED_LOCALLY,
+ CREATED_ON_REMOTE,
+ DELETED_ON_REMOTE;
+ String getVisibleName() {
+ return toString();
+ }
+ }
+
+ private final Direction direction;
+ private final Map counters;
+
+ ReportBuilder(Direction direction) {
+ this.direction = direction;
+ counters = new EnumMap(Property.class);
+ for (Property property : Property.class.getEnumConstants()) {
+ counters.put(property, new AtomicInteger(0));
+ }
+ }
+
+ public void increment(Property property) {
+ counters.get(property).addAndGet(1);
+ }
+
+ public String build() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("direction=").append(direction); //$NON-NLS-1$
+ for (Map.Entry en : counters.entrySet()) {
+ int number = en.getValue().get();
+ if (number == 0) {
+ continue;
+ }
+ builder.append(" ").append(en.getKey().getVisibleName()); //$NON-NLS-1$
+ builder.append("=").append(number); //$NON-NLS-1$
+ }
+ return builder.toString();
+ }
+ }
+
+ /**
+ * A handler for properties of breakpoint type B that helps reading them.
+ */
+ private static abstract class PropertyHandler {
+ /** @return vm resource name or null */
+ abstract VmResourceId getVmResourceId(B breakpoint);
+ /** @return 0-based number */
+ abstract long getLineNumber(B breakpoint);
+ }
+
+ private final PropertyHandler uiBreakpointHandler =
+ new PropertyHandler() {
+ @Override
+ long getLineNumber(ChromiumLineBreakpoint chromiumLineBreakpoint) {
+ int lineNumber;
+ try {
+ // TODO(peter.rybin): Consider supporting inline scripts here.
+ return chromiumLineBreakpoint.getLineNumber() - 1;
+ } catch (CoreException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ VmResourceId getVmResourceId(ChromiumLineBreakpoint chromiumLineBreakpoint) {
+ IMarker marker = chromiumLineBreakpoint.getMarker();
+ if (marker == null) {
+ return null;
+ }
+ IResource resource = marker.getResource();
+ if (resource instanceof IFile == false) {
+ return null;
+ }
+ IFile file = (IFile) resource;
+ try {
+ return sourceDirector.getReverseSourceLookup().findVmResource(file);
+ } catch (CoreException e) {
+ throw new RuntimeException("Failed to read script name from breakpoint", e); //$NON-NLS-1$
+ }
+ }
+ };
+
+ private static final PropertyHandler sdkBreakpointHandler =
+ new PropertyHandler() {
+ @Override
+ long getLineNumber(Breakpoint breakpoint) {
+ return breakpoint.getLineNumber();
+ }
+
+ @Override
+ VmResourceId getVmResourceId(Breakpoint breakpoint) {
+ if (breakpoint.getType() == Breakpoint.Type.SCRIPT_NAME) {
+ return VmResourceId.forName(breakpoint.getScriptName());
+ } else {
+ Long scriptId = breakpoint.getScriptId();
+ if (scriptId == null) {
+ return null;
+ }
+ return VmResourceId.forId(scriptId);
+ }
+ }
+ };
+
+ /**
+ * A helping structure that holds field of complicated type.
+ */
+ private static class SortedBreakpoints {
+ final Map> data;
+
+ SortedBreakpoints(Map> data) {
+ this.data = data;
+ }
+ }
+
+ /**
+ * Put all breakpoints into map script_name -> line_number -> breakpoint.
+ */
+ private static SortedBreakpoints sortBreakpoints(Collection extends B> breakpoints,
+ PropertyHandler handler) {
+ Map> result = new HashMap>();
+ for (B breakpoint : breakpoints) {
+ VmResourceId vmResourceId = handler.getVmResourceId(breakpoint);
+ if (vmResourceId == null) {
+ continue;
+ }
+ Map subMap = result.get(vmResourceId);
+ if (subMap == null) {
+ subMap = new HashMap(3);
+ result.put(vmResourceId, subMap);
+ }
+ long line = handler.getLineNumber(breakpoint);
+ // For simplicity we ignore multiple breakpoints on the same line.
+ subMap.put(line, breakpoint);
+ }
+ return new SortedBreakpoints(result);
+ }
+
+ /**
+ * A class that implements merge operation for a particular complete/incomplete pair of values.
+ */
+ private static abstract class Merger {
+ abstract void onlyFirst(V1 v1);
+ abstract void onlySecond(V2 v2);
+ abstract void both(V1 v1, V2 v2);
+ }
+
+ /**
+ * Merges values of 2 maps.
+ * @param map2 must implement {@link Map#remove} method.
+ */
+ private static void mergeMaps(Map map1, Map map2,
+ Merger merger) {
+ for (Map.Entry en : map1.entrySet()) {
+ V2 v2 = map2.remove(en.getKey());
+ if (v2 == null) {
+ merger.onlyFirst(en.getValue());
+ } else {
+ merger.both(en.getValue(), v2);
+ }
+ }
+ for (V2 v2 : map2.values()) {
+ merger.onlySecond(v2);
+ }
+ }
+
+ private static void mergeBreakpoints(final Merger perBreakpointMerger,
+ SortedBreakpoints side1, SortedBreakpoints side2) {
+ Merger