/******************************************************************************* * Copyright (c) 2006, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.debug.ui; 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.debug.core.DebugPlugin; import org.eclipse.debug.core.IBreakpointManager; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; import org.eclipse.jdt.debug.core.IJavaPatternBreakpoint; import org.eclipse.jdt.debug.core.IJavaStratumLineBreakpoint; import org.eclipse.jdt.debug.core.IJavaWatchpoint; import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; import org.eclipse.jdt.internal.debug.core.JavaDebugUtils; import org.eclipse.jdt.internal.debug.core.breakpoints.JavaLineBreakpoint; import org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator; import org.eclipse.jdt.ui.SharedASTProvider; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Position; import org.eclipse.ui.texteditor.IMarkerUpdater; import org.eclipse.ui.texteditor.MarkerUtilities; /** * This class provides a mechanism to correct the placement of a * breakpoint marker when the related document is edited. * * This updater is used to cover the line number discrepancy cases that <code>BasicMarkerUpdater</code> does not: * <ul> * <li>If you insert a blank line at the start of the line of code, the breakpoint * is moved from the blank line to the next viable line down, * following the same breakpoint placement rules as creating a breakpoint</li> * * <li>If you select the contents of an entire line and delete them * (leaving the line blank), the breakpoint is moved to the next viable line down, * following the same breakpoint placement rules as creating a breakpoint</li> * * <li>If the breakpoint is on the last viable line of a class file and the line is removed via either of * the aforementioned deletion cases, the breakpoint is removed</li> * * <li>If a line breakpoint would be moved to a valid method location with an invalid line number it is removed, * see {@link https://bugs.eclipse.org/bugs/show_bug.cgi?id=188676} for details</li> * * <li>If a line breakpoint will be moved to a line that already has a line breakpoint on it, the one * being moved is removed, see {@link https://bugs.eclipse.org/bugs/show_bug.cgi?id=129066} for details</li> * * <li>In the general deletion case if a valid breakpoint location can not be determined, it is removed</li> * </ul> * * @since 3.3 */ public class BreakpointMarkerUpdater implements IMarkerUpdater { public BreakpointMarkerUpdater() {} /* (non-Javadoc) * @see org.eclipse.ui.texteditor.IMarkerUpdater#getAttribute() */ @Override public String[] getAttribute() { return new String[] {IMarker.LINE_NUMBER}; } /* (non-Javadoc) * @see org.eclipse.ui.texteditor.IMarkerUpdater#getMarkerType() */ @Override public String getMarkerType() { return "org.eclipse.debug.core.breakpointMarker"; //$NON-NLS-1$ } /* (non-Javadoc) * @see org.eclipse.ui.texteditor.IMarkerUpdater#updateMarker(org.eclipse.core.resources.IMarker, org.eclipse.jface.text.IDocument, org.eclipse.jface.text.Position) */ @Override public boolean updateMarker(IMarker marker, IDocument document, Position position) { if(position.isDeleted()) { return false; } IBreakpointManager manager = DebugPlugin.getDefault().getBreakpointManager(); IBreakpoint breakpoint = manager.getBreakpoint(marker); if(breakpoint == null) { return false; } if (breakpoint instanceof IJavaStratumLineBreakpoint || breakpoint instanceof IJavaPatternBreakpoint) { return true; } ICompilationUnit cunit = JavaCore.createCompilationUnitFrom((IFile) marker.getResource()); if(cunit == null) { return false; } CompilationUnit unit = SharedASTProvider.getAST(cunit, SharedASTProvider.WAIT_YES, null); if(unit == null) { //remove it - in case it would be left in a bad location return false; } try { ValidBreakpointLocationLocator loc = new ValidBreakpointLocationLocator(unit, document.getLineOfOffset(position.getOffset())+1, true, true); unit.accept(loc); if(loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_NOT_FOUND) { return false; } // Remove the watch point if it is not a valid watch point now if (loc.getLocationType() != ValidBreakpointLocationLocator.LOCATION_FIELD && breakpoint instanceof IJavaWatchpoint) { return false; } int line = loc.getLineLocation(); //if the line number is already good, perform no marker updating if(MarkerUtilities.getLineNumber(marker) == line) { //if there exists a breakpoint on the line remove this one if(isLineBreakpoint(marker)) { ensureRanges(document, marker, line); return lineBreakpointExists(marker.getResource(), ((IJavaLineBreakpoint)breakpoint).getTypeName(), line, marker) == null; } return true; } //if the line info is a valid location with an invalid line number, //a line breakpoint must be removed if(isLineBreakpoint(marker) & line == -1) { return false; } MarkerUtilities.setLineNumber(marker, line); if(isLineBreakpoint(marker)) { ensureRanges(document, marker, line); } return true; } catch (BadLocationException e) {JDIDebugUIPlugin.log(e);} catch (CoreException e) {JDIDebugUIPlugin.log(e);} return false; } /** * Updates the charstart and charend ranges if necessary for the given line. * Returns immediately if the line is not valid (< 0 or greater than the total line number count) * @param document * @param marker * @param line * @throws BadLocationException */ private void ensureRanges(IDocument document, IMarker marker, int line) throws BadLocationException { if(line < 0 || line > document.getNumberOfLines()) { return; } IRegion region = document.getLineInformation(line - 1); int charstart = region.getOffset(); int charend = charstart + region.getLength(); MarkerUtilities.setCharStart(marker, charstart); MarkerUtilities.setCharEnd(marker, charend); } /** * Returns if the specified marker is for an <code>IJavaLineBreakpoint</code> * @param marker * @return true if the marker is for an <code>IJavalineBreakpoint</code>, false otherwise * * @since 3.4 */ private boolean isLineBreakpoint(IMarker marker) { return MarkerUtilities.isMarkerType(marker, "org.eclipse.jdt.debug.javaLineBreakpointMarker"); //$NON-NLS-1$ } /** * Searches for an existing line breakpoint on the specified line in the current type that does not match the id of the specified marker * @param resource the resource to care about * @param typeName the name of the type the breakpoint is in * @param lineNumber the number of the line the breakpoint is on * @param currentmarker the current marker we are comparing to see if it will be moved onto an existing one * @return an existing line breakpoint on the current line of the given resource and type if there is one * @throws CoreException * * @since 3.4 */ private IJavaLineBreakpoint lineBreakpointExists(IResource resource, String typeName, int lineNumber, IMarker currentmarker) throws CoreException { String modelId = JDIDebugPlugin.getUniqueIdentifier(); String markerType= JavaLineBreakpoint.getMarkerType(); IBreakpointManager manager= DebugPlugin.getDefault().getBreakpointManager(); IBreakpoint[] breakpoints= manager.getBreakpoints(modelId); for (int i = 0; i < breakpoints.length; i++) { if (!(breakpoints[i] instanceof IJavaLineBreakpoint)) { continue; } IJavaLineBreakpoint breakpoint = (IJavaLineBreakpoint) breakpoints[i]; IMarker marker = breakpoint.getMarker(); if (marker != null && marker.exists() && marker.getType().equals(markerType) && currentmarker.getId() != marker.getId()) { String breakpointTypeName = breakpoint.getTypeName(); if ((JavaDebugUtils.typeNamesEqual(breakpointTypeName, typeName) || (breakpointTypeName != null && breakpointTypeName.startsWith(typeName + '$'))) && breakpoint.getLineNumber() == lineNumber && resource.equals(marker.getResource())) { return breakpoint; } } } return null; } }