1 /*****************************************************************
2  * Copyright (c) 2009, 2018 Texas Instruments and others
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     Patrick Chuong (Texas Instruments) - Initial API and implementation (Bug 238956)
13  *     IBM Corporation - ongoing enhancements and bug fixing
14  *****************************************************************/
15 package org.eclipse.debug.internal.ui.views.breakpoints;
16 
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23 
24 import org.eclipse.core.commands.operations.IUndoContext;
25 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.debug.core.DebugPlugin;
27 import org.eclipse.debug.core.IBreakpointManagerListener;
28 import org.eclipse.debug.core.model.IBreakpoint;
29 import org.eclipse.debug.internal.ui.DebugUIPlugin;
30 import org.eclipse.debug.internal.ui.IDebugHelpContextIds;
31 import org.eclipse.debug.internal.ui.VariablesViewModelPresentation;
32 import org.eclipse.debug.internal.ui.actions.breakpointGroups.PasteBreakpointsAction;
33 import org.eclipse.debug.internal.ui.actions.breakpointGroups.RemoveFromWorkingSetAction;
34 import org.eclipse.debug.internal.ui.actions.breakpoints.OpenBreakpointMarkerAction;
35 import org.eclipse.debug.internal.ui.actions.breakpoints.ShowTargetBreakpointsAction;
36 import org.eclipse.debug.internal.ui.actions.breakpoints.SkipAllBreakpointsAction;
37 import org.eclipse.debug.internal.ui.breakpoints.provisional.IBreakpointContainer;
38 import org.eclipse.debug.internal.ui.breakpoints.provisional.IBreakpointOrganizer;
39 import org.eclipse.debug.internal.ui.breakpoints.provisional.IBreakpointUIConstants;
40 import org.eclipse.debug.internal.ui.elements.adapters.DefaultBreakpointsViewInput;
41 import org.eclipse.debug.internal.ui.preferences.IDebugPreferenceConstants;
42 import org.eclipse.debug.internal.ui.viewers.model.VirtualFindAction;
43 import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
44 import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
45 import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
46 import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputUpdate;
47 import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
48 import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
49 import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
50 import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer;
51 import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer;
52 import org.eclipse.debug.internal.ui.views.DebugUIViewsMessages;
53 import org.eclipse.debug.internal.ui.views.variables.VariablesView;
54 import org.eclipse.debug.internal.ui.views.variables.details.AvailableDetailPanesAction;
55 import org.eclipse.debug.ui.DebugUITools;
56 import org.eclipse.debug.ui.IBreakpointOrganizerDelegateExtension;
57 import org.eclipse.debug.ui.IDebugModelPresentation;
58 import org.eclipse.debug.ui.IDebugUIConstants;
59 import org.eclipse.jface.action.IAction;
60 import org.eclipse.jface.action.IMenuManager;
61 import org.eclipse.jface.action.IToolBarManager;
62 import org.eclipse.jface.action.Separator;
63 import org.eclipse.jface.util.LocalSelectionTransfer;
64 import org.eclipse.jface.viewers.ISelection;
65 import org.eclipse.jface.viewers.IStructuredSelection;
66 import org.eclipse.jface.viewers.ITreeSelection;
67 import org.eclipse.jface.viewers.StructuredSelection;
68 import org.eclipse.jface.viewers.TreePath;
69 import org.eclipse.jface.viewers.Viewer;
70 import org.eclipse.swt.SWT;
71 import org.eclipse.swt.dnd.Clipboard;
72 import org.eclipse.swt.dnd.DND;
73 import org.eclipse.swt.dnd.Transfer;
74 import org.eclipse.swt.widgets.Composite;
75 import org.eclipse.swt.widgets.Display;
76 import org.eclipse.swt.widgets.TreeItem;
77 import org.eclipse.ui.IMemento;
78 import org.eclipse.ui.ISharedImages;
79 import org.eclipse.ui.IWorkbenchActionConstants;
80 import org.eclipse.ui.IWorkbenchCommandConstants;
81 import org.eclipse.ui.PlatformUI;
82 import org.eclipse.ui.actions.ActionFactory;
83 import org.eclipse.ui.actions.SelectionListenerAction;
84 import org.eclipse.ui.operations.RedoActionHandler;
85 import org.eclipse.ui.operations.UndoActionHandler;
86 
87 /**
88  * This class implements the breakpoints view.
89  */
90 public class BreakpointsView extends VariablesView implements IBreakpointManagerListener {
91 	private static final String ACTION_GOTO_MARKER				= "GotoMarker";				//$NON-NLS-1$
92 	private static final String ACTION_SKIP_BREAKPOINTS			= "SkipBreakpoints";		//$NON-NLS-1$
93 	private static final String ACTION_SHOW_MODEL_BREAKPOINT	= "ShowBreakpointsForModel";//$NON-NLS-1$
94 	private static final String ACTION_REMOVE_FROM_GROUP 		= "RemoveFromGroup"; 		//$NON-NLS-1$
95 
96 
97 	private static final String KEY_VALUE						= "value";					//$NON-NLS-1$
98 
99 	private Clipboard fClipboard;
100 	private IBreakpointOrganizer[] fOrganizers;
101 
102 	/**
103 	 * Flag used to determine whether the viewer input is being set for the
104 	 * fist time.  If this is the case the view contents are expanded.
105 	 * (bug 297762)
106 	 */
107 	private boolean fFirstInputSet = false;
108 
109 	private UndoActionHandler fUndoAction;
110 	private RedoActionHandler fRedoAction;
111 
112 
113 	@Override
dispose()114 	public void dispose() {
115 		if (fClipboard != null) {
116 			fClipboard.dispose();
117 		}
118 		DebugPlugin.getDefault().getBreakpointManager().removeBreakpointManagerListener(this);
119 
120 		fUndoAction.dispose();
121 		fRedoAction.dispose();
122 
123 		super.dispose();
124 	}
125 
126 	@Override
getDetailPanePreferenceKey()127 	protected String getDetailPanePreferenceKey() {
128 		return IDebugPreferenceConstants.BREAKPOINTS_DETAIL_PANE_ORIENTATION;
129 	}
130 
131 	@Override
getHelpContextId()132 	protected String getHelpContextId() {
133 		return IDebugHelpContextIds.BREAKPOINT_VIEW;
134 	}
135 
136 	@Override
getViewerStyle()137 	protected int getViewerStyle() {
138 		return SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.VIRTUAL | SWT.FULL_SELECTION | SWT.CHECK;
139 	}
140 
141 	@Override
createViewer(Composite parent)142 	public Viewer createViewer(Composite parent) {
143 		TreeModelViewer viewer = (TreeModelViewer) super.createViewer(parent);
144 
145 		initBreakpointOrganizers(getMemento());
146 
147 		IPresentationContext presentationContext = viewer.getPresentationContext();
148 		presentationContext.setProperty(IBreakpointUIConstants.PROP_BREAKPOINTS_ORGANIZERS, fOrganizers);
149 		presentationContext.setProperty(IBreakpointUIConstants.PROP_BREAKPOINTS_ELEMENT_COMPARATOR, new ElementComparator(presentationContext));
150 
151 		return viewer;
152 	}
153 
154 	@Override
getModelPresentation()155 	protected IDebugModelPresentation getModelPresentation() {
156 		if (fModelPresentation == null) {
157 			fModelPresentation = new VariablesViewModelPresentation() {
158 				/**
159 				 * Undo double slashes.
160 				 */
161 				@Override
162 				public String getText(Object element) {
163 					IDebugModelPresentation lp= getConfiguredPresentation(element);
164 					if (lp != null) {
165 						return lp.getText(element);
166 					}
167 					return getDefaultText(element);
168 				}
169 			};
170 		}
171 		return fModelPresentation;
172 	}
173 
174 	/**
175 	 * Returns the tree model viewer.
176 	 * @return the backin gviewer
177 	 */
getTreeModelViewer()178 	public TreeModelViewer getTreeModelViewer() {
179 		return (TreeModelViewer) getViewer();
180 	}
181 
182 	@Override
configureToolBar(IToolBarManager tbm)183 	protected void configureToolBar(IToolBarManager tbm) {
184 		tbm.add(new Separator(IDebugUIConstants.BREAKPOINT_GROUP));
185 		tbm.add(getAction(ACTION_SHOW_MODEL_BREAKPOINT));
186 		tbm.add(getAction(ACTION_GOTO_MARKER));
187 		tbm.add(getAction(ACTION_SKIP_BREAKPOINTS));
188 		tbm.add(new Separator(IDebugUIConstants.RENDER_GROUP));
189 	}
190 
191 	@Override
fillContextMenu(IMenuManager menu)192 	protected void fillContextMenu(IMenuManager menu) {
193 		updateObjects();
194 		menu.add(new Separator(IDebugUIConstants.EMPTY_NAVIGATION_GROUP));
195 		menu.add(new Separator(IDebugUIConstants.NAVIGATION_GROUP));
196 		menu.add(getAction(ACTION_GOTO_MARKER));
197 		menu.add(new Separator(IDebugUIConstants.EMPTY_BREAKPOINT_GROUP));
198 		menu.add(new Separator(IDebugUIConstants.BREAKPOINT_GROUP));
199 		menu.add(getAction(PASTE_ACTION));
200 		IAction action = getAction(ACTION_REMOVE_FROM_GROUP);
201 		if (action != null && action.isEnabled()) {
202 			menu.add(action);
203 		}
204 		menu.add(new Separator(IDebugUIConstants.EMPTY_RENDER_GROUP));
205 		action = new AvailableDetailPanesAction(this);
206 		if (isDetailPaneVisible() && action.isEnabled()) {
207 			menu.add(action);
208 		}
209 		menu.add(new Separator(IDebugUIConstants.BREAKPOINT_GROUP_GROUP));
210 
211 		menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
212 	}
213 
214 	@Override
createActions()215 	protected void createActions() {
216 		IAction action = new OpenBreakpointMarkerAction(getViewer());
217 		setAction(ACTION_GOTO_MARKER, action);
218 		setAction(DOUBLE_CLICK_ACTION, action);
219 		setAction(ACTION_SHOW_MODEL_BREAKPOINT, new ShowTargetBreakpointsAction(this));
220 		SkipAllBreakpointsAction skipAll = new SkipAllBreakpointsAction(this);
221 		setAction(ACTION_SKIP_BREAKPOINTS, skipAll);
222 		skipAll.setActionDefinitionId(SkipAllBreakpointsAction.ACTION_DEFINITION_ID);
223 		DebugPlugin.getDefault().getBreakpointManager().addBreakpointManagerListener(this);
224 
225 		fClipboard = new Clipboard(getSite().getShell().getDisplay());
226 
227 		PasteBreakpointsAction paste = new PasteBreakpointsAction(this);
228 		setAction(PASTE_ACTION, paste);
229 		paste.setActionDefinitionId(ActionFactory.PASTE.getCommandId());
230 		//actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), paste);
231 		setGlobalAction(PASTE_ACTION, paste);
232 		getViewer().addSelectionChangedListener(paste);
233 		paste.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_PASTE));
234 
235 		SelectionListenerAction remove = new RemoveFromWorkingSetAction(this);
236 		setAction(ACTION_REMOVE_FROM_GROUP, remove);
237 		getViewer().addSelectionChangedListener(remove);
238 
239 		IUndoContext undoContext= DebugUITools.getBreakpointsUndoContext();
240 		fUndoAction= new UndoActionHandler(getSite(), undoContext);
241 		fUndoAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_UNDO);
242 		fRedoAction= new RedoActionHandler(getSite(), undoContext);
243 		fRedoAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_REDO);
244 		//actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(), fUndoAction);
245 		//actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(), fRedoAction);
246 		setGlobalAction(ActionFactory.UNDO.getId(), fUndoAction);
247 		setGlobalAction(ActionFactory.REDO.getId(), fRedoAction);
248 		setGlobalAction(FIND_ACTION, new VirtualFindAction(getVariablesViewer()));
249 	}
250 
251 	@Override
getToggleActionLabel()252 	protected String getToggleActionLabel() {
253 		return DebugUIViewsMessages.BreakpointsView_12;
254 	}
255 
256 	@Override
getPresentationContextId()257 	protected String getPresentationContextId() {
258 		return IDebugUIConstants.ID_BREAKPOINT_VIEW;
259 	}
260 
261 	@Override
contextActivated(ISelection selection)262 	protected void contextActivated(ISelection selection) {
263 		if (!isAvailable() || !isVisible()) {
264 			return;
265 		}
266 
267 		IPresentationContext presentationContext = getTreeModelViewer().getPresentationContext();
268 
269 		if (selection == null || selection.isEmpty()) {
270 			Object input = new DefaultBreakpointsViewInput(presentationContext);
271 			super.contextActivated(new StructuredSelection(input));
272 		} else {
273 			super.contextActivated(selection);
274 		}
275 		if (isAvailable() && isVisible()) {
276 			updateAction("ContentAssist"); //$NON-NLS-1$
277 		}
278 	}
279 
280 	@Override
setViewerInput(Object context)281 	protected void setViewerInput(Object context) {
282 		Object current = getViewer().getInput();
283 		if (current == null && context == null) {
284 			return;
285 		}
286 
287 		if (current != null && current.equals(context)) {
288 			return;
289 		}
290 
291 		showViewer();
292 		getViewer().setInput(context);
293 
294 		// Expand all elements when the view is first shown. (bug 297762)
295 		if (!fFirstInputSet) {
296 			fFirstInputSet = true;
297 			expandAllElementsInViewer();
298 		}
299 	}
300 
301 	@Override
viewerInputUpdateComplete(IViewerInputUpdate update)302 	protected void viewerInputUpdateComplete(IViewerInputUpdate update) {
303 		// handles non-standard debug model
304 		IStatus status = update.getStatus();
305 		if ( (status == null || status.isOK()) && update.getElement() != null) {
306 			setViewerInput(update.getInputElement());
307 		} else {
308 			setViewerInput(new DefaultBreakpointsViewInput(getTreeModelViewer().getPresentationContext()));
309 		}
310 	}
311 
312 
313 	/**
314 	 * Returns whether this view is currently tracking the selection from the debug view.
315 	 *
316 	 * @return whether this view is currently tracking the debug view's selection
317 	 */
isTrackingSelection()318 	public boolean isTrackingSelection() {
319 		final TreeModelViewer viewer = getTreeModelViewer();
320 		if (viewer != null) {
321 			return Boolean.TRUE.equals(
322 				viewer.getPresentationContext().getProperty(IBreakpointUIConstants.PROP_BREAKPOINTS_TRACK_SELECTION) );
323 		}
324 		return false;
325 	}
326 
327 	/**
328 	 * Sets whether this view should track the selection from the debug view.
329 	 *
330 	 * @param trackSelection whether or not this view should track the debug view's selection.
331 	 */
setTrackSelection(boolean trackSelection)332 	public void setTrackSelection(boolean trackSelection) {
333 		// set the track selection property for non-standard model to track the debug context
334 		final TreeModelViewer viewer = getTreeModelViewer();
335 		if (viewer != null) {
336 			viewer.getPresentationContext().setProperty(
337 				IBreakpointUIConstants.PROP_BREAKPOINTS_TRACK_SELECTION,
338 				trackSelection ? Boolean.TRUE : Boolean.FALSE);
339 		}
340 	}
341 
342 	/**
343 	 * Initializes the persisted breakpoints organizers.
344 	 * @param memento the memento to read
345 	 */
initBreakpointOrganizers(IMemento memento)346 	private void initBreakpointOrganizers(IMemento memento) {
347 		if (memento != null) {
348 			IMemento node = memento.getChild(IDebugUIConstants.EXTENSION_POINT_BREAKPOINT_ORGANIZERS);
349 			if (node == null) {
350 				fOrganizers = null;
351 			} else {
352 				String value = node.getString(KEY_VALUE);
353 				if (value != null) {
354 					BreakpointOrganizerManager manager = BreakpointOrganizerManager.getDefault();
355 					List<IBreakpointOrganizer> organizers = new ArrayList<>();
356 					for (String id : value.split(",")) { //$NON-NLS-1$
357 						IBreakpointOrganizer organizer = manager.getOrganizer(id);
358 						if (organizer != null) {
359 							organizers.add(organizer);
360 						}
361 					}
362 					fOrganizers = organizers.toArray(new IBreakpointOrganizer[organizers.size()]);
363 
364 					for (IBreakpointOrganizer organizer : fOrganizers) {
365 						organizer.addPropertyChangeListener(this);
366 					}
367 				}
368 			}
369 		}
370 	}
371 
372 	/**
373 	 * Initializes drag and drop for the breakpoints viewer
374 	 * @param viewer the viewer to add drag and drop support to
375 	 */
376 	@Override
initDragAndDrop(TreeModelViewer viewer)377 	protected void initDragAndDrop(TreeModelViewer viewer) {
378 		int ops = DND.DROP_MOVE | DND.DROP_COPY;
379 		// drop
380 		viewer.addDropSupport(ops, new Transfer[] {LocalSelectionTransfer.getTransfer()}, new BreakpointsDropAdapter(viewer, this));
381 		// Drag
382 		viewer.addDragSupport(ops, new Transfer[] {LocalSelectionTransfer.getTransfer()}, new BreakpointsDragAdapter(viewer, this));
383 	}
384 
385 	@Override
saveViewerState(IMemento memento)386 	public void saveViewerState(IMemento memento) {
387 		StringBuilder buffer = new StringBuilder();
388 		if (fOrganizers != null) {
389 			for (int i = 0; i < fOrganizers.length; i++) {
390 				IBreakpointOrganizer organizer = fOrganizers[i];
391 				buffer.append(organizer.getIdentifier());
392 				if (i < (fOrganizers.length - 1)) {
393 					buffer.append(',');
394 				}
395 			}
396 			IMemento node = memento.createChild(IDebugUIConstants.EXTENSION_POINT_BREAKPOINT_ORGANIZERS);
397 			node.putString(KEY_VALUE, buffer.toString());
398 		}
399 		super.saveViewerState(memento);
400 	}
401 
402 	/**
403 	 * Preserves the selection.
404 	 *
405 	 * @param selection the selection
406 	 */
preserveSelection(IStructuredSelection selection)407 	public void preserveSelection(IStructuredSelection selection) {
408 		if (selection instanceof ITreeSelection && !selection.isEmpty()) {
409 			TreePath path = ((ITreeSelection) selection).getPaths()[0];
410 			TreeItem item = (TreeItem) ((TreeModelViewer) getViewer()).findItem(path);
411 			Object toselect = null;
412 			TreeItem[] siblings = null;
413 			if (item != null) {
414 				TreeItem parent = item.getParentItem();
415 				if (parent != null) {
416 					siblings = parent.getItems();
417 				} else {
418 					siblings = item.getParent().getItems();
419 				}
420 				if (siblings.length > 1) {
421 					for (int i = 0; i < siblings.length; i++) {
422 						if (item.equals(siblings[i])) {
423 							if (i + 1 >= siblings.length) {
424 								toselect = siblings[i - 1].getData();
425 								break;
426 							} else {
427 								toselect = siblings[i + 1].getData();
428 								break;
429 							}
430 
431 						}
432 					}
433 				}
434 			}
435 			if (toselect != null) {
436 				getViewer().setSelection(new StructuredSelection(toselect),true);
437 			}
438 		}
439 	}
440 
441 	/**
442 	 * Sets the breakpoint organizers for this view.
443 	 *
444 	 * @param organizers the organizers, can be <code>null</code>.
445 	 */
setBreakpointOrganizers(IBreakpointOrganizer[] organizers)446 	public void setBreakpointOrganizers(IBreakpointOrganizer[] organizers) {
447 		fOrganizers = organizers;
448 
449 		TreeModelViewer viewer = getTreeModelViewer();
450 		if (viewer != null) {
451 			// update the presentation context organizer
452 			viewer.getPresentationContext().setProperty(IBreakpointUIConstants.PROP_BREAKPOINTS_ORGANIZERS, fOrganizers);
453 		}
454 		System.out.println();
455 	}
456 
457 	/**
458 	 * Sets the breakpoint filter for this view.
459 	 * @param filter the selection to act as a filter
460 	 */
setFilterSelection(boolean filter)461 	public void setFilterSelection(boolean filter) {
462 		TreeModelViewer viewer = getTreeModelViewer();
463 		if (viewer != null) {
464 			// update the presentation context filter
465 			viewer.getPresentationContext().setProperty(
466 				IBreakpointUIConstants.PROP_BREAKPOINTS_FILTER_SELECTION, filter ? Boolean.TRUE : Boolean.FALSE);
467 		}
468 	}
469 
470 	@Override
breakpointManagerEnablementChanged(boolean enabled)471 	public void breakpointManagerEnablementChanged(boolean enabled) {
472 		DebugUIPlugin.getStandardDisplay().asyncExec(() -> {
473 			IAction action = getAction(ACTION_SKIP_BREAKPOINTS);
474 			if (action != null) {
475 				((SkipAllBreakpointsAction) action).updateActionCheckedState();
476 			}
477 		});
478 	}
479 
480 	/**
481 	 * Expands all elements in the viewer.
482 	 */
expandAllElementsInViewer()483 	public void expandAllElementsInViewer() {
484 		Display display = getSite().getShell().getDisplay();
485 
486 		final VirtualTreeModelViewer virtualViewer = new VirtualTreeModelViewer(
487 			display, 0, ((ITreeModelViewer)getViewer()).getPresentationContext());
488 
489 		virtualViewer.setAutoExpandLevel(-1);
490 		virtualViewer.addViewerUpdateListener(new IViewerUpdateListener() {
491 			@Override
492 			public void viewerUpdatesComplete() {
493 				ModelDelta stateDelta = new ModelDelta(virtualViewer.getInput(), IModelDelta.NO_CHANGE);
494 				virtualViewer.saveElementState(TreePath.EMPTY, stateDelta, IModelDelta.EXPAND);
495 				ITreeModelViewer treeModelViewer = ((ITreeModelViewer) getViewer());
496 				if (treeModelViewer != null) {
497 					((ITreeModelViewer) getViewer()).updateViewer(stateDelta);
498 				}
499 				virtualViewer.dispose();
500 			}
501 			@Override
502 			public void viewerUpdatesBegin() {}
503 			@Override
504 			public void updateStarted(IViewerUpdate update) {}
505 			@Override
506 			public void updateComplete(IViewerUpdate update) {}
507 		});
508 		virtualViewer.setInput(getViewer().getInput());
509 	}
510 
511 
512 	/**
513 	 * Returns the breakpoint organizers for this view.
514 	 *
515 	 * @return the breakpoint organizers.
516 	 */
getBreakpointOrganizers()517 	public IBreakpointOrganizer[] getBreakpointOrganizers() {
518 		return fOrganizers;
519 	}
520 
521 	/**
522 	 * Returns whether the given selection can be pasted into the given target.
523 	 * <p>
524 	 * Scheme:
525 	 * <ul>
526 	 * <li>Breakpoints can only be pasted into allowable containers (i..e. like workings sets)</li>
527 	 * <li>Breakpoints can only be pasted into containers that they do not already reside in</li>
528 	 * <li>Breakpoints can only be pasted into containers, not other breakpoints</li>
529 	 * </ul>
530 	 * </p>
531 	 *
532 	 * @param target target of the paste
533 	 * @param selection the selection to paste
534 	 * @return whether the given selection can be pasted into the given target
535 	 *
536 	 * TODO Remove in favor of using <code>TreeItem</code>s and <code>TreePath</code>s to determine paste targets
537 	 */
canPaste(Object target, ISelection selection)538 	public boolean canPaste(Object target, ISelection selection) {
539 		if(!(target instanceof IBreakpointContainer) || !(selection instanceof IStructuredSelection)) {
540 			return false;
541 		}
542 		if(selection == null || selection.isEmpty()) {
543 			return false;
544 		}
545 		IStructuredSelection ss = (IStructuredSelection) selection;
546 		IBreakpointContainer container = (IBreakpointContainer) target;
547 		for (Iterator<?> iter = ss.iterator(); iter.hasNext();) {
548 			IBreakpoint breakpoint = (IBreakpoint)DebugPlugin.getAdapter(iter.next(), IBreakpoint.class);
549 			if (breakpoint == null || container.contains(breakpoint) || !container.getOrganizer().canAdd(breakpoint, container.getCategory())) {
550 				return false;
551 			}
552 		}
553 		return true;
554 	}
555 
556 	/**
557 	 * Pastes the selection into the given target
558 	 *
559 	 * @param target target of the paste, either a IBreakpointContainer,
560 	 * or a Breakpoint within a IBreakpointContainer
561 	 * @param selection breakpoints
562 	 * @return whether successful
563 	 *
564 	 * TODO remove in favor of using <code>TreeItem</code> as paste target
565 	 */
performPaste(Object target, ISelection selection)566 	public boolean performPaste(Object target, ISelection selection) {
567 		if (target instanceof IBreakpointContainer && selection instanceof IStructuredSelection) {
568 			IBreakpointContainer container = (IBreakpointContainer) target;
569 			for (Object object : (IStructuredSelection) selection) {
570 				IBreakpoint breakpoint = (IBreakpoint)DebugPlugin.getAdapter(object, IBreakpoint.class);
571 				if (breakpoint != null) {
572 					container.getOrganizer().addBreakpoint(breakpoint, container.getCategory());
573 				}
574 			}
575 			return true;
576 		}
577 		return false;
578 	}
579 
580 	/**
581 	 * Returns the container from within the specified path that is the container the breakpoint can be removed from
582 	 * @param path the path to get the container from
583 	 * @return the first found container that includes the breakpoint that allows removal, or <code>null</code> if none found
584 	 * @since 3.3
585 	 */
getRemovableContainer(TreePath path)586 	public IBreakpointContainer getRemovableContainer(TreePath path) {
587 		if (path != null) {
588 			IBreakpoint breakpoint = (IBreakpoint)DebugPlugin.getAdapter(path.getLastSegment(), IBreakpoint.class);
589 			if (breakpoint != null) {
590 				IBreakpointContainer container = null;
591 				for(int i = path.getSegmentCount()-2; i > -1; i--) {
592 					Object segment = path.getSegment(i);
593 					if (segment instanceof IBreakpointContainer) {
594 						container = (IBreakpointContainer) segment;
595 						if(container.contains(breakpoint) &&
596 							container.getOrganizer() != null &&
597 							container.getOrganizer().canRemove(breakpoint, container.getCategory())) {
598 							return container;
599 						}
600 					}
601 				}
602 			}
603 		}
604 		return null;
605 	}
606 
607 	/**
608 	 * Returns the addable breakpoint container of the specified tree path
609 	 * @param path the path to get the container for
610 	 * @return the first found addable container for the specified tree path or <code>null</code> if none found
611 	 * @since 3.3
612 	 */
getAddableContainer(TreePath path)613 	protected IBreakpointContainer getAddableContainer(TreePath path) {
614 		if (path != null) {
615 			Object element = path.getLastSegment();
616 			if (element instanceof IBreakpointContainer) {
617 				return (IBreakpointContainer)element;
618 			}
619 			IBreakpoint breakpoint = (IBreakpoint)DebugPlugin.getAdapter(element, IBreakpoint.class);
620 			if (breakpoint != null) {
621 				IBreakpointContainer container = null;
622 				for (int i = path.getSegmentCount()-2; i > -1; i--) {
623 					Object segment = path.getSegment(i);
624 					if (segment instanceof IBreakpointContainer) {
625 						container = (IBreakpointContainer) segment;
626 						if (container.contains(breakpoint) && container.getOrganizer().canAdd(breakpoint, container.getCategory())) {
627 							return container;
628 						}
629 					}
630 				}
631 			}
632 		}
633 		return null;
634 	}
635 	/**
636 	 * This method is used to determine if there is an addable parent container available for the specified drop target.
637 	 * <p>
638 	 * A drop target can be either a <code>IBreakpointContainer</code> or an <code>IBreakpoint</code>. This method always checks the entire hierarchy
639 	 * of the tree path for the specified target in the event one of the parent element does not support dropping.
640 	 * </p>
641 	 * @param path the path
642 	 * @param breakpoint the breakpoint
643 	 * @return <code>true</code> if there is a parent container available for the drop target <code>false</code> otherwise
644 	 */
checkAddableParentContainers(TreePath path, IBreakpoint breakpoint)645 	private boolean checkAddableParentContainers(TreePath path, IBreakpoint breakpoint) {
646 		if (path != null) {
647 			Object element = null;
648 			for (int i = path.getSegmentCount()-1; i > -1; i--) {
649 				element = path.getSegment(i);
650 				if (element instanceof IBreakpointContainer) {
651 					IBreakpointContainer container = (IBreakpointContainer) element;
652 					if (container.contains(breakpoint) || !container.getOrganizer().canAdd(breakpoint, container.getCategory())) {
653 						return false;
654 					}
655 				}
656 			}
657 		}
658 		return true;
659 	}
660 
661 	/**
662 	 * Returns if the selected item in the tree can be dragged
663 	 * <p>
664 	 * Scheme:
665 	 * <ul>
666 	 * <li>breakpoint containers cannot be dragged</li>
667 	 * <li>breakpoints can be dragged iff the container they reside in supports the removal of breakpoints</li>
668 	 * </ul>
669 	 * </p>
670 	 * @param items the tree paths to check if they can be dragged
671 	 * @return true if the selected element can be dragged, false otherwise
672 	 * @since 3.3
673 	 */
canDrag(TreePath[] items)674 	boolean canDrag(TreePath[] items) {
675 		if(items == null) {
676 			return false;
677 		}
678 		if (items.length == 0) {
679 			return false;
680 		}
681 		for (TreePath item : items) {
682 			if (getRemovableContainer(item) == null) {
683 				return false;
684 			}
685 		}
686 		return true;
687 	}
688 
689 	/**
690 	 * Performs the actual removal of breakpoints from their respective (removable) containers on a successful drag operation
691 	 * @param paths the tree paths to drag
692 	 * @since 3.3
693 	 */
performDrag(TreePath[] paths)694 	void performDrag(TreePath[] paths) {
695 		if (paths == null) {
696 			return;
697 		}
698 		Map<IBreakpointContainer, List<IBreakpoint>> containersToBreakpoints = new HashMap<>();
699 		for (TreePath path : paths) {
700 			IBreakpoint breakpoint = (IBreakpoint)DebugPlugin.getAdapter(path.getLastSegment(), IBreakpoint.class);
701 			if (breakpoint != null) {
702 				IBreakpointContainer container = getRemovableContainer(path);
703 				if(container != null) {
704 					List<IBreakpoint> list = containersToBreakpoints.get(container);
705 					if (list == null) {
706 						list = new ArrayList<>();
707 						containersToBreakpoints.put(container, list);
708 					}
709 					list.add(breakpoint);
710 				}
711 			}
712 		}
713 		for (Entry<IBreakpointContainer, List<IBreakpoint>> entry : containersToBreakpoints.entrySet()) {
714 			IBreakpointContainer container = entry.getKey();
715 			List<IBreakpoint> list = entry.getValue();
716 			IBreakpointOrganizer organizer = container.getOrganizer();
717 			IBreakpoint[] breakpoints = list.toArray(new IBreakpoint[list.size()]);
718 			if (organizer instanceof IBreakpointOrganizerDelegateExtension) {
719 				IBreakpointOrganizerDelegateExtension extension = (IBreakpointOrganizerDelegateExtension) organizer;
720 				extension.removeBreakpoints(breakpoints, container.getCategory());
721 			} else {
722 				for (IBreakpoint breakpoint : breakpoints) {
723 					organizer.removeBreakpoint(breakpoint, container.getCategory());
724 				}
725 			}
726 		}
727 	}
728 
729 	/**
730 	 * Performs the actual addition of the selected breakpoints to the specified target
731 	 * @param target the target to add the selection of breakpoints to
732 	 * @param selection the selection of breakpoints
733 	 * @return true if the drop occurred, false otherwise
734 	 * @since 3.3
735 	 */
performDrop(TreePath target, ITreeSelection selection)736 	protected boolean performDrop(TreePath target, ITreeSelection selection) {
737 		if(target == null || selection == null) {
738 			return false;
739 		}
740 		IBreakpointContainer container = getAddableContainer(target);
741 		if (container == null) {
742 			return false;
743 		}
744 
745 		IBreakpointOrganizer organizer = container.getOrganizer();
746 		List<IBreakpoint> breakpoints = new ArrayList<>(selection.size());
747 		for (Iterator<?> iter = selection.iterator(); iter.hasNext();) {
748 			IBreakpoint breakpoint = (IBreakpoint) DebugPlugin.getAdapter(iter.next(), IBreakpoint.class);
749 			if (breakpoint != null) {
750 				breakpoints.add(breakpoint);
751 			}
752 		}
753 		if (organizer instanceof IBreakpointOrganizerDelegateExtension) {
754 			IBreakpointOrganizerDelegateExtension extension = (IBreakpointOrganizerDelegateExtension) organizer;
755 			extension.addBreakpoints(
756 				breakpoints.toArray(new IBreakpoint[breakpoints.size()]),
757 				container.getCategory());
758 		} else {
759 			for (IBreakpoint breakpoint : breakpoints) {
760 				organizer.addBreakpoint(breakpoint, container.getCategory());
761 			}
762 		}
763 		// TODO expandToLevel(target.getData(), ALL_LEVELS);
764 		return true;
765 	}
766 
767 	/**
768 	 * Determines if the specified element can be dropped into the specified target
769 	 * <p>
770 	 * Scheme:
771 	 * <ul>
772 	 * <li>Breakpoints can be dropped into working sets</li>
773 	 * <li>Breakpoints can be dropped into breakpoints, provided there is a drop-able parent of the target breakpoint</li>
774 	 * </ul>
775 	 * </p>
776 	 * @param target the target for the drop
777 	 * @param selection the selection to see if we can drop
778 	 * @return true if the specified element can be dropped into the specified target, false otherwise
779 	 * @since 3.3
780 	 */
canDrop(TreePath target, ITreeSelection selection)781 	boolean canDrop(TreePath target, ITreeSelection selection) {
782 		if (selection == null  || target == null) {
783 			return false;
784 		}
785 		for (Iterator<?> iter = selection.iterator(); iter.hasNext();) {
786 			IBreakpoint breakpoint = (IBreakpoint)DebugPlugin.getAdapter(iter.next(), IBreakpoint.class);
787 			if (breakpoint == null || !checkAddableParentContainers(target, breakpoint)){
788 				return false;
789 			}
790 		}
791 		return true;
792 	}
793 }
794