1   /*
2    *  AnnotationSetImpl.java
3    *
4    *  Copyright (c) 1998-2005, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Hamish Cunningham, 7/Feb/2000
12   *
13   *  Developer notes:
14   *  ---
15   *
16   *  the addToIndex... and indexBy... methods could be refactored as I'm
17   *  sure they can be made simpler
18   *
19   *  every set to which annotation will be added has to have positional
20   *  indexing, so that we can find or create the nodes on the new annotations
21   *
22   *  note that annotations added anywhere other than sets that are
23   *  stored on the document will not get stored anywhere...
24   *
25   *  nodes aren't doing anything useful now. needs some interface that allows
26   *  their creation, defaulting to no coterminous duplicates, but allowing such
27   *  if required
28   *
29   *  $Id: AnnotationSetImpl.java,v 1.95 2005/08/25 11:26:33 ian_roberts Exp $
30   */
31  
32  package gate.annotation;
33  
34  import java.util.*;
35  
36  import gate.*;
37  import gate.corpora.DocumentImpl;
38  import gate.event.*;
39  import gate.util.InvalidOffsetException;
40  import gate.util.RBTreeMap;
41  
42  /** Implementation of AnnotationSet. Has a number of indices, all bar one
43   * of which are null by default and are only constructed when asked
44   * for. Has lots of get methods with various selection criteria; these
45   * return views into the set, which are nonetheless valid sets in
46   * their own right (but will not necesarily be fully indexed).
47   * Has a name, which is null by default; clients of Document can
48   * request named AnnotationSets if they so desire. Has a reference to the
49   * Document it is attached to. Contrary to Collections convention,
50   * there is no no-arg constructor, as this would leave the set in
51   * an inconsistent state.
52   * <P>
53   * There are five indices: annotation by id, annotations by type, annotations
54   * by start/end node and nodes by offset. The last three jointly provide
55   * positional indexing; construction of these is triggered by
56   * indexByStart/EndOffset(),
57   * or by calling a get method that selects on offset. The type
58   * index is triggered by indexByType(), or calling a get method that selects
59   * on type. The id index is always present.
60   */
61  public class AnnotationSetImpl
62      extends AbstractSet
63      implements AnnotationSet {
64    /** Debug flag */
65    private static final boolean DEBUG = false;
66  
67    /** Construction from Document. */
68    public AnnotationSetImpl(Document doc) {
69  //    annotsById = new VerboseHashMap();
70      annotsById = new HashMap();
71      this.doc = (DocumentImpl) doc;
72    } // construction from document
73  
74    /** Construction from Document and name. */
75    public AnnotationSetImpl(Document doc, String name) {
76      this(doc);
77      this.name = name;
78    } // construction from document and name
79  
80    /** Construction from Collection (which must be an AnnotationSet) */
81  //<<<dam: speedup constructor
82    /*
83      public AnnotationSetImpl(Collection c) throws ClassCastException {
84        this(((AnnotationSet) c).getDocument());
85        addAll(c);
86      } // construction from collection
87     */
88  //===dam: now
89    /** Construction from Collection (which must be an AnnotationSet) */
90    public AnnotationSetImpl(Collection c) throws ClassCastException {
91      this( ( (AnnotationSet) c).getDocument(), ( (AnnotationSet) c).getName());
92      if (c instanceof AnnotationSetImpl) {
93        AnnotationSetImpl theC = (AnnotationSetImpl) c;
94        annotsById = (HashMap) theC.annotsById.clone();
95        if (theC.annotsByEndNode != null) {
96          annotsByEndNode = (Map) ( (HashMap) theC.annotsByEndNode).clone();
97          annotsByStartNode = (Map) ( (HashMap) theC.annotsByStartNode).clone();
98        }
99        if (theC.annotsByType != null)
100         annotsByType = (Map) ( (HashMap) theC.annotsByType).clone();
101       if (theC.nodesByOffset != null) {
102         nodesByOffset = (RBTreeMap) theC.nodesByOffset.clone();
103       }
104     }
105     else
106       addAll(c);
107   } // construction from collection
108 
109 //>>>dam: end
110 
111   /** This inner class serves as the return value from the iterator()
112    * method.
113    */
114   class AnnotationSetIterator
115       implements Iterator {
116     private Iterator iter;
117     protected Annotation lastNext = null;
118     AnnotationSetIterator() {
119       iter = annotsById.values().iterator();
120     }
121 
122     public boolean hasNext() {
123       return iter.hasNext();
124     }
125 
126     public Object next() {
127       return (lastNext = (Annotation) iter.next());
128     }
129 
130     public void remove() {
131       // this takes care of the ID index
132       iter.remove();
133       // remove from type index
134       removeFromTypeIndex(lastNext);
135       // remove from offset indices
136       removeFromOffsetIndex(lastNext);
137       //that's the second way of removing annotations from a set
138       //apart from calling remove() on the set itself
139       fireAnnotationRemoved(new AnnotationSetEvent(
140           AnnotationSetImpl.this,
141           AnnotationSetEvent.ANNOTATION_REMOVED,
142           getDocument(), (Annotation) lastNext));      
143     } // remove()
144   }; // AnnotationSetIterator
145 
146   /**
147    *  This is a {@link java.util.HashMap}
148    * that fires events when elements are removed.
149    * 
150    * This class has been used in a previous version for the indexById structure 
151    * which now uses a simple HashMap.
152    * 
153    * This class is kept here for backwards compatibility of old serial 
154    * datastores.
155    *  
156    */
157   public class VerboseHashMap
158       extends HashMap {
159     VerboseHashMap() {
160       super(Gate.HASH_STH_SIZE);
161     } //contructor
162 
163     public Object remove(Object key) {
164       Object res = super.remove(key);
165       if (res != null) {
166         if (owner == null) {
167           fireAnnotationRemoved(new AnnotationSetEvent(
168               AnnotationSetImpl.this,
169               AnnotationSetEvent.ANNOTATION_REMOVED,
170               getDocument(), (Annotation) res));
171         }
172         else {
173           owner.fireAnnotationRemoved(new AnnotationSetEvent(
174               AnnotationSetImpl.this,
175               AnnotationSetEvent.ANNOTATION_REMOVED,
176               getDocument(), (Annotation) res));
177         }
178       }
179       return res;
180     } //public Object remove(Object key)
181 
182     static final long serialVersionUID = -4832487354063073511L;
183 
184     /**
185      * The annotation set this maps is part of.
186      * This is an ugly hack in order to fix a bug: database annotation sets
187      * didn't fire annotation removed events.
188      */
189     private transient AnnotationSetImpl owner;
190 
191     /**
192      * Sets the annotation set this maps is part of.
193      * This is an ugly hack in order to fix a bug: database annotation sets
194      * didn't fire annotation removed events.
195      */
196     public void setOwner(AnnotationSetImpl newOwner) {
197       this.owner = newOwner;
198     }
199   } //protected class VerboseHashMap extends HashMap
200 
201   /** Get an iterator for this set */
202   public Iterator iterator() {
203     return new AnnotationSetIterator();
204   }
205 
206   /** Remove an element from this set. */
207   public boolean remove(Object o) throws ClassCastException {
208     Annotation a = (Annotation) o;
209     boolean wasPresent = removeFromIdIndex(a);
210     if (wasPresent) {
211       removeFromTypeIndex(a);
212       removeFromOffsetIndex(a);
213     }
214     //fire the event
215     fireAnnotationRemoved(new AnnotationSetEvent(
216         AnnotationSetImpl.this,
217         AnnotationSetEvent.ANNOTATION_REMOVED,
218         getDocument(), a));
219     
220     return wasPresent;
221   } // remove(o)
222   
223 
224   /** Remove from the ID index. */
225   protected boolean removeFromIdIndex(Annotation a) {
226     if (annotsById.remove(a.getId()) == null)
227       return false;
228     return true;
229   } // removeFromIdIndex(a)
230 
231   /** Remove from the type index. */
232   protected void removeFromTypeIndex(Annotation a) {
233     if (annotsByType != null) {
234       AnnotationSet sameType = (AnnotationSet) annotsByType.get(a.getType());
235       if (sameType != null)
236         sameType.remove(a);
237       if (sameType.isEmpty()) // none left of this type
238         annotsByType.remove(a.getType());
239     }
240   } // removeFromTypeIndex(a)
241 
242   /** Remove from the offset indices. */
243   protected void removeFromOffsetIndex(Annotation a) {
244     if (nodesByOffset != null) {
245       // knowing when a node is no longer needed would require keeping a reference
246       // count on annotations, or using a weak reference to the nodes in
247       // nodesByOffset
248     }
249     if (annotsByStartNode != null) {
250       Integer id = a.getStartNode().getId();
251       AnnotationSet starterAnnots = (AnnotationSet) annotsByStartNode.get(id);
252       starterAnnots.remove(a);
253       if (starterAnnots.isEmpty()) // no annotations start here any more
254         annotsByStartNode.remove(id);
255     }
256     if (annotsByEndNode != null) {
257       Integer id = a.getEndNode().getId();
258       AnnotationSet endingAnnots = (AnnotationSet) annotsByEndNode.get(id);
259       endingAnnots.remove(a);
260       if (endingAnnots.isEmpty()) // no annotations start here any more
261         annotsByEndNode.remove(id);
262     }
263   } // removeFromOffsetIndex(a)
264 
265   /** The size of this set */
266   public int size() {
267     return annotsById.size();
268   }
269 
270   /** Find annotations by id */
271   public Annotation get(Integer id) {
272     return (Annotation) annotsById.get(id);
273   } // get(id)
274 
275   /** Get all annotations */
276   public AnnotationSet get() {
277     AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
278     resultSet.addAllKeepIDs(annotsById.values());
279     if (resultSet.isEmpty())
280       return null;
281     return resultSet;
282   } // get()
283 
284   /** Select annotations by type */
285   public AnnotationSet get(String type) {
286     if (annotsByType == null)
287       indexByType();
288       // the aliasing that happens when returning a set directly from the
289       // types index can cause concurrent access problems; but the fix below
290       // breaks the tests....
291       //AnnotationSet newSet =
292       //  new AnnotationSetImpl((Collection) annotsByType.get(type));
293       //return newSet;
294     return (AnnotationSet) annotsByType.get(type);
295   } // get(type)
296 
297   /** Select annotations by a set of types. Expects a Set of String. */
298   public AnnotationSet get(Set types) throws ClassCastException {
299     if (annotsByType == null)
300       indexByType();
301     Iterator iter = types.iterator();
302     AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
303     while (iter.hasNext()) {
304       String type = (String) iter.next();
305       AnnotationSet as = (AnnotationSet) annotsByType.get(type);
306       if (as != null)
307         resultSet.addAllKeepIDs(as);
308         // need an addAllOfOneType method
309     } // while
310     if (resultSet.isEmpty())
311       return null;
312     return resultSet;
313   } // get(types)
314 
315   /** 
316    * Select annotations by type and features 
317    * 
318    * This will return an annotation set containing just those annotations of a
319    * particular type (i.e. with a particular name) and which have features with 
320    * specific names and values. (It will also return annotations that have features
321    * besides those specified, but it will not return any annotations that do not
322    * have all the specified feature-value pairs.)
323    * 
324    * However, if constraints contains a feature whose value is equal to 
325    * gate.creole.ANNIEConstants.LOOKUP_CLASS_FEATURE_NAME (which is normally 
326    * "class"), then GATE will attempt to match that feature using an ontology
327    * which it will try to retreive from a feature on the both the annotation
328    * and in constraints. If these do not return identical ontologies, or if
329    * either the annotation or constraints does not contain an ontology, then 
330    * matching will fail, and the annotation will not be added. In summary, 
331    * this method will not work normally for features with the name "class".
332    * 
333    * @param type The name of the annotations to return.
334    * @param constraints A feature map containing all of the feature value pairs
335    * that the annotation must have in order for them to be returned.
336    * @return An annotation set containing only those annotations with the given
337    * name and which have the specified set of feature-value pairs.
338    */
339   public AnnotationSet get(String type, FeatureMap constraints) {
340     if (annotsByType == null)
341       indexByType();
342     AnnotationSet typeSet = get(type);
343     if (typeSet == null)
344       return null;
345     AnnotationSet resultSet = new AnnotationSetImpl(doc);
346     Iterator iter = typeSet.iterator();
347     while (iter.hasNext()) {
348       Annotation a = (Annotation) iter.next();
349       // we check for matching constraints by simple equality. a
350       // feature map satisfies the constraints if it contains all the
351       // key/value pairs from the constraints map
352 
353 //      if (a.getFeatures().entrySet().containsAll(constraints.entrySet()))
354       if( a.getFeatures().subsumes(constraints))
355         resultSet.add(a);
356     } // while
357     if (resultSet.isEmpty())
358       return null;
359     return resultSet;
360   } // get(type, constraints)
361 
362   /** Select annotations by type and feature names */
363   public AnnotationSet get(String type, Set featureNames) {
364     if (annotsByType == null)
365       indexByType();
366     AnnotationSet typeSet = null;
367     if (type != null) {
368       //if a type is provided, try finding annotations of this type
369       typeSet = get(type);
370       //if none exist, then return coz nothing left to do
371       if (typeSet == null)
372         return null;
373     }
374     AnnotationSet resultSet = new AnnotationSetImpl(doc);
375     Iterator iter = null;
376     if (type != null)
377       iter = typeSet.iterator();
378     else
379       iter = annotsById.values().iterator();
380     while (iter.hasNext()) {
381       Annotation a = (Annotation) iter.next();
382       // we check for matching constraints by simple equality. a
383       // feature map satisfies the constraints if it contains all the
384       // key/value pairs from the constraints map
385       if (a.getFeatures().keySet().containsAll(featureNames))
386         resultSet.add(a);
387     } // while
388     if (resultSet.isEmpty())
389       return null;
390     return resultSet;
391   } // get(type, featureNames)
392 
393   /** Select annotations by offset. This returns the set of annotations
394    * whose start node is the least such that it is less than or equal
395    * to offset. If a positional index doesn't exist it is created.
396    * If there are no nodes at or beyond the offset param then it will return
397    * null.
398    */
399   public AnnotationSet get(Long offset) {
400     if (annotsByStartNode == null)
401       indexByStartOffset();
402       // find the next node at or after offset; get the annots starting there
403     Node nextNode = (Node) nodesByOffset.getNextOf(offset);
404     if (nextNode == null) // no nodes at or beyond this offset
405       return null;
406     AnnotationSet res = (AnnotationSet) annotsByStartNode.get(nextNode.getId());
407     //get ready for next test
408     nextNode = (Node) nodesByOffset.getNextOf(new Long(offset.longValue() + 1));
409     //skip all the nodes that have no starting annotations
410     while (res == null && nextNode != null) {
411       res = (AnnotationSet) annotsByStartNode.get(nextNode.getId());
412       //get ready for next test
413       nextNode = (Node) nodesByOffset.getNextOf(
414           new Long(nextNode.getOffset().longValue() + 1)
415           );
416     }
417     //res it either null (no suitable node found) or the correct result
418     return res;
419   } // get(offset)
420 
421   /**
422    * Select annotations by offset. This returns the set of annotations
423    * that overlap totaly or partially with the interval defined by the two
424    * provided offsets.The result will include all the annotations that either:
425    * <ul>
426    * <li>start before the start offset and end strictly after it</li>
427    * <li>OR</li>
428    * <li>start at a position between the start and the end offsets</li>
429    */
430   public AnnotationSet get(Long startOffset, Long endOffset) {
431     //the result will include all the annotations that either:
432     //-start before the start offset and end strictly after it
433     //or
434     //-start at a position between the start and the end offsets
435     if (annotsByStartNode == null)
436       indexByStartOffset();
437     AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
438     Iterator nodesIter;
439     Iterator annotsIter;
440     Node currentNode;
441     Annotation currentAnnot;
442     //find all the annots that start strictly before the start offset and end
443     //strictly after it
444     nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
445     while (nodesIter.hasNext()) {
446       currentNode = (Node) nodesIter.next();
447       Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
448       if (fromPoint != null) {
449         annotsIter = (fromPoint).iterator();
450         while (annotsIter.hasNext()) {
451           currentAnnot = (Annotation) annotsIter.next();
452           if (currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0) {
453             resultSet.add(currentAnnot);
454           }
455         }
456       }
457     }
458     //find all the annots that start at or after the start offset but strictly
459     //before the end offset
460     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
461     while (nodesIter.hasNext()) {
462       currentNode = (Node) nodesIter.next();
463       Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
464       if (fromPoint != null)
465         resultSet.addAllKeepIDs(fromPoint);
466     }
467     return resultSet;
468   } //get(startOfset, endOffset)
469 
470   /**
471    * Select annotations by offset. This returns the set of annotations
472    * that overlap strictly with the interval defined by the two
473    * provided offsets.The result will include all the annotations that
474    * start at the start offset and end strictly at the end offset
475    */
476   public AnnotationSet getStrict(Long startOffset, Long endOffset) {
477     //the result will include all the annotations that
478     //start at the start offset and end strictly at the end offset
479     if (annotsByStartNode == null)
480       indexByStartOffset();
481     AnnotationSet resultSet = new AnnotationSetImpl(doc);
482     Iterator annotsIter;
483     Node currentNode;
484     Annotation currentAnnot;
485     //find all the annots that start at the start offset
486     currentNode = (Node) nodesByOffset.get(startOffset);
487     if (currentNode != null) {
488       Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
489       if (fromPoint != null) {
490         annotsIter = fromPoint.iterator();
491         while (annotsIter.hasNext()) {
492           currentAnnot = (Annotation) annotsIter.next();
493           if (currentAnnot.getEndNode().getOffset().compareTo(endOffset) == 0) {
494             resultSet.add(currentAnnot);
495           } // if
496         } // while
497       } // if
498     } // if
499     return resultSet;
500   } //getStrict(startOfset, endOffset)
501 
502   /**
503    * Select annotations by offset. This returns the set of annotations
504    * of the given type
505    * that overlap totaly or partially with the interval defined by the two
506    * provided offsets.The result will include all the annotations that either:
507    * <ul>
508    * <li>start before the start offset and end strictly after it</li>
509    * <li>OR</li>
510    * <li>start at a position between the start and the end offsets</li>
511    */
512   public AnnotationSet get(String neededType, Long startOffset, Long endOffset) {
513     //the result will include all the annotations that either:
514     //-start before the start offset and end strictly after it
515     //or
516     //-start at a position between the start and the end offsets
517     if (annotsByStartNode == null)
518       indexByStartOffset();
519     AnnotationSet resultSet = new AnnotationSetImpl(doc);
520     Iterator nodesIter;
521     Iterator annotsIter;
522     Node currentNode;
523     Annotation currentAnnot;
524     //find all the annots that start strictly before the start offset and end
525     //strictly after it
526     nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
527     while (nodesIter.hasNext()) {
528       currentNode = (Node) nodesIter.next();
529       Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
530       if (fromPoint != null) {
531         annotsIter = (fromPoint).iterator();
532         while (annotsIter.hasNext()) {
533           currentAnnot = (Annotation) annotsIter.next();
534           if (currentAnnot.getType().equals(neededType) &&
535               currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0
536               ) {
537             resultSet.add(currentAnnot);
538           } //if
539         } //while
540       }
541     }
542     //find all the annots that start at or after the start offset but strictly
543     //before the end offset
544     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
545     while (nodesIter.hasNext()) {
546       currentNode = (Node) nodesIter.next();
547       Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
548       if (fromPoint != null) {
549         annotsIter = (fromPoint).iterator();
550         while (annotsIter.hasNext()) {
551           currentAnnot = (Annotation) annotsIter.next();
552           if (currentAnnot.getType().equals(neededType)) {
553             resultSet.add(currentAnnot);
554           } //if
555         } //while
556       } //if
557     }
558     return resultSet;
559   } //get(type, startOfset, endOffset)
560 
561   /** Select annotations by type, features and offset */
562   public AnnotationSet get(String type, FeatureMap constraints, Long offset) {
563     // select by offset
564     AnnotationSet nextAnnots = (AnnotationSet) get(offset);
565     if (nextAnnots == null)
566       return null;
567     // select by type and constraints from the next annots
568     return nextAnnots.get(type, constraints);
569   } // get(type, constraints, offset)
570 
571   /**
572    * Select annotations by offset that
573    * start at a position between the start and end before the end offset
574    */
575   public AnnotationSet getContained(Long startOffset, Long endOffset) {
576     //the result will include all the annotations that either:
577     //start at a position between the start and end before the end offsets
578     if (annotsByStartNode == null)
579       indexByStartOffset();
580     AnnotationSet resultSet = new AnnotationSetImpl(doc);
581     Iterator nodesIter;
582     Node currentNode;
583     //find all the annots that start at or after the start offset but strictly
584     //before the end offset
585     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
586     while (nodesIter.hasNext()) {
587       currentNode = (Node) nodesIter.next();
588       Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
589       if (fromPoint == null)
590         continue;
591       //loop through the annotations and find only those that
592       //also end before endOffset
593       Iterator annotIter = fromPoint.iterator();
594       while (annotIter.hasNext()) {
595         Annotation annot = (Annotation) annotIter.next();
596         if (annot.getEndNode().getOffset().compareTo(endOffset) <= 0)
597           resultSet.add(annot);
598       }
599     }
600     return resultSet;
601   } //get(startOfset, endOffset)
602 
603   /** Get the node with the smallest offset */
604   public Node firstNode() {
605     indexByStartOffset();
606     if (nodesByOffset.isEmpty())
607       return null;
608     else
609       return (Node) nodesByOffset.get(nodesByOffset.firstKey());
610   } // firstNode
611 
612   /** Get the node with the largest offset */
613   public Node lastNode() {
614     indexByStartOffset();
615     indexByEndOffset();
616     if (nodesByOffset.isEmpty())
617       return null;
618     else
619       return (Node) nodesByOffset.get(nodesByOffset.lastKey());
620   } // lastNode
621 
622   /**
623    * Get the first node that is relevant for this annotation set and which has
624    * the offset larger than the one of the node provided.
625    */
626   public Node nextNode(Node node) {
627     indexByStartOffset();
628     indexByEndOffset();
629     return (Node) nodesByOffset.getNextOf(
630         new Long(node.getOffset().longValue() + 1)
631         );
632   }
633   
634   protected static AnnotationFactory annFactory;
635 
636   /**
637    * Set the annotation factory used to create annotation objects.  The default
638    * factory is {@link DefaultAnnotationFactory}.
639    */
640   public static void setAnnotationFactory (AnnotationFactory newFactory) {
641     annFactory = newFactory;
642   }
643 
644   static {
645     // set the default factory to always create AnnotationImpl objects
646     setAnnotationFactory(new DefaultAnnotationFactory());
647   }
648 
649   /** Create and add an annotation with pre-existing nodes,
650    * and return its id
651    */
652   public Integer add(Node start, Node end, String type, FeatureMap features) {
653     // the id of the new annotation
654     Integer id = doc.getNextAnnotationId();
655     // construct an annotation
656     annFactory.createAnnotationInSet(this, id, start, end, type, features);
657     return id;
658   } // add(Node, Node, String, FeatureMap)
659 
660   /** Add an existing annotation. Returns true when the set is modified. */
661   public boolean add(Object o) throws ClassCastException {
662     Annotation a = (Annotation) o;
663     Object oldValue = annotsById.put(a.getId(), a);
664     if (annotsByType != null)
665       addToTypeIndex(a);
666     if (annotsByStartNode != null || annotsByEndNode != null)
667       addToOffsetIndex(a);
668     AnnotationSetEvent evt = new AnnotationSetEvent(
669         this,
670         AnnotationSetEvent.ANNOTATION_ADDED,
671         doc, a);
672     fireAnnotationAdded(evt);
673     fireGateEvent(evt);
674     return oldValue != a;
675   } // add(o)
676 
677   /**
678    * Adds multiple annotations to this set in one go.
679    * All the objects in the provided collection should be of
680    * {@link gate.Annotation} type, otherwise a ClassCastException will be
681    * thrown.
682    * The provided annotations will be used to create new annotations using the
683    * appropriate add() methods from this set. The new annotations will have
684        * different IDs from the old ones (which is required in order to preserve the
685    * uniqueness of IDs inside an annotation set).
686    * @param c a collection of annotations
687    * @return <tt>true</tt> if the set has been modified as a result of this
688    * call.
689    */
690   public boolean addAll(Collection c) {
691     Iterator annIter = c.iterator();
692     boolean changed = false;
693     while (annIter.hasNext()) {
694       Annotation a = (Annotation) annIter.next();
695       try {
696         add(a.getStartNode().getOffset(),
697             a.getEndNode().getOffset(),
698             a.getType(),
699             a.getFeatures());
700         changed = true;
701       }
702       catch (InvalidOffsetException ioe) {
703         throw new IllegalArgumentException(ioe.toString());
704       }
705     }
706     return changed;
707   }
708 
709   /**
710    * Adds multiple annotations to this set in one go.
711    * All the objects in the provided collection should be of
712    * {@link gate.Annotation} type, otherwise a ClassCastException will be
713    * thrown.
714    * This method does not create copies of the annotations like addAll() does
715    * but simply adds the new annotations to the set.
716    * It is intended to be used solely by annotation sets in order to construct
717    * the results for various get(...) methods.
718    * @param c a collection of annotations
719    * @return <tt>true</tt> if the set has been modified as a result of this
720    * call.
721    */
722   protected boolean addAllKeepIDs(Collection c) {
723     Iterator annIter = c.iterator();
724     boolean changed = false;
725     while (annIter.hasNext()) {
726       Annotation a = (Annotation) annIter.next();
727       changed |= add(a);
728     }
729     return changed;
730   }
731 
732   /** Create and add an annotation and return its id */
733   public Integer add(
734       Long start, Long end, String type, FeatureMap features
735       ) throws InvalidOffsetException {
736     // are the offsets valid?
737     if (!doc.isValidOffsetRange(start, end))
738       throw new InvalidOffsetException();
739     // the set has to be indexed by position in order to add, as we need
740     // to find out if nodes need creating or if they exist already
741     if (nodesByOffset == null) {
742       indexByStartOffset();
743       indexByEndOffset();
744     }
745     // find existing nodes if appropriate nodes don't already exist, create them
746     Node startNode = (Node) nodesByOffset.getNextOf(start);
747     if (startNode == null || !startNode.getOffset().equals(start))
748       startNode = new NodeImpl(doc.getNextNodeId(), start);
749     Node endNode = null;
750     if (start.equals(end))
751       endNode = startNode;
752     else
753       endNode = (Node) nodesByOffset.getNextOf(end);
754     if (endNode == null || !endNode.getOffset().equals(end))
755       endNode = new NodeImpl(doc.getNextNodeId(), end);
756       // delegate to the method that adds annotations with existing nodes
757     return add(startNode, endNode, type, features);
758   } // add(start, end, type, features)
759 
760   /** Create and add an annotation from database read data
761    * In this case the id is already known being previously fetched from the
762    * database
763    */
764   public void add(
765       Integer id, Long start, Long end, String type, FeatureMap features
766       ) throws InvalidOffsetException {
767     // are the offsets valid?
768     if (!doc.isValidOffsetRange(start, end))
769       throw new InvalidOffsetException();
770     // the set has to be indexed by position in order to add, as we need
771     // to find out if nodes need creating or if they exist already
772     if (nodesByOffset == null) {
773       indexByStartOffset();
774       indexByEndOffset();
775     }
776     // find existing nodes if appropriate nodes don't already exist, create them
777     Node startNode = (Node) nodesByOffset.getNextOf(start);
778     if (startNode == null || !startNode.getOffset().equals(start))
779       startNode = new NodeImpl(doc.getNextNodeId(), start);
780     Node endNode = null;
781     if (start.equals(end))
782       endNode = startNode;
783     else
784       endNode = (Node) nodesByOffset.getNextOf(end);
785     if (endNode == null || !endNode.getOffset().equals(end))
786       endNode = new NodeImpl(doc.getNextNodeId(), end);
787       // construct an annotation
788     annFactory.createAnnotationInSet(this, id, startNode, endNode, type, features);
789   } // add(id, start, end, type, features)
790 
791   /** Construct the positional index. */
792   protected void indexByType() {
793     if (annotsByType != null)
794       return;
795     annotsByType = new HashMap(Gate.HASH_STH_SIZE);
796     Iterator annotIter = annotsById.values().iterator();
797     while (annotIter.hasNext())
798       addToTypeIndex( (Annotation) annotIter.next());
799   } // indexByType()
800 
801   /** Construct the positional indices for annotation start */
802   protected void indexByStartOffset() {
803     if (annotsByStartNode != null) return;
804     if (nodesByOffset == null) nodesByOffset = new RBTreeMap();
805     annotsByStartNode = new HashMap(Gate.HASH_STH_SIZE);
806     Iterator annotIter = annotsById.values().iterator();
807     while (annotIter.hasNext())
808       addToStartOffsetIndex( (Annotation) annotIter.next());
809   } // indexByStartOffset()
810 
811   /** Construct the positional indices for annotation end */
812   protected void indexByEndOffset() {
813     if (annotsByEndNode != null)
814       return;
815     if (nodesByOffset == null)
816       nodesByOffset = new RBTreeMap();
817     annotsByEndNode = new HashMap(Gate.HASH_STH_SIZE);
818     Iterator annotIter = annotsById.values().iterator();
819     while (annotIter.hasNext())
820       addToEndOffsetIndex( (Annotation) annotIter.next());
821   } // indexByEndOffset()
822 
823   /** Add an annotation to the type index. Does nothing if the index
824    * doesn't exist.
825    */
826   void addToTypeIndex(Annotation a) {
827     if (annotsByType == null)
828       return;
829     String type = a.getType();
830     AnnotationSet sameType = (AnnotationSet) annotsByType.get(type);
831     if (sameType == null) {
832       sameType = new AnnotationSetImpl(doc);
833       annotsByType.put(type, sameType);
834     }
835     sameType.add(a);
836   } // addToTypeIndex(a)
837 
838   /** Add an annotation to the offset indices. Does nothing if they
839    * don't exist.
840    */
841   void addToOffsetIndex(Annotation a) {
842     addToStartOffsetIndex(a);
843     addToEndOffsetIndex(a);
844   } // addToOffsetIndex(a)
845 
846   /** Add an annotation to the start offset index. Does nothing if the
847    * index doesn't exist.
848    */
849   void addToStartOffsetIndex(Annotation a) {
850     Node startNode = a.getStartNode();
851     Long start = startNode.getOffset();
852     // add a's nodes to the offset index
853     if (nodesByOffset != null)
854       nodesByOffset.put(start, startNode);
855       // if there's no appropriate index give up
856     if (annotsByStartNode == null)
857       return;
858     // get the annotations that start at the same node, or create new set
859     AnnotationSet thisNodeAnnots =
860         (AnnotationSet) annotsByStartNode.get(startNode.getId());
861     if (thisNodeAnnots == null) {
862       thisNodeAnnots = new AnnotationSetImpl(doc);
863       annotsByStartNode.put(startNode.getId(), thisNodeAnnots);
864     }
865     // add to the annots listed for a's start node
866     thisNodeAnnots.add(a);
867   } // addToStartOffsetIndex(a)
868 
869   /** Add an annotation to the end offset index. Does nothing if the
870    * index doesn't exist.
871    */
872   void addToEndOffsetIndex(Annotation a) {
873     Node endNode = a.getEndNode();
874     Long end = endNode.getOffset();
875     // add a's nodes to the offset index
876     if (nodesByOffset != null)
877       nodesByOffset.put(end, endNode);
878       // if there's no appropriate index give up
879     if (annotsByEndNode == null)
880       return;
881     // get the annotations that start at the same node, or create new set
882     AnnotationSet thisNodeAnnots =
883         (AnnotationSet) annotsByEndNode.get(endNode.getId());
884     if (thisNodeAnnots == null) {
885       thisNodeAnnots = new AnnotationSetImpl(doc);
886       annotsByEndNode.put(endNode.getId(), thisNodeAnnots);
887     }
888     // add to the annots listed for a's start node
889     thisNodeAnnots.add(a);
890   } // addToEndOffsetIndex(a)
891 
892   /** Propagate changes to the document content. Has, unfortunately,
893    * to be public, to allow DocumentImpls to get at it. Oh for a
894    * "friend" declaration. Doesn't throw InvalidOffsetException as
895    * DocumentImpl is the only client, and that checks the offsets
896    * before calling this method.
897    */
898   public void edit(Long start, Long end, DocumentContent replacement) {
899     //make sure we have the indices computed
900     indexByStartOffset();
901     indexByEndOffset();
902     if(end.compareTo(start) > 0){
903       //get the nodes that need to be processed (the nodes internal to the
904       //removed section plus the marginal ones
905       List affectedNodes = new ArrayList(nodesByOffset.subMap(start,
906           new Long(end.longValue() + 1)).values());
907       //if we have more than 1 node we need to delete all apart from the first
908       //and move the annotations so that they refer to the one we keep (the first)
909       NodeImpl firstNode = null;
910       if (!affectedNodes.isEmpty()) {
911         firstNode = (NodeImpl) affectedNodes.get(0);
912         List startingAnnotations = new ArrayList();
913         List endingAnnotations = new ArrayList();
914         for (int i = 1; i < affectedNodes.size(); i++) {
915           Node aNode = (Node) affectedNodes.get(i);
916           //save the annotations
917           AnnotationSet annSet = (AnnotationSet) annotsByStartNode.get(aNode.
918               getId());
919           if (annSet != null)
920             startingAnnotations.addAll(annSet);
921           annSet = (AnnotationSet) annotsByEndNode.get(aNode.getId());
922           if (annSet != null)
923             endingAnnotations.addAll(annSet);
924             //remove the node
925             nodesByOffset.remove(aNode.getOffset());
926             annotsByStartNode.remove(aNode);
927             annotsByEndNode.remove(aNode);
928         }
929         //modify the annotations so they point to the saved node
930         Iterator annIter = startingAnnotations.iterator();
931         while (annIter.hasNext()) {
932           AnnotationImpl anAnnot = (AnnotationImpl) annIter.next();
933           anAnnot.start = firstNode;
934           addToStartOffsetIndex(anAnnot);
935         }
936         annIter = endingAnnotations.iterator();
937         while (annIter.hasNext()) {
938           AnnotationImpl anAnnot = (AnnotationImpl) annIter.next();
939           anAnnot.end = firstNode;
940           addToEndOffsetIndex(anAnnot);
941         }
942         //repair the first node
943         //remove from offset index
944         nodesByOffset.remove(firstNode.getOffset());
945         //change the offset for the saved node
946         firstNode.setOffset(start);
947         //add back to the offset index
948         nodesByOffset.put(firstNode.getOffset(), firstNode);
949       }
950     }
951 
952     //now handle the insert and/or update the rest of the nodes' position
953     //get the user selected behaviour (defaults to append)
954     boolean shouldPrepend = Gate.getUserConfig().
955         getBoolean(GateConstants.DOCEDIT_INSERT_PREPEND).booleanValue();
956 
957     long s = start.longValue(), e = end.longValue();
958     long rlen = // length of the replacement value
959         ( (replacement == null) ? 0 : replacement.size().longValue());
960 
961     // update the offsets and the index by offset for the rest of the nodes
962     List nodesAfterReplacement = new ArrayList(
963         nodesByOffset.tailMap(start).values());
964 
965     //remove from the index by offset
966     Iterator nodesAfterReplacementIter = nodesAfterReplacement.iterator();
967     while (nodesAfterReplacementIter.hasNext()) {
968       NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
969       nodesByOffset.remove(n.getOffset());
970     }
971     //change the offsets
972     nodesAfterReplacementIter = nodesAfterReplacement.iterator();
973     while (nodesAfterReplacementIter.hasNext()) {
974       NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
975       long oldOffset = n.getOffset().longValue();
976       //by default we move all nodes back
977       long newOffset = oldOffset - (e - s) + rlen;
978       //for the first node we need behave differently
979       if (oldOffset == s){
980         //the first offset never moves back
981         if(newOffset < s) newOffset = s;
982         //if we're prepending we don't move forward
983         if(shouldPrepend) newOffset = s;
984       }
985       n.setOffset(new Long(newOffset));
986     }
987     //add back to the index by offset with the new offsets
988     nodesAfterReplacementIter = nodesAfterReplacement.iterator();
989     while (nodesAfterReplacementIter.hasNext()) {
990       NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
991       nodesByOffset.put(n.getOffset(), n);
992     }
993 
994 //    //rebuild the indices with the new offsets
995 //    nodesByOffset = null;
996 //    annotsByStartNode = null;
997 //    annotsByEndNode = null;
998 //    indexByStartOffset();
999 //    indexByEndOffset();
1000  } // edit(start,end,replacement)
1001
1002  /** Get the name of this set. */
1003  public String getName() {
1004    return name;
1005  }
1006
1007  /** Get the document this set is attached to. */
1008  public Document getDocument() {
1009    return doc;
1010  }
1011
1012  /** Get a set of java.lang.String objects representing all the annotation
1013   * types present in this annotation set.
1014   */
1015  public Set getAllTypes() {
1016    indexByType();
1017    return annotsByType.keySet();
1018  }
1019
1020  /**
1021   *
1022   * @return a clone of this set.
1023   * @throws CloneNotSupportedException
1024   */
1025  public Object clone() throws CloneNotSupportedException {
1026    return super.clone();
1027  }
1028
1029  /**
1030   *
1031   * @param l
1032   */
1033  public synchronized void removeAnnotationSetListener(AnnotationSetListener l) {
1034    if (annotationSetListeners != null && annotationSetListeners.contains(l)) {
1035      Vector v = (Vector) annotationSetListeners.clone();
1036      v.removeElement(l);
1037      annotationSetListeners = v;
1038    }
1039  }
1040
1041  /**
1042   *
1043   * @param l
1044   */
1045  public synchronized void addAnnotationSetListener(AnnotationSetListener l) {
1046    Vector v = annotationSetListeners == null ? new Vector(2) :
1047        (Vector) annotationSetListeners.clone();
1048    if (!v.contains(l)) {
1049      v.addElement(l);
1050      annotationSetListeners = v;
1051    }
1052  }
1053
1054  /** String representation of the set */
1055  /*public String toString() {
1056    // annotsById
1057    SortedSet sortedAnnots = new TreeSet();
1058    sortedAnnots.addAll(annotsById.values());
1059    String aBI = sortedAnnots.toString();
1060    // annotsByType
1061    StringBuffer buf = new StringBuffer();
1062    Iterator iter = annotsByType.iterator();
1063    while(iter.hasNext()) {
1064      HashMap thisType = iter.next().entrySet();
1065      sortedAnnots.clear();
1066      sortedAnnots.addAll(thisType.);
1067      buf.append("[type: " +
1068    }
1069    return
1070      "AnnotationSetImpl: " +
1071      "name=" + name +
1072    //  "; doc.getURL()=" + doc +
1073      "; annotsById=" + aBI +
1074    //  "; annotsByType=" + aBT +
1075      "; "
1076      ;
1077     } // toString()
1078   */
1079//  public int hashCode() {
1080//    int hash = 0;
1081//    Iterator i = this.iterator();
1082//    while (i.hasNext()) {
1083//        Annotation annot = (Annotation)i.next();
1084//        if ( annot != null)
1085//            hash += annot.hashCode();
1086//    }
1087//    int nameHash = (name == null ? 0 : name.hashCode());
1088//    //int docHash = (doc == null ? 0 : doc.hashCode());
1089//
1090//    return hash ^ nameHash;// ^ docHash;
1091//  }
1092  /** The name of this set */
1093  String name = null;
1094  /** The document this set belongs to */
1095  DocumentImpl doc;
1096  /** Maps annotation ids (Integers) to Annotations */
1097  protected HashMap annotsById;
1098  /** Maps annotation types (Strings) to AnnotationSets */
1099  Map annotsByType = null;
1100  /** Maps offsets (Longs) to nodes */
1101  RBTreeMap nodesByOffset = null;
1102  /** Maps node ids (Integers) to AnnotationSets representing those
1103   * annotations that start from that node
1104   */
1105  Map annotsByStartNode;
1106  /** Maps node ids (Integers) to AnnotationSets representing those
1107   * annotations that end at that node
1108   */
1109  Map annotsByEndNode;
1110  protected transient Vector annotationSetListeners;
1111  private transient Vector gateListeners;
1112  /**
1113   *
1114   * @param e
1115   */
1116  protected void fireAnnotationAdded(AnnotationSetEvent e) {
1117    if (annotationSetListeners != null) {
1118      Vector listeners = annotationSetListeners;
1119      int count = listeners.size();
1120      for (int i = 0; i < count; i++) {
1121        ( (AnnotationSetListener) listeners.elementAt(i)).annotationAdded(e);
1122      }
1123    }
1124  }
1125
1126  /**
1127   *
1128   * @param e
1129   */
1130  protected void fireAnnotationRemoved(AnnotationSetEvent e) {
1131    if (annotationSetListeners != null) {
1132      Vector listeners = annotationSetListeners;
1133      int count = listeners.size();
1134      for (int i = 0; i < count; i++) {
1135        ( (AnnotationSetListener) listeners.elementAt(i)).annotationRemoved(e);
1136      }
1137    }
1138  }
1139
1140  /**
1141   *
1142   * @param l
1143   */
1144  public synchronized void removeGateListener(GateListener l) {
1145    if (gateListeners != null && gateListeners.contains(l)) {
1146      Vector v = (Vector) gateListeners.clone();
1147      v.removeElement(l);
1148      gateListeners = v;
1149    }
1150  }
1151
1152  /**
1153   *
1154   * @param l
1155   */
1156  public synchronized void addGateListener(GateListener l) {
1157    Vector v = gateListeners == null ? new Vector(2) :
1158        (Vector) gateListeners.clone();
1159    if (!v.contains(l)) {
1160      v.addElement(l);
1161      gateListeners = v;
1162    }
1163  }
1164
1165  /**
1166   *
1167   * @param e
1168   */
1169  protected void fireGateEvent(GateEvent e) {
1170    if (gateListeners != null) {
1171      Vector listeners = gateListeners;
1172      int count = listeners.size();
1173      for (int i = 0; i < count; i++) {
1174        ( (GateListener) listeners.elementAt(i)).processGateEvent(e);
1175      }
1176    }
1177  }
1178
1179  /** Freeze the serialization UID. */
1180  static final long serialVersionUID = 1479426765310434166L;
1181} // AnnotationSetImpl
1182