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