1   /*
2   
3    *  AnnotationSetImpl.java
4   
5    *
6   
7    *  Copyright (c) 1998-2001, The University of Sheffield.
8   
9    *
10  
11   *  This file is part of GATE (see http://gate.ac.uk/), and is free
12  
13   *  software, licenced under the GNU Library General Public License,
14  
15   *  Version 2, June 1991 (in the distribution as file licence.html,
16  
17   *  and also available at http://gate.ac.uk/gate/licence.html).
18  
19   *
20  
21   *  Hamish Cunningham, 7/Feb/2000
22  
23   *
24  
25   *  Developer notes:
26  
27   *  ---
28  
29   *
30  
31   *  the addToIndex... and indexBy... methods could be refactored as I'm
32  
33   *  sure they can be made simpler
34  
35   *
36  
37   *  every set to which annotation will be added has to have positional
38  
39   *  indexing, so that we can find or create the nodes on the new annotations
40  
41   *
42  
43   *  note that annotations added anywhere other than sets that are
44  
45   *  stored on the document will not get stored anywhere...
46  
47   *
48  
49   *  nodes aren't doing anything useful now. needs some interface that allows
50  
51   *  their creation, defaulting to no coterminous duplicates, but allowing such
52  
53   *  if required
54  
55   *
56  
57   *  $Id: AnnotationSetImpl.java,v 1.78 2003/06/25 16:04:04 marin Exp $
58  
59   */
60  
61  
62  
63  package gate.annotation;
64  
65  
66  
67  import java.util.*;
68  
69  import gate.util.*;
70  
71  
72  
73  import gate.*;
74  
75  import gate.corpora.*;
76  
77  import gate.event.*;
78  
79  
80  
81  
82  
83  /** Implementation of AnnotationSet. Has a number of indices, all bar one
84  
85    * of which are null by default and are only constructed when asked
86  
87    * for. Has lots of get methods with various selection criteria; these
88  
89    * return views into the set, which are nonetheless valid sets in
90  
91    * their own right (but will not necesarily be fully indexed).
92  
93    * Has a name, which is null by default; clients of Document can
94  
95    * request named AnnotationSets if they so desire. Has a reference to the
96  
97    * Document it is attached to. Contrary to Collections convention,
98  
99    * there is no no-arg constructor, as this would leave the set in
100 
101   * an inconsistent state.
102 
103   * <P>
104 
105   * There are five indices: annotation by id, annotations by type, annotations
106 
107   * by start/end node and nodes by offset. The last three jointly provide
108 
109   * positional indexing; construction of these is triggered by
110 
111   * indexByStart/EndOffset(),
112 
113   * or by calling a get method that selects on offset. The type
114 
115   * index is triggered by indexByType(), or calling a get method that selects
116 
117   * on type. The id index is always present.
118 
119   */
120 
121 public class AnnotationSetImpl
122 
123 extends AbstractSet
124 
125 implements AnnotationSet
126 
127 {
128 
129   /** Debug flag */
130 
131   private static final boolean DEBUG = false;
132 
133 
134 
135   /** Construction from Document. */
136 
137   public AnnotationSetImpl(Document doc) {
138 
139     annotsById = new VerboseHashMap();
140 
141     this.doc = (DocumentImpl) doc;
142 
143   } // construction from document
144 
145 
146 
147   /** Construction from Document and name. */
148 
149   public AnnotationSetImpl(Document doc, String name) {
150 
151     this(doc);
152 
153     this.name = name;
154 
155   } // construction from document and name
156 
157 
158 
159   /** Construction from Collection (which must be an AnnotationSet) */
160 
161 //<<<dam: speedup constructor
162 
163 /*
164 
165   public AnnotationSetImpl(Collection c) throws ClassCastException {
166 
167     this(((AnnotationSet) c).getDocument());
168 
169     addAll(c);
170 
171   } // construction from collection
172 
173 */
174 
175 //===dam: now
176 
177   /** Construction from Collection (which must be an AnnotationSet) */
178 
179   public AnnotationSetImpl(Collection c) throws ClassCastException {
180 
181     this(((AnnotationSet) c).getDocument());
182 
183 
184 
185     if (c instanceof AnnotationSetImpl)
186 
187     {
188 
189         AnnotationSetImpl theC = (AnnotationSetImpl)c;
190 
191         annotsById = (HashMap)theC.annotsById.clone();
192 
193         if(theC.annotsByEndNode != null)
194 
195         {
196 
197             annotsByEndNode = (Map)((HashMap)theC.annotsByEndNode).clone();
198 
199             annotsByStartNode = (Map)((HashMap)theC.annotsByStartNode).clone();
200 
201         }
202 
203         if (theC.annotsByType != null)
204 
205             annotsByType = (Map)((HashMap)theC.annotsByType).clone();
206 
207         if (theC.nodesByOffset != null)
208 
209         {
210 
211             nodesByOffset = (RBTreeMap)theC.nodesByOffset.clone();
212 
213         }
214 
215 
216 
217     } else
218 
219         addAll(c);
220 
221   } // construction from collection
222 
223 //>>>dam: end
224 
225 
226 
227   /** This inner class serves as the return value from the iterator()
228 
229     * method.
230 
231     */
232 
233   class AnnotationSetIterator implements Iterator {
234 
235 
236 
237     private Iterator iter;
238 
239 
240 
241     protected Annotation lastNext = null;
242 
243 
244 
245     AnnotationSetIterator()  { iter = annotsById.values().iterator(); }
246 
247 
248 
249     public boolean hasNext() { return iter.hasNext(); }
250 
251 
252 
253     public Object next()     { return (lastNext = (Annotation) iter.next());}
254 
255 
256 
257     public void remove()     {
258 
259       // this takes care of the ID index
260 
261       iter.remove();
262 
263       //that's the second way of removing annotations from a set
264 
265       //apart from calling remove() on the set itself
266 
267       fireAnnotationRemoved(new AnnotationSetEvent(
268 
269                             AnnotationSetImpl.this,
270 
271                             AnnotationSetEvent.ANNOTATION_REMOVED,
272 
273                             getDocument(), (Annotation)lastNext));
274 
275 
276 
277       // remove from type index
278 
279       removeFromTypeIndex(lastNext);
280 
281 
282 
283       // remove from offset indices
284 
285       removeFromOffsetIndex(lastNext);
286 
287 
288 
289     } // remove()
290 
291 
292 
293   }; // AnnotationSetIterator
294 
295 
296 
297   /**
298 
299    * Class used for the indexById structure. This is a {@link java.util.HashMap}
300 
301    * that fires events when elements are removed.
302 
303    */
304 
305   public class VerboseHashMap extends HashMap{
306 
307 
308 
309     VerboseHashMap() {
310 
311       super(Gate.HASH_STH_SIZE);
312 
313     } //contructor
314 
315 
316 
317     public Object remove(Object key){
318 
319       Object res = super.remove(key);
320 
321       if(res != null) {
322 
323         if(owner == null){
324 
325           fireAnnotationRemoved(new AnnotationSetEvent(
326 
327                                               AnnotationSetImpl.this,
328 
329                                               AnnotationSetEvent.ANNOTATION_REMOVED,
330 
331                                               getDocument(), (Annotation)res));
332 
333         }else{
334 
335           owner.fireAnnotationRemoved(new AnnotationSetEvent(
336 
337               AnnotationSetImpl.this,
338 
339               AnnotationSetEvent.ANNOTATION_REMOVED,
340 
341           getDocument(), (Annotation) res));
342 
343         }
344 
345       }
346 
347       return res;
348 
349     }//public Object remove(Object key)
350 
351     static final long serialVersionUID = -4832487354063073511L;
352 
353 
354 
355     /**
356 
357      * The annotation set this maps is part of.
358 
359      * This is an ugly hack in order to fix a bug: database annotation sets
360 
361      * didn't fire annotation removed events.
362 
363      */
364 
365     private transient AnnotationSetImpl owner;
366 
367     /**
368 
369      * Sets the annotation set this maps is part of.
370 
371      * This is an ugly hack in order to fix a bug: database annotation sets
372 
373      * didn't fire annotation removed events.
374 
375      */
376 
377     public void setOwner(AnnotationSetImpl newOwner){
378 
379       this.owner = newOwner;
380 
381     }
382 
383 
384 
385   }//protected class VerboseHashMap extends HashMap
386 
387 
388 
389   /** Get an iterator for this set */
390 
391   public Iterator iterator() { return new AnnotationSetIterator(); }
392 
393 
394 
395   /** Remove an element from this set. */
396 
397   public boolean remove(Object o) throws ClassCastException {
398 
399 
400 
401     Annotation a = (Annotation) o;
402 
403 
404 
405     boolean wasPresent = removeFromIdIndex(a);
406 
407     if(wasPresent){
408 
409       removeFromTypeIndex(a);
410 
411       removeFromOffsetIndex(a);
412 
413     }
414 
415     return wasPresent;
416 
417   } // remove(o)
418 
419 
420 
421   /** Remove from the ID index. */
422 
423   protected boolean removeFromIdIndex(Annotation a) {
424 
425     if(annotsById.remove(a.getId()) == null)
426 
427       return false;
428 
429 
430 
431     return true;
432 
433   } // removeFromIdIndex(a)
434 
435 
436 
437   /** Remove from the type index. */
438 
439   protected void removeFromTypeIndex(Annotation a) {
440 
441     if(annotsByType != null) {
442 
443 
444 
445       AnnotationSet sameType = (AnnotationSet) annotsByType.get(a.getType());
446 
447 
448 
449       if(sameType != null) sameType.remove(a);
450 
451 
452 
453       if(sameType.isEmpty()) // none left of this type
454 
455         annotsByType.remove(a.getType());
456 
457     }
458 
459   } // removeFromTypeIndex(a)
460 
461 
462 
463   /** Remove from the offset indices. */
464 
465   protected void removeFromOffsetIndex(Annotation a) {
466 
467     if(nodesByOffset != null) {
468 
469     // knowing when a node is no longer needed would require keeping a reference
470 
471     // count on annotations, or using a weak reference to the nodes in
472 
473     // nodesByOffset
474 
475     }
476 
477 
478 
479     if(annotsByStartNode != null) {
480 
481       Integer id = a.getStartNode().getId();
482 
483       AnnotationSet starterAnnots = (AnnotationSet) annotsByStartNode.get(id);
484 
485       starterAnnots.remove(a);
486 
487       if(starterAnnots.isEmpty()) // no annotations start here any more
488 
489         annotsByStartNode.remove(id);
490 
491     }
492 
493 
494 
495     if(annotsByEndNode != null) {
496 
497       Integer id = a.getEndNode().getId();
498 
499       AnnotationSet endingAnnots = (AnnotationSet) annotsByEndNode.get(id);
500 
501       endingAnnots.remove(a);
502 
503       if(endingAnnots.isEmpty()) // no annotations start here any more
504 
505         annotsByEndNode.remove(id);
506 
507     }
508 
509 
510 
511   } // removeFromOffsetIndex(a)
512 
513 
514 
515   /** The size of this set */
516 
517   public int size() { return annotsById.size(); }
518 
519 
520 
521   /** Find annotations by id */
522 
523   public Annotation get(Integer id) {
524 
525     return (Annotation) annotsById.get(id);
526 
527   } // get(id)
528 
529 
530 
531   /** Get all annotations */
532 
533   public AnnotationSet get() {
534 
535     AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
536 
537     Iterator iter = annotsById.values().iterator();
538 
539     resultSet.addAllKeepIDs(annotsById.values());
540 
541     if(resultSet.isEmpty())
542 
543       return null;
544 
545     return resultSet;
546 
547   } // get()
548 
549 
550 
551   /** Select annotations by type */
552 
553   public AnnotationSet get(String type) {
554 
555     if(annotsByType == null) indexByType();
556 
557 
558 
559     // the aliasing that happens when returning a set directly from the
560 
561     // types index can cause concurrent access problems; but the fix below
562 
563     // breaks the tests....
564 
565     //AnnotationSet newSet =
566 
567     //  new AnnotationSetImpl((Collection) annotsByType.get(type));
568 
569     //return newSet;
570 
571 
572 
573     return (AnnotationSet) annotsByType.get(type);
574 
575   } // get(type)
576 
577 
578 
579   /** Select annotations by a set of types. Expects a Set of String. */
580 
581   public AnnotationSet get(Set types) throws ClassCastException {
582 
583 
584 
585     if(annotsByType == null) indexByType();
586 
587 
588 
589     Iterator iter = types.iterator();
590 
591     AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
592 
593 
594 
595     while(iter.hasNext()) {
596 
597       String type = (String) iter.next();
598 
599       AnnotationSet as = (AnnotationSet) annotsByType.get(type);
600 
601       if(as != null)
602 
603         resultSet.addAllKeepIDs(as);
604 
605       // need an addAllOfOneType method
606 
607     } // while
608 
609 
610 
611     if(resultSet.isEmpty())
612 
613       return null;
614 
615     return resultSet;
616 
617   } // get(types)
618 
619 
620 
621   /** Select annotations by type and features */
622 
623   public AnnotationSet get(String type, FeatureMap constraints) {
624 
625     if(annotsByType == null) indexByType();
626 
627 
628 
629     AnnotationSet typeSet = get(type);
630 
631     if(typeSet == null)
632 
633       return null;
634 
635     AnnotationSet resultSet = new AnnotationSetImpl(doc);
636 
637 
638 
639     Iterator iter = typeSet.iterator();
640 
641     while(iter.hasNext()) {
642 
643       Annotation a = (Annotation) iter.next();
644 
645 
646 
647       // we check for matching constraints by simple equality. a
648 
649       // feature map satisfies the constraints if it contains all the
650 
651       // key/value pairs from the constraints map
652 
653       if( a.getFeatures().entrySet().containsAll( constraints.entrySet() ) )
654 
655         resultSet.add(a);
656 
657     } // while
658 
659 
660 
661     if(resultSet.isEmpty())
662 
663       return null;
664 
665     return resultSet;
666 
667   } // get(type, constraints)
668 
669 
670 
671   /** Select annotations by type and feature names */
672 
673   public AnnotationSet get(String type, Set featureNames) {
674 
675     if(annotsByType == null) indexByType();
676 
677 
678 
679     AnnotationSet typeSet= null;
680 
681     if (type != null) {
682 
683       //if a type is provided, try finding annotations of this type
684 
685       typeSet = get(type);
686 
687       //if none exist, then return coz nothing left to do
688 
689       if(typeSet == null)
690 
691        return null;
692 
693     }
694 
695 
696 
697     AnnotationSet resultSet = new AnnotationSetImpl(doc);
698 
699 
700 
701     Iterator iter = null;
702 
703     if (type != null)
704 
705       iter = typeSet.iterator();
706 
707     else
708 
709       iter = annotsById.values().iterator();
710 
711 
712 
713     while(iter.hasNext()) {
714 
715       Annotation a = (Annotation) iter.next();
716 
717 
718 
719       // we check for matching constraints by simple equality. a
720 
721       // feature map satisfies the constraints if it contains all the
722 
723       // key/value pairs from the constraints map
724 
725       if( a.getFeatures().keySet().containsAll( featureNames ) )
726 
727         resultSet.add(a);
728 
729     } // while
730 
731 
732 
733     if(resultSet.isEmpty())
734 
735       return null;
736 
737     return resultSet;
738 
739   } // get(type, featureNames)
740 
741 
742 
743   /** Select annotations by offset. This returns the set of annotations
744 
745     * whose start node is the least such that it is less than or equal
746 
747     * to offset. If a positional index doesn't exist it is created.
748 
749     * If there are no nodes at or beyond the offset param then it will return
750 
751     * null.
752 
753     */
754 
755   public AnnotationSet get(Long offset) {
756 
757     if(annotsByStartNode == null) indexByStartOffset();
758 
759 
760 
761     // find the next node at or after offset; get the annots starting there
762 
763     Node nextNode = (Node) nodesByOffset.getNextOf(offset);
764 
765     if(nextNode == null) // no nodes at or beyond this offset
766 
767       return null;
768 
769 
770 
771     AnnotationSet res = (AnnotationSet) annotsByStartNode.get(nextNode.getId());
772 
773 
774 
775     //get ready for next test
776 
777     nextNode = (Node) nodesByOffset.getNextOf(new Long(offset.longValue() + 1));
778 
779 
780 
781     //skip all the nodes that have no starting annotations
782 
783     while(res == null && nextNode != null){
784 
785       res = (AnnotationSet) annotsByStartNode.get(nextNode.getId());
786 
787 
788 
789       //get ready for next test
790 
791       nextNode = (Node) nodesByOffset.getNextOf(
792 
793         new Long(nextNode.getOffset().longValue() + 1)
794 
795       );
796 
797     }
798 
799 
800 
801     //res it either null (no suitable node found) or the correct result
802 
803     return res;
804 
805   } // get(offset)
806 
807 
808 
809   /**
810 
811     * Select annotations by offset. This returns the set of annotations
812 
813     * that overlap totaly or partially with the interval defined by the two
814 
815     * provided offsets.The result will include all the annotations that either:
816 
817     * <ul>
818 
819     * <li>start before the start offset and end strictly after it</li>
820 
821     * <li>OR</li>
822 
823     * <li>start at a position between the start and the end offsets</li>
824 
825     */
826 
827   public AnnotationSet get(Long startOffset, Long endOffset) {
828 
829     //the result will include all the annotations that either:
830 
831     //-start before the start offset and end strictly after it
832 
833     //or
834 
835     //-start at a position between the start and the end offsets
836 
837     if(annotsByStartNode == null) indexByStartOffset();
838 
839     AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
840 
841     Iterator nodesIter;
842 
843     Iterator annotsIter;
844 
845     Node currentNode;
846 
847     Annotation currentAnnot;
848 
849     //find all the annots that start strictly before the start offset and end
850 
851     //strictly after it
852 
853     nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
854 
855     while(nodesIter.hasNext()){
856 
857       currentNode = (Node)nodesIter.next();
858 
859       Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
860 
861       if(fromPoint != null){
862 
863         annotsIter = (fromPoint).iterator();
864 
865         while(annotsIter.hasNext()){
866 
867           currentAnnot = (Annotation)annotsIter.next();
868 
869           if(currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0){
870 
871             resultSet.add(currentAnnot);
872 
873           }
874 
875         }
876 
877       }
878 
879     }
880 
881     //find all the annots that start at or after the start offset but strictly
882 
883     //before the end offset
884 
885     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
886 
887     while(nodesIter.hasNext()){
888 
889       currentNode = (Node)nodesIter.next();
890 
891       Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
892 
893       if(fromPoint != null) resultSet.addAllKeepIDs(fromPoint);
894 
895     }
896 
897     return resultSet;
898 
899   }//get(startOfset, endOffset)
900 
901 
902 
903 
904 
905   /**
906 
907     * Select annotations by offset. This returns the set of annotations
908 
909     * that overlap strictly with the interval defined by the two
910 
911     * provided offsets.The result will include all the annotations that
912 
913     * start at the start offset and end strictly at the end offset
914 
915     */
916 
917   public AnnotationSet getStrict(Long startOffset, Long endOffset) {
918 
919     //the result will include all the annotations that
920 
921     //start at the start offset and end strictly at the end offset
922 
923     if(annotsByStartNode == null) indexByStartOffset();
924 
925     AnnotationSet resultSet = new AnnotationSetImpl(doc);
926 
927     Iterator nodesIter;
928 
929     Iterator annotsIter;
930 
931     Node currentNode;
932 
933     Annotation currentAnnot;
934 
935     //find all the annots that start at the start offset
936 
937     currentNode = (Node) nodesByOffset.get(startOffset);
938 
939     if(currentNode != null) {
940 
941       Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
942 
943       if (fromPoint != null) {
944 
945         annotsIter = fromPoint.iterator();
946 
947         while (annotsIter.hasNext()) {
948 
949           currentAnnot = (Annotation) annotsIter.next();
950 
951           if(currentAnnot.getEndNode().getOffset().compareTo(endOffset) == 0){
952 
953             resultSet.add(currentAnnot);
954 
955           } // if
956 
957         } // while
958 
959       } // if
960 
961     } // if
962 
963     return resultSet;
964 
965   }//getStrict(startOfset, endOffset)
966 
967 
968 
969   /**
970 
971     * Select annotations by offset. This returns the set of annotations
972 
973     * of the given type
974 
975     * that overlap totaly or partially with the interval defined by the two
976 
977     * provided offsets.The result will include all the annotations that either:
978 
979     * <ul>
980 
981     * <li>start before the start offset and end strictly after it</li>
982 
983     * <li>OR</li>
984 
985     * <li>start at a position between the start and the end offsets</li>
986 
987     */
988 
989   public AnnotationSet get(String neededType, Long startOffset, Long endOffset) {
990 
991     //the result will include all the annotations that either:
992 
993     //-start before the start offset and end strictly after it
994 
995     //or
996 
997     //-start at a position between the start and the end offsets
998 
999     if(annotsByStartNode == null) indexByStartOffset();
1000
1001    AnnotationSet resultSet = new AnnotationSetImpl(doc);
1002
1003    Iterator nodesIter;
1004
1005    Iterator annotsIter;
1006
1007    Node currentNode;
1008
1009    Annotation currentAnnot;
1010
1011    //find all the annots that start strictly before the start offset and end
1012
1013    //strictly after it
1014
1015    nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
1016
1017    while(nodesIter.hasNext()){
1018
1019      currentNode = (Node)nodesIter.next();
1020
1021      Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
1022
1023      if(fromPoint != null){
1024
1025        annotsIter = (fromPoint).iterator();
1026
1027        while(annotsIter.hasNext()){
1028
1029          currentAnnot = (Annotation)annotsIter.next();
1030
1031          if(currentAnnot.getType().equals(neededType) &&
1032
1033             currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0
1034
1035            ) {
1036
1037            resultSet.add(currentAnnot);
1038
1039          }//if
1040
1041        }//while
1042
1043      }
1044
1045    }
1046
1047    //find all the annots that start at or after the start offset but strictly
1048
1049    //before the end offset
1050
1051    nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
1052
1053    while(nodesIter.hasNext()){
1054
1055      currentNode = (Node)nodesIter.next();
1056
1057      Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
1058
1059      if(fromPoint != null) {
1060
1061        annotsIter = (fromPoint).iterator();
1062
1063        while(annotsIter.hasNext()){
1064
1065          currentAnnot = (Annotation)annotsIter.next();
1066
1067          if(currentAnnot.getType().equals(neededType)) {
1068
1069            resultSet.add(currentAnnot);
1070
1071          }//if
1072
1073        }//while
1074
1075      } //if
1076
1077    }
1078
1079    return resultSet;
1080
1081  }//get(type, startOfset, endOffset)
1082
1083
1084
1085
1086
1087  /** Select annotations by type, features and offset */
1088
1089  public AnnotationSet get(String type, FeatureMap constraints, Long offset) {
1090
1091
1092
1093    // select by offset
1094
1095    AnnotationSet nextAnnots = (AnnotationSet) get(offset);
1096
1097
1098
1099    if(nextAnnots == null) return null;
1100
1101
1102
1103    // select by type and constraints from the next annots
1104
1105    return nextAnnots.get(type, constraints);
1106
1107
1108
1109  } // get(type, constraints, offset)
1110
1111
1112
1113  /**
1114
1115    * Select annotations by offset that
1116
1117    * start at a position between the start and end before the end offset
1118
1119    */
1120
1121  public AnnotationSet getContained(Long startOffset, Long endOffset) {
1122
1123    //the result will include all the annotations that either:
1124
1125    //start at a position between the start and end before the end offsets
1126
1127    if(annotsByStartNode == null) indexByStartOffset();
1128
1129    AnnotationSet resultSet = new AnnotationSetImpl(doc);
1130
1131    Iterator nodesIter;
1132
1133    Iterator annotsIter;
1134
1135    Node currentNode;
1136
1137    Annotation currentAnnot;
1138
1139    //find all the annots that start at or after the start offset but strictly
1140
1141    //before the end offset
1142
1143    nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
1144
1145    while(nodesIter.hasNext()){
1146
1147      currentNode = (Node)nodesIter.next();
1148
1149      Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId());
1150
1151      if (fromPoint == null) continue;
1152
1153      //loop through the annotations and find only those that
1154
1155      //also end before endOffset
1156
1157      Iterator annotIter = fromPoint.iterator();
1158
1159      while (annotIter.hasNext()) {
1160
1161        Annotation annot = (Annotation) annotIter.next();
1162
1163        if (annot.getEndNode().getOffset().compareTo(endOffset) <= 0)
1164
1165          resultSet.add(annot);
1166
1167      }
1168
1169    }
1170
1171    return resultSet;
1172
1173  }//get(startOfset, endOffset)
1174
1175
1176
1177
1178
1179
1180
1181  /** Get the node with the smallest offset */
1182
1183  public Node firstNode() {
1184
1185    indexByStartOffset();
1186
1187    if(nodesByOffset.isEmpty()) return null;
1188
1189    else return (Node) nodesByOffset.get(nodesByOffset.firstKey());
1190
1191  } // firstNode
1192
1193
1194
1195  /** Get the node with the largest offset */
1196
1197  public Node lastNode() {
1198
1199
1200
1201    indexByStartOffset();
1202
1203    indexByEndOffset();
1204
1205
1206
1207    if(nodesByOffset.isEmpty())return null;
1208
1209    else return (Node) nodesByOffset.get(nodesByOffset.lastKey());
1210
1211
1212
1213  } // lastNode
1214
1215
1216
1217  /**
1218
1219    * Get the first node that is relevant for this annotation set and which has
1220
1221    * the offset larger than the one of the node provided.
1222
1223    */
1224
1225  public Node nextNode(Node node) {
1226
1227    indexByStartOffset();
1228
1229    indexByEndOffset();
1230
1231    return (Node)nodesByOffset.getNextOf(
1232
1233                               new Long(node.getOffset().longValue() + 1)
1234
1235                               );
1236
1237  }
1238
1239
1240
1241  /** Create and add an annotation with pre-existing nodes,
1242
1243    * and return its id
1244
1245    */
1246
1247  public Integer add(Node start, Node end, String type, FeatureMap features) {
1248
1249
1250
1251    // the id of the new annotation
1252
1253    Integer id = doc.getNextAnnotationId();
1254
1255
1256
1257    // construct an annotation
1258
1259    Annotation a = new AnnotationImpl(id, start, end, type, features);
1260
1261
1262
1263    // delegate to the method that adds existing annotations
1264
1265    add(a);
1266
1267
1268
1269    return id;
1270
1271  } // add(Node, Node, String, FeatureMap)
1272
1273
1274
1275  /** Add an existing annotation. Returns true when the set is modified. */
1276
1277  public boolean add(Object o) throws ClassCastException {
1278
1279    Annotation a = (Annotation) o;
1280
1281    Object oldValue = annotsById.put(a.getId(), a);
1282
1283    if (annotsByType != null)
1284
1285      addToTypeIndex(a);
1286
1287    if (annotsByStartNode != null || annotsByEndNode != null)
1288
1289      addToOffsetIndex(a);
1290
1291      AnnotationSetEvent evt = new AnnotationSetEvent(
1292
1293                                    this,
1294
1295                                    AnnotationSetEvent.ANNOTATION_ADDED,
1296
1297                                    doc, a);
1298
1299      fireAnnotationAdded(evt);
1300
1301      fireGateEvent(evt);
1302
1303    return oldValue != a;
1304
1305  } // add(o)
1306
1307
1308
1309  /**
1310
1311   * Adds multiple annotations to this set in one go.
1312
1313   * All the objects in the provided collection should be of
1314
1315   * {@link gate.Annotation} type, otherwise a ClassCastException will be
1316
1317   * thrown.
1318
1319   * The provided annotations will be used to create new annotations using the
1320
1321   * appropriate add() methods from this set. The new annotations will have
1322
1323   * different IDs from the old ones (which is required in order to preserve the
1324
1325   * uniqueness of IDs inside an annotation set).
1326
1327   * @param c a collection of annotations
1328
1329   * @return <tt>true</tt> if the set has been modified as a result of this
1330
1331   * call.
1332
1333   */
1334
1335  public boolean addAll(Collection c){
1336
1337    Iterator annIter = c.iterator();
1338
1339    boolean changed = false;
1340
1341    while(annIter.hasNext()){
1342
1343      Annotation a = (Annotation)annIter.next();
1344
1345      try{
1346
1347        add(a.getStartNode().getOffset(),
1348
1349            a.getEndNode().getOffset(),
1350
1351            a.getType(),
1352
1353            a.getFeatures());
1354
1355        changed = true;
1356
1357      }catch(InvalidOffsetException ioe){
1358
1359        throw new IllegalArgumentException(ioe.toString());
1360
1361      }
1362
1363    }
1364
1365    return changed;
1366
1367  }
1368
1369
1370
1371  /**
1372
1373   * Adds multiple annotations to this set in one go.
1374
1375   * All the objects in the provided collection should be of
1376
1377   * {@link gate.Annotation} type, otherwise a ClassCastException will be
1378
1379   * thrown.
1380
1381   * This method does not create copies of the annotations like addAll() does
1382
1383   * but simply adds the new annotations to the set.
1384
1385   * It is intended to be used solely by annotation sets in order to construct
1386
1387   * the results for various get(...) methods.
1388
1389   * @param c a collection of annotations
1390
1391   * @return <tt>true</tt> if the set has been modified as a result of this
1392
1393   * call.
1394
1395   */
1396
1397  protected boolean addAllKeepIDs(Collection c){
1398
1399    Iterator annIter = c.iterator();
1400
1401    boolean changed = false;
1402
1403    while(annIter.hasNext()){
1404
1405      Annotation a = (Annotation)annIter.next();
1406
1407      changed |= add(a);
1408
1409    }
1410
1411    return changed;
1412
1413  }
1414
1415
1416
1417  /** Create and add an annotation and return its id */
1418
1419  public Integer add(
1420
1421    Long start, Long end, String type, FeatureMap features
1422
1423  ) throws InvalidOffsetException {
1424
1425
1426
1427    // are the offsets valid?
1428
1429    if(! doc.isValidOffsetRange(start, end))
1430
1431      throw new InvalidOffsetException();
1432
1433
1434
1435    // the set has to be indexed by position in order to add, as we need
1436
1437    // to find out if nodes need creating or if they exist already
1438
1439    if(nodesByOffset == null) {
1440
1441      indexByStartOffset();
1442
1443      indexByEndOffset();
1444
1445    }
1446
1447
1448
1449    // find existing nodes if appropriate nodes don't already exist, create them
1450
1451    Node startNode  = (Node) nodesByOffset.getNextOf(start);
1452
1453    if(startNode == null || ! startNode.getOffset().equals(start))
1454
1455      startNode = new NodeImpl(doc.getNextNodeId(), start);
1456
1457
1458
1459    Node endNode = null;
1460
1461    if(start.equals(end))
1462
1463      endNode = startNode;
1464
1465    else
1466
1467      endNode = (Node) nodesByOffset.getNextOf(end);
1468
1469
1470
1471    if(endNode == null   || ! endNode.getOffset().equals(end))
1472
1473      endNode = new NodeImpl(doc.getNextNodeId(), end);
1474
1475
1476
1477    // delegate to the method that adds annotations with existing nodes
1478
1479    return add(startNode, endNode, type, features);
1480
1481  } // add(start, end, type, features)
1482
1483
1484
1485  /** Create and add an annotation from database read data
1486
1487    * In this case the id is already known being previously fetched from the
1488
1489    * database
1490
1491    */
1492
1493  public void add(
1494
1495    Integer id, Long start, Long end, String type, FeatureMap features
1496
1497  ) throws InvalidOffsetException {
1498
1499
1500
1501    // are the offsets valid?
1502
1503    if(! doc.isValidOffsetRange(start, end))
1504
1505      throw new InvalidOffsetException();
1506
1507
1508
1509    // the set has to be indexed by position in order to add, as we need
1510
1511    // to find out if nodes need creating or if they exist already
1512
1513    if(nodesByOffset == null){
1514
1515      indexByStartOffset();
1516
1517      indexByEndOffset();
1518
1519    }
1520
1521
1522
1523    // find existing nodes if appropriate nodes don't already exist, create them
1524
1525    Node startNode  = (Node) nodesByOffset.getNextOf(start);
1526
1527    if(startNode == null || ! startNode.getOffset().equals(start))
1528
1529      startNode = new NodeImpl(doc.getNextNodeId(), start);
1530
1531
1532
1533    Node endNode = null;
1534
1535    if(start.equals(end))
1536
1537      endNode = startNode;
1538
1539    else
1540
1541      endNode = (Node) nodesByOffset.getNextOf(end);
1542
1543
1544
1545    if(endNode == null   || ! endNode.getOffset().equals(end))
1546
1547      endNode = new NodeImpl(doc.getNextNodeId(), end);
1548
1549
1550
1551    // construct an annotation
1552
1553    Annotation a = new AnnotationImpl(id, startNode, endNode, type, features);
1554
1555    add(a);
1556
1557
1558
1559  } // add(id, start, end, type, features)
1560
1561
1562
1563  /** Construct the positional index. */
1564
1565  protected void indexByType() {
1566
1567
1568
1569    if(annotsByType != null) return;
1570
1571
1572
1573    annotsByType = new HashMap(Gate.HASH_STH_SIZE);
1574
1575
1576
1577    Annotation a;
1578
1579    Iterator annotIter = annotsById.values().iterator();
1580
1581
1582
1583    while (annotIter.hasNext())
1584
1585      addToTypeIndex( (Annotation) annotIter.next() );
1586
1587
1588
1589  } // indexByType()
1590
1591
1592
1593  /** Construct the positional indices for annotation start */
1594
1595  protected void indexByStartOffset() {
1596
1597
1598
1599    if(annotsByStartNode != null) return;
1600
1601
1602
1603    if(nodesByOffset == null)
1604
1605      nodesByOffset = new RBTreeMap();
1606
1607    annotsByStartNode = new HashMap(Gate.HASH_STH_SIZE);
1608
1609
1610
1611    Annotation a;
1612
1613    Iterator annotIter = annotsById.values().iterator();
1614
1615
1616
1617    while(annotIter.hasNext())
1618
1619      addToStartOffsetIndex( (Annotation) annotIter.next() );
1620
1621
1622
1623  } // indexByStartOffset()
1624
1625
1626
1627  /** Construct the positional indices for annotation end */
1628
1629  protected void indexByEndOffset() {
1630
1631
1632
1633    if(annotsByEndNode != null) return;
1634
1635
1636
1637    if(nodesByOffset == null)
1638
1639      nodesByOffset = new RBTreeMap();
1640
1641    annotsByEndNode = new HashMap(Gate.HASH_STH_SIZE);
1642
1643
1644
1645    Annotation a;
1646
1647    Iterator annotIter = annotsById.values().iterator();
1648
1649
1650
1651    while(annotIter.hasNext())
1652
1653      addToEndOffsetIndex( (Annotation) annotIter.next() );
1654
1655
1656
1657  } // indexByEndOffset()
1658
1659
1660
1661  /** Add an annotation to the type index. Does nothing if the index
1662
1663    * doesn't exist.
1664
1665    */
1666
1667  void addToTypeIndex(Annotation a) {
1668
1669    if(annotsByType == null) return;
1670
1671
1672
1673    String type = a.getType();
1674
1675    AnnotationSet sameType = (AnnotationSet) annotsByType.get(type);
1676
1677
1678
1679    if(sameType == null) {
1680
1681      sameType = new AnnotationSetImpl(doc);
1682
1683      annotsByType.put(type, sameType);
1684
1685    }
1686
1687    sameType.add(a);
1688
1689  } // addToTypeIndex(a)
1690
1691
1692
1693  /** Add an annotation to the offset indices. Does nothing if they
1694
1695    * don't exist.
1696
1697    */
1698
1699  void addToOffsetIndex(Annotation a) {
1700
1701    addToStartOffsetIndex(a);
1702
1703    addToEndOffsetIndex(a);
1704
1705  } // addToOffsetIndex(a)
1706
1707
1708
1709  /** Add an annotation to the start offset index. Does nothing if the
1710
1711    * index doesn't exist.
1712
1713    */
1714
1715  void addToStartOffsetIndex(Annotation a) {
1716
1717    Node startNode  = a.getStartNode();
1718
1719    Node endNode    = a.getEndNode();
1720
1721    Long start      = startNode.getOffset();
1722
1723    Long end        = endNode.getOffset();
1724
1725
1726
1727    // add a's nodes to the offset index
1728
1729    if(nodesByOffset != null)
1730
1731      nodesByOffset.put(start, startNode);
1732
1733
1734
1735    // if there's no appropriate index give up
1736
1737    if(annotsByStartNode == null) return;
1738
1739
1740
1741    // get the annotations that start at the same node, or create new set
1742
1743    AnnotationSet thisNodeAnnots =
1744
1745      (AnnotationSet) annotsByStartNode.get(startNode.getId());
1746
1747
1748
1749    if(thisNodeAnnots == null) {
1750
1751      thisNodeAnnots = new AnnotationSetImpl(doc);
1752
1753      annotsByStartNode.put(startNode.getId(), thisNodeAnnots);
1754
1755    }
1756
1757    // add to the annots listed for a's start node
1758
1759    thisNodeAnnots.add(a);
1760
1761
1762
1763  } // addToStartOffsetIndex(a)
1764
1765
1766
1767  /** Add an annotation to the end offset index. Does nothing if the
1768
1769    * index doesn't exist.
1770
1771    */
1772
1773  void addToEndOffsetIndex(Annotation a) {
1774
1775    Node startNode  = a.getStartNode();
1776
1777    Node endNode    = a.getEndNode();
1778
1779    Long start      = startNode.getOffset();
1780
1781    Long end        = endNode.getOffset();
1782
1783
1784
1785    // add a's nodes to the offset index
1786
1787    if(nodesByOffset != null) nodesByOffset.put(end, endNode);
1788
1789
1790
1791    // if there's no appropriate index give up
1792
1793    if(annotsByEndNode == null)
1794
1795      return;
1796
1797
1798
1799    // get the annotations that start at the same node, or create new set
1800
1801    AnnotationSet thisNodeAnnots =
1802
1803      (AnnotationSet) annotsByEndNode.get(endNode.getId());
1804
1805
1806
1807    if(thisNodeAnnots == null) {
1808
1809      thisNodeAnnots = new AnnotationSetImpl(doc);
1810
1811      annotsByEndNode.put(endNode.getId(), thisNodeAnnots);
1812
1813    }
1814
1815    // add to the annots listed for a's start node
1816
1817    thisNodeAnnots.add(a);
1818
1819
1820
1821  } // addToEndOffsetIndex(a)
1822
1823
1824
1825  /** Propagate changes to the document content. Has, unfortunately,
1826
1827    * to be public, to allow DocumentImpls to get at it. Oh for a
1828
1829    * "friend" declaration. Doesn't thow InvalidOffsetException as
1830
1831    * DocumentImpl is the only client, and that checks the offsets
1832
1833    * before calling this method.
1834
1835    */
1836
1837  public void edit(Long start, Long end, DocumentContent replacement) {
1838
1839    long s = start.longValue(), e = end.longValue();
1840
1841    long rlen = // length of the replacement value
1842
1843      ( (replacement == null) ? 0 : replacement.size().longValue() );
1844
1845
1846
1847    indexByStartOffset();
1848
1849    indexByEndOffset();
1850
1851
1852
1853    Iterator replacedAreaNodesIter =
1854
1855      nodesByOffset.subMap(start, end).values().iterator();
1856
1857    while(replacedAreaNodesIter.hasNext()) {
1858
1859      Node n = (Node) replacedAreaNodesIter.next();
1860
1861
1862
1863      // remove from nodes map
1864
1865//      if(true)
1866
1867//        throw new LazyProgrammerException("this next call tries to remove " +
1868
1869//          "from a map based on the value; note index is key; also note that " +
1870
1871//          "some nodes may have no index....");
1872
1873
1874
1875//There is at most one node at any given location so removing is safe.
1876
1877//Also note that unrooted nodes have never been implemented so all nodes have
1878
1879//offset
1880
1881      nodesByOffset.remove(n.getOffset());
1882
1883
1884
1885      // remove annots that start at this node
1886
1887      AnnotationSet invalidatedAnnots =
1888
1889        (AnnotationSet) annotsByStartNode.get(n.getId());
1890
1891      if(invalidatedAnnots != null)
1892
1893        removeAll(invalidatedAnnots);
1894
1895
1896
1897      // remove annots that end at this node
1898
1899      invalidatedAnnots =
1900
1901        (AnnotationSet) annotsByEndNode.get(n.getId());
1902
1903      if(invalidatedAnnots != null)
1904
1905        removeAll(invalidatedAnnots);
1906
1907    } // loop over replaced area nodes
1908
1909
1910
1911    // update the offsets of the other nodes
1912
1913    Iterator nodesAfterReplacementIter =
1914
1915      nodesByOffset.tailMap(end).values().iterator();
1916
1917    while(nodesAfterReplacementIter.hasNext()) {
1918
1919      NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
1920
1921      long oldOffset = n.getOffset().longValue();
1922
1923
1924
1925      n.setOffset(new Long( oldOffset - ( (e-s) - rlen ) ));
1926
1927    } // loop over nodes after replacement area
1928
1929
1930
1931  } // edit(start,end,replacement)
1932
1933
1934
1935  /** Get the name of this set. */
1936
1937  public String getName() { return name; }
1938
1939
1940
1941  /** Get the document this set is attached to. */
1942
1943  public Document getDocument() { return doc; }
1944
1945
1946
1947  /** Get a set of java.lang.String objects representing all the annotation
1948
1949    * types present in this annotation set.
1950
1951    */
1952
1953  public Set getAllTypes() {
1954
1955    indexByType();
1956
1957    return annotsByType.keySet();
1958
1959  }
1960
1961
1962
1963  /**
1964
1965   *
1966
1967   * @return
1968
1969   * @throws CloneNotSupportedException
1970
1971   */
1972
1973  public Object clone() throws CloneNotSupportedException{
1974
1975    return super.clone();
1976
1977  }
1978
1979  /**
1980
1981   *
1982
1983   * @param l
1984
1985   */
1986
1987  public synchronized void removeAnnotationSetListener(AnnotationSetListener l) {
1988
1989    if (annotationSetListeners != null && annotationSetListeners.contains(l)) {
1990
1991      Vector v = (Vector) annotationSetListeners.clone();
1992
1993      v.removeElement(l);
1994
1995      annotationSetListeners = v;
1996
1997    }
1998
1999  }
2000
2001  /**
2002
2003   *
2004
2005   * @param l
2006
2007   */
2008
2009  public synchronized void addAnnotationSetListener(AnnotationSetListener l) {
2010
2011    Vector v = annotationSetListeners == null ? new Vector(2) : (Vector) annotationSetListeners.clone();
2012
2013    if (!v.contains(l)) {
2014
2015      v.addElement(l);
2016
2017      annotationSetListeners = v;
2018
2019    }
2020
2021  }
2022
2023  /** String representation of the set */
2024
2025  /*public String toString() {
2026
2027
2028
2029    // annotsById
2030
2031    SortedSet sortedAnnots = new TreeSet();
2032
2033    sortedAnnots.addAll(annotsById.values());
2034
2035    String aBI = sortedAnnots.toString();
2036
2037
2038
2039    // annotsByType
2040
2041
2042
2043    StringBuffer buf = new StringBuffer();
2044
2045    Iterator iter = annotsByType.iterator();
2046
2047    while(iter.hasNext()) {
2048
2049      HashMap thisType = iter.next().entrySet();
2050
2051      sortedAnnots.clear();
2052
2053      sortedAnnots.addAll(thisType.);
2054
2055      buf.append("[type: " +
2056
2057    }
2058
2059
2060
2061
2062
2063    return
2064
2065      "AnnotationSetImpl: " +
2066
2067      "name=" + name +
2068
2069    //  "; doc.getURL()=" + doc +
2070
2071      "; annotsById=" + aBI +
2072
2073    //  "; annotsByType=" + aBT +
2074
2075      "; "
2076
2077      ;
2078
2079  } // toString()
2080
2081  */
2082
2083
2084
2085//  public int hashCode() {
2086
2087//    int hash = 0;
2088
2089//    Iterator i = this.iterator();
2090
2091//    while (i.hasNext()) {
2092
2093//        Annotation annot = (Annotation)i.next();
2094
2095//        if ( annot != null)
2096
2097//            hash += annot.hashCode();
2098
2099//    }
2100
2101//    int nameHash = (name == null ? 0 : name.hashCode());
2102
2103//    //int docHash = (doc == null ? 0 : doc.hashCode());
2104
2105//
2106
2107//    return hash ^ nameHash;// ^ docHash;
2108
2109//  }
2110
2111
2112
2113  /** The name of this set */
2114
2115  String name = null;
2116
2117
2118
2119  /** The document this set belongs to */
2120
2121  DocumentImpl doc;
2122
2123
2124
2125  /** Maps annotation ids (Integers) to Annotations */
2126
2127  protected HashMap annotsById;
2128
2129
2130
2131  /** Maps annotation types (Strings) to AnnotationSets */
2132
2133  Map annotsByType = null;
2134
2135
2136
2137  /** Maps offsets (Longs) to nodes */
2138
2139  RBTreeMap nodesByOffset = null;
2140
2141
2142
2143  /** Maps node ids (Integers) to AnnotationSets representing those
2144
2145    * annotations that start from that node
2146
2147    */
2148
2149  Map annotsByStartNode;
2150
2151
2152
2153  /** Maps node ids (Integers) to AnnotationSets representing those
2154
2155    * annotations that end at that node
2156
2157    */
2158
2159  Map annotsByEndNode;
2160
2161  protected transient Vector annotationSetListeners;
2162
2163  private transient Vector gateListeners;
2164
2165  /**
2166
2167   *
2168
2169   * @param e
2170
2171   */
2172
2173  protected void fireAnnotationAdded(AnnotationSetEvent e) {
2174
2175    if (annotationSetListeners != null) {
2176
2177      Vector listeners = annotationSetListeners;
2178
2179      int count = listeners.size();
2180
2181      for (int i = 0; i < count; i++) {
2182
2183        ((AnnotationSetListener) listeners.elementAt(i)).annotationAdded(e);
2184
2185      }
2186
2187    }
2188
2189  }
2190
2191  /**
2192
2193   *
2194
2195   * @param e
2196
2197   */
2198
2199  protected void fireAnnotationRemoved(AnnotationSetEvent e) {
2200
2201    if (annotationSetListeners != null) {
2202
2203      Vector listeners = annotationSetListeners;
2204
2205      int count = listeners.size();
2206
2207      for (int i = 0; i < count; i++) {
2208
2209        ((AnnotationSetListener) listeners.elementAt(i)).annotationRemoved(e);
2210
2211      }
2212
2213    }
2214
2215  }
2216
2217  /**
2218
2219   *
2220
2221   * @param l
2222
2223   */
2224
2225  public synchronized void removeGateListener(GateListener l) {
2226
2227    if (gateListeners != null && gateListeners.contains(l)) {
2228
2229      Vector v = (Vector) gateListeners.clone();
2230
2231      v.removeElement(l);
2232
2233      gateListeners = v;
2234
2235    }
2236
2237  }
2238
2239  /**
2240
2241   *
2242
2243   * @param l
2244
2245   */
2246
2247  public synchronized void addGateListener(GateListener l) {
2248
2249    Vector v = gateListeners == null ? new Vector(2) : (Vector) gateListeners.clone();
2250
2251    if (!v.contains(l)) {
2252
2253      v.addElement(l);
2254
2255      gateListeners = v;
2256
2257    }
2258
2259  }
2260
2261  /**
2262
2263   *
2264
2265   * @param e
2266
2267   */
2268
2269  protected void fireGateEvent(GateEvent e) {
2270
2271    if (gateListeners != null) {
2272
2273      Vector listeners = gateListeners;
2274
2275      int count = listeners.size();
2276
2277      for (int i = 0; i < count; i++) {
2278
2279        ((GateListener) listeners.elementAt(i)).processGateEvent(e);
2280
2281      }
2282
2283    }
2284
2285  }
2286
2287
2288
2289 /** Freeze the serialization UID. */
2290
2291  static final long serialVersionUID = 1479426765310434166L;
2292
2293} // AnnotationSetImpl
2294
2295