| AnnotationSetImpl |

    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.67 2001/11/20 08:27:54 nasso 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
    816     long s = start.longValue(), e = end.longValue();
    817     long rlen = // length of the replacement value
    818       ( (replacement == null) ? 0 : replacement.size().longValue() );
    819
    820     indexByStartOffset();
    821     indexByEndOffset();
    822
    823     Iterator replacedAreaNodesIter =
    824       nodesByOffset.subMap(start, end).values().iterator();
    825     while(replacedAreaNodesIter.hasNext()) {
    826       Node n = (Node) replacedAreaNodesIter.next();
    827
    828       // remove from nodes map
    829       if(true)
    830         throw new LazyProgrammerException("this next call tries to remove " +
    831           "from a map based on the value; note index is key; also note that " +
    832           "some nodes may have no index....");
    833
    834       nodesByOffset.remove(n);
    835
    836       // remove annots that start at this node
    837       AnnotationSet invalidatedAnnots =
    838         (AnnotationSet) annotsByStartNode.get(n.getId());
    839       if(invalidatedAnnots != null)
    840         removeAll(invalidatedAnnots);
    841
    842       // remove annots that end at this node
    843       invalidatedAnnots =
    844         (AnnotationSet) annotsByEndNode.get(n.getId());
    845       if(invalidatedAnnots != null)
    846         removeAll(invalidatedAnnots);
    847     } // loop over replaced area nodes
    848
    849     // update the offsets of the other nodes
    850     Iterator nodesAfterReplacementIter =
    851       nodesByOffset.tailMap(end).values().iterator();
    852     while(nodesAfterReplacementIter.hasNext()) {
    853       NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
    854       long oldOffset = n.getOffset().longValue();
    855
    856       n.setOffset(new Long( oldOffset - ( (e-s) - rlen ) ));
    857     } // loop over nodes after replacement area
    858
    859   } // edit(start,end,replacement)
    860
    861   /** Get the name of this set. */
    862   public String getName() { return name; }
    863
    864   /** Get the document this set is attached to. */
    865   public Document getDocument() { return doc; }
    866
    867   /** Get a set of java.lang.String objects representing all the annotation
    868     * types present in this annotation set.
    869     */
    870   public Set getAllTypes() {
    871     indexByType();
    872     return annotsByType.keySet();
    873   }
    874
    875   /**
    876    *
    877    * @return
    878    * @throws CloneNotSupportedException
    879    */
    880   public Object clone() throws CloneNotSupportedException{
    881     return super.clone();
    882   }
    883   /**
    884    *
    885    * @param l
    886    */
    887   public synchronized void removeAnnotationSetListener(AnnotationSetListener l) {
    888     if (annotationSetListeners != null && annotationSetListeners.contains(l)) {
    889       Vector v = (Vector) annotationSetListeners.clone();
    890       v.removeElement(l);
    891       annotationSetListeners = v;
    892     }
    893   }
    894   /**
    895    *
    896    * @param l
    897    */
    898   public synchronized void addAnnotationSetListener(AnnotationSetListener l) {
    899     Vector v = annotationSetListeners == null ? new Vector(2) : (Vector) annotationSetListeners.clone();
    900     if (!v.contains(l)) {
    901       v.addElement(l);
    902       annotationSetListeners = v;
    903     }
    904   }
    905   /** String representation of the set */
    906   /*public String toString() {
    907
    908     // annotsById
    909     SortedSet sortedAnnots = new TreeSet();
    910     sortedAnnots.addAll(annotsById.values());
    911     String aBI = sortedAnnots.toString();
    912
    913     // annotsByType
    914
    915     StringBuffer buf = new StringBuffer();
    916     Iterator iter = annotsByType.iterator();
    917     while(iter.hasNext()) {
    918       HashMap thisType = iter.next().entrySet();
    919       sortedAnnots.clear();
    920       sortedAnnots.addAll(thisType.);
    921       buf.append("[type: " +
    922     }
    923
    924
    925     return
    926       "AnnotationSetImpl: " +
    927       "name=" + name +
    928     //  "; doc.getURL()=" + doc +
    929       "; annotsById=" + aBI +
    930     //  "; annotsByType=" + aBT +
    931       "; "
    932       ;
    933   } // toString()
    934   */
    935
    936   /** Two AnnotationSet are equal if their name, the documents of which belong
    937     *  to the AnnotationSets and annotations from the sets are the same
    938     */
    939   public boolean equals(Object other) {
    940
    941     if (!super.equals(other)) return false;
    942
    943     if (other instanceof AnnotationSetImpl) {
    944       AnnotationSet annotSet = (AnnotationSet) other;
    945
    946       // verify the documents which belong to
    947 //this leads to cyclic checks Document -> AnnotationSet -> Document
    948 //REMOVED!
    949 //      if ((doc == null)^ (annotSet.getDocument() == null)) return false;
    950 //      if ((doc != null)&& (!doc.equals(annotSet.getDocument()))) return false;
    951
    952       // verify the name of the AnnotationSets
    953       if ((name == null)^ (annotSet.getName() == null)) return false;
    954       if ((name!=null)&& (!name.equals(annotSet.getName()))) return false;
    955     } else {
    956       if (!(other instanceof Set)) return false;
    957     }
    958
    959     return true;
    960   } // equals
    961
    962   /**
    963    *
    964    * @return
    965    */
    966   public int hashCode() {
    967     int hash = 0;
    968     Iterator i = this.iterator();
    969     while (i.hasNext()) {
    970         Annotation annot = (Annotation)i.next();
    971         if ( annot != null)
    972             hash += annot.hashCode();
    973     }
    974     int nameHash = (name == null ? 0 : name.hashCode());
    975     //int docHash = (doc == null ? 0 : doc.hashCode());
    976
    977     return hash ^ nameHash;// ^ docHash;
    978   }
    979
    980   /** The name of this set */
    981   String name = null;
    982
    983   /** The document this set belongs to */
    984   DocumentImpl doc;
    985
    986   /** Maps annotation ids (Integers) to Annotations */
    987   protected HashMap annotsById;
    988
    989   /** Maps annotation types (Strings) to AnnotationSets */
    990   Map annotsByType = null;
    991
    992   /** Maps offsets (Longs) to nodes */
    993   RBTreeMap nodesByOffset = null;
    994
    995   /** Maps node ids (Integers) to AnnotationSets representing those
    996     * annotations that start from that node
    997     */
    998   Map annotsByStartNode;
    999
    1000  /** Maps node ids (Integers) to AnnotationSets representing those
    1001    * annotations that end at that node
    1002    */
    1003  Map annotsByEndNode;
    1004  private transient Vector annotationSetListeners;
    1005  private transient Vector gateListeners;
    1006  /**
    1007   *
    1008   * @param e
    1009   */
    1010  protected void fireAnnotationAdded(AnnotationSetEvent e) {
    1011    if (annotationSetListeners != null) {
    1012      Vector listeners = annotationSetListeners;
    1013      int count = listeners.size();
    1014      for (int i = 0; i < count; i++) {
    1015        ((AnnotationSetListener) listeners.elementAt(i)).annotationAdded(e);
    1016      }
    1017    }
    1018  }
    1019  /**
    1020   *
    1021   * @param e
    1022   */
    1023  protected void fireAnnotationRemoved(AnnotationSetEvent e) {
    1024    if (annotationSetListeners != null) {
    1025      Vector listeners = annotationSetListeners;
    1026      int count = listeners.size();
    1027      for (int i = 0; i < count; i++) {
    1028        ((AnnotationSetListener) listeners.elementAt(i)).annotationRemoved(e);
    1029      }
    1030    }
    1031  }
    1032  /**
    1033   *
    1034   * @param l
    1035   */
    1036  public synchronized void removeGateListener(GateListener l) {
    1037    if (gateListeners != null && gateListeners.contains(l)) {
    1038      Vector v = (Vector) gateListeners.clone();
    1039      v.removeElement(l);
    1040      gateListeners = v;
    1041    }
    1042  }
    1043  /**
    1044   *
    1045   * @param l
    1046   */
    1047  public synchronized void addGateListener(GateListener l) {
    1048    Vector v = gateListeners == null ? new Vector(2) : (Vector) gateListeners.clone();
    1049    if (!v.contains(l)) {
    1050      v.addElement(l);
    1051      gateListeners = v;
    1052    }
    1053  }
    1054  /**
    1055   *
    1056   * @param e
    1057   */
    1058  protected void fireGateEvent(GateEvent e) {
    1059    if (gateListeners != null) {
    1060      Vector listeners = gateListeners;
    1061      int count = listeners.size();
    1062      for (int i = 0; i < count; i++) {
    1063        ((GateListener) listeners.elementAt(i)).processGateEvent(e);
    1064      }
    1065    }
    1066  }
    1067
    1068 /** Freeze the serialization UID. */
    1069  static final long serialVersionUID = 1479426765310434166L;
    1070} // AnnotationSetImpl
    1071

| AnnotationSetImpl |