| /******************************************************************************* |
| * Copyright (c) 2000, 2007 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.ui.texteditor; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.osgi.framework.Bundle; |
| |
| import org.eclipse.core.runtime.Assert; |
| 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.core.resources.IMarker; |
| import org.eclipse.core.resources.IResourceStatus; |
| |
| import org.eclipse.core.filebuffers.IPersistableAnnotationModel; |
| |
| 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.text.source.IAnnotationMap; |
| |
| import org.eclipse.ui.PlatformUI; |
| |
| import org.eclipse.ui.editors.text.EditorsUI; |
| |
| |
| /** |
| * 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 {@link #updateMarkers(IDocument)} 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 a {@link org.eclipse.ui.texteditor.IMarkerUpdater} |
| * 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> |
| * <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 implements IPersistableAnnotationModel { |
| |
| /** List of annotations whose text range became invalid because of document changes */ |
| private List fDeletedAnnotations= new ArrayList(2); |
| /** 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.</p> |
| * |
| * @return the list of markers |
| * @throws 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.</p> |
| * |
| * @param markers the array of markers |
| * @throws 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.</p> |
| * |
| * @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.</p> |
| * |
| * @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 the client's responsibility to ensure the consistency |
| * 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.</p> |
| * |
| * @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) { |
| |
| Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID); |
| ILog log= Platform.getLog(bundle); |
| if (message != null) |
| log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, message, exception)); |
| else |
| log.log(exception.getStatus()); |
| } |
| |
| /** |
| * Creates and returns the character position of the given marker based |
| * on its attributes. |
| * <p> |
| * Subclasses may override.</p> |
| * |
| * @param marker the marker |
| * @return the new position or <code>null</code> if the marker attributes do not specify a 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(IMarker) |
| */ |
| protected final void addMarkerAnnotation(IMarker marker) { |
| |
| if (isAcceptable(marker)) { |
| Position p= createPositionFromMarker(marker); |
| if (p != null) |
| try { |
| MarkerAnnotation annotation= createMarkerAnnotation(marker); |
| if (annotation != null) |
| addAnnotation(annotation, p, false); |
| } catch (BadLocationException e) { |
| // ignore invalid position |
| } |
| } |
| } |
| |
| /** |
| * Connects to the source of markers as marker change listener. |
| * @see AnnotationModel#connected() |
| */ |
| protected void connected() { |
| |
| listenToMarkerChanges(true); |
| |
| try { |
| catchupWithMarkers(); |
| } catch (CoreException x) { |
| if (x.getStatus().getCode() != IResourceStatus.RESOURCE_NOT_FOUND) |
| handleCoreException(x, TextEditorMessages.AbstractMarkerAnnotationModel_connected); |
| } |
| |
| 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.getExtensionRegistry().getExtensionPoint(EditorsUI.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) getAnnotationMap().get(a); |
| } |
| return null; |
| } |
| |
| /** |
| * Updates the annotation corresponding to the given marker which has changed |
| * in some way. |
| * <p> |
| * Subclasses may override.</p> |
| * |
| * @param marker the marker |
| */ |
| protected void modifyMarkerAnnotation(IMarker marker) { |
| MarkerAnnotation a= getMarkerAnnotation(marker); |
| if (a != null) { |
| Position p= createPositionFromMarker(marker); |
| if (p != null) { |
| a.update(); |
| modifyAnnotationPosition(a, p, false); |
| } |
| } else |
| addMarkerAnnotation(marker); |
| } |
| |
| /* |
| * @see AnnotationModel#removeAnnotations(List, boolean, boolean) |
| */ |
| 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, TextEditorMessages.AbstractMarkerAnnotationModel_removeAnnotations); |
| } |
| 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>. |
| * |
| * @throws 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, TextEditorMessages.AbstractMarkerAnnotationModel_createMarkerUpdater); |
| } |
| |
| 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>. |
| * <p> |
| * <strong>Note:</strong> This implementation queries the registered |
| * {@linkplain IMarkerUpdater}s. If any of these updaters returns |
| * <code>false</code> this method also returns <code>false</code>. |
| * </p> |
| * |
| * @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 |
| * @return <code>false</code> if the marker is invalid |
| * @throws CoreException if there is a problem updating the marker |
| * @since 2.0 |
| * @deprecated use <code>updateMarker(IDocument, IMarker, Position)</code> instead. This method will be changed to protected. |
| */ |
| 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 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 |
| * @return <code>false</code> if the marker is invalid |
| * @throws CoreException if there is a problem updating the marker |
| * @since 3.0 |
| */ |
| public boolean updateMarker(IDocument document, IMarker marker, Position position) throws CoreException { |
| listenToMarkerChanges(false); |
| try { |
| return updateMarker(marker, document, position); |
| } finally { |
| listenToMarkerChanges(true); |
| } |
| } |
| |
| /** |
| * 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 |
| * @throws CoreException if there is a problem updating the markers |
| */ |
| public void updateMarkers(IDocument document) throws CoreException { |
| |
| Assert.isTrue(fDocument == document); |
| |
| IAnnotationMap annotationMap= getAnnotationMap(); |
| |
| if (annotationMap.size() == 0 && fDeletedAnnotations.size() == 0) |
| return; |
| |
| if (fMarkerUpdaterSpecifications == null) |
| installMarkerUpdaters(); |
| |
| listenToMarkerChanges(false); |
| |
| try { |
| |
| // 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) annotationMap.get(a); |
| if ( !updateMarker(marker, document, position)) { |
| if ( !fDeletedAnnotations.contains(a)) |
| fDeletedAnnotations.add(a); |
| } |
| } |
| } |
| |
| if (!fDeletedAnnotations.isEmpty()) { |
| removeAnnotations(fDeletedAnnotations, true, true); |
| fDeletedAnnotations.clear(); |
| } |
| |
| } finally { |
| |
| listenToMarkerChanges(true); |
| |
| } |
| } |
| |
| /** |
| * Resets all the markers to their original state. |
| */ |
| public void resetMarkers() { |
| |
| // re-initializes 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); |
| try { |
| addAnnotation(a, p, false); |
| } catch (BadLocationException e1) { |
| // ignore invalid position |
| } |
| } |
| } |
| } |
| |
| // 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) |
| try { |
| addAnnotation(a, p, false); |
| } catch (BadLocationException e1) { |
| // ignore invalid position |
| } |
| } |
| } |
| fDeletedAnnotations.clear(); |
| |
| // fire annotation model changed |
| fireModelChanged(); |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.source.IPersistableAnnotationModel#commit(org.eclipse.jface.text.IDocument) |
| */ |
| public void commit(IDocument document) throws CoreException { |
| updateMarkers(document); |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.source.IPersistableAnnotationModel#revert(org.eclipse.jface.text.IDocument) |
| */ |
| public void revert(IDocument document) { |
| resetMarkers(); |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.source.IPersistableAnnotationModel#reinitialize(org.eclipse.jface.text.IDocument) |
| */ |
| public void reinitialize(IDocument document) { |
| resetMarkers(); |
| } |
| } |