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 c) throws ClassCastException {
0128 this(c.getDocument(), c.getName());
0129 // the original annotationset is of the same implementation
0130 if(c 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 == null) return;
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 o) throws 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()) == null) return 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 != null) sameType.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 == null) indexByType();
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> types) throws ClassCastException {
0294 if(annotsByType == null) indexByType();
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 == null) indexByType();
0339 AnnotationSet typeSet = get(type);
0340 if(typeSet == null) return 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 == null) indexByType();
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 == null) return 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 == null) indexByStartOffset();
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==null) return emptyAnnotationSet;
0403 annotationsToAdd = getAnnotsByStartNode(nextNode.getId());
0404 }
0405 if(annotationsToAdd == null) return 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 == null) indexByStartOffset();
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 == null) annotationsToAdd = 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 == null) indexByStartOffset();
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 < 0) searchStart = 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 == null) continue;
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(startOffset) > 0) {
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 == null) continue;
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 < startOffset) return emptyAnnotationSet;
0541 //ensure index
0542 if(annotsByStartNode == null) indexByStartOffset();
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 > longestAnnot) return 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 < 0) searchStart = 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 == null) continue;
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 == null) return 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 < <code>endOffset</code>
0595 * and whose end position is <= <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 < startOffset) return emptyAnnotationSet;
0603 //ensure index
0604 if(annotsByStartNode == null) indexByStartOffset();
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 == null) continue;
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 == null) annotationsToAdd = 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 a) throws ClassCastException {
0692 Object oldValue = annotsById.put(a.getId(), a);
0693 if(annotsByType != null) addToTypeIndex(a);
0694 if(annotsByStartNode != null) addToStartOffsetIndex(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 end) throws 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 features) throws 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 != null) return;
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 != null) return;
0819 if(nodesByOffset == null) nodesByOffset = 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 == null) return;
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 == null) return;
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(start) > 0) {
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 == null) continue;
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) ? 0 : 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 < s) newOffset = s;
1011 // if we're prepending we don't move forward
1012 if(shouldPrepend) newOffset = 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 == null) return 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 out) throws 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 in) throws 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
|