org.chromium.debug.core/src/org/chromium/debug/core/model/BreakpointSynchronizer.java
changeset 355 8726e95bcbba
equal deleted inserted replaced
354:0bceeb415e7f 355:8726e95bcbba
       
     1 package org.chromium.debug.core.model;
       
     2 
       
     3 import java.util.ArrayList;
       
     4 import java.util.Collection;
       
     5 import java.util.Collections;
       
     6 import java.util.EnumMap;
       
     7 import java.util.HashMap;
       
     8 import java.util.HashSet;
       
     9 import java.util.List;
       
    10 import java.util.Map;
       
    11 import java.util.Set;
       
    12 import java.util.concurrent.atomic.AtomicInteger;
       
    13 
       
    14 import org.chromium.debug.core.ChromiumDebugPlugin;
       
    15 import org.chromium.debug.core.ChromiumSourceDirector;
       
    16 import org.chromium.sdk.Breakpoint;
       
    17 import org.chromium.sdk.CallbackSemaphore;
       
    18 import org.chromium.sdk.JavascriptVm;
       
    19 import org.chromium.sdk.SyncCallback;
       
    20 import org.eclipse.core.resources.IFile;
       
    21 import org.eclipse.core.resources.IMarker;
       
    22 import org.eclipse.core.resources.IResource;
       
    23 import org.eclipse.core.runtime.CoreException;
       
    24 import org.eclipse.core.runtime.IStatus;
       
    25 import org.eclipse.core.runtime.MultiStatus;
       
    26 import org.eclipse.core.runtime.Status;
       
    27 import org.eclipse.debug.core.DebugPlugin;
       
    28 import org.eclipse.debug.core.IBreakpointManager;
       
    29 import org.eclipse.debug.core.model.IBreakpoint;
       
    30 
       
    31 /**
       
    32  * A class responsible for comparing breakpoints in workspace and on remote VM and synchronizing
       
    33  * them in both directions. {@link Direction#RESET_REMOTE} allows several synchronization
       
    34  * jobs to different VMs.
       
    35  */
       
    36 public class BreakpointSynchronizer {
       
    37   private final JavascriptVm javascriptVm;
       
    38   private final BreakpointMap.InTargetMap breakpointInTargetMap;
       
    39   private final ChromiumSourceDirector sourceDirector;
       
    40   private final BreakpointHelper breakpointHelper;
       
    41   private final String debugModelId;
       
    42 
       
    43   public BreakpointSynchronizer(JavascriptVm javascriptVm,
       
    44       BreakpointMap.InTargetMap breakpointInTargetMap,
       
    45       ChromiumSourceDirector sourceDirector, BreakpointHelper breakpointHelper,
       
    46       String debugModelId) {
       
    47     this.javascriptVm = javascriptVm;
       
    48     this.breakpointInTargetMap = breakpointInTargetMap;
       
    49     this.sourceDirector = sourceDirector;
       
    50     this.breakpointHelper = breakpointHelper;
       
    51     this.debugModelId = debugModelId;
       
    52   }
       
    53 
       
    54   /**
       
    55    * Describes a direction the breakpoint synchronization should be performed in.
       
    56    */
       
    57   public enum Direction {
       
    58 
       
    59     /**
       
    60      * All breakpoints in remote VM/VMs are cleared/updated/created to conform to breakpoints in
       
    61      * Eclipse workspace.
       
    62      */
       
    63     RESET_REMOTE,
       
    64 
       
    65     /**
       
    66      * All breakpoints in local workspace are cleared/updated/created to conform to breakpoints in
       
    67      * remote VM (not applicable for multiple VMs).
       
    68      */
       
    69     RESET_LOCAL,
       
    70 
       
    71     /**
       
    72      * Breakpoints are created locally or remotely or tied together so that every breakpoint
       
    73      * has a counterpart on other side.
       
    74      */
       
    75     MERGE
       
    76   }
       
    77 
       
    78   /**
       
    79    * Additional interface used by {@link BreakpointSynchronizer}.
       
    80    */
       
    81   public interface BreakpointHelper {
       
    82     /**
       
    83      * Create breakpoint on remote VM (asynchronously) and link it to uiBreakpoint.
       
    84      */
       
    85     void createBreakpointOnRemote(ChromiumLineBreakpoint uiBreakpoint,
       
    86         VmResourceId vmResourceId,
       
    87         CreateCallback createCallback, SyncCallback syncCallback);
       
    88 
       
    89     interface CreateCallback {
       
    90       void failure(Exception ex);
       
    91       void success();
       
    92     }
       
    93   }
       
    94 
       
    95   public interface Callback {
       
    96     void onDone(IStatus status);
       
    97   }
       
    98 
       
    99   /**
       
   100    * The main entry method of the class. Asynchronously performs synchronization job.
       
   101    * TODO(peter.rybin): consider some end-of-job notification for this method and possibly locks
       
   102    */
       
   103   public void syncBreakpoints(Direction direction, Callback callback) {
       
   104     ReportBuilder reportBuilder = new ReportBuilder(direction);
       
   105     StatusBuilder statusBuilder = new StatusBuilder(callback, reportBuilder);
       
   106 
       
   107     statusBuilder.plan();
       
   108     Exception ex = null;
       
   109     try {
       
   110       syncBreakpointsImpl(direction, statusBuilder);
       
   111     } catch (RuntimeException e) {
       
   112       ex = e;
       
   113     } finally {
       
   114       statusBuilder.done(ex);
       
   115     }
       
   116   }
       
   117 
       
   118   private void syncBreakpointsImpl(final Direction direction, final StatusBuilder statusBuilder) {
       
   119     // Collect the remote breakpoints.
       
   120     Collection<? extends Breakpoint> sdkBreakpoints = readSdkBreakpoints(javascriptVm);
       
   121     // Collect all local breakpoints.
       
   122     Set<ChromiumLineBreakpoint> uiBreakpoints = getUiBreakpoints();
       
   123 
       
   124     List<Breakpoint> sdkBreakpoints2 = new ArrayList<Breakpoint>(sdkBreakpoints.size());
       
   125 
       
   126     if (direction != Direction.MERGE) {
       
   127       breakpointInTargetMap.clear();
       
   128     }
       
   129 
       
   130     // Throw away all already linked breakpoints and put remaining into sdkBreakpoints2 list.
       
   131     for (Breakpoint sdkBreakpoint : sdkBreakpoints) {
       
   132       ChromiumLineBreakpoint uiBreakpoint = breakpointInTargetMap.getUiBreakpoint(sdkBreakpoint);
       
   133       if (uiBreakpoint == null) {
       
   134         // No mapping. Schedule for further processing.
       
   135         sdkBreakpoints2.add(sdkBreakpoint);
       
   136       } else {
       
   137         // There is a live mapping. This set should also contain this breakpoint.
       
   138         uiBreakpoints.remove(uiBreakpoint);
       
   139         statusBuilder.getReportBuilder().increment(ReportBuilder.Property.LINKED);
       
   140       }
       
   141     }
       
   142 
       
   143     // Sort all breakpoints by (script_name, line_number).
       
   144     SortedBreakpoints<ChromiumLineBreakpoint> sortedUiBreakpoints =
       
   145         sortBreakpoints(uiBreakpoints, uiBreakpointHandler);
       
   146     SortedBreakpoints<Breakpoint> sortedSdkBreakpoints =
       
   147         sortBreakpoints(sdkBreakpoints2, sdkBreakpointHandler);
       
   148 
       
   149     BreakpointMerger breakpointMerger = new BreakpointMerger(direction, breakpointInTargetMap);
       
   150 
       
   151     // Find all unlinked breakpoints on both sides.
       
   152     mergeBreakpoints(breakpointMerger, sortedUiBreakpoints, sortedSdkBreakpoints);
       
   153 
       
   154     List<Breakpoint> sdkBreakpointsToDelete;
       
   155     List<Breakpoint> sdkBreakpointsToCreate;
       
   156     List<ChromiumLineBreakpoint> uiBreakpointsToDelete;
       
   157     List<ChromiumLineBreakpoint> uiBreakpointsToCreate;
       
   158 
       
   159     // Plan actions for all breakpoints without pair.
       
   160     if (direction == Direction.RESET_REMOTE) {
       
   161       sdkBreakpointsToDelete = breakpointMerger.getMissingSdk();
       
   162       sdkBreakpointsToCreate = Collections.emptyList();
       
   163     } else {
       
   164       sdkBreakpointsToCreate = breakpointMerger.getMissingSdk();
       
   165       sdkBreakpointsToDelete = Collections.emptyList();
       
   166     }
       
   167 
       
   168     if (direction == Direction.RESET_LOCAL) {
       
   169       uiBreakpointsToDelete = breakpointMerger.getMissingUi();
       
   170       uiBreakpointsToCreate = Collections.emptyList();
       
   171     } else {
       
   172       uiBreakpointsToCreate = breakpointMerger.getMissingUi();
       
   173       uiBreakpointsToDelete = Collections.emptyList();
       
   174     }
       
   175 
       
   176     // First delete everything, then create (we may need to re-create some breakpoints, so order
       
   177     // is significant).
       
   178     deteleBreakpoints(sdkBreakpointsToDelete, uiBreakpointsToDelete, statusBuilder);
       
   179     createBreakpoints(sdkBreakpointsToCreate, uiBreakpointsToCreate, statusBuilder);
       
   180   }
       
   181 
       
   182   private void deteleBreakpoints(List<Breakpoint> sdkBreakpointsToDelete,
       
   183       List<ChromiumLineBreakpoint> uiBreakpointsToDelete, final StatusBuilder statusBuilder) {
       
   184     for (Breakpoint sdkBreakpoint : sdkBreakpointsToDelete) {
       
   185       final PlannedTaskHelper deleteTaskHelper = new PlannedTaskHelper(statusBuilder);
       
   186       JavascriptVm.BreakpointCallback callback = new JavascriptVm.BreakpointCallback() {
       
   187         public void failure(String errorMessage) {
       
   188           deleteTaskHelper.setException(new Exception(errorMessage));
       
   189         }
       
   190         public void success(Breakpoint breakpoint) {
       
   191           statusBuilder.getReportBuilder().increment(ReportBuilder.Property.DELETED_ON_REMOTE);
       
   192         }
       
   193       };
       
   194       sdkBreakpoint.clear(callback, deleteTaskHelper);
       
   195     }
       
   196     for (ChromiumLineBreakpoint uiBreakpoint : uiBreakpointsToDelete) {
       
   197       ChromiumLineBreakpoint.getIgnoreList().add(uiBreakpoint);
       
   198       try {
       
   199         try {
       
   200           uiBreakpoint.delete();
       
   201         } catch (CoreException e) {
       
   202           throw new RuntimeException(e);
       
   203         }
       
   204       } finally {
       
   205         ChromiumLineBreakpoint.getIgnoreList().remove(uiBreakpoint);
       
   206       }
       
   207       statusBuilder.getReportBuilder().increment(ReportBuilder.Property.DELETED_LOCALLY);
       
   208     }
       
   209   }
       
   210 
       
   211   private void createBreakpoints(List<Breakpoint> sdkBreakpointsToCreate,
       
   212       List<ChromiumLineBreakpoint> uiBreakpointsToCreate, final StatusBuilder statusBuilder) {
       
   213     IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
       
   214     for (Breakpoint sdkBreakpoint : sdkBreakpointsToCreate) {
       
   215       Object sourceElement = sourceDirector.getSourceElement(sdkBreakpoint);
       
   216       if (sourceElement instanceof IFile == false) {
       
   217         continue;
       
   218       }
       
   219       // We do not actually support working files for scripts with offset.
       
   220       int script_line_offset = 0;
       
   221       IFile resource = (IFile) sourceElement;
       
   222       ChromiumLineBreakpoint uiBreakpoint;
       
   223       try {
       
   224         uiBreakpoint = ChromiumLineBreakpoint.Helper.createLocal(sdkBreakpoint, breakpointManager,
       
   225             resource, script_line_offset, debugModelId);
       
   226         breakpointInTargetMap.add(sdkBreakpoint, uiBreakpoint);
       
   227       } catch (CoreException e) {
       
   228         throw new RuntimeException(e);
       
   229       }
       
   230       statusBuilder.getReportBuilder().increment(ReportBuilder.Property.CREATED_LOCALLY);
       
   231     }
       
   232     for (ChromiumLineBreakpoint uiBreakpoint : uiBreakpointsToCreate) {
       
   233       VmResourceId vmResourceId = uiBreakpointHandler.getVmResourceId(uiBreakpoint);
       
   234       if (vmResourceId == null) {
       
   235         // Actually we should not get here, because getScript call succeeded before.
       
   236         continue;
       
   237       }
       
   238 
       
   239       final PlannedTaskHelper createTaskHelper = new PlannedTaskHelper(statusBuilder);
       
   240       BreakpointHelper.CreateCallback createCallback = new BreakpointHelper.CreateCallback() {
       
   241         public void success() {
       
   242           statusBuilder.getReportBuilder().increment(ReportBuilder.Property.CREATED_ON_REMOTE);
       
   243         }
       
   244         public void failure(Exception ex) {
       
   245           createTaskHelper.setException(ex);
       
   246         }
       
   247       };
       
   248       breakpointHelper.createBreakpointOnRemote(uiBreakpoint, vmResourceId, createCallback,
       
   249           createTaskHelper);
       
   250     }
       
   251   }
       
   252 
       
   253   private static class BreakpointMerger extends Merger<ChromiumLineBreakpoint, Breakpoint> {
       
   254     private final Direction direction;
       
   255     private final List<ChromiumLineBreakpoint> missingUi = new ArrayList<ChromiumLineBreakpoint>();
       
   256     private final List<Breakpoint> missingSdk = new ArrayList<Breakpoint>();
       
   257     private final BreakpointMap.InTargetMap breakpointMap;
       
   258 
       
   259     BreakpointMerger(Direction direction, BreakpointMap.InTargetMap breakpointMap) {
       
   260       this.direction = direction;
       
   261       this.breakpointMap = breakpointMap;
       
   262     }
       
   263     @Override
       
   264     void both(ChromiumLineBreakpoint v1, Breakpoint v2) {
       
   265       if (direction == Direction.MERGE) {
       
   266         breakpointMap.add(v2, v1);
       
   267       } else {
       
   268         onlyFirst(v1);
       
   269         onlySecond(v2);
       
   270       }
       
   271     }
       
   272     @Override
       
   273     void onlyFirst(ChromiumLineBreakpoint v1) {
       
   274       missingUi.add(v1);
       
   275     }
       
   276     @Override
       
   277     void onlySecond(Breakpoint v2) {
       
   278       missingSdk.add(v2);
       
   279     }
       
   280     List<ChromiumLineBreakpoint> getMissingUi() {
       
   281       return missingUi;
       
   282     }
       
   283     List<Breakpoint> getMissingSdk() {
       
   284       return missingSdk;
       
   285     }
       
   286   }
       
   287 
       
   288   /**
       
   289    * A class responsible for creating a summary status of synchronization operation. The status
       
   290    * is created once all asynchronous jobs have finished. Each job first registers itself
       
   291    * via {@link #plan()} method and
       
   292    * later reports its result via {@link #done(Exception)} method.
       
   293    * When the last job is reporting its finishing, the status gets built and sent to
       
   294    * {@link #callback}. If no exceptions were registered,
       
   295    * status contains text report from {@link ReportBuilder}.
       
   296    */
       
   297   private static class StatusBuilder {
       
   298     private final Callback callback;
       
   299     private int plannedNumber = 0;
       
   300     private final List<Exception> exceptions = new ArrayList<Exception>(0);
       
   301     private boolean alreadyReported = false;
       
   302     private final ReportBuilder reportBuilder;
       
   303 
       
   304     StatusBuilder(Callback callback, ReportBuilder reportBuilder) {
       
   305       this.callback = callback;
       
   306       this.reportBuilder = reportBuilder;
       
   307     }
       
   308 
       
   309     ReportBuilder getReportBuilder() {
       
   310       return reportBuilder;
       
   311     }
       
   312 
       
   313     public synchronized void plan() {
       
   314       if (alreadyReported) {
       
   315         throw new IllegalStateException();
       
   316       }
       
   317       plannedNumber++;
       
   318     }
       
   319 
       
   320     public void done(Exception ex) {
       
   321       boolean timeToReport = doneImpl(ex);
       
   322       if (timeToReport) {
       
   323         reportResult();
       
   324       }
       
   325     }
       
   326 
       
   327     private synchronized boolean doneImpl(Exception ex) {
       
   328       if (ex != null) {
       
   329         exceptions.add(ex);
       
   330       }
       
   331       plannedNumber--;
       
   332       if (plannedNumber == 0) {
       
   333         if (!alreadyReported) {
       
   334           alreadyReported = true;
       
   335           return true;
       
   336         }
       
   337       }
       
   338       return false;
       
   339     }
       
   340 
       
   341     private void reportResult() {
       
   342       IStatus status;
       
   343       if (exceptions.isEmpty()) {
       
   344         status = new Status(IStatus.OK, ChromiumDebugPlugin.PLUGIN_ID,
       
   345             "Breakpoint synchronization done: " + reportBuilder.build(), null); //$NON-NLS-1$
       
   346       } else {
       
   347         IStatus[] subStatuses = new IStatus[exceptions.size()];
       
   348         for (int i = 0 ; i < subStatuses.length; i++) {
       
   349           subStatuses[i] = new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
       
   350               exceptions.get(i).getMessage(), exceptions.get(i));
       
   351         }
       
   352         status = new MultiStatus(ChromiumDebugPlugin.PLUGIN_ID, IStatus.ERROR, subStatuses,
       
   353             "Breakpoint synchronization errors", null); //$NON-NLS-1$
       
   354       }
       
   355       if (callback != null) {
       
   356         callback.onDone(status);
       
   357       }
       
   358     }
       
   359   }
       
   360 
       
   361   private static class PlannedTaskHelper implements SyncCallback {
       
   362     private final StatusBuilder statusBuilder;
       
   363     private volatile Exception exception = null;
       
   364     PlannedTaskHelper(StatusBuilder statusBuilder) {
       
   365       this.statusBuilder = statusBuilder;
       
   366       statusBuilder.plan();
       
   367     }
       
   368     public void callbackDone(RuntimeException e) {
       
   369       if (e != null) {
       
   370         exception = e;
       
   371       }
       
   372       statusBuilder.done(exception);
       
   373     }
       
   374     void setException(Exception ex) {
       
   375       exception = ex;
       
   376     }
       
   377   }
       
   378 
       
   379   /**
       
   380    * A class that contains several conunters.
       
   381    */
       
   382   private static class ReportBuilder {
       
   383     enum Property {
       
   384       LINKED,
       
   385       CREATED_LOCALLY,
       
   386       DELETED_LOCALLY,
       
   387       CREATED_ON_REMOTE,
       
   388       DELETED_ON_REMOTE;
       
   389       String getVisibleName() {
       
   390         return toString();
       
   391       }
       
   392     }
       
   393 
       
   394     private final Direction direction;
       
   395     private final Map<Property, AtomicInteger> counters;
       
   396 
       
   397     ReportBuilder(Direction direction) {
       
   398       this.direction = direction;
       
   399       counters = new EnumMap<Property, AtomicInteger>(Property.class);
       
   400       for (Property property : Property.class.getEnumConstants()) {
       
   401         counters.put(property, new AtomicInteger(0));
       
   402       }
       
   403     }
       
   404 
       
   405     public void increment(Property property) {
       
   406       counters.get(property).addAndGet(1);
       
   407     }
       
   408 
       
   409     public String build() {
       
   410       StringBuilder builder = new StringBuilder();
       
   411       builder.append("direction=").append(direction); //$NON-NLS-1$
       
   412       for (Map.Entry<Property, AtomicInteger> en : counters.entrySet()) {
       
   413         int number = en.getValue().get();
       
   414         if (number == 0) {
       
   415           continue;
       
   416         }
       
   417         builder.append(" ").append(en.getKey().getVisibleName()); //$NON-NLS-1$
       
   418         builder.append("=").append(number); //$NON-NLS-1$
       
   419       }
       
   420       return builder.toString();
       
   421     }
       
   422   }
       
   423 
       
   424   /**
       
   425    * A handler for properties of breakpoint type B that helps reading them.
       
   426    */
       
   427   private static abstract class PropertyHandler<B> {
       
   428     /** @return vm resource name or null */
       
   429     abstract VmResourceId getVmResourceId(B breakpoint);
       
   430     /** @return 0-based number */
       
   431     abstract long getLineNumber(B breakpoint);
       
   432   }
       
   433 
       
   434   private final PropertyHandler<ChromiumLineBreakpoint> uiBreakpointHandler =
       
   435       new PropertyHandler<ChromiumLineBreakpoint>() {
       
   436       @Override
       
   437     long getLineNumber(ChromiumLineBreakpoint chromiumLineBreakpoint) {
       
   438       int lineNumber;
       
   439       try {
       
   440         // TODO(peter.rybin): Consider supporting inline scripts here.
       
   441         return chromiumLineBreakpoint.getLineNumber() - 1;
       
   442       } catch (CoreException e) {
       
   443         throw new RuntimeException(e);
       
   444       }
       
   445     }
       
   446 
       
   447     @Override
       
   448     VmResourceId getVmResourceId(ChromiumLineBreakpoint chromiumLineBreakpoint) {
       
   449       IMarker marker = chromiumLineBreakpoint.getMarker();
       
   450       if (marker == null) {
       
   451         return null;
       
   452       }
       
   453       IResource resource = marker.getResource();
       
   454       if (resource instanceof IFile == false) {
       
   455         return null;
       
   456       }
       
   457       IFile file = (IFile) resource;
       
   458       try {
       
   459         return sourceDirector.getReverseSourceLookup().findVmResource(file);
       
   460       } catch (CoreException e) {
       
   461         throw new RuntimeException("Failed to read script name from breakpoint", e); //$NON-NLS-1$
       
   462       }
       
   463     }
       
   464   };
       
   465 
       
   466   private static final PropertyHandler<Breakpoint> sdkBreakpointHandler =
       
   467       new PropertyHandler<Breakpoint>() {
       
   468     @Override
       
   469     long getLineNumber(Breakpoint breakpoint) {
       
   470       return breakpoint.getLineNumber();
       
   471     }
       
   472 
       
   473     @Override
       
   474     VmResourceId getVmResourceId(Breakpoint breakpoint) {
       
   475       if (breakpoint.getType() == Breakpoint.Type.SCRIPT_NAME) {
       
   476         return VmResourceId.forName(breakpoint.getScriptName());
       
   477       } else {
       
   478         Long scriptId = breakpoint.getScriptId();
       
   479         if (scriptId == null) {
       
   480           return null;
       
   481         }
       
   482         return VmResourceId.forId(scriptId);
       
   483       }
       
   484     }
       
   485   };
       
   486 
       
   487   /**
       
   488    * A helping structure that holds field of complicated type.
       
   489    */
       
   490   private static class SortedBreakpoints<B> {
       
   491     final Map<VmResourceId, Map<Long, B>> data;
       
   492 
       
   493     SortedBreakpoints(Map<VmResourceId, Map<Long, B>> data) {
       
   494       this.data = data;
       
   495     }
       
   496   }
       
   497 
       
   498   /**
       
   499    * Put all breakpoints into map script_name -> line_number -> breakpoint.
       
   500    */
       
   501   private static <B> SortedBreakpoints<B> sortBreakpoints(Collection<? extends B> breakpoints,
       
   502       PropertyHandler<B> handler) {
       
   503     Map<VmResourceId, Map<Long, B>> result = new HashMap<VmResourceId, Map<Long, B>>();
       
   504     for (B breakpoint : breakpoints) {
       
   505       VmResourceId vmResourceId = handler.getVmResourceId(breakpoint);
       
   506       if (vmResourceId == null) {
       
   507         continue;
       
   508       }
       
   509       Map<Long, B> subMap = result.get(vmResourceId);
       
   510       if (subMap == null) {
       
   511         subMap = new HashMap<Long, B>(3);
       
   512         result.put(vmResourceId, subMap);
       
   513       }
       
   514       long line = handler.getLineNumber(breakpoint);
       
   515       // For simplicity we ignore multiple breakpoints on the same line.
       
   516       subMap.put(line, breakpoint);
       
   517     }
       
   518     return new SortedBreakpoints<B>(result);
       
   519   }
       
   520 
       
   521   /**
       
   522    * A class that implements merge operation for a particular complete/incomplete pair of values.
       
   523    */
       
   524   private static abstract class Merger<V1, V2> {
       
   525     abstract void onlyFirst(V1 v1);
       
   526     abstract void onlySecond(V2 v2);
       
   527     abstract void both(V1 v1, V2 v2);
       
   528   }
       
   529 
       
   530   /**
       
   531    * Merges values of 2 maps.
       
   532    * @param map2 must implement {@link Map#remove} method.
       
   533    */
       
   534   private static <K, V1, V2> void mergeMaps(Map<K, V1> map1, Map<K, V2> map2,
       
   535       Merger<V1, V2> merger) {
       
   536     for (Map.Entry<K, V1> en : map1.entrySet()) {
       
   537       V2 v2 = map2.remove(en.getKey());
       
   538       if (v2 == null) {
       
   539         merger.onlyFirst(en.getValue());
       
   540       } else {
       
   541         merger.both(en.getValue(), v2);
       
   542       }
       
   543     }
       
   544     for (V2 v2 : map2.values()) {
       
   545       merger.onlySecond(v2);
       
   546     }
       
   547   }
       
   548 
       
   549   private static <B1, B2> void mergeBreakpoints(final Merger<B1, B2> perBreakpointMerger,
       
   550       SortedBreakpoints<B1> side1, SortedBreakpoints<B2> side2) {
       
   551     Merger<Map<Long, B1>, Map<Long, B2>> perScriptMerger =
       
   552         new Merger<Map<Long,B1>, Map<Long,B2>>() {
       
   553       @Override
       
   554       void both(Map<Long, B1> v1, Map<Long, B2> v2) {
       
   555         mergeMaps(v1, v2, perBreakpointMerger);
       
   556       }
       
   557 
       
   558       @Override
       
   559       void onlyFirst(Map<Long, B1> v1) {
       
   560         mergeMaps(v1, Collections.<Long, B2>emptyMap(), perBreakpointMerger);
       
   561       }
       
   562 
       
   563       @Override
       
   564       void onlySecond(Map<Long, B2> v2) {
       
   565         mergeMaps(Collections.<Long, B1>emptyMap(), v2, perBreakpointMerger);
       
   566       }
       
   567     };
       
   568     mergeMaps(side1.data, side2.data, perScriptMerger);
       
   569   }
       
   570 
       
   571 
       
   572   private static Collection<? extends Breakpoint> readSdkBreakpoints(JavascriptVm javascriptVm) {
       
   573     class CallbackImpl implements JavascriptVm.ListBreakpointsCallback {
       
   574       public void failure(Exception exception) {
       
   575         problem = exception;
       
   576       }
       
   577 
       
   578       public void success(Collection<? extends Breakpoint> breakpoints) {
       
   579         result = breakpoints;
       
   580       }
       
   581       Collection<? extends Breakpoint> getResult() {
       
   582         if (problem != null) {
       
   583           throw new RuntimeException("Failed to synchronize breakpoints", problem); //$NON-NLS-1$
       
   584         }
       
   585         return result;
       
   586       }
       
   587       Exception problem = null;
       
   588       Collection<? extends Breakpoint> result = null;
       
   589     }
       
   590 
       
   591     CallbackImpl callback = new CallbackImpl();
       
   592     CallbackSemaphore callbackSemaphore = new CallbackSemaphore();
       
   593 
       
   594     javascriptVm.listBreakpoints(callback, callbackSemaphore);
       
   595     boolean res = callbackSemaphore.tryAcquireDefault();
       
   596     if (!res) {
       
   597       throw new RuntimeException("Timeout"); //$NON-NLS-1$
       
   598     }
       
   599 
       
   600     return callback.getResult();
       
   601   }
       
   602 
       
   603   // We need this method to return Set for future purposes.
       
   604   private Set<ChromiumLineBreakpoint> getUiBreakpoints() {
       
   605     IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
       
   606     Set<ChromiumLineBreakpoint> result = new HashSet<ChromiumLineBreakpoint>();
       
   607 
       
   608     for (IBreakpoint breakpoint: breakpointManager.getBreakpoints(debugModelId)) {
       
   609       if (breakpoint instanceof ChromiumLineBreakpoint == false) {
       
   610         continue;
       
   611       }
       
   612       ChromiumLineBreakpoint chromiumLineBreakpoint = (ChromiumLineBreakpoint) breakpoint;
       
   613       result.add(chromiumLineBreakpoint);
       
   614     }
       
   615     return result;
       
   616   }
       
   617 
       
   618   public static class ProtocolNotSupportedOnRemote extends Exception {
       
   619     ProtocolNotSupportedOnRemote() {
       
   620     }
       
   621     ProtocolNotSupportedOnRemote(String message, Throwable cause) {
       
   622       super(message, cause);
       
   623     }
       
   624     ProtocolNotSupportedOnRemote(String message) {
       
   625       super(message);
       
   626     }
       
   627     ProtocolNotSupportedOnRemote(Throwable cause) {
       
   628       super(cause);
       
   629     }
       
   630   }
       
   631 }