|
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 } |