Skip to main content
summaryrefslogtreecommitdiffstats
blob: efe3587996649e0486208d5ce21dca347d02c572 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
/*******************************************************************************
 * 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 (Annotation element : annotationsToRemove)
				removeAnnotation(element, 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 (Object object : fAttachments.keySet()) {
			IAnnotationModel model= fAttachments.get(object);
			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 (Object object : fAttachments.keySet()) {
			IAnnotationModel model= fAttachments.get(object);
			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;
	}
}

Back to the top