/*******************************************************************************
* Copyright (c) 2006, 2008 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.erlide.debug.ui.utils;
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.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;
import org.erlide.backend.debug.ErlDebugConstants;
import org.erlide.backend.debug.ErlangLineBreakpoint;
import org.erlide.backend.debug.IErlangBreakpoint;
import org.erlide.backend.debug.model.ErlangDebugTarget;
import org.erlide.util.ErlLogger;
/**
* 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() {
}
@Override
public String[] getAttribute() {
return new String[] { IMarker.LINE_NUMBER };
}
@Override
public String getMarkerType() {
return "org.eclipse.debug.core.breakpointMarker"; //$NON-NLS-1$
}
@Override
public boolean updateMarker(final IMarker marker, final IDocument document,
final Position position) {
if (position.isDeleted()) {
return false;
}
try {
final int line = MarkerUtilities.getLineNumber(marker);
final int newLine = document.getLineOfOffset(position.getOffset()) + 1;
if (line == newLine) {
return true;
}
final IBreakpointManager manager = DebugPlugin.getDefault()
.getBreakpointManager();
final IBreakpoint breakpoint = manager.getBreakpoint(marker);
if (breakpoint == null) {
return false;
}
if (breakpoint instanceof ErlangLineBreakpoint) {
final ErlangLineBreakpoint erlangLineBreakpoint = (ErlangLineBreakpoint) breakpoint;
final ErlangDebugTarget target = erlangLineBreakpoint.getTarget();
erlangLineBreakpoint.remove(target);
MarkerUtilities.setLineNumber(marker, newLine);
erlangLineBreakpoint.install(target);
return true;
}
// if there exists a breakpoint on the line remove this one
if (isLineBreakpointMarker(marker)) {
ensureRanges(document, marker, line);
return lineBreakpointExists(marker.getResource(), line, marker) == null;
}
// if the line info is a valid location with an invalid line
// number,
// a line breakpoint must be removed
if (isLineBreakpointMarker(marker) && line == -1) {
return false;
}
MarkerUtilities.setLineNumber(marker, line);
if (isLineBreakpointMarker(marker)) {
ensureRanges(document, marker, line);
}
return true;
} catch (final BadLocationException e) {
ErlLogger.error(e);
} catch (final CoreException e) {
ErlLogger.error(e);
}
return false;
}
private static boolean isLineBreakpointMarker(final IMarker marker)
throws CoreException {
return marker
.getType() == ErlangLineBreakpoint.ERLANG_LINE_BREAKPOINT_MARKER_TYPE;
}
/**
* 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(final IDocument document, final IMarker marker,
final int line) throws BadLocationException {
if (line < 0 || line > document.getNumberOfLines()) {
return;
}
final IRegion region = document.getLineInformation(line - 1);
final int charstart = region.getOffset();
final int charend = charstart + region.getLength();
MarkerUtilities.setCharStart(marker, charstart);
MarkerUtilities.setCharEnd(marker, charend);
}
/**
* 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 IErlangBreakpoint lineBreakpointExists(final IResource resource,
final int lineNumber, final IMarker currentmarker) throws CoreException {
final IBreakpointManager manager = DebugPlugin.getDefault()
.getBreakpointManager();
final IBreakpoint[] breakpoints = manager
.getBreakpoints(ErlDebugConstants.ID_ERLANG_DEBUG_MODEL);
final String markerType = currentmarker.getType();
for (int i = 0; i < breakpoints.length; i++) {
if (!(breakpoints[i] instanceof IErlangBreakpoint)) {
continue;
}
final IErlangBreakpoint breakpoint = (IErlangBreakpoint) breakpoints[i];
final IMarker marker = breakpoint.getMarker();
if (marker != null && marker.exists() && marker.getType().equals(markerType)
&& currentmarker.getId() != marker.getId()) {
if (marker instanceof ErlangLineBreakpoint) {
final ErlangLineBreakpoint erlangLineBreakpoint = (ErlangLineBreakpoint) marker;
if (erlangLineBreakpoint.getLineNumber() == lineNumber) {
return erlangLineBreakpoint;
}
}
}
}
return null;
}
}