diff options
Diffstat (limited to 'org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractMarkerAnnotationModel.java')
-rw-r--r-- | org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractMarkerAnnotationModel.java | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractMarkerAnnotationModel.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractMarkerAnnotationModel.java new file mode 100644 index 00000000000..d84e2bf2167 --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractMarkerAnnotationModel.java @@ -0,0 +1,571 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.ui.texteditor; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.AnnotationModel; +import org.eclipse.jface.util.Assert; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtensionPoint; +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; + +import org.eclipse.ui.PlatformUI; + + +/** + * Abstract implementation of a marker-based annotation model. + * <p> + * Markers are provided by an underlying source (a subclass responsibility). + * Markers whose textual range gets deleted during text editing are removed + * from the model on save. The <code>updateMarkers</code> method can be used + * to force the model to update the source's markers with any changes to their + * locations due to edits. Clients can register <code>IMarkerUpdater</code> + * objects in order to define the process of marker updating. Registration can be done + * using the <code>"org.eclipse.ui.markerUpdaters"</code> extension point. + * <p> + * Subclasses must implement the following methods: + * <ul> + * <li><code>retrieveMarkers</code></li> + * <li><code>isAcceptable</code></li> + * <li><code>deleteMarkers</code></li> + * <li><code>listenToMarkerChanges</code></li> + * </ul> + * </p> + */ +public abstract class AbstractMarkerAnnotationModel extends AnnotationModel { + + /** List of annotations whose text range became invalid because of document changes */ + private List fDeletedAnnotations= new ArrayList(2); + /** Reference counters to track how often an annotation model is connected to its document */ + private HashMap fRefcountTable= new HashMap(); + /** List of registered and instantiated marker updaters */ + private List fInstantiatedMarkerUpdaters= null; + /** List of registered but not yet instantiated marker updaters */ + private List fMarkerUpdaterSpecifications= null; + + + /** + * Retrieves all markers from this model.<p> + * Subclasses must implement this method. + * + * @return the list of markers + * @exception CoreException if there is a problem getting the markers + */ + protected abstract IMarker[] retrieveMarkers() throws CoreException; + + /** + * Deletes the given markers from this model.<p> + * Subclasses must implement this method. + * + * @param markers the list of markers + * @exception CoreException if there are problems deleting the markers + */ + protected abstract void deleteMarkers(IMarker[] markers) throws CoreException; + + /** + * Tells the model whether it should listen for marker changes. <p> + * Subclasses must implement this method. + * + * @param listen <code>true</code> if this model should listen, and + * <code>false</code> otherwise + */ + protected abstract void listenToMarkerChanges(boolean listen); + + /** + * Determines whether the marker is acceptable as an addition to this model. + * If the marker, say, represents an aspect or range of no interest to this + * model, the marker is rejected.<p> + * Subclasses must implement this method. + * + * @param marker the marker + * @return <code>true</code> if the marker is acceptable + */ + protected abstract boolean isAcceptable(IMarker marker); + + /** + * Creates a new annotation model. The annotation model does not manage any + * annotations and is not connected to any document. + */ + protected AbstractMarkerAnnotationModel() { + } + + /** + * Adds the given marker updater to this annotation model. + * It is client's responsibility to ensure the consitency of the + * set of registered marker updaters. + * + * @param markerUpdater the marker updater to be added + */ + protected void addMarkerUpdater(IMarkerUpdater markerUpdater) { + if (!fInstantiatedMarkerUpdaters.contains(markerUpdater)) + fInstantiatedMarkerUpdaters.add(markerUpdater); + } + + /** + * Removes the given marker updater from this annotation model. + * + * @param markerUpdater the marker updater to be removed + */ + protected void removeMarkerUpdater(IMarkerUpdater markerUpdater) { + fInstantiatedMarkerUpdaters.remove(markerUpdater); + } + + /** + * Creates a new annotation for the given marker.<p> + * Subclasses may override. + * + * @param marker the marker + * @return the new marker annotation + */ + protected MarkerAnnotation createMarkerAnnotation(IMarker marker) { + return new MarkerAnnotation(marker); + } + + /** + * Handles an unanticipated <code>CoreException</code> in + * a standard manner. + * + * @param exception the exception + * @param message a message to aid debugging + */ + protected void handleCoreException(CoreException exception, String message) { + + ILog log= Platform.getPlugin(PlatformUI.PLUGIN_ID).getLog(); + + if (message != null) + log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, 0, message, null)); + + log.log(exception.getStatus()); + } + + /** + * Creates and returns the character position of the given marker based + * on its attributes. <p> + * Subclasses may override. + * + * @param marker the marker + * @return the new position or <code>null</code> if no valid position + */ + protected Position createPositionFromMarker(IMarker marker) { + + int start= MarkerUtilities.getCharStart(marker); + int end= MarkerUtilities.getCharEnd(marker); + + if (start > end) { + end= start + end; + start= end - start; + end= end - start; + } + + if (start == -1 && end == -1) { + // marker line number is 1-based + int line= MarkerUtilities.getLineNumber(marker); + if (line > 0 && fDocument != null) { + try { + start= fDocument.getLineOffset(line - 1); + end= start; + } catch (BadLocationException x) { + } + } + } + + if (start > -1 && end > -1) + return new Position(start, end - start); + + return null; + } + + /** + * Creates an annotation for the given marker and adds it to this model. + * Does nothing if the marker is not acceptable to this model. + * + * @param marker the marker + * @see #isAcceptable + */ + protected final void addMarkerAnnotation(IMarker marker) { + + if (isAcceptable(marker)) { + Position p= createPositionFromMarker(marker); + if (p != null) + addAnnotation(createMarkerAnnotation(marker), p, false); + } + } + + /** + * Connects to the source of markers as marker change listener. + * @see AnnotationModel#connected + */ + protected void connected() { + + listenToMarkerChanges(true); + + try { + catchupWithMarkers(); + } catch (CoreException x) { + handleCoreException(x, EditorMessages.getString("AbstractMarkerAnnotationModel.connected")); //$NON-NLS-1$ + } + + fireModelChanged(); + } + + /** + * Installs all marker updaters for this marker annotation model. + */ + private void installMarkerUpdaters() { + + // initialize lists - indicates that the initialization happened + fMarkerUpdaterSpecifications= new ArrayList(2); + fInstantiatedMarkerUpdaters= new ArrayList(2); + + // populate list + IExtensionPoint extensionPoint= Platform.getPluginRegistry().getExtensionPoint(PlatformUI.PLUGIN_ID, "markerUpdaters"); //$NON-NLS-1$ + if (extensionPoint != null) { + IConfigurationElement[] elements= extensionPoint.getConfigurationElements(); + for (int i= 0; i < elements.length; i++) + fMarkerUpdaterSpecifications.add(elements[i]); + } + } + + /** + * Uninstalls all marker updaters. + */ + private void uninstallMarkerUpdaters() { + if (fInstantiatedMarkerUpdaters != null) { + fInstantiatedMarkerUpdaters.clear(); + fInstantiatedMarkerUpdaters= null; + } + + if (fMarkerUpdaterSpecifications != null) { + fMarkerUpdaterSpecifications.clear(); + fMarkerUpdaterSpecifications= null; + } + } + + /** + * Removes the marker change listener. + * @see AnnotationModel#disconnected + */ + protected void disconnected() { + listenToMarkerChanges(false); + uninstallMarkerUpdaters(); + } + + /** + * Returns the position known to this annotation model for the given marker. + * + * @param marker the marker + * @return the position, or <code>null</code> if none + */ + public Position getMarkerPosition(IMarker marker) { + MarkerAnnotation a= getMarkerAnnotation(marker); + if (a != null) { + return (Position) fAnnotations.get(a); + } + return null; + } + + /** + * Updates the annotation corresponding to the given marker which has changed + * in some way. <p> + * Subclasses may override. + * + * @param marker the marker + */ + protected void modifyMarkerAnnotation(IMarker marker) { + MarkerAnnotation a= getMarkerAnnotation(marker); + if (a != null) { + + // update annotation presentation + a.update(); + + // update annotation position + Position p1= createPositionFromMarker(marker); + if (p1 != null) { + Position p0= (Position) fAnnotations.get(a); + p0.setOffset(p1.getOffset()); + p0.setLength(p1.getLength()); + } + } + } + + /* + * @see AnnotationModel#removeAnnotations + */ + protected void removeAnnotations(List annotations, boolean fireModelChanged, boolean modelInitiated) { + if (annotations != null && annotations.size() > 0) { + + List markerAnnotations= new ArrayList(); + for (Iterator e= annotations.iterator(); e.hasNext();) { + Annotation a= (Annotation) e.next(); + if (a instanceof MarkerAnnotation) + markerAnnotations.add(a); + + // remove annotations from annotation model + removeAnnotation(a, false); + } + + if (markerAnnotations.size() > 0) { + + if (modelInitiated) { + // if model initiated also remove it from the marker manager + + listenToMarkerChanges(false); + try { + + IMarker[] m= new IMarker[markerAnnotations.size()]; + for (int i= 0; i < m.length; i++) { + MarkerAnnotation ma = (MarkerAnnotation) markerAnnotations.get(i); + m[i]= ma.getMarker(); + } + deleteMarkers(m); + + } catch (CoreException x) { + handleCoreException(x, EditorMessages.getString("AbstractMarkerAnnotationModel.removeAnnotations")); //$NON-NLS-1$ + } + listenToMarkerChanges(true); + + } else { + // remember deleted annotations in order to remove their markers later on + fDeletedAnnotations.addAll(markerAnnotations); + } + } + + if (fireModelChanged) + fireModelChanged(); + } + } + + /** + * Removes the annotation corresponding to the given marker. Does nothing + * if there is no annotation for this marker. + * + * @param marker the marker + */ + protected final void removeMarkerAnnotation(IMarker marker) { + MarkerAnnotation a= getMarkerAnnotation(marker); + if (a != null) { + removeAnnotation(a, false); + } + } + + /** + * Re-populates this model with annotations for all markers retrieved + * from the maker source via <code>retrieveMarkers</code>. + * + * @exception CoreException if there is a problem getting the markers + */ + private void catchupWithMarkers() throws CoreException { + + for (Iterator e=getAnnotationIterator(false); e.hasNext();) { + Annotation a= (Annotation) e.next(); + if (a instanceof MarkerAnnotation) + removeAnnotation(a, false); + } + + IMarker[] markers= retrieveMarkers(); + if (markers != null) { + for (int i= 0; i < markers.length; i++) + addMarkerAnnotation(markers[i]); + } + } + + /** + * Returns this model's annotation for the given marker. + * + * @param marker the marker + * @return the annotation, or <code>null</code> if none + */ + public final MarkerAnnotation getMarkerAnnotation(IMarker marker) { + Iterator e= getAnnotationIterator(false); + while (e.hasNext()) { + Object o= e.next(); + if (o instanceof MarkerAnnotation) { + MarkerAnnotation a= (MarkerAnnotation) o; + if (marker.equals(a.getMarker())) { + return a; + } + } + } + return null; + } + + /** + * Creates a marker updater as specified in the given configuration element. + * + * @param element the configuration element + * @return the created marker updater or <code>null</code> if none could be created + */ + private IMarkerUpdater createMarkerUpdater(IConfigurationElement element) { + try { + return (IMarkerUpdater) element.createExecutableExtension("class"); //$NON-NLS-1$ + } catch (CoreException x) { + handleCoreException(x, EditorMessages.getString("AbstractMarkerAnnotationModel.createMarkerUpdater")); //$NON-NLS-1$ + } + + return null; + } + + /** + * Checks whether a marker updater is registered for the type of the + * given marker but not yet instantiated. If so, the method instantiates + * the marker updater and registers it with this model. + * + * @param marker the marker for which to look for an updater + * @since 2.0 + */ + private void checkMarkerUpdaters(IMarker marker) { + List toBeDeleted= new ArrayList(); + for (int i= 0; i < fMarkerUpdaterSpecifications.size(); i++) { + IConfigurationElement spec= (IConfigurationElement) fMarkerUpdaterSpecifications.get(i); + String markerType= spec.getAttribute("markerType"); //$NON-NLS-1$ + if (markerType == null || MarkerUtilities.isMarkerType(marker, markerType)) { + toBeDeleted.add(spec); + IMarkerUpdater updater= createMarkerUpdater(spec); + if (updater != null) + addMarkerUpdater(updater); + } + } + + for (int i= 0; i < toBeDeleted.size(); i++) + fMarkerUpdaterSpecifications.remove(toBeDeleted.get(i)); + } + + /** + * Updates the given marker according to the given position in the given + * document. If the given position is <code>null</code>, the marker is + * assumed to carry the correct positional information. If it is detected + * that the marker is invalid and should thus be deleted, this method + * returns <code>false</code>. + * + * @param marker the marker to be updated + * @param document the document into which the given position points + * @param position the current position of the marker inside the given document + * @exception CoreException if there is a problem updating the marker + * @since 2.0 + */ + public boolean updateMarker(IMarker marker, IDocument document, Position position) throws CoreException { + + if (fMarkerUpdaterSpecifications == null) + installMarkerUpdaters(); + + if (!fMarkerUpdaterSpecifications.isEmpty()) + checkMarkerUpdaters(marker); + + boolean isOK= true; + + for (int i= 0; i < fInstantiatedMarkerUpdaters.size(); i++) { + IMarkerUpdater updater= (IMarkerUpdater) fInstantiatedMarkerUpdaters.get(i); + String markerType= updater.getMarkerType(); + if (markerType == null || MarkerUtilities.isMarkerType(marker, markerType)) { + + if (position == null) { + /* compatibility code */ + position= createPositionFromMarker(marker); + } + + isOK= (isOK && updater.updateMarker(marker, document, position)); + } + } + + return isOK; + } + + /** + * Updates the markers managed by this annotation model by calling + * all registered marker updaters (<code>IMarkerUpdater</code>). + * + * @param document the document to which this model is currently connected + * @exception CoreException if there is a problem updating the markers + */ + public void updateMarkers(IDocument document) throws CoreException { + + Assert.isTrue(fDocument == document); + + if (fAnnotations.size() == 0 && fDeletedAnnotations.size() == 0) + return; + + if (fMarkerUpdaterSpecifications == null) + installMarkerUpdaters(); + + listenToMarkerChanges(false); + + // update all markers with the positions known by the annotation model + for (Iterator e= getAnnotationIterator(false); e.hasNext();) { + Object o= e.next(); + if (o instanceof MarkerAnnotation) { + MarkerAnnotation a= (MarkerAnnotation) o; + IMarker marker= a.getMarker(); + Position position= (Position) fAnnotations.get(a); + if ( !updateMarker(marker, document, position)) { + if ( !fDeletedAnnotations.contains(a)) + fDeletedAnnotations.add(a); + } + } + } + + if (!fDeletedAnnotations.isEmpty()) { + removeAnnotations(fDeletedAnnotations, true, true); + fDeletedAnnotations.clear(); + } + + listenToMarkerChanges(true); + } + + /** + * Resets all the markers to their original state. + */ + public void resetMarkers() { + + // reinitializes the positions from the markers + for (Iterator e= getAnnotationIterator(false); e.hasNext();) { + Object o= e.next(); + if (o instanceof MarkerAnnotation) { + MarkerAnnotation a= (MarkerAnnotation) o; + Position p= createPositionFromMarker(a.getMarker()); + if (p != null) { + removeAnnotation(a, false); + addAnnotation(a, p, false); + } + } + } + + // add the markers of deleted positions back to the annotation model + for (Iterator e= fDeletedAnnotations.iterator(); e.hasNext();) { + Object o= e.next(); + if (o instanceof MarkerAnnotation) { + MarkerAnnotation a= (MarkerAnnotation) o; + Position p= createPositionFromMarker(a.getMarker()); + if (p != null) + addAnnotation(a, p, false); + } + } + fDeletedAnnotations.clear(); + + // fire annotation model changed + fireModelChanged(); + } +} |