blob: d3ae54c0c69bde50fc24a46574471b82d594611d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 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
* Anton Leherbauer <anton.leherbauer@windriver.com> - [implementation] AnnotationModel.fModificationStamp leaks annotations - http://bugs.eclipse.org/345715
*******************************************************************************/
package org.eclipse.jface.text.source;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.Position;
/**
* Standard implementation of {@link IAnnotationModel} and its extension
* interfaces. This class can directly be used by clients. Subclasses may adapt
* this annotation model to other existing annotation mechanisms. This class
* also implements {@link org.eclipse.jface.text.ISynchronizable}. All
* modifications of the model's internal annotation map are synchronized using
* the model's lock object.
*/
public class AnnotationModel implements IAnnotationModel, IAnnotationModelExtension, IAnnotationModelExtension2, ISynchronizable {
/**
* Iterator that returns the annotations for a given region.
*
* @since 3.4
* @see AnnotationModel.RegionIterator#RegionIterator(Iterator, IAnnotationModel, int, int, boolean, boolean)
*/
private static final class RegionIterator implements Iterator<Annotation> {
private final Iterator<Annotation> fParentIterator;
private final boolean fCanEndAfter;
private final boolean fCanStartBefore;
private final IAnnotationModel fModel;
private Annotation fNext;
private Position fRegion;
/**
* Iterator that returns all annotations from the parent iterator which
* have a position in the given model inside the given region.
* <p>
* See {@link IAnnotationModelExtension2} for a definition of inside.
* </p>
*
* @param parentIterator iterator containing all annotations
* @param model the model to use to retrieve positions from for each
* annotation
* @param offset start position of the region
* @param length length of the region
* @param canStartBefore include annotations starting before region
* @param canEndAfter include annotations ending after region
* @see IAnnotationModelExtension2
*/
public RegionIterator(Iterator<Annotation> parentIterator, IAnnotationModel model, int offset, int length, boolean canStartBefore, boolean canEndAfter) {
fParentIterator= parentIterator;
fModel= model;
fRegion= new Position(offset, length);
fCanEndAfter= canEndAfter;
fCanStartBefore= canStartBefore;
fNext= findNext();
}
@Override
public boolean hasNext() {
return fNext != null;
}
@Override
public Annotation next() {
if (!hasNext())
throw new NoSuchElementException();
Annotation result= fNext;
fNext= findNext();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private Annotation findNext() {
while (fParentIterator.hasNext()) {
Annotation next= fParentIterator.next();
Position position= fModel.getPosition(next);
if (position != null) {
int offset= position.getOffset();
if (isWithinRegion(offset, position.getLength()))
return next;
}
}
return null;
}
private boolean isWithinRegion(int start, int length) {
if (fCanStartBefore && fCanEndAfter)
return fRegion.overlapsWith(start, length);
else if (fCanStartBefore)
return fRegion.includes(start + length - (length > 0 ? 1 : 0));
else if (fCanEndAfter)
return fRegion.includes(start);
else
return fRegion.includes(start) && fRegion.includes(start + length - (length > 0 ? 1 : 0));
}
}
/**
* An iterator iteration over a Positions and mapping positions to
* annotations using a provided map if the provided map contains the element.
*
* @since 3.4
*/
private static final class AnnotationsInterator implements Iterator<Annotation> {
private Annotation fNext;
private final Position[] fPositions;
private int fIndex;
private final Map<Position, Annotation> fMap;
/**
* @param positions positions to iterate over
* @param map a map to map positions to annotations
*/
public AnnotationsInterator(Position[] positions, Map<Position, Annotation> map) {
fPositions= positions;
fIndex= 0;
fMap= map;
fNext= findNext();
}
@Override
public boolean hasNext() {
return fNext != null;
}
@Override
public Annotation next() {
Annotation result= fNext;
fNext= findNext();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private Annotation findNext() {
while (fIndex < fPositions.length) {
Position position= fPositions[fIndex];
fIndex++;
if (fMap.containsKey(position))
return fMap.get(position);
}
return null;
}
}
/**
* A single iterator builds its behavior based on a sequence of iterators.
*
* @param <E> the type of elements returned by this iterator
* @since 3.1
*/
private static class MetaIterator<E> implements Iterator<E> {
/** The iterator over a list of iterators. */
private Iterator<? extends Iterator<? extends E>> fSuperIterator;
/** The current iterator. */
private Iterator<? extends E> fCurrent;
/** The current element. */
private E fCurrentElement;
public MetaIterator(Iterator<? extends Iterator<? extends E>> iterator) {
fSuperIterator= iterator;
fCurrent= fSuperIterator.next(); // there is at least one.
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public boolean hasNext() {
if (fCurrentElement != null)
return true;
if (fCurrent.hasNext()) {
fCurrentElement= fCurrent.next();
return true;
} else if (fSuperIterator.hasNext()) {
fCurrent= fSuperIterator.next();
return hasNext();
} else
return false;
}
@Override
public E next() {
if (!hasNext())
throw new NoSuchElementException();
E element= fCurrentElement;
fCurrentElement= null;
return element;
}
}
/**
* Internal annotation model listener for forwarding annotation model changes from the attached models to the
* registered listeners of the outer most annotation model.
*
* @since 3.0
*/
private class InternalModelListener implements IAnnotationModelListener, IAnnotationModelListenerExtension {
@Override
public void modelChanged(IAnnotationModel model) {
AnnotationModel.this.fireModelChanged(new AnnotationModelEvent(model, true));
}
@Override
public void modelChanged(AnnotationModelEvent event) {
AnnotationModel.this.fireModelChanged(event);
}
}
/**
* The list of managed annotations
* @deprecated since 3.0 use <code>getAnnotationMap</code> instead
*/
@Deprecated
protected Map<Annotation, Position> fAnnotations;
/**
* The map which maps {@link Position} to {@link Annotation}.
* @since 3.4
**/
private IdentityHashMap<Position, Annotation> fPositions;
/** The list of annotation model listeners */
protected ArrayList<IAnnotationModelListener> fAnnotationModelListeners;
/** The document connected with this model */
protected IDocument fDocument;
/** The number of open connections to the same document */
private int fOpenConnections= 0;
/** The document listener for tracking whether document positions might have been changed. */
private IDocumentListener fDocumentListener;
/** The flag indicating whether the document positions might have been changed. */
private boolean fDocumentChanged= true;
/**
* The model's attachment.
* @since 3.0
*/
private Map<Object, IAnnotationModel> fAttachments= new HashMap<>();
/**
* The annotation model listener on attached sub-models.
* @since 3.0
*/
private IAnnotationModelListener fModelListener= new InternalModelListener();
/**
* The current annotation model event.
* @since 3.0
*/
private AnnotationModelEvent fModelEvent;
/**
* The modification stamp.
* @since 3.0
*/
private Object fModificationStamp= new Object();
/**
* Creates a new annotation model. The annotation is empty, i.e. does not
* manage any annotations and is not connected to any document.
*/
public AnnotationModel() {
fAnnotations= new AnnotationMap(10);
fPositions= new IdentityHashMap<>(10);
fAnnotationModelListeners= new ArrayList<>(2);
fDocumentListener= new IDocumentListener() {
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
@Override
public void documentChanged(DocumentEvent event) {
fDocumentChanged= true;
}
};
}
/**
* Returns the annotation map internally used by this annotation model.
*
* @return the annotation map internally used by this annotation model
* @since 3.0
*/
protected IAnnotationMap getAnnotationMap() {
return (IAnnotationMap) fAnnotations;
}
@Override
public Object getLockObject() {
return getAnnotationMap().getLockObject();
}
@Override
public void setLockObject(Object lockObject) {
getAnnotationMap().setLockObject(lockObject);
}
/**
* Returns the current annotation model event. This is the event that will be sent out
* when calling <code>fireModelChanged</code>.
*
* @return the current annotation model event
* @since 3.0
*/
protected final AnnotationModelEvent getAnnotationModelEvent() {
synchronized (getLockObject()) {
if (fModelEvent == null) {
fModelEvent= createAnnotationModelEvent();
fModelEvent.markWorldChange(false);
fModificationStamp= new Object();
}
return fModelEvent;
}
}
@Override
public void addAnnotation(Annotation annotation, Position position) {
try {
addAnnotation(annotation, position, true);
} catch (BadLocationException e) {
// ignore invalid position
}
}
@Override
public void replaceAnnotations(Annotation[] annotationsToRemove, Map<? extends Annotation, ? extends Position> annotationsToAdd) {
try {
replaceAnnotations(annotationsToRemove, annotationsToAdd, true);
} catch (BadLocationException x) {
}
}
/**
* Replaces the given annotations in this model and if advised fires a
* model change event.
*
* @param annotationsToRemove the annotations to be removed
* @param annotationsToAdd the annotations to be added
* @param fireModelChanged <code>true</code> if a model change event
* should be fired, <code>false</code> otherwise
* @throws BadLocationException in case an annotation should be added at an
* invalid position
* @since 3.0
*/
protected void replaceAnnotations(Annotation[] annotationsToRemove, Map<? extends Annotation, ? extends Position> annotationsToAdd, boolean fireModelChanged) throws BadLocationException {
if (annotationsToRemove != null) {
for (int i= 0, length= annotationsToRemove.length; i < length; i++)
removeAnnotation(annotationsToRemove[i], false);
}
if (annotationsToAdd != null) {
Iterator<? extends Entry<? extends Annotation, ? extends Position>> iter= annotationsToAdd.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<? extends Annotation, ? extends Position> mapEntry= iter.next();
Annotation annotation= mapEntry.getKey();
Position position= mapEntry.getValue();
addAnnotation(annotation, position, false);
}
}
if (fireModelChanged)
fireModelChanged();
}
/**
* Adds the given annotation to this model. Associates the
* annotation with the given position. If requested, all annotation
* model listeners are informed about this model change. If the annotation
* is already managed by this model nothing happens.
*
* @param annotation the annotation to add
* @param position the associate position
* @param fireModelChanged indicates whether to notify all model listeners
* @throws BadLocationException if the position is not a valid document position
*/
protected void addAnnotation(Annotation annotation, Position position, boolean fireModelChanged) throws BadLocationException {
if (!fAnnotations.containsKey(annotation)) {
addPosition(fDocument, position);
fAnnotations.put(annotation, position);
fPositions.put(position, annotation);
synchronized (getLockObject()) {
getAnnotationModelEvent().annotationAdded(annotation);
}
if (fireModelChanged)
fireModelChanged();
}
}
@Override
public void addAnnotationModelListener(IAnnotationModelListener listener) {
if (!fAnnotationModelListeners.contains(listener)) {
fAnnotationModelListeners.add(listener);
if (listener instanceof IAnnotationModelListenerExtension) {
IAnnotationModelListenerExtension extension= (IAnnotationModelListenerExtension) listener;
AnnotationModelEvent event= createAnnotationModelEvent();
event.markSealed();
extension.modelChanged(event);
} else
listener.modelChanged(this);
}
}
/**
* Adds the given position to the default position category of the
* given document.
*
* @param document the document to which to add the position
* @param position the position to add
* @throws BadLocationException if the position is not a valid document position
*/
protected void addPosition(IDocument document, Position position) throws BadLocationException {
if (document != null)
document.addPosition(position);
}
/**
* Removes the given position from the default position category of the
* given document.
*
* @param document the document to which to add the position
* @param position the position to add
*
* @since 3.0
*/
protected void removePosition(IDocument document, Position position) {
if (document != null)
document.removePosition(position);
}
@Override
public void connect(IDocument document) {
Assert.isTrue(fDocument == null || fDocument == document);
if (fDocument == null) {
fDocument= document;
Iterator<Position> e= getAnnotationMap().valuesIterator();
while (e.hasNext())
try {
addPosition(document, e.next());
} catch (BadLocationException x) {
// ignore invalid position
}
}
++ fOpenConnections;
if (fOpenConnections == 1) {
document.addDocumentListener(fDocumentListener);
connected();
}
for (Iterator<Object> it= fAttachments.keySet().iterator(); it.hasNext();) {
IAnnotationModel model= fAttachments.get(it.next());
model.connect(document);
}
}
/**
* Hook method. Is called as soon as this model becomes connected to a document.
* Subclasses may re-implement.
*/
protected void connected() {
}
/**
* Hook method. Is called as soon as this model becomes disconnected from its document.
* Subclasses may re-implement.
*/
protected void disconnected() {
}
@Override
public void disconnect(IDocument document) {
Assert.isTrue(fDocument == document);
for (Iterator<Object> it= fAttachments.keySet().iterator(); it.hasNext();) {
IAnnotationModel model= fAttachments.get(it.next());
model.disconnect(document);
}
-- fOpenConnections;
if (fOpenConnections == 0) {
disconnected();
document.removeDocumentListener(fDocumentListener);
Iterator<Position> e= getAnnotationMap().valuesIterator();
while (e.hasNext()) {
Position p= e.next();
removePosition(document, p);
}
fDocument= null;
}
}
/**
* Informs all annotation model listeners that this model has been changed.
*/
protected void fireModelChanged() {
AnnotationModelEvent modelEvent= null;
synchronized(getLockObject()) {
if (fModelEvent != null) {
modelEvent= fModelEvent;
fModelEvent= null;
}
}
if (modelEvent != null)
fireModelChanged(modelEvent);
}
/**
* Creates and returns a new annotation model event. Subclasses may override.
*
* @return a new and empty annotation model event
* @since 3.0
*/
protected AnnotationModelEvent createAnnotationModelEvent() {
return new AnnotationModelEvent(this);
}
/**
* Informs all annotation model listeners that this model has been changed
* as described in the annotation model event. The event is sent out
* to all listeners implementing <code>IAnnotationModelListenerExtension</code>.
* All other listeners are notified by just calling <code>modelChanged(IAnnotationModel)</code>.
*
* @param event the event to be sent out to the listeners
* @since 2.0
*/
protected void fireModelChanged(AnnotationModelEvent event) {
event.markSealed();
if (event.isEmpty())
return;
ArrayList<IAnnotationModelListener> v= new ArrayList<>(fAnnotationModelListeners);
Iterator<IAnnotationModelListener> e= v.iterator();
while (e.hasNext()) {
IAnnotationModelListener l= e.next();
if (l instanceof IAnnotationModelListenerExtension)
((IAnnotationModelListenerExtension) l).modelChanged(event);
else if (l != null)
l.modelChanged(this);
}
}
/**
* Removes the given annotations from this model. If requested all
* annotation model listeners will be informed about this change.
* <code>modelInitiated</code> indicates whether the deletion has
* been initiated by this model or by one of its clients.
*
* @param annotations the annotations to be removed
* @param fireModelChanged indicates whether to notify all model listeners
* @param modelInitiated indicates whether this changes has been initiated by this model
*/
protected void removeAnnotations(List<? extends Annotation> annotations, boolean fireModelChanged, boolean modelInitiated) {
if (annotations.size() > 0) {
Iterator<? extends Annotation> e= annotations.iterator();
while (e.hasNext())
removeAnnotation(e.next(), false);
if (fireModelChanged)
fireModelChanged();
}
}
/**
* Removes all annotations from the model whose associated positions have been
* deleted. If requested inform all model listeners about the change.
*
* @param fireModelChanged indicates whether to notify all model listeners
*/
protected void cleanup(boolean fireModelChanged) {
cleanup(fireModelChanged, true);
}
/**
* Removes all annotations from the model whose associated positions have been
* deleted. If requested inform all model listeners about the change. If requested
* a new thread is created for the notification of the model listeners.
*
* @param fireModelChanged indicates whether to notify all model listeners
* @param forkNotification <code>true</code> iff notification should be done in a new thread
* @since 3.0
*/
private void cleanup(boolean fireModelChanged, boolean forkNotification) {
if (fDocumentChanged) {
fDocumentChanged= false;
ArrayList<Annotation> deleted= new ArrayList<>();
Iterator<Annotation> e= getAnnotationMap().keySetIterator();
while (e.hasNext()) {
Annotation a= e.next();
Position p= fAnnotations.get(a);
if (p == null || p.isDeleted())
deleted.add(a);
}
if (fireModelChanged && forkNotification) {
removeAnnotations(deleted, false, false);
synchronized (getLockObject()) {
if (fModelEvent != null)
new Thread() {
@Override
public void run() {
fireModelChanged();
}
}.start();
}
} else
removeAnnotations(deleted, fireModelChanged, false);
}
}
@Override
public Iterator<Annotation> getAnnotationIterator() {
return getAnnotationIterator(true, true);
}
/**
* {@inheritDoc}
*
* @since 3.4
*/
@Override
public Iterator<Annotation> getAnnotationIterator(int offset, int length, boolean canStartBefore, boolean canEndAfter) {
Iterator<Annotation> regionIterator= getRegionAnnotationIterator(offset, length, canStartBefore, canEndAfter);
if (fAttachments.isEmpty())
return regionIterator;
List<Iterator<Annotation>> iterators= new ArrayList<>(fAttachments.size() + 1);
iterators.add(regionIterator);
Iterator<Object> it= fAttachments.keySet().iterator();
while (it.hasNext()) {
IAnnotationModel attachment= fAttachments.get(it.next());
if (attachment instanceof IAnnotationModelExtension2)
iterators.add(((IAnnotationModelExtension2) attachment).getAnnotationIterator(offset, length, canStartBefore, canEndAfter));
else
iterators.add(new RegionIterator(attachment.getAnnotationIterator(), attachment, offset, length, canStartBefore, canEndAfter));
}
return new MetaIterator<>(iterators.iterator());
}
/**
* Returns an iterator as specified in {@link IAnnotationModelExtension2#getAnnotationIterator(int, int, boolean, boolean)}
*
* @param offset region start
* @param length region length
* @param canStartBefore position can start before region
* @param canEndAfter position can end after region
* @return an iterator to iterate over annotations in region
* @see IAnnotationModelExtension2#getAnnotationIterator(int, int, boolean, boolean)
* @since 3.4
*/
private Iterator<Annotation> getRegionAnnotationIterator(int offset, int length, boolean canStartBefore, boolean canEndAfter) {
if (!(fDocument instanceof AbstractDocument))
return new RegionIterator(getAnnotationIterator(true), this, offset, length, canStartBefore, canEndAfter);
AbstractDocument document= (AbstractDocument) fDocument;
cleanup(true);
try {
Position[] positions= document.getPositions(IDocument.DEFAULT_CATEGORY, offset, length, canStartBefore, canEndAfter);
return new AnnotationsInterator(positions, fPositions);
} catch (BadPositionCategoryException e) {
// can happen if e.g. the document doesn't contain such a category, or when removed in a different thread
return Collections.<Annotation>emptyList().iterator();
}
}
/**
* Returns all annotations managed by this model. <code>cleanup</code>
* indicates whether all annotations whose associated positions are
* deleted should previously be removed from the model. <code>recurse</code> indicates
* whether annotations of attached sub-models should also be returned.
*
* @param cleanup indicates whether annotations with deleted associated positions are removed
* @param recurse whether to return annotations managed by sub-models.
* @return all annotations managed by this model
* @since 3.0
*/
private Iterator<Annotation> getAnnotationIterator(boolean cleanup, boolean recurse) {
Iterator<Annotation> iter= getAnnotationIterator(cleanup);
if (!recurse || fAttachments.isEmpty())
return iter;
List<Iterator<Annotation>> iterators= new ArrayList<>(fAttachments.size() + 1);
iterators.add(iter);
Iterator<Object> it= fAttachments.keySet().iterator();
while (it.hasNext())
iterators.add(fAttachments.get(it.next()).getAnnotationIterator());
return new MetaIterator<>(iterators.iterator());
}
/**
* Returns all annotations managed by this model. <code>cleanup</code>
* indicates whether all annotations whose associated positions are
* deleted should previously be removed from the model.
*
* @param cleanup indicates whether annotations with deleted associated positions are removed
* @return all annotations managed by this model
*/
protected Iterator<Annotation> getAnnotationIterator(boolean cleanup) {
if (cleanup)
cleanup(true);
return getAnnotationMap().keySetIterator();
}
@Override
public Position getPosition(Annotation annotation) {
Position position= fAnnotations.get(annotation);
if (position != null)
return position;
Iterator<IAnnotationModel> it= fAttachments.values().iterator();
while (position == null && it.hasNext())
position= it.next().getPosition(annotation);
return position;
}
@Override
public void removeAllAnnotations() {
removeAllAnnotations(true);
}
/**
* Removes all annotations from the annotation model. If requested
* inform all model change listeners about this change.
*
* @param fireModelChanged indicates whether to notify all model listeners
*/
protected void removeAllAnnotations(boolean fireModelChanged) {
if (fDocument != null) {
Iterator<Annotation> e= getAnnotationMap().keySetIterator();
while (e.hasNext()) {
Annotation a= e.next();
Position p= fAnnotations.get(a);
removePosition(fDocument, p);
// p.delete();
synchronized (getLockObject()) {
getAnnotationModelEvent().annotationRemoved(a, p);
}
}
}
fAnnotations.clear();
fPositions.clear();
if (fireModelChanged)
fireModelChanged();
}
@Override
public void removeAnnotation(Annotation annotation) {
removeAnnotation(annotation, true);
}
/**
* Removes the given annotation from the annotation model.
* If requested inform all model change listeners about this change.
*
* @param annotation the annotation to be removed
* @param fireModelChanged indicates whether to notify all model listeners
*/
protected void removeAnnotation(Annotation annotation, boolean fireModelChanged) {
if (fAnnotations.containsKey(annotation)) {
Position p= null;
p= fAnnotations.get(annotation);
if (fDocument != null) {
removePosition(fDocument, p);
// p.delete();
}
fAnnotations.remove(annotation);
fPositions.remove(p);
synchronized (getLockObject()) {
getAnnotationModelEvent().annotationRemoved(annotation, p);
}
if (fireModelChanged)
fireModelChanged();
}
}
@Override
public void modifyAnnotationPosition(Annotation annotation, Position position) {
modifyAnnotationPosition(annotation, position, true);
}
/**
* Modifies the associated position of the given annotation to the given
* position. If the annotation is not yet managed by this annotation model,
* the annotation is added. When the position is <code>null</code>, the
* annotation is removed from the model.
* <p>
* If requested, all annotation model change listeners will be informed
* about the change.
*
* @param annotation the annotation whose associated position should be
* modified
* @param position the position to whose values the associated position
* should be changed
* @param fireModelChanged indicates whether to notify all model listeners
* @since 3.0
*/
protected void modifyAnnotationPosition(Annotation annotation, Position position, boolean fireModelChanged) {
if (position == null) {
removeAnnotation(annotation, fireModelChanged);
} else {
Position p= fAnnotations.get(annotation);
if (p != null) {
if (position.getOffset() != p.getOffset() || position.getLength() != p.getLength()) {
fDocument.removePosition(p);
p.setOffset(position.getOffset());
p.setLength(position.getLength());
try {
fDocument.addPosition(p);
} catch (BadLocationException e) {
// ignore invalid position
}
}
synchronized (getLockObject()) {
getAnnotationModelEvent().annotationChanged(annotation);
}
if (fireModelChanged)
fireModelChanged();
} else {
try {
addAnnotation(annotation, position, fireModelChanged);
} catch (BadLocationException x) {
// ignore invalid position
}
}
}
}
/**
* Modifies the given annotation if the annotation is managed by this
* annotation model.
* <p>
* If requested, all annotation model change listeners will be informed
* about the change.
*
* @param annotation the annotation to be modified
* @param fireModelChanged indicates whether to notify all model listeners
* @since 3.0
*/
protected void modifyAnnotation(Annotation annotation, boolean fireModelChanged) {
if (fAnnotations.containsKey(annotation)) {
synchronized (getLockObject()) {
getAnnotationModelEvent().annotationChanged(annotation);
}
if (fireModelChanged)
fireModelChanged();
}
}
@Override
public void removeAnnotationModelListener(IAnnotationModelListener listener) {
fAnnotationModelListeners.remove(listener);
}
/*
* @see org.eclipse.jface.text.source.IAnnotationModelExtension#attach(java.lang.Object, java.lang.Object)
* @since 3.0
*/
@Override
public void addAnnotationModel(Object key, IAnnotationModel attachment) {
Assert.isNotNull(attachment);
if (!fAttachments.containsValue(attachment)) {
fAttachments.put(key, attachment);
for (int i= 0; i < fOpenConnections; i++)
attachment.connect(fDocument);
attachment.addAnnotationModelListener(fModelListener);
}
}
/*
* @see org.eclipse.jface.text.source.IAnnotationModelExtension#get(java.lang.Object)
* @since 3.0
*/
@Override
public IAnnotationModel getAnnotationModel(Object key) {
return fAttachments.get(key);
}
/*
* @see org.eclipse.jface.text.source.IAnnotationModelExtension#detach(java.lang.Object)
* @since 3.0
*/
@Override
public IAnnotationModel removeAnnotationModel(Object key) {
IAnnotationModel ret= fAttachments.remove(key);
if (ret != null) {
for (int i= 0; i < fOpenConnections; i++)
ret.disconnect(fDocument);
ret.removeAnnotationModelListener(fModelListener);
}
return ret;
}
@Override
public Object getModificationStamp() {
return fModificationStamp;
}
}