AnnotationSetImpl.java
0001 /*
0002  *  AnnotationSetImpl.java
0003  *
0004  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
0005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0008  *  software, licenced under the GNU Library General Public License,
0009  *  Version 2, June 1991 (in the distribution as file licence.html,
0010  *  and also available at http://gate.ac.uk/gate/licence.html).
0011  *
0012  *  Hamish Cunningham, 7/Feb/2000
0013  *
0014  *  Developer notes:
0015  *  ---
0016  *
0017  *  the addToIndex... and indexBy... methods could be refactored as I'm
0018  *  sure they can be made simpler
0019  *
0020  *  every set to which annotation will be added has to have positional
0021  *  indexing, so that we can find or create the nodes on the new annotations
0022  *
0023  *  note that annotations added anywhere other than sets that are
0024  *  stored on the document will not get stored anywhere...
0025  *
0026  *  nodes aren't doing anything useful now. needs some interface that allows
0027  *  their creation, defaulting to no coterminous duplicates, but allowing such
0028  *  if required
0029  *
0030  *  $Id: AnnotationSetImpl.java 12344 2010-03-11 13:07:15Z markagreenwood $
0031  */
0032 package gate.annotation;
0033 
0034 import java.io.*;
0035 import java.util.*;
0036 
0037 import org.apache.commons.lang.StringUtils;
0038 import org.apache.log4j.Logger;
0039 
0040 import gate.*;
0041 import gate.corpora.DocumentImpl;
0042 import gate.event.*;
0043 import gate.util.*;
0044 
0045 /**
0046  * Implementation of AnnotationSet. Has a number of indices, all bar one of
0047  * which are null by default and are only constructed when asked for. Has lots
0048  * of get methods with various selection criteria; these return views into the
0049  * set, which are nonetheless valid sets in their own right (but will not
0050  * necesarily be fully indexed). Has a name, which is null by default; clients
0051  * of Document can request named AnnotationSets if they so desire. Has a
0052  * reference to the Document it is attached to. Contrary to Collections
0053  * convention, there is no no-arg constructor, as this would leave the set in an
0054  * inconsistent state.
0055  <P>
0056  * There are four indices: annotation by id, annotations by type, annotations by
0057  * start node and nodes by offset. The last two jointly provide positional
0058  * indexing; construction of these is triggered by indexByStart(), or by calling
0059  * a get method that selects on offset. The type index is triggered by
0060  * indexByType(), or calling a get method that selects on type. The id index is
0061  * always present.
0062  */
0063 public class AnnotationSetImpl extends AbstractSet<Annotation> implements
0064                                                               AnnotationSet {
0065   private static final Logger log = Logger.getLogger(AnnotationSetImpl.class);
0066   /** Freeze the serialization UID. */
0067   static final long serialVersionUID = 1479426765310434166L;
0068   /** The name of this set */
0069   String name = null;
0070   /** The document this set belongs to */
0071   DocumentImpl doc;
0072   /** Maps annotation ids (Integers) to Annotations */
0073   transient protected HashMap<Integer, Annotation> annotsById;
0074   /** Maps offsets (Longs) to nodes */
0075   transient RBTreeMap nodesByOffset = null;
0076   /**
0077    * This field is used temporarily during serialisation to store all the
0078    * annotations that need to be saved. At all other times, this will be null;
0079    */
0080   private Annotation[] annotations;
0081   /** Maps annotation types (Strings) to AnnotationSets */
0082   transient Map<String, AnnotationSet> annotsByType = null;
0083   /**
0084    * Maps node ids (Integers) to Annotations or a Collection of Annotations that
0085    * start from that node
0086    */
0087   transient Map<Integer, Object> annotsByStartNode;
0088   protected transient Vector<AnnotationSetListener> annotationSetListeners;
0089   private transient Vector<GateListener> gateListeners;
0090 
0091   /**
0092    * A caching value that greatly improves the performance of get
0093    * methods that have a defined beginning and end. By tracking the
0094    * maximum length that an annotation can be, we know the maximum
0095    * amount of nodes outside of a specified range that must be checked
0096    * to see if an annotation starting at one of those nodes crosses into
0097    * the range. This mechanism is not perfect because we do not check if
0098    * we have to decrease it if an annotation is removed from the set.
0099    * However, usually annotations are removed because they are about to
0100    * be replaced with another one that is >= to the length of the one
0101    * being replaced, so this isn't a big deal. At worst, it means that
0102    * the get methods simply checks a few more start positions than it
0103    * needs to.
0104    */
0105   protected transient Long longestAnnot = 0l;
0106 
0107   // Empty AnnotationSet to be returned instead of null
0108    public final static AnnotationSet emptyAnnotationSet;
0109 
0110    static {
0111    emptyAnnotationSet = new ImmutableAnnotationSetImpl(null,null);
0112    }
0113 
0114   /** Construction from Document. */
0115   public AnnotationSetImpl(Document doc) {
0116     annotsById = new HashMap<Integer, Annotation>();
0117     this.doc = (DocumentImpl)doc;
0118   // construction from document
0119 
0120   /** Construction from Document and name. */
0121   public AnnotationSetImpl(Document doc, String name) {
0122     this(doc);
0123     this.name = name;
0124   // construction from document and name
0125 
0126   /** Construction from an existing AnnotationSet */
0127   public AnnotationSetImpl(AnnotationSet cthrows ClassCastException {
0128     this(c.getDocument(), c.getName());
0129     // the original annotationset is of the same implementation
0130     if(instanceof AnnotationSetImpl) {
0131       AnnotationSetImpl theC = (AnnotationSetImpl)c;
0132       annotsById.putAll(theC.annotsById);
0133       if(theC.annotsByStartNode != null) {
0134         annotsByStartNode = new HashMap<Integer, Object>(Gate.HASH_STH_SIZE);
0135         annotsByStartNode.putAll(theC.annotsByStartNode);
0136       }
0137       if(theC.annotsByType != null) {
0138         annotsByType = new HashMap<String, AnnotationSet>(Gate.HASH_STH_SIZE);
0139         annotsByType.putAll(theC.annotsByType);
0140       }
0141       if(theC.nodesByOffset != null) {
0142         nodesByOffset = (RBTreeMap)theC.nodesByOffset.clone();
0143       }
0144     }
0145     // the implementation is not the default one
0146     // let's add the annotations one by one
0147     else {
0148       Iterator<Annotation> iterannots = c.iterator();
0149       while(iterannots.hasNext()) {
0150         add(iterannots.next());
0151       }
0152     }
0153   }
0154 
0155   /**
0156    * This inner class serves as the return value from the iterator() method.
0157    */
0158   class AnnotationSetIterator implements Iterator<Annotation> {
0159     private Iterator<Annotation> iter;
0160     protected Annotation lastNext = null;
0161 
0162     AnnotationSetIterator() {
0163       iter = annotsById.values().iterator();
0164     }
0165 
0166     public boolean hasNext() {
0167       return iter.hasNext();
0168     }
0169 
0170     public Annotation next() {
0171       return (lastNext = iter.next());
0172     }
0173 
0174     public void remove() {
0175       // this takes care of the ID index
0176       iter.remove();
0177 
0178       // what if lastNext is null
0179       if(lastNext == nullreturn;
0180 
0181       // remove from type index
0182       removeFromTypeIndex(lastNext);
0183       // remove from offset indices
0184       removeFromOffsetIndex(lastNext);
0185       // that's the second way of removing annotations from a set
0186       // apart from calling remove() on the set itself
0187       fireAnnotationRemoved(new AnnotationSetEvent(AnnotationSetImpl.this,
0188               AnnotationSetEvent.ANNOTATION_REMOVED, getDocument(),
0189               lastNext));
0190     // remove()
0191   }// AnnotationSetIterator
0192 
0193   /** Get an iterator for this set */
0194   public Iterator<Annotation> iterator() {
0195     return new AnnotationSetIterator();
0196   }
0197 
0198   /** Remove an element from this set. */
0199   public boolean remove(Object othrows ClassCastException {
0200     Annotation a = (Annotation)o;
0201     boolean wasPresent = removeFromIdIndex(a);
0202     if(wasPresent) {
0203       removeFromTypeIndex(a);
0204       removeFromOffsetIndex(a);
0205     }
0206     // fire the event
0207     fireAnnotationRemoved(new AnnotationSetEvent(AnnotationSetImpl.this,
0208             AnnotationSetEvent.ANNOTATION_REMOVED, getDocument(), a));
0209     return wasPresent;
0210   // remove(o)
0211 
0212   /** Remove from the ID index. */
0213   protected boolean removeFromIdIndex(Annotation a) {
0214     if(annotsById.remove(a.getId()) == nullreturn false;
0215     return true;
0216   // removeFromIdIndex(a)
0217 
0218   /** Remove from the type index. */
0219   protected void removeFromTypeIndex(Annotation a) {
0220     if(annotsByType != null) {
0221       AnnotationSet sameType = annotsByType.get(a.getType());
0222       if(sameType != nullsameType.remove(a);
0223       if(sameType != null && sameType.isEmpty()) // none left of this type
0224         annotsByType.remove(a.getType());
0225     }
0226   // removeFromTypeIndex(a)
0227 
0228   /** Remove from the offset indices. */
0229   protected void removeFromOffsetIndex(Annotation a) {
0230     if(nodesByOffset != null) {
0231       // knowing when a node is no longer needed would require keeping a
0232       // reference
0233       // count on annotations, or using a weak reference to the nodes in
0234       // nodesByOffset
0235     }
0236     if(annotsByStartNode != null) {
0237       Integer id = a.getStartNode().getId();
0238       // might be an annotation or an annotationset
0239       Object objectAtNode = annotsByStartNode.get(id);
0240       if(objectAtNode instanceof Annotation) {
0241         annotsByStartNode.remove(id)// no annotations start here any
0242         // more
0243         return;
0244       }
0245       // otherwise it is a Collection
0246       Collection<Annotation> starterAnnots = (Collection<Annotation>)objectAtNode;
0247       starterAnnots.remove(a);
0248       // if there is only one annotation left
0249       // we discard the set and put directly the annotation
0250       if(starterAnnots.size() == 1)
0251         annotsByStartNode.put(id, starterAnnots.iterator().next());
0252     }
0253   // removeFromOffsetIndex(a)
0254 
0255   /** The size of this set */
0256   public int size() {
0257     return annotsById.size();
0258   }
0259 
0260   /** Find annotations by id */
0261   public Annotation get(Integer id) {
0262     return annotsById.get(id);
0263   // get(id)
0264 
0265   /**
0266    * Get all annotations.
0267    *
0268    @return an ImmutableAnnotationSet, empty or not
0269    */
0270   public AnnotationSet get() {
0271     if (annotsById.isEmpty()) return emptyAnnotationSet;
0272     return new ImmutableAnnotationSetImpl(doc, annotsById.values());
0273   // get()
0274 
0275   /**
0276    * Select annotations by type
0277    *
0278    @return an ImmutableAnnotationSet
0279    */
0280   public AnnotationSet get(String type) {
0281     if(annotsByType == nullindexByType();
0282     AnnotationSet byType = annotsByType.get(type);
0283     if (byType==null)return emptyAnnotationSet;
0284     // convert the mutable AS into an immutable one
0285     return byType.get();
0286   // get(type)
0287 
0288   /**
0289    * Select annotations by a set of types. Expects a Set of String.
0290    *
0291    @return an ImmutableAnnotationSet
0292    */
0293   public AnnotationSet get(Set<String> typesthrows ClassCastException {
0294     if(annotsByType == nullindexByType();
0295     Iterator<String> iter = types.iterator();
0296     List<Annotation> annotations = new ArrayList<Annotation>();
0297     while(iter.hasNext()) {
0298       String type = iter.next();
0299       AnnotationSet as = annotsByType.get(type);
0300       if(as != null) {
0301         Iterator<Annotation> iterAnnot = as.iterator();
0302         while(iterAnnot.hasNext()) {
0303           annotations.add(iterAnnot.next());
0304         }
0305       }
0306     // while
0307     if(annotations.isEmpty()) return emptyAnnotationSet;
0308     return new ImmutableAnnotationSetImpl(doc, annotations);
0309   // get(types)
0310 
0311   /**
0312    * Select annotations by type and features
0313    *
0314    * This will return an annotation set containing just those annotations of a
0315    * particular type (i.e. with a particular name) and which have features with
0316    * specific names and values. (It will also return annotations that have
0317    * features besides those specified, but it will not return any annotations
0318    * that do not have all the specified feature-value pairs.)
0319    *
0320    * However, if constraints contains a feature whose value is equal to
0321    * gate.creole.ANNIEConstants.LOOKUP_CLASS_FEATURE_NAME (which is normally
0322    * "class"), then GATE will attempt to match that feature using an ontology
0323    * which it will try to retreive from a feature on the both the annotation and
0324    * in constraints. If these do not return identical ontologies, or if either
0325    * the annotation or constraints does not contain an ontology, then matching
0326    * will fail, and the annotation will not be added. In summary, this method
0327    * will not work normally for features with the name "class".
0328    *
0329    @param type
0330    *          The name of the annotations to return.
0331    @param constraints
0332    *          A feature map containing all of the feature value pairs that the
0333    *          annotation must have in order for them to be returned.
0334    @return An annotation set containing only those annotations with the given
0335    *         name and which have the specified set of feature-value pairs.
0336    */
0337   public AnnotationSet get(String type, FeatureMap constraints) {
0338     if(annotsByType == nullindexByType();
0339     AnnotationSet typeSet = get(type);
0340     if(typeSet == nullreturn null;
0341     Iterator<Annotation> iter = typeSet.iterator();
0342     List<Annotation> annotationsToAdd = new ArrayList<Annotation>();
0343     while(iter.hasNext()) {
0344       Annotation a = iter.next();
0345       // we check for matching constraints by simple equality. a
0346       // feature map satisfies the constraints if it contains all the
0347       // key/value pairs from the constraints map
0348       // if
0349       // (a.getFeatures().entrySet().containsAll(constraints.entrySet()))
0350       if(a.getFeatures().subsumes(constraints)) annotationsToAdd.add(a);
0351     // while
0352     if(annotationsToAdd.isEmpty()) return emptyAnnotationSet;
0353     return new ImmutableAnnotationSetImpl(doc, annotationsToAdd);
0354   // get(type, constraints)
0355 
0356   /** Select annotations by type and feature names */
0357   public AnnotationSet get(String type, Set featureNames) {
0358     if(annotsByType == nullindexByType();
0359     AnnotationSet typeSet = null;
0360     if(type != null) {
0361       // if a type is provided, try finding annotations of this type
0362       typeSet = get(type);
0363       // if none exist, then return coz nothing left to do
0364       if(typeSet == nullreturn null;
0365     }
0366     List<Annotation> annotationsToAdd = new ArrayList<Annotation>();
0367     Iterator<Annotation> iter = null;
0368     if(type != null)
0369       iter = typeSet.iterator();
0370     else iter = annotsById.values().iterator();
0371     while(iter.hasNext()) {
0372       Annotation a = iter.next();
0373       // we check for matching constraints by simple equality. a
0374       // feature map satisfies the constraints if it contains all the
0375       // key/value pairs from the constraints map
0376       if(a.getFeatures().keySet().containsAll(featureNames))
0377         annotationsToAdd.add(a);
0378     // while
0379     if(annotationsToAdd.isEmpty()) return emptyAnnotationSet;
0380     return new ImmutableAnnotationSetImpl(doc, annotationsToAdd);
0381   // get(type, featureNames)
0382 
0383   /**
0384    * Select annotations by offset. This returns the set of annotations whose
0385    * start node is the least such that it is less than or equal to offset. If a
0386    * positional index doesn't exist it is created. If there are no nodes at or
0387    * beyond the offset param then it will return an empty annotationset.
0388    */
0389   public AnnotationSet get(Long offset) {
0390     if(annotsByStartNode == nullindexByStartOffset();
0391     // find the next node at or after offset; get the annots starting
0392     // there
0393     Node nextNode = (Node)nodesByOffset.getNextOf(offset);
0394     if(nextNode == null// no nodes at or beyond this offset
0395       return emptyAnnotationSet;
0396     Collection<Annotation> annotationsToAdd = getAnnotsByStartNode(nextNode
0397             .getId());
0398     // skip all the nodes that have no starting annotations
0399     while(annotationsToAdd == null) {
0400       nextNode = (Node)nodesByOffset.getNextOf(new Long(nextNode.getOffset()
0401               .longValue() 1));
0402       if (nextNode==nullreturn emptyAnnotationSet;
0403       annotationsToAdd = getAnnotsByStartNode(nextNode.getId());
0404     }
0405     if(annotationsToAdd == nullreturn emptyAnnotationSet;
0406     return new ImmutableAnnotationSetImpl(doc, annotationsToAdd);
0407   }
0408 
0409   /**
0410    * Select annotations by offset. This returns the set of annotations that
0411    * overlap totaly or partially with the interval defined by the two provided
0412    * offsets.The result will include all the annotations that either:
0413    <ul>
0414    <li>start before the start offset and end strictly after it</li>
0415    <li>OR</li>
0416    <li>start at a position between the start and the end offsets</li>
0417    *
0418    @return an ImmutableAnnotationSet
0419    */
0420   public AnnotationSet get(Long startOffset, Long endOffset) {
0421     return get(null, startOffset, endOffset);
0422   // get(startOfset, endOffset)
0423 
0424   /**
0425    * Select annotations by offset. This returns the set of annotations that
0426    * overlap strictly with the interval defined by the two provided offsets.The
0427    * result will include all the annotations that start at the start offset and
0428    * end strictly at the end offset
0429    */
0430   public AnnotationSet getStrict(Long startOffset, Long endOffset) {
0431     // the result will include all the annotations that
0432     // start at the start offset and end strictly at the end offset
0433     if(annotsByStartNode == nullindexByStartOffset();
0434     List<Annotation> annotationsToAdd = null;
0435     Iterator<Annotation> annotsIter;
0436     Node currentNode;
0437     Annotation currentAnnot;
0438     // find all the annots that start at the start offset
0439     currentNode = (Node)nodesByOffset.get(startOffset);
0440     if(currentNode != null) {
0441       Collection<Annotation> objFromPoint = getAnnotsByStartNode(currentNode
0442               .getId());
0443       if(objFromPoint != null) {
0444         annotsIter = objFromPoint.iterator();
0445         while(annotsIter.hasNext()) {
0446           currentAnnot = annotsIter.next();
0447           if(currentAnnot.getEndNode().getOffset().compareTo(endOffset== 0) {
0448             if(annotationsToAdd == nullannotationsToAdd = new ArrayList<Annotation>();
0449             annotationsToAdd.add(currentAnnot);
0450           // if
0451         // while
0452       // if
0453     // if
0454     return new ImmutableAnnotationSetImpl(doc, annotationsToAdd);
0455   // getStrict(startOfset, endOffset)
0456 
0457   /**
0458    * Select annotations by offset. This returns the set of annotations of the
0459    * given type that overlap totaly or partially with the interval defined by
0460    * the two provided offsets.The result will include all the annotations that
0461    * either:
0462    <ul>
0463    <li>start before the start offset and end strictly after it</li>
0464    <li>OR</li>
0465    <li>start at a position between the start and the end offsets</li>
0466    */
0467   public AnnotationSet get(String neededType, Long startOffset, Long endOffset) {
0468     if(annotsByStartNode == nullindexByStartOffset();
0469     List<Annotation> annotationsToAdd = new ArrayList<Annotation>();
0470     Iterator<Node> nodesIter;
0471     Iterator<Annotation> annotsIter;
0472     Node currentNode;
0473     Annotation currentAnnot;
0474     boolean checkType = StringUtils.isNotBlank(neededType);
0475     // find all the annots that start strictly before the start offset
0476     // and end
0477     // strictly after it
0478     Long searchStart = (startOffset - longestAnnot);
0479     if (searchStart < 0searchStart = 0l;
0480     //nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
0481     nodesIter = nodesByOffset.subMap(searchStart, startOffset).values().iterator();
0482     while(nodesIter.hasNext()) {
0483       currentNode = nodesIter.next();
0484       Collection<Annotation> objFromPoint = getAnnotsByStartNode(currentNode
0485               .getId());
0486       if(objFromPoint == nullcontinue;
0487       annotsIter = objFromPoint.iterator();
0488       while(annotsIter.hasNext()) {
0489         currentAnnot = annotsIter.next();
0490         //if neededType is set, make sure this is the right type
0491         if (checkType && !currentAnnot.getType().equals(neededType))
0492           continue;
0493         if(currentAnnot.getEndNode().getOffset().compareTo(startOffset0) {
0494           annotationsToAdd.add(currentAnnot);
0495         // if
0496       // while
0497     }
0498     // find all the annots that start at or after the start offset but
0499     // before the end offset
0500     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values()
0501             .iterator();
0502     while(nodesIter.hasNext()) {
0503       currentNode = nodesIter.next();
0504       Collection<Annotation> objFromPoint = getAnnotsByStartNode(currentNode
0505               .getId());
0506       if(objFromPoint == nullcontinue;
0507       //if no specific type requested, add all of the annots
0508       if (!checkType)
0509         annotationsToAdd.addAll(objFromPoint);
0510       else {
0511         //check the type of each annot
0512         annotsIter = objFromPoint.iterator();
0513         while(annotsIter.hasNext()) {
0514           currentAnnot = annotsIter.next();
0515           if (currentAnnot.getType().equals(neededType))
0516             annotationsToAdd.add(currentAnnot);
0517         // while
0518       }
0519     }
0520     return new ImmutableAnnotationSetImpl(doc, annotationsToAdd);
0521   // get(type, startOfset, endOffset)
0522 
0523   /**
0524    * Select annotations of the given type that completely span the range.
0525    * Formally, for any annotation a, a will be included in the return
0526    * set if:
0527    <ul>
0528    <li>a.getStartNode().getOffset() <= startOffset</li>
0529    <li>and</li>
0530    <li>a.getEndNode().getOffset() >= endOffset</li>
0531    *
0532    @param neededType Type of annotation to return. If empty, all
0533    *          annotation types will be returned.
0534    @param startOffset
0535    @param endOffset
0536    @return
0537    */
0538   public AnnotationSet getCovering(String neededType, Long startOffset, Long endOffset) {
0539     //check the range
0540     if(endOffset < startOffsetreturn emptyAnnotationSet;
0541     //ensure index
0542     if(annotsByStartNode == nullindexByStartOffset();
0543     //if the requested range is longer than the longest annotation in this set, 
0544     //then there can be no annotations covering the range
0545     // so we return an empty set.
0546     if(endOffset - startOffset > longestAnnotreturn emptyAnnotationSet;
0547     
0548     List<Annotation> annotationsToAdd = new ArrayList<Annotation>();
0549     Iterator<Node> nodesIter;
0550     Iterator<Annotation> annotsIter;
0551     Node currentNode;
0552     Annotation currentAnnot;
0553     boolean checkType = StringUtils.isNotBlank(neededType);
0554     // find all the annots with startNode <= startOffset.  Need the + 1 because
0555     // headMap returns strictly less than.
0556     // the length of the longest annot from the endOffset since we know that nothing
0557     // that starts earlier will be long enough to cover the entire span.
0558     Long searchStart = ((endOffset - 1- longestAnnot);
0559     if (searchStart < 0searchStart = 0l;
0560     //nodesIter = nodesByOffset.headMap(startOffset + 1).values().iterator();
0561     nodesIter = nodesByOffset.subMap(searchStart, startOffset + 1).values().iterator();
0562 
0563     while(nodesIter.hasNext()) {
0564       currentNode = nodesIter.next();
0565       Collection<Annotation> objFromPoint = getAnnotsByStartNode(currentNode
0566               .getId());
0567       if(objFromPoint == nullcontinue;
0568       annotsIter = objFromPoint.iterator();
0569       while(annotsIter.hasNext()) {
0570         currentAnnot = annotsIter.next();
0571         //if neededType is set, make sure this is the right type
0572         if (checkType && !currentAnnot.getType().equals(neededType))
0573           continue;
0574         //check that the annot ends at or after the endOffset
0575         if(currentAnnot.getEndNode().getOffset().compareTo(endOffset>= 0)
0576           annotationsToAdd.add(currentAnnot);
0577       // while
0578     }
0579     return new ImmutableAnnotationSetImpl(doc, annotationsToAdd);
0580   // get(type, startOfset, endOffset)
0581 
0582   /** Select annotations by type, features and offset */
0583   public AnnotationSet get(String type, FeatureMap constraints, Long offset) {
0584     // select by offset
0585     AnnotationSet nextAnnots = get(offset);
0586     if(nextAnnots == nullreturn emptyAnnotationSet;
0587     // select by type and constraints from the next annots
0588     return nextAnnots.get(type, constraints);
0589   // get(type, constraints, offset)
0590 
0591   /**
0592    * Select annotations contained within an interval, i.e.
0593    * those annotations whose start position is
0594    * >= <code>startOffset</code> and &lt; <code>endOffset</code>
0595    * and whose end position is &lt;= <code>endOffset</code>.
0596    */
0597   public AnnotationSet getContained(Long startOffset, Long endOffset) {
0598     // the result will include all the annotations that either:
0599     // start at a position between the start and end before the end
0600     // offsets
0601     //check the range
0602     if(endOffset < startOffsetreturn emptyAnnotationSet;
0603     //ensure index
0604     if(annotsByStartNode == nullindexByStartOffset();
0605     List<Annotation> annotationsToAdd = null;
0606     Iterator<Node> nodesIter;
0607     Node currentNode;
0608     Iterator<Annotation> annotIter;
0609     // find all the annots that start at or after the start offset but
0610     // strictly
0611     // before the end offset
0612     nodesIter = nodesByOffset.subMap(startOffset, endOffset).values()
0613             .iterator();
0614     while(nodesIter.hasNext()) {
0615       currentNode = nodesIter.next();
0616       Collection<Annotation> objFromPoint = getAnnotsByStartNode(currentNode
0617               .getId());
0618       if(objFromPoint == nullcontinue;
0619       // loop through the annotations and find only those that
0620       // also end before endOffset
0621       annotIter = objFromPoint.iterator();
0622       while(annotIter.hasNext()) {
0623         Annotation annot = annotIter.next();
0624         if(annot.getEndNode().getOffset().compareTo(endOffset<= 0) {
0625           if(annotationsToAdd == nullannotationsToAdd = new ArrayList<Annotation>();
0626           annotationsToAdd.add(annot);
0627         }
0628       }
0629     }
0630     return new ImmutableAnnotationSetImpl(doc, annotationsToAdd);
0631   // get(startOfset, endOffset)
0632 
0633   /** Get the node with the smallest offset */
0634   public Node firstNode() {
0635     indexByStartOffset();
0636     if(nodesByOffset.isEmpty())
0637       return null;
0638     else return (Node)nodesByOffset.get(nodesByOffset.firstKey());
0639   // firstNode
0640 
0641   /** Get the node with the largest offset */
0642   public Node lastNode() {
0643     indexByStartOffset();
0644     if(nodesByOffset.isEmpty())
0645       return null;
0646     else return (Node)nodesByOffset.get(nodesByOffset.lastKey());
0647   // lastNode
0648 
0649   /**
0650    * Get the first node that is relevant for this annotation set and which has
0651    * the offset larger than the one of the node provided.
0652    */
0653   public Node nextNode(Node node) {
0654     indexByStartOffset();
0655     return (Node)nodesByOffset.getNextOf(new Long(
0656             node.getOffset().longValue() 1));
0657   }
0658 
0659   protected static AnnotationFactory annFactory;
0660 
0661   /**
0662    * Set the annotation factory used to create annotation objects. The default
0663    * factory is {@link DefaultAnnotationFactory}.
0664    */
0665   public static void setAnnotationFactory(AnnotationFactory newFactory) {
0666     annFactory = newFactory;
0667   }
0668 
0669   static {
0670     // set the default factory to always create AnnotationImpl objects
0671     setAnnotationFactory(new DefaultAnnotationFactory());
0672   }
0673 
0674   /**
0675    * Create and add an annotation with pre-existing nodes, and return its id.
0676    <B>Note that only Nodes retrieved from the same annotation set should be used
0677    * to create a new annotation using this method. Using Nodes from other annotation
0678    * sets may lead to undefined behaviour. If in any doubt use the Long based add
0679    * method instead of this one.</B>
0680    */
0681   public Integer add(Node start, Node end, String type, FeatureMap features) {
0682     // the id of the new annotation
0683     Integer id = doc.getNextAnnotationId();
0684     // construct an annotation
0685     annFactory.createAnnotationInSet(this, id, start, end, type, features);
0686 
0687     return id;
0688   // add(Node, Node, String, FeatureMap)
0689 
0690   /** Add an existing annotation. Returns true when the set is modified. */
0691   public boolean add(Annotation athrows ClassCastException {
0692     Object oldValue = annotsById.put(a.getId(), a);
0693     if(annotsByType != nulladdToTypeIndex(a);
0694     if(annotsByStartNode != nulladdToStartOffsetIndex(a);
0695     AnnotationSetEvent evt = new AnnotationSetEvent(this,
0696             AnnotationSetEvent.ANNOTATION_ADDED, doc, a);
0697     fireAnnotationAdded(evt);
0698     fireGateEvent(evt);
0699     return oldValue != a;
0700   // add(o)
0701 
0702   /**
0703    * Adds multiple annotations to this set in one go. All the objects in the
0704    * provided collection should be of {@link gate.Annotation} type, otherwise a
0705    * ClassCastException will be thrown. The provided annotations will be used to
0706    * create new annotations using the appropriate add() methods from this set.
0707    * The new annotations will have different IDs from the old ones (which is
0708    * required in order to preserve the uniqueness of IDs inside an annotation
0709    * set).
0710    *
0711    @param c
0712    *          a collection of annotations
0713    @return <tt>true</tt> if the set has been modified as a result of this
0714    *         call.
0715    */
0716   public boolean addAll(Collection<? extends Annotation> c) {
0717     Iterator<? extends Annotation> annIter = c.iterator();
0718     boolean changed = false;
0719     while(annIter.hasNext()) {
0720       Annotation a = annIter.next();
0721       try {
0722         add(a.getStartNode().getOffset(), a.getEndNode().getOffset(), a
0723                 .getType(), a.getFeatures());
0724         changed = true;
0725       catch(InvalidOffsetException ioe) {
0726         throw new IllegalArgumentException(ioe.toString());
0727       }
0728     }
0729     return changed;
0730   }
0731 
0732   /**
0733    * Adds multiple annotations to this set in one go. All the objects in the
0734    * provided collection should be of {@link gate.Annotation} type, otherwise a
0735    * ClassCastException will be thrown. This method does not create copies of
0736    * the annotations like addAll() does but simply adds the new annotations to
0737    * the set. It is intended to be used solely by annotation sets in order to
0738    * construct the results for various get(...) methods.
0739    *
0740    @param c
0741    *          a collection of annotations
0742    @return <tt>true</tt> if the set has been modified as a result of this
0743    *         call.
0744    */
0745   protected boolean addAllKeepIDs(Collection<? extends Annotation> c) {
0746     Iterator<? extends Annotation> annIter = c.iterator();
0747     boolean changed = false;
0748     while(annIter.hasNext()) {
0749       Annotation a = annIter.next();
0750       changed |= add(a);
0751     }
0752     return changed;
0753   }
0754 
0755   /** Returns the nodes corresponding to the Longs. The Nodes are created if
0756    * they don't exist.
0757    **/
0758   private final Node[] getNodes(Long start, Long endthrows InvalidOffsetException
0759   {
0760     // are the offsets valid?
0761     if(!doc.isValidOffsetRange(start, end)) throw new InvalidOffsetException();
0762     // the set has to be indexed by position in order to add, as we need
0763     // to find out if nodes need creating or if they exist already
0764     if(nodesByOffset == null) {
0765       indexByStartOffset();
0766     }
0767     // find existing nodes if appropriate nodes don't already exist,
0768     // create them
0769     Node startNode = (Node)nodesByOffset.get(start);
0770     if(startNode == null)
0771       startNode = new NodeImpl(doc.getNextNodeId(), start);
0772 
0773     Node endNode = null;
0774     if(start.equals(end)){
0775       endNode = startNode;
0776       return new Node[]{startNode,endNode};
0777     }
0778 
0779     endNode = (Node)nodesByOffset.get(end);
0780     if(endNode == null)
0781       endNode = new NodeImpl(doc.getNextNodeId(), end);
0782 
0783     return new Node[]{startNode,endNode};
0784   }
0785 
0786 
0787   /** Create and add an annotation and return its id */
0788   public Integer add(Long start, Long end, String type, FeatureMap features)
0789           throws InvalidOffsetException {
0790     Node[] nodes = getNodes(start,end);
0791     // delegate to the method that adds annotations with existing nodes
0792     return add(nodes[0], nodes[1], type, features);
0793   // add(start, end, type, features)
0794 
0795   /**
0796    * Create and add an annotation from database read data In this case the id is
0797    * already known being previously fetched from the database
0798    */
0799   public void add(Integer id, Long start, Long end, String type,
0800           FeatureMap featuresthrows InvalidOffsetException {
0801     Node[] nodes = getNodes(start,end);
0802     // construct an annotation
0803     annFactory.createAnnotationInSet(this, id, nodes[0], nodes[1], type,
0804             features);
0805   // add(id, start, end, type, features)
0806 
0807   /** Construct the positional index. */
0808   protected void indexByType() {
0809     if(annotsByType != nullreturn;
0810     annotsByType = new HashMap<String, AnnotationSet>(Gate.HASH_STH_SIZE);
0811     Iterator<Annotation> annotIter = annotsById.values().iterator();
0812     while(annotIter.hasNext())
0813       addToTypeIndex(annotIter.next());
0814   // indexByType()
0815 
0816   /** Construct the positional indices for annotation start */
0817   protected void indexByStartOffset() {
0818     if(annotsByStartNode != nullreturn;
0819     if(nodesByOffset == nullnodesByOffset = new RBTreeMap();
0820     annotsByStartNode = new HashMap<Integer, Object>(annotsById.size());
0821     Iterator<Annotation> annotIter = annotsById.values().iterator();
0822     while(annotIter.hasNext())
0823       addToStartOffsetIndex(annotIter.next());
0824   // indexByStartOffset()
0825 
0826   /**
0827    * Add an annotation to the type index. Does nothing if the index doesn't
0828    * exist.
0829    */
0830   void addToTypeIndex(Annotation a) {
0831     if(annotsByType == nullreturn;
0832     String type = a.getType();
0833     AnnotationSet sameType = annotsByType.get(type);
0834     if(sameType == null) {
0835       sameType = new AnnotationSetImpl(doc);
0836       annotsByType.put(type, sameType);
0837     }
0838     sameType.add(a);
0839   // addToTypeIndex(a)
0840 
0841   /**
0842    * Add an annotation to the start offset index. Does nothing if the index
0843    * doesn't exist.
0844    */
0845   void addToStartOffsetIndex(Annotation a) {
0846     Node startNode = a.getStartNode();
0847     Node endNode = a.getEndNode();
0848     Long start = startNode.getOffset();
0849     Long end = endNode.getOffset();
0850     // add a's nodes to the offset index
0851     if(nodesByOffset != null) {
0852       nodesByOffset.put(start, startNode);
0853       nodesByOffset.put(end, endNode);
0854     }
0855 
0856     //add marking for longest annot
0857     long annotLength = end - start;
0858     if (annotLength > longestAnnot)
0859         longestAnnot = annotLength;
0860 
0861     // if there's no appropriate index give up
0862     if(annotsByStartNode == nullreturn;
0863     // get the annotations that start at the same node, or create new
0864     // set
0865     Object thisNodeObject = annotsByStartNode.get(startNode.getId());
0866     if(thisNodeObject == null) {
0867       // put directly the annotation
0868       annotsByStartNode.put(startNode.getId(), a);
0869     else // already something there : a single Annotation or a
0870       // Collection
0871       Set<Annotation> newCollection = null;
0872       if(thisNodeObject instanceof Annotation) {
0873         // we need to create a set - we have more than one annotation
0874         // starting
0875         // at this Node
0876         if(thisNodeObject.equals(a)) return;
0877         newCollection = new HashSet<Annotation>(3);
0878         newCollection.add((Annotation)thisNodeObject);
0879         annotsByStartNode.put(startNode.getId(), newCollection);
0880       else newCollection = (Set<Annotation>)thisNodeObject;
0881       // get the existing set
0882       // add the new node annotation
0883       newCollection.add(a);
0884     }
0885   // addToStartOffsetIndex(a)
0886 
0887   /**
0888    * Propagate changes to the document content. Has, unfortunately, to be
0889    * public, to allow DocumentImpls to get at it. Oh for a "friend" declaration.
0890    * Doesn't throw InvalidOffsetException as DocumentImpl is the only client,
0891    * and that checks the offsets before calling this method.
0892    */
0893   public void edit(Long start, Long end, DocumentContent replacement) {
0894     // make sure we have the indices computed
0895     indexByStartOffset();
0896     if(end.compareTo(start0) {
0897       // get the nodes that need to be processed (the nodes internal to
0898       // the
0899       // removed section plus the marginal ones
0900       List<Node> affectedNodes = new ArrayList<Node>(nodesByOffset.subMap(start,
0901               new Long(end.longValue() 1)).values());
0902       // if we have more than 1 node we need to delete all apart from
0903       // the first
0904       // and move the annotations so that they refer to the one we keep
0905       // (the
0906       // first)
0907       NodeImpl firstNode = null;
0908       if(!affectedNodes.isEmpty()) {
0909         firstNode = (NodeImpl)affectedNodes.get(0);
0910         List<Annotation> startingAnnotations = new ArrayList<Annotation>();
0911         List<Annotation> endingAnnotations = new ArrayList<Annotation>();
0912         // now we need to find all the annotations
0913         // ending in the zone
0914         List<Node> beforeNodes = new ArrayList<Node>(nodesByOffset.subMap(new Long(0),
0915                 new Long(end.longValue() 1)).values());
0916         Iterator<Node> beforeNodesIter = beforeNodes.iterator();
0917         while(beforeNodesIter.hasNext()) {
0918           Node currentNode = beforeNodesIter.next();
0919           Collection<Annotation> annotations = getAnnotsByStartNode(currentNode.getId());
0920           if(annotations == nullcontinue;
0921           // iterates on the annotations in this set
0922           Iterator<Annotation> localIterator = annotations.iterator();
0923           while(localIterator.hasNext()) {
0924             Annotation annotation = localIterator.next();
0925             long offsetEndAnnotation = annotation.getEndNode().getOffset()
0926                     .longValue();
0927             // we are interested only in the annotations ending
0928             // inside the zone
0929             if(offsetEndAnnotation >= start.longValue()
0930                     && offsetEndAnnotation <= end.longValue())
0931               endingAnnotations.add(annotation);
0932           }
0933         }
0934         for(int i = 1; i < affectedNodes.size(); i++) {
0935           Node aNode = affectedNodes.get(i);
0936           Collection<Annotation> annSet = getAnnotsByStartNode(aNode.getId());
0937           if(annSet != null) {
0938             startingAnnotations.addAll(annSet);
0939           }
0940           // remove the node
0941           // nodesByOffset.remove(aNode.getOffset());
0942           // annotsByStartNode.remove(aNode);
0943         }
0944         // modify the annotations so they point to the saved node
0945         Iterator<Annotation> annIter = startingAnnotations.iterator();
0946         while(annIter.hasNext()) {
0947           AnnotationImpl anAnnot = (AnnotationImpl)annIter.next();
0948           anAnnot.start = firstNode;
0949           // remove the modified annotation if it has just become
0950           // zero-length
0951           if(anAnnot.start == anAnnot.end) {
0952             remove(anAnnot);
0953           else {
0954             addToStartOffsetIndex(anAnnot);
0955           }
0956         }
0957         annIter = endingAnnotations.iterator();
0958         while(annIter.hasNext()) {
0959           AnnotationImpl anAnnot = (AnnotationImpl)annIter.next();
0960           anAnnot.end = firstNode;
0961           // remove the modified annotation if it has just become
0962           // zero-length
0963           if(anAnnot.start == anAnnot.end) {
0964             remove(anAnnot);
0965           }
0966         }
0967         // remove the unused nodes inside the area
0968         for(int i = 1; i < affectedNodes.size(); i++) {
0969           Node aNode = affectedNodes.get(i);
0970           nodesByOffset.remove(aNode.getOffset());
0971           annotsByStartNode.remove(aNode.getId());
0972         }
0973         // repair the first node
0974         // remove from offset index
0975         nodesByOffset.remove(firstNode.getOffset());
0976         // change the offset for the saved node
0977         firstNode.setOffset(start);
0978         // add back to the offset index
0979         nodesByOffset.put(firstNode.getOffset(), firstNode);
0980       }
0981     }
0982     // now handle the insert and/or update the rest of the nodes'
0983     // position
0984     // get the user selected behaviour (defaults to append)
0985     boolean shouldPrepend = Gate.getUserConfig().getBoolean(
0986             GateConstants.DOCEDIT_INSERT_PREPEND).booleanValue();
0987     long s = start.longValue(), e = end.longValue();
0988     long rlen = // length of the replacement value
0989     ((replacement == null: replacement.size().longValue());
0990     // update the offsets and the index by offset for the rest of the
0991     // nodes
0992     List<Node> nodesAfterReplacement = new ArrayList<Node>(nodesByOffset.tailMap(start)
0993             .values());
0994     // remove from the index by offset
0995     Iterator<Node> nodesAfterReplacementIter = nodesAfterReplacement.iterator();
0996     while(nodesAfterReplacementIter.hasNext()) {
0997       NodeImpl n = (NodeImpl)nodesAfterReplacementIter.next();
0998       nodesByOffset.remove(n.getOffset());
0999     }
1000     // change the offsets
1001     nodesAfterReplacementIter = nodesAfterReplacement.iterator();
1002     while(nodesAfterReplacementIter.hasNext()) {
1003       NodeImpl n = (NodeImpl)nodesAfterReplacementIter.next();
1004       long oldOffset = n.getOffset().longValue();
1005       // by default we move all nodes back
1006       long newOffset = oldOffset - (e - s+ rlen;
1007       // for the first node we need behave differently
1008       if(oldOffset == s) {
1009         // the first offset never moves back
1010         if(newOffset < snewOffset = s;
1011         // if we're prepending we don't move forward
1012         if(shouldPrependnewOffset = s;
1013       }
1014       n.setOffset(new Long(newOffset));
1015     }
1016     // add back to the index by offset with the new offsets
1017     nodesAfterReplacementIter = nodesAfterReplacement.iterator();
1018     while(nodesAfterReplacementIter.hasNext()) {
1019       NodeImpl n = (NodeImpl)nodesAfterReplacementIter.next();
1020       nodesByOffset.put(n.getOffset(), n);
1021     }
1022     // //rebuild the indices with the new offsets
1023     // nodesByOffset = null;
1024     // annotsByStartNode = null;
1025     // annotsByEndNode = null;
1026     // indexByStartOffset();
1027     // indexByEndOffset();
1028   // edit(start,end,replacement)
1029 
1030   /** Get the name of this set. */
1031   public String getName() {
1032     return name;
1033   }
1034 
1035   /** Get the document this set is attached to. */
1036   public Document getDocument() {
1037     return doc;
1038   }
1039 
1040   /**
1041    * Get a set of java.lang.String objects representing all the annotation types
1042    * present in this annotation set.
1043    */
1044   public Set<String> getAllTypes() {
1045     indexByType();
1046     return Collections.unmodifiableSet(annotsByType.keySet());
1047   }
1048 
1049   /**
1050    * Returns a set of annotations starting at that position This intermediate
1051    * method is used to simplify the code as the values of the annotsByStartNode
1052    * hashmap can be Annotations or a Collection of Annotations. Returns null if
1053    * there are no Annotations at that position
1054    */
1055   private final Collection<Annotation> getAnnotsByStartNode(Integer id) {
1056     Object objFromPoint = annotsByStartNode.get(id);
1057     if(objFromPoint == nullreturn null;
1058     if(objFromPoint instanceof Annotation) {
1059       List<Annotation> al = new ArrayList<Annotation>(2);
1060       al.add((Annotation)objFromPoint);
1061       return al;
1062     }
1063     // it is already a collection
1064     // return it
1065     return (Collection<Annotation>)objFromPoint;
1066   }
1067 
1068   /**
1069    *
1070    @return a clone of this set.
1071    @throws CloneNotSupportedException
1072    */
1073   public Object clone() throws CloneNotSupportedException {
1074     return super.clone();
1075   }
1076 
1077   public synchronized void removeAnnotationSetListener(AnnotationSetListener l) {
1078     if(annotationSetListeners != null && annotationSetListeners.contains(l)) {
1079       Vector<AnnotationSetListener> v = (Vector<AnnotationSetListener>)annotationSetListeners.clone();
1080       v.removeElement(l);
1081       annotationSetListeners = v;
1082     }
1083   }
1084 
1085   public synchronized void addAnnotationSetListener(AnnotationSetListener l) {
1086     Vector<AnnotationSetListener> v = annotationSetListeners == null
1087             new Vector<AnnotationSetListener>(2)
1088             (Vector<AnnotationSetListener>)annotationSetListeners.clone();
1089     if(!v.contains(l)) {
1090       v.addElement(l);
1091       annotationSetListeners = v;
1092     }
1093   }
1094 
1095   protected void fireAnnotationAdded(AnnotationSetEvent e) {
1096     if(annotationSetListeners != null) {
1097       Vector<AnnotationSetListener> listeners = annotationSetListeners;
1098       int count = listeners.size();
1099       for(int i = 0; i < count; i++) {
1100         listeners.elementAt(i).annotationAdded(e);
1101       }
1102     }
1103   }
1104 
1105   protected void fireAnnotationRemoved(AnnotationSetEvent e) {
1106     if(annotationSetListeners != null) {
1107       Vector<AnnotationSetListener> listeners = annotationSetListeners;
1108       int count = listeners.size();
1109       for(int i = 0; i < count; i++) {
1110         listeners.elementAt(i).annotationRemoved(e);
1111       }
1112     }
1113   }
1114 
1115   public synchronized void removeGateListener(GateListener l) {
1116     if(gateListeners != null && gateListeners.contains(l)) {
1117       Vector<GateListener> v = (Vector<GateListener>)gateListeners.clone();
1118       v.removeElement(l);
1119       gateListeners = v;
1120     }
1121   }
1122 
1123   public synchronized void addGateListener(GateListener l) {
1124     Vector<GateListener> v = gateListeners == null new Vector<GateListener>(2(Vector<GateListener>)gateListeners
1125             .clone();
1126     if(!v.contains(l)) {
1127       v.addElement(l);
1128       gateListeners = v;
1129     }
1130   }
1131 
1132   protected void fireGateEvent(GateEvent e) {
1133     if(gateListeners != null) {
1134       Vector<GateListener> listeners = gateListeners;
1135       int count = listeners.size();
1136       for(int i = 0; i < count; i++) {
1137         listeners.elementAt(i).processGateEvent(e);
1138       }
1139     }
1140   }
1141 
1142   // how to serialize this object?
1143   // there is no need to serialize the indices
1144   // so it's probably as fast to just recreate them
1145   // if required
1146   private void writeObject(java.io.ObjectOutputStream outthrows IOException {
1147     ObjectOutputStream.PutField pf = out.putFields();
1148     pf.put("name"this.name);
1149     pf.put("doc"this.doc);
1150     //
1151     // out.writeObject(this.name);
1152     // out.writeObject(this.doc);
1153     // save only the annotations
1154     // in an array that will prevent the need for casting
1155     // when deserializing
1156     annotations = new Annotation[this.annotsById.size()];
1157     annotations = this.annotsById.values().toArray(annotations);
1158     // out.writeObject(annotations);
1159     pf.put("annotations"this.annotations);
1160     out.writeFields();
1161     annotations = null;
1162     boolean isIndexedByType = (this.annotsByType != null);
1163     boolean isIndexedByStartNode = (this.annotsByStartNode != null);
1164     out.writeBoolean(isIndexedByType);
1165     out.writeBoolean(isIndexedByStartNode);
1166   }
1167 
1168   private void readObject(java.io.ObjectInputStream inthrows IOException,
1169           ClassNotFoundException {
1170     this.longestAnnot = 0l;
1171     ObjectInputStream.GetField gf = in.readFields();
1172     this.name = (String)gf.get("name"null);
1173     this.doc = (DocumentImpl)gf.get("doc"null);
1174     boolean isIndexedByType = false;
1175     boolean isIndexedByStartNode = false;
1176     this.annotations = (Annotation[])gf.get("annotations"null);
1177     if(this.annotations == null) {
1178       // old style serialised version
1179       Map<Integer, Annotation> annotsByIdMap = (Map<Integer, Annotation>)gf
1180               .get("annotsById"null);
1181       if(annotsByIdMap == null)
1182         throw new IOException(
1183                 "Invalid serialised data: neither annotations array or map by id"
1184                         " are present.");
1185       annotations = annotsByIdMap.values().toArray(new Annotation[]{});
1186     else {
1187       // new style serialised version
1188       isIndexedByType = in.readBoolean();
1189       isIndexedByStartNode = in.readBoolean();
1190     }
1191     // this.name = (String)in.readObject();
1192     // this.doc = (DocumentImpl)in.readObject();
1193     // Annotation[] annotations = (Annotation[])in.readObject();
1194     // do we need to create the indices?
1195     // boolean isIndexedByType = in.readBoolean();
1196     // boolean isIndexedByStartNode = in.readBoolean();
1197     this.annotsById = new HashMap<Integer, Annotation>(annotations.length);
1198     // rebuilds the indices if required
1199     if(isIndexedByType) {
1200       annotsByType = new HashMap<String, AnnotationSet>(Gate.HASH_STH_SIZE);
1201     }
1202     if(isIndexedByStartNode) {
1203       nodesByOffset = new RBTreeMap();
1204       annotsByStartNode = new HashMap<Integer, Object>(annotations.length);
1205     }
1206     // add all the annotations one by one
1207     for(int i = 0; i < annotations.length; i++) {
1208       add(annotations[i]);
1209     }
1210     annotations = null;
1211   }
1212 // AnnotationSetImpl