1   /*
2    *  AnnotationSetImpl.java
3    *
4    *  Copyright (c) 1998-2001, 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.71 2002/07/12 13:24:27 valyt Exp $
30   */
31  
32  package gate.annotation;
33  
34  import java.util.*;
35  import gate.util.*;
36  
37  import gate.*;
38  import gate.corpora.*;
39  import gate.event.*;
40  
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  {
65    /** Debug flag */
66    private static final boolean DEBUG = false;
67  
68    /** Construction from Document. */
69    public AnnotationSetImpl(Document doc) {
70      annotsById = new VerboseHashMap();
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());
92  
93      if (c instanceof AnnotationSetImpl)
94      {
95          AnnotationSetImpl theC = (AnnotationSetImpl)c;
96          annotsById = (HashMap)theC.annotsById.clone();
97          if(theC.annotsByEndNode != null)
98          {
99              annotsByEndNode = (Map)((HashMap)theC.annotsByEndNode).clone();
100             annotsByStartNode = (Map)((HashMap)theC.annotsByStartNode).clone();
101         }
102         if (theC.annotsByType != null)
103             annotsByType = (Map)((HashMap)theC.annotsByType).clone();
104         if (theC.nodesByOffset != null)
105         {
106             nodesByOffset = (RBTreeMap)theC.nodesByOffset.clone();
107         }
108 
109     } else
110         addAll(c);
111   } // construction from collection
112 //>>>dam: end
113 
114   /** This inner class serves as the return value from the iterator()
115     * method.
116     */
117   class AnnotationSetIterator implements Iterator {
118 
119     private Iterator iter;
120 
121     private Annotation lastNext = null;
122 
123     AnnotationSetIterator()  { iter = annotsById.values().iterator(); }
124 
125     public boolean hasNext() { return iter.hasNext(); }
126 
127     public Object next()     { return (lastNext = (Annotation) iter.next());}
128 
129     public void remove()     {
130       // this takes care of the ID index
131       iter.remove();
132       //that's the second way of removing annotations from a set
133       //apart from calling remove() on the set itself
134       fireAnnotationRemoved(new AnnotationSetEvent(
135                             AnnotationSetImpl.this,
136                             AnnotationSetEvent.ANNOTATION_REMOVED,
137                             getDocument(), (Annotation)lastNext));
138 
139       // remove from type index
140       removeFromTypeIndex(lastNext);
141 
142       // remove from offset indices
143       removeFromOffsetIndex(lastNext);
144 
145     } // remove()
146 
147   }; // AnnotationSetIterator
148 
149   /**
150    * Class used for the indexById structure. This is a {@link java.util.HashMap}
151    * that fires events when elements are removed.
152    */
153   public class VerboseHashMap extends HashMap{
154 
155     VerboseHashMap() {
156       super(Gate.HASH_STH_SIZE);
157     } //contructor
158 
159     public Object remove(Object key){
160       Object res = super.remove(key);
161       if(res != null) {
162         fireAnnotationRemoved(new AnnotationSetEvent(
163                                     AnnotationSetImpl.this,
164                                     AnnotationSetEvent.ANNOTATION_REMOVED,
165                                     getDocument(), (Annotation)res));
166       }
167       return res;
168     }//public Object remove(Object key)
169     static final long serialVersionUID = -4832487354063073511L;
170   }//protected class VerboseHashMap extends HashMap
171 
172   /** Get an iterator for this set */
173   public Iterator iterator() { return new AnnotationSetIterator(); }
174 
175   /** Remove an element from this set. */
176   public boolean remove(Object o) throws ClassCastException {
177 
178     Annotation a = (Annotation) o;
179 
180     boolean wasPresent = removeFromIdIndex(a);
181     if(wasPresent){
182       removeFromTypeIndex(a);
183       removeFromOffsetIndex(a);
184     }
185     return wasPresent;
186   } // remove(o)
187 
188   /** Remove from the ID index. */
189   protected boolean removeFromIdIndex(Annotation a) {
190     if(annotsById.remove(a.getId()) == null)
191       return false;
192 
193     return true;
194   } // removeFromIdIndex(a)
195 
196   /** Remove from the type index. */
197   protected void removeFromTypeIndex(Annotation a) {
198     if(annotsByType != null) {
199 
200       AnnotationSet sameType = (AnnotationSet) annotsByType.get(a.getType());
201 
202       if(sameType != null) sameType.remove(a);
203 
204       if(sameType.isEmpty()) // none left of this type
205         annotsByType.remove(a.getType());
206     }
207   } // removeFromTypeIndex(a)
208 
209   /** Remove from the offset indices. */
210   protected void removeFromOffsetIndex(Annotation a) {
211     if(nodesByOffset != null) {
212     // knowing when a node is no longer needed would require keeping a reference
213     // count on annotations, or using a weak reference to the nodes in
214     // nodesByOffset
215     }
216 
217     if(annotsByStartNode != null) {
218       Integer id = a.getStartNode().getId();
219       AnnotationSet starterAnnots = (AnnotationSet) annotsByStartNode.get(id);
220       starterAnnots.remove(a);
221       if(starterAnnots.isEmpty()) // no annotations start here any more
222         annotsByStartNode.remove(id);
223     }
224 
225     if(annotsByEndNode != null) {
226       Integer id = a.getEndNode().getId();
227       AnnotationSet endingAnnots = (AnnotationSet) annotsByEndNode.get(id);
228       endingAnnots.remove(a);
229       if(endingAnnots.isEmpty()) // no annotations start here any more
230         annotsByEndNode.remove(id);
231     }
232 
233   } // removeFromOffsetIndex(a)
234 
235   /** The size of this set */
236   public int size() { return annotsById.size(); }
237 
238   /** Find annotations by id */
239   public Annotation get(Integer id) {
240     return (Annotation) annotsById.get(id);
241   } // get(id)
242 
243   /** Get all annotations */
244   public AnnotationSet get() {
245     AnnotationSet resultSet = new AnnotationSetImpl(doc);
246     Iterator iter = annotsById.values().iterator();
247     while (iter.hasNext()) {
248       Out.prln(iter.next().toString());
249     }
250 
251     resultSet.addAll(annotsById.values());
252     if(resultSet.isEmpty())
253       return null;
254     return resultSet;
255   } // get()
256 
257   /** Select annotations by type */
258   public AnnotationSet get(String type) {
259     if(annotsByType == null) indexByType();
260 
261     // the aliasing that happens when returning a set directly from the
262     // types index can cause concurrent access problems; but the fix below
263     // breaks the tests....
264     //AnnotationSet newSet =
265     //  new AnnotationSetImpl((Collection) annotsByType.get(type));
266     //return newSet;
267 
268     return (AnnotationSet) annotsByType.get(type);
269   } // get(type)
270 
271   /** Select annotations by a set of types. Expects a Set of String. */
272   public AnnotationSet get(Set types) throws ClassCastException {
273 
274     if(annotsByType == null) indexByType();
275 
276     Iterator iter = types.iterator();
277     AnnotationSet resultSet = new AnnotationSetImpl(doc);
278 
279     while(iter.hasNext()) {
280       String type = (String) iter.next();
281       AnnotationSet as = (AnnotationSet) annotsByType.get(type);
282       if(as != null)
283         resultSet.addAll(as);
284       // need an addAllOfOneType method
285     } // while
286 
287     if(resultSet.isEmpty())
288       return null;
289     return resultSet;
290   } // get(types)
291 
292   /** Select annotations by type and features */
293   public AnnotationSet get(String type, FeatureMap constraints) {
294     if(annotsByType == null) indexByType();
295 
296     AnnotationSet typeSet = get(type);
297     if(typeSet == null)
298       return null;
299     AnnotationSet resultSet = new AnnotationSetImpl(doc);
300 
301     Iterator iter = typeSet.iterator();
302     while(iter.hasNext()) {
303       Annotation a = (Annotation) iter.next();
304 
305       // we check for matching constraints by simple equality. a
306       // feature map satisfies the constraints if it contains all the
307       // key/value pairs from the constraints map
308       if( a.getFeatures().entrySet().containsAll( constraints.entrySet() ) )
309         resultSet.add(a);
310     } // while
311 
312     if(resultSet.isEmpty())
313       return null;
314     return resultSet;
315   } // get(type, constraints)
316 
317   /** Select annotations by type and feature names */
318   public AnnotationSet get(String type, Set featureNames) {
319     if(annotsByType == null) indexByType();
320 
321     AnnotationSet typeSet= null;
322     if (type != null) {
323       //if a type is provided, try finding annotations of this type
324       typeSet = get(type);
325       //if none exist, then return coz nothing left to do
326       if(typeSet == null)
327        return null;
328     }
329 
330     AnnotationSet resultSet = new AnnotationSetImpl(doc);
331 
332     Iterator iter = null;
333     if (type != null)
334       iter = typeSet.iterator();
335     else
336       iter = annotsById.values().iterator();
337 
338     while(iter.hasNext()) {
339       Annotation a = (Annotation) iter.next();
340 
341       // we check for matching constraints by simple equality. a
342       // feature map satisfies the constraints if it contains all the
343       // key/value pairs from the constraints map
344       if( a.getFeatures().keySet().containsAll( featureNames ) )
345         resultSet.add(a);
346     } // while
347 
348     if(resultSet.isEmpty())
349       return null;
350     return resultSet;
351   } // get(type, featureNames)
352 
353   /** Select annotations by offset. This returns the set of annotations
354     * whose start node is the least such that it is less than or equal
355     * to offset. If a positional index doesn't exist it is created.
356     * If there are no nodes at or beyond the offset param then it will return
357     * null.
358     */
359   public AnnotationSet get(Long offset) {
360     if(annotsByStartNode == null) indexByStartOffset();
361 
362     // find the next node at or after offset; get the annots starting there
363     Node nextNode = (Node) nodesByOffset.getNextOf(offset);
364     if(nextNode == null) // no nodes at or beyond this offset
365       return null;
366 
367     AnnotationSet res = (AnnotationSet) annotsByStartNode.get(nextNode.getId());
368 
369     //get ready for next test
370     nextNode = (Node) nodesByOffset.getNextOf(new Long(offset.longValue() + 1));
371 
372     //skip all the nodes that have no starting annotations
373     while(res == null && nextNode != null){
374       res = (AnnotationSet) annotsByStartNode.get(nextNode.getId());
375 
376       //get ready for next test
377       nextNode = (Node) nodesByOffset.getNextOf(
378         new Long(nextNode.getOffset().longValue() + 1)
379       );
380     }
381 
382     //res it either null (no suitable node found) or the correct result
383     return res;
384   } // get(offset)
385 
386   /**
387     * Select annotations by offset. This returns the set of annotations
388     * that overlap totaly or partially with the interval defined by the two
389     * provided offsets.The result will include all the annotations that either:
390     * <ul>
391     * <li>start before the start offset and end strictly after it</li>
392     * <li>OR</li>
393     * <li>start at a position between the start and the end offsets</li>
394     */
395   public AnnotationSet get(Long startOffset, Long endOffset) {
396     //the result will include all the annotations that either:
397     //-start before the start offset and end strictly after it
398     //or
399     //-start at a position between the start and the end offsets
400     if(annotsByStartNode == null) indexByStartOffset();
401     AnnotationSet resultSet = new AnnotationSetImpl(doc);
402     Iterator nodesIter;
403     Iterator annotsIter;
404     Node currentNode;
405     Annotation currentAnnot;
406     //find all the annots that start strictly before the start offset and end
407     //strictly after it
408     nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
409     while(nodesIter.hasNext()){
410       currentNode = (Node)nodesIter.next();
411       Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
412       if(fromPoint != null){
413         annotsIter = (fromPoint).iterator();
414         while(annotsIter.hasNext()){
415           currentAnnot = (Annotation)annotsIter.next();
416           if(currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0){
417             resultSet.add(currentAnnot);
418           }
419         }
420       }
421     }
422     //find all the annots that start at or after the start offset but strictly
423     //before the end offset
424     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
425     while(nodesIter.hasNext()){
426       currentNode = (Node)nodesIter.next();
427       Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
428       if(fromPoint != null) resultSet.addAll(fromPoint);
429     }
430     return resultSet;
431   }//get(startOfset, endOffset)
432 
433 
434   /**
435     * Select annotations by offset. This returns the set of annotations
436     * of the given type
437     * that overlap totaly or partially with the interval defined by the two
438     * provided offsets.The result will include all the annotations that either:
439     * <ul>
440     * <li>start before the start offset and end strictly after it</li>
441     * <li>OR</li>
442     * <li>start at a position between the start and the end offsets</li>
443     */
444   public AnnotationSet get(String neededType, Long startOffset, Long endOffset) {
445     //the result will include all the annotations that either:
446     //-start before the start offset and end strictly after it
447     //or
448     //-start at a position between the start and the end offsets
449     if(annotsByStartNode == null) indexByStartOffset();
450     AnnotationSet resultSet = new AnnotationSetImpl(doc);
451     Iterator nodesIter;
452     Iterator annotsIter;
453     Node currentNode;
454     Annotation currentAnnot;
455     //find all the annots that start strictly before the start offset and end
456     //strictly after it
457     nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
458     while(nodesIter.hasNext()){
459       currentNode = (Node)nodesIter.next();
460       Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
461       if(fromPoint != null){
462         annotsIter = (fromPoint).iterator();
463         while(annotsIter.hasNext()){
464           currentAnnot = (Annotation)annotsIter.next();
465           if(currentAnnot.getType().equals(neededType) &&
466              currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0
467             ) {
468             resultSet.add(currentAnnot);
469           }//if
470         }//while
471       }
472     }
473     //find all the annots that start at or after the start offset but strictly
474     //before the end offset
475     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
476     while(nodesIter.hasNext()){
477       currentNode = (Node)nodesIter.next();
478       Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
479       if(fromPoint != null) {
480         annotsIter = (fromPoint).iterator();
481         while(annotsIter.hasNext()){
482           currentAnnot = (Annotation)annotsIter.next();
483           if(currentAnnot.getType().equals(neededType)) {
484             resultSet.add(currentAnnot);
485           }//if
486         }//while
487       } //if
488     }
489     return resultSet;
490   }//get(type, startOfset, endOffset)
491 
492 
493   /** Select annotations by type, features and offset */
494   public AnnotationSet get(String type, FeatureMap constraints, Long offset) {
495 
496     // select by offset
497     AnnotationSet nextAnnots = (AnnotationSet) get(offset);
498 
499     if(nextAnnots == null) return null;
500 
501     // select by type and constraints from the next annots
502     return nextAnnots.get(type, constraints);
503 
504   } // get(type, constraints, offset)
505 
506   /**
507     * Select annotations by offset that
508     * start at a position between the start and end before the end offset
509     */
510   public AnnotationSet getContained(Long startOffset, Long endOffset) {
511     //the result will include all the annotations that either:
512     //start at a position between the start and end before the end offsets
513     if(annotsByStartNode == null) indexByStartOffset();
514     AnnotationSet resultSet = new AnnotationSetImpl(doc);
515     Iterator nodesIter;
516     Iterator annotsIter;
517     Node currentNode;
518     Annotation currentAnnot;
519     //find all the annots that start at or after the start offset but strictly
520     //before the end offset
521     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
522     while(nodesIter.hasNext()){
523       currentNode = (Node)nodesIter.next();
524       Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
525       if (fromPoint == null) continue;
526       //loop through the annotations and find only those that
527       //also end before endOffset
528       Iterator annotIter = fromPoint.iterator();
529       while (annotIter.hasNext()) {
530         Annotation annot = (Annotation) annotIter.next();
531         if (annot.getEndNode().getOffset().compareTo(endOffset) <= 0)
532           resultSet.add(annot);
533       }
534     }
535     return resultSet;
536   }//get(startOfset, endOffset)
537 
538 
539 
540   /** Get the node with the smallest offset */
541   public Node firstNode() {
542     indexByStartOffset();
543     if(nodesByOffset.isEmpty()) return null;
544     else return (Node) nodesByOffset.get(nodesByOffset.firstKey());
545   } // firstNode
546 
547   /** Get the node with the largest offset */
548   public Node lastNode() {
549 
550     indexByStartOffset();
551     indexByEndOffset();
552 
553     if(nodesByOffset.isEmpty())return null;
554     else return (Node) nodesByOffset.get(nodesByOffset.lastKey());
555 
556   } // lastNode
557 
558   /**
559     * Get the first node that is relevant for this annotation set and which has
560     * the offset larger than the one of the node provided.
561     */
562   public Node nextNode(Node node) {
563     indexByStartOffset();
564     indexByEndOffset();
565     return (Node)nodesByOffset.getNextOf(
566                                new Long(node.getOffset().longValue() + 1)
567                                );
568   }
569 
570   /** Create and add an annotation with pre-existing nodes,
571     * and return its id
572     */
573   public Integer add(Node start, Node end, String type, FeatureMap features) {
574 
575     // the id of the new annotation
576     Integer id = doc.getNextAnnotationId();
577 
578     // construct an annotation
579     Annotation a = new AnnotationImpl(id, start, end, type, features);
580 
581     // delegate to the method that adds existing annotations
582     add(a);
583 
584     return id;
585   } // add(Node, Node, String, FeatureMap)
586 
587   /** Add an existing annotation. Returns true when the set is modified. */
588   public boolean add(Object o) throws ClassCastException {
589     Annotation a = (Annotation) o;
590     Object oldValue = annotsById.put(a.getId(), a);
591     if (annotsByType != null)
592       addToTypeIndex(a);
593     if (annotsByStartNode != null || annotsByEndNode != null)
594       addToOffsetIndex(a);
595       AnnotationSetEvent evt = new AnnotationSetEvent(
596                                     this,
597                                     AnnotationSetEvent.ANNOTATION_ADDED,
598                                     doc, a);
599       fireAnnotationAdded(evt);
600       fireGateEvent(evt);
601     return oldValue != a;
602   } // add(o)
603 
604   /** Create and add an annotation and return its id */
605   public Integer add(
606     Long start, Long end, String type, FeatureMap features
607   ) throws InvalidOffsetException {
608 
609     // are the offsets valid?
610     if(! doc.isValidOffsetRange(start, end))
611       throw new InvalidOffsetException();
612 
613     // the set has to be indexed by position in order to add, as we need
614     // to find out if nodes need creating or if they exist already
615     if(nodesByOffset == null) {
616       indexByStartOffset();
617       indexByEndOffset();
618     }
619 
620     // find existing nodes if appropriate nodes don't already exist, create them
621     Node startNode  = (Node) nodesByOffset.getNextOf(start);
622     if(startNode == null || ! startNode.getOffset().equals(start))
623       startNode = new NodeImpl(doc.getNextNodeId(), start);
624 
625     Node endNode = null;
626     if(start.equals(end))
627       endNode = startNode;
628     else
629       endNode = (Node) nodesByOffset.getNextOf(end);
630 
631     if(endNode == null   || ! endNode.getOffset().equals(end))
632       endNode = new NodeImpl(doc.getNextNodeId(), end);
633 
634     // delegate to the method that adds annotations with existing nodes
635     return add(startNode, endNode, type, features);
636   } // add(start, end, type, features)
637 
638   /** Create and add an annotation from database read data
639     * In this case the id is already known being previously fetched from the
640     * database
641     */
642   public void add(
643     Integer id, Long start, Long end, String type, FeatureMap features
644   ) throws InvalidOffsetException {
645 
646     // are the offsets valid?
647     if(! doc.isValidOffsetRange(start, end))
648       throw new InvalidOffsetException();
649 
650     // the set has to be indexed by position in order to add, as we need
651     // to find out if nodes need creating or if they exist already
652     if(nodesByOffset == null){
653       indexByStartOffset();
654       indexByEndOffset();
655     }
656 
657     // find existing nodes if appropriate nodes don't already exist, create them
658     Node startNode  = (Node) nodesByOffset.getNextOf(start);
659     if(startNode == null || ! startNode.getOffset().equals(start))
660       startNode = new NodeImpl(doc.getNextNodeId(), start);
661 
662     Node endNode = null;
663     if(start.equals(end))
664       endNode = startNode;
665     else
666       endNode = (Node) nodesByOffset.getNextOf(end);
667 
668     if(endNode == null   || ! endNode.getOffset().equals(end))
669       endNode = new NodeImpl(doc.getNextNodeId(), end);
670 
671     // construct an annotation
672     Annotation a = new AnnotationImpl(id, startNode, endNode, type, features);
673     add(a);
674 
675   } // add(id, start, end, type, features)
676 
677   /** Construct the positional index. */
678   protected void indexByType() {
679 
680     if(annotsByType != null) return;
681 
682     annotsByType = new HashMap(Gate.HASH_STH_SIZE);
683 
684     Annotation a;
685     Iterator annotIter = annotsById.values().iterator();
686 
687     while (annotIter.hasNext())
688       addToTypeIndex( (Annotation) annotIter.next() );
689 
690   } // indexByType()
691 
692   /** Construct the positional indices for annotation start */
693   protected void indexByStartOffset() {
694 
695     if(annotsByStartNode != null) return;
696 
697     if(nodesByOffset == null)
698       nodesByOffset = new RBTreeMap();
699     annotsByStartNode = new HashMap(Gate.HASH_STH_SIZE);
700 
701     Annotation a;
702     Iterator annotIter = annotsById.values().iterator();
703 
704     while(annotIter.hasNext())
705       addToStartOffsetIndex( (Annotation) annotIter.next() );
706 
707   } // indexByStartOffset()
708 
709   /** Construct the positional indices for annotation end */
710   protected void indexByEndOffset() {
711 
712     if(annotsByEndNode != null) return;
713 
714     if(nodesByOffset == null)
715       nodesByOffset = new RBTreeMap();
716     annotsByEndNode = new HashMap(Gate.HASH_STH_SIZE);
717 
718     Annotation a;
719     Iterator annotIter = annotsById.values().iterator();
720 
721     while(annotIter.hasNext())
722       addToEndOffsetIndex( (Annotation) annotIter.next() );
723 
724   } // indexByEndOffset()
725 
726   /** Add an annotation to the type index. Does nothing if the index
727     * doesn't exist.
728     */
729   void addToTypeIndex(Annotation a) {
730     if(annotsByType == null) return;
731 
732     String type = a.getType();
733     AnnotationSet sameType = (AnnotationSet) annotsByType.get(type);
734 
735     if(sameType == null) {
736       sameType = new AnnotationSetImpl(doc);
737       annotsByType.put(type, sameType);
738     }
739     sameType.add(a);
740   } // addToTypeIndex(a)
741 
742   /** Add an annotation to the offset indices. Does nothing if they
743     * don't exist.
744     */
745   void addToOffsetIndex(Annotation a) {
746     addToStartOffsetIndex(a);
747     addToEndOffsetIndex(a);
748   } // addToOffsetIndex(a)
749 
750   /** Add an annotation to the start offset index. Does nothing if the
751     * index doesn't exist.
752     */
753   void addToStartOffsetIndex(Annotation a) {
754     Node startNode  = a.getStartNode();
755     Node endNode    = a.getEndNode();
756     Long start      = startNode.getOffset();
757     Long end        = endNode.getOffset();
758 
759     // add a's nodes to the offset index
760     if(nodesByOffset != null)
761       nodesByOffset.put(start, startNode);
762 
763     // if there's no appropriate index give up
764     if(annotsByStartNode == null) return;
765 
766     // get the annotations that start at the same node, or create new set
767     AnnotationSet thisNodeAnnots =
768       (AnnotationSet) annotsByStartNode.get(startNode.getId());
769 
770     if(thisNodeAnnots == null) {
771       thisNodeAnnots = new AnnotationSetImpl(doc);
772       annotsByStartNode.put(startNode.getId(), thisNodeAnnots);
773     }
774     // add to the annots listed for a's start node
775     thisNodeAnnots.add(a);
776 
777   } // addToStartOffsetIndex(a)
778 
779   /** Add an annotation to the end offset index. Does nothing if the
780     * index doesn't exist.
781     */
782   void addToEndOffsetIndex(Annotation a) {
783     Node startNode  = a.getStartNode();
784     Node endNode    = a.getEndNode();
785     Long start      = startNode.getOffset();
786     Long end        = endNode.getOffset();
787 
788     // add a's nodes to the offset index
789     if(nodesByOffset != null) nodesByOffset.put(end, endNode);
790 
791     // if there's no appropriate index give up
792     if(annotsByEndNode == null)
793       return;
794 
795     // get the annotations that start at the same node, or create new set
796     AnnotationSet thisNodeAnnots =
797       (AnnotationSet) annotsByEndNode.get(endNode.getId());
798 
799     if(thisNodeAnnots == null) {
800       thisNodeAnnots = new AnnotationSetImpl(doc);
801       annotsByEndNode.put(endNode.getId(), thisNodeAnnots);
802     }
803     // add to the annots listed for a's start node
804     thisNodeAnnots.add(a);
805 
806   } // addToEndOffsetIndex(a)
807 
808   /** Propagate changes to the document content. Has, unfortunately,
809     * to be public, to allow DocumentImpls to get at it. Oh for a
810     * "friend" declaration. Doesn't thow InvalidOffsetException as
811     * DocumentImpl is the only client, and that checks the offsets
812     * before calling this method.
813     */
814   public void edit(Long start, Long end, DocumentContent replacement) {
815     long s = start.longValue(), e = end.longValue();
816     long rlen = // length of the replacement value
817       ( (replacement == null) ? 0 : replacement.size().longValue() );
818 
819     indexByStartOffset();
820     indexByEndOffset();
821 
822     Iterator replacedAreaNodesIter =
823       nodesByOffset.subMap(start, end).values().iterator();
824     while(replacedAreaNodesIter.hasNext()) {
825       Node n = (Node) replacedAreaNodesIter.next();
826 
827       // remove from nodes map
828 //      if(true)
829 //        throw new LazyProgrammerException("this next call tries to remove " +
830 //          "from a map based on the value; note index is key; also note that " +
831 //          "some nodes may have no index....");
832 
833 //There is at most one node at any given location so removing is safe.
834 //Also note that unrooted nodes have never been implemented so all nodes have
835 //offset
836       nodesByOffset.remove(n.getOffset());
837 
838       // remove annots that start at this node
839       AnnotationSet invalidatedAnnots =
840         (AnnotationSet) annotsByStartNode.get(n.getId());
841       if(invalidatedAnnots != null)
842         removeAll(invalidatedAnnots);
843 
844       // remove annots that end at this node
845       invalidatedAnnots =
846         (AnnotationSet) annotsByEndNode.get(n.getId());
847       if(invalidatedAnnots != null)
848         removeAll(invalidatedAnnots);
849     } // loop over replaced area nodes
850 
851     // update the offsets of the other nodes
852     Iterator nodesAfterReplacementIter =
853       nodesByOffset.tailMap(end).values().iterator();
854     while(nodesAfterReplacementIter.hasNext()) {
855       NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
856       long oldOffset = n.getOffset().longValue();
857 
858       n.setOffset(new Long( oldOffset - ( (e-s) - rlen ) ));
859     } // loop over nodes after replacement area
860 
861   } // edit(start,end,replacement)
862 
863   /** Get the name of this set. */
864   public String getName() { return name; }
865 
866   /** Get the document this set is attached to. */
867   public Document getDocument() { return doc; }
868 
869   /** Get a set of java.lang.String objects representing all the annotation
870     * types present in this annotation set.
871     */
872   public Set getAllTypes() {
873     indexByType();
874     return annotsByType.keySet();
875   }
876 
877   /**
878    *
879    * @return
880    * @throws CloneNotSupportedException
881    */
882   public Object clone() throws CloneNotSupportedException{
883     return super.clone();
884   }
885   /**
886    *
887    * @param l
888    */
889   public synchronized void removeAnnotationSetListener(AnnotationSetListener l) {
890     if (annotationSetListeners != null && annotationSetListeners.contains(l)) {
891       Vector v = (Vector) annotationSetListeners.clone();
892       v.removeElement(l);
893       annotationSetListeners = v;
894     }
895   }
896   /**
897    *
898    * @param l
899    */
900   public synchronized void addAnnotationSetListener(AnnotationSetListener l) {
901     Vector v = annotationSetListeners == null ? new Vector(2) : (Vector) annotationSetListeners.clone();
902     if (!v.contains(l)) {
903       v.addElement(l);
904       annotationSetListeners = v;
905     }
906   }
907   /** String representation of the set */
908   /*public String toString() {
909 
910     // annotsById
911     SortedSet sortedAnnots = new TreeSet();
912     sortedAnnots.addAll(annotsById.values());
913     String aBI = sortedAnnots.toString();
914 
915     // annotsByType
916 
917     StringBuffer buf = new StringBuffer();
918     Iterator iter = annotsByType.iterator();
919     while(iter.hasNext()) {
920       HashMap thisType = iter.next().entrySet();
921       sortedAnnots.clear();
922       sortedAnnots.addAll(thisType.);
923       buf.append("[type: " +
924     }
925 
926 
927     return
928       "AnnotationSetImpl: " +
929       "name=" + name +
930     //  "; doc.getURL()=" + doc +
931       "; annotsById=" + aBI +
932     //  "; annotsByType=" + aBT +
933       "; "
934       ;
935   } // toString()
936   */
937 
938 //  public int hashCode() {
939 //    int hash = 0;
940 //    Iterator i = this.iterator();
941 //    while (i.hasNext()) {
942 //        Annotation annot = (Annotation)i.next();
943 //        if ( annot != null)
944 //            hash += annot.hashCode();
945 //    }
946 //    int nameHash = (name == null ? 0 : name.hashCode());
947 //    //int docHash = (doc == null ? 0 : doc.hashCode());
948 //
949 //    return hash ^ nameHash;// ^ docHash;
950 //  }
951 
952   /** The name of this set */
953   String name = null;
954 
955   /** The document this set belongs to */
956   DocumentImpl doc;
957 
958   /** Maps annotation ids (Integers) to Annotations */
959   protected HashMap annotsById;
960 
961   /** Maps annotation types (Strings) to AnnotationSets */
962   Map annotsByType = null;
963 
964   /** Maps offsets (Longs) to nodes */
965   RBTreeMap nodesByOffset = null;
966 
967   /** Maps node ids (Integers) to AnnotationSets representing those
968     * annotations that start from that node
969     */
970   Map annotsByStartNode;
971 
972   /** Maps node ids (Integers) to AnnotationSets representing those
973     * annotations that end at that node
974     */
975   Map annotsByEndNode;
976   private transient Vector annotationSetListeners;
977   private transient Vector gateListeners;
978   /**
979    *
980    * @param e
981    */
982   protected void fireAnnotationAdded(AnnotationSetEvent e) {
983     if (annotationSetListeners != null) {
984       Vector listeners = annotationSetListeners;
985       int count = listeners.size();
986       for (int i = 0; i < count; i++) {
987         ((AnnotationSetListener) listeners.elementAt(i)).annotationAdded(e);
988       }
989     }
990   }
991   /**
992    *
993    * @param e
994    */
995   protected void fireAnnotationRemoved(AnnotationSetEvent e) {
996     if (annotationSetListeners != null) {
997       Vector listeners = annotationSetListeners;
998       int count = listeners.size();
999       for (int i = 0; i < count; i++) {
1000        ((AnnotationSetListener) listeners.elementAt(i)).annotationRemoved(e);
1001      }
1002    }
1003  }
1004  /**
1005   *
1006   * @param l
1007   */
1008  public synchronized void removeGateListener(GateListener l) {
1009    if (gateListeners != null && gateListeners.contains(l)) {
1010      Vector v = (Vector) gateListeners.clone();
1011      v.removeElement(l);
1012      gateListeners = v;
1013    }
1014  }
1015  /**
1016   *
1017   * @param l
1018   */
1019  public synchronized void addGateListener(GateListener l) {
1020    Vector v = gateListeners == null ? new Vector(2) : (Vector) gateListeners.clone();
1021    if (!v.contains(l)) {
1022      v.addElement(l);
1023      gateListeners = v;
1024    }
1025  }
1026  /**
1027   *
1028   * @param e
1029   */
1030  protected void fireGateEvent(GateEvent e) {
1031    if (gateListeners != null) {
1032      Vector listeners = gateListeners;
1033      int count = listeners.size();
1034      for (int i = 0; i < count; i++) {
1035        ((GateListener) listeners.elementAt(i)).processGateEvent(e);
1036      }
1037    }
1038  }
1039
1040 /** Freeze the serialization UID. */
1041  static final long serialVersionUID = 1479426765310434166L;
1042} // AnnotationSetImpl
1043