| AnnotationSetImpl.java |
1 /*
2 * AnnotationSetImpl.java
3 *
4 * Copyright (c) 1998-2005, The University of Sheffield.
5 *
6 * This file is part of GATE (see http://gate.ac.uk/), and is free
7 * software, licenced under the GNU Library General Public License,
8 * Version 2, June 1991 (in the distribution as file licence.html,
9 * and also available at http://gate.ac.uk/gate/licence.html).
10 *
11 * Hamish Cunningham, 7/Feb/2000
12 *
13 * Developer notes:
14 * ---
15 *
16 * the addToIndex... and indexBy... methods could be refactored as I'm
17 * sure they can be made simpler
18 *
19 * every set to which annotation will be added has to have positional
20 * indexing, so that we can find or create the nodes on the new annotations
21 *
22 * note that annotations added anywhere other than sets that are
23 * stored on the document will not get stored anywhere...
24 *
25 * nodes aren't doing anything useful now. needs some interface that allows
26 * their creation, defaulting to no coterminous duplicates, but allowing such
27 * if required
28 *
29 * $Id: AnnotationSetImpl.java,v 1.95 2005/08/25 11:26:33 ian_roberts Exp $
30 */
31
32 package gate.annotation;
33
34 import java.util.*;
35
36 import gate.*;
37 import gate.corpora.DocumentImpl;
38 import gate.event.*;
39 import gate.util.InvalidOffsetException;
40 import gate.util.RBTreeMap;
41
42 /** Implementation of AnnotationSet. Has a number of indices, all bar one
43 * of which are null by default and are only constructed when asked
44 * for. Has lots of get methods with various selection criteria; these
45 * return views into the set, which are nonetheless valid sets in
46 * their own right (but will not necesarily be fully indexed).
47 * Has a name, which is null by default; clients of Document can
48 * request named AnnotationSets if they so desire. Has a reference to the
49 * Document it is attached to. Contrary to Collections convention,
50 * there is no no-arg constructor, as this would leave the set in
51 * an inconsistent state.
52 * <P>
53 * There are five indices: annotation by id, annotations by type, annotations
54 * by start/end node and nodes by offset. The last three jointly provide
55 * positional indexing; construction of these is triggered by
56 * indexByStart/EndOffset(),
57 * or by calling a get method that selects on offset. The type
58 * index is triggered by indexByType(), or calling a get method that selects
59 * on type. The id index is always present.
60 */
61 public class AnnotationSetImpl
62 extends AbstractSet
63 implements AnnotationSet {
64 /** Debug flag */
65 private static final boolean DEBUG = false;
66
67 /** Construction from Document. */
68 public AnnotationSetImpl(Document doc) {
69 // annotsById = new VerboseHashMap();
70 annotsById = new HashMap();
71 this.doc = (DocumentImpl) doc;
72 } // construction from document
73
74 /** Construction from Document and name. */
75 public AnnotationSetImpl(Document doc, String name) {
76 this(doc);
77 this.name = name;
78 } // construction from document and name
79
80 /** Construction from Collection (which must be an AnnotationSet) */
81 //<<<dam: speedup constructor
82 /*
83 public AnnotationSetImpl(Collection c) throws ClassCastException {
84 this(((AnnotationSet) c).getDocument());
85 addAll(c);
86 } // construction from collection
87 */
88 //===dam: now
89 /** Construction from Collection (which must be an AnnotationSet) */
90 public AnnotationSetImpl(Collection c) throws ClassCastException {
91 this( ( (AnnotationSet) c).getDocument(), ( (AnnotationSet) c).getName());
92 if (c instanceof AnnotationSetImpl) {
93 AnnotationSetImpl theC = (AnnotationSetImpl) c;
94 annotsById = (HashMap) theC.annotsById.clone();
95 if (theC.annotsByEndNode != null) {
96 annotsByEndNode = (Map) ( (HashMap) theC.annotsByEndNode).clone();
97 annotsByStartNode = (Map) ( (HashMap) theC.annotsByStartNode).clone();
98 }
99 if (theC.annotsByType != null)
100 annotsByType = (Map) ( (HashMap) theC.annotsByType).clone();
101 if (theC.nodesByOffset != null) {
102 nodesByOffset = (RBTreeMap) theC.nodesByOffset.clone();
103 }
104 }
105 else
106 addAll(c);
107 } // construction from collection
108
109 //>>>dam: end
110
111 /** This inner class serves as the return value from the iterator()
112 * method.
113 */
114 class AnnotationSetIterator
115 implements Iterator {
116 private Iterator iter;
117 protected Annotation lastNext = null;
118 AnnotationSetIterator() {
119 iter = annotsById.values().iterator();
120 }
121
122 public boolean hasNext() {
123 return iter.hasNext();
124 }
125
126 public Object next() {
127 return (lastNext = (Annotation) iter.next());
128 }
129
130 public void remove() {
131 // this takes care of the ID index
132 iter.remove();
133 // remove from type index
134 removeFromTypeIndex(lastNext);
135 // remove from offset indices
136 removeFromOffsetIndex(lastNext);
137 //that's the second way of removing annotations from a set
138 //apart from calling remove() on the set itself
139 fireAnnotationRemoved(new AnnotationSetEvent(
140 AnnotationSetImpl.this,
141 AnnotationSetEvent.ANNOTATION_REMOVED,
142 getDocument(), (Annotation) lastNext));
143 } // remove()
144 }; // AnnotationSetIterator
145
146 /**
147 * This is a {@link java.util.HashMap}
148 * that fires events when elements are removed.
149 *
150 * This class has been used in a previous version for the indexById structure
151 * which now uses a simple HashMap.
152 *
153 * This class is kept here for backwards compatibility of old serial
154 * datastores.
155 *
156 */
157 public class VerboseHashMap
158 extends HashMap {
159 VerboseHashMap() {
160 super(Gate.HASH_STH_SIZE);
161 } //contructor
162
163 public Object remove(Object key) {
164 Object res = super.remove(key);
165 if (res != null) {
166 if (owner == null) {
167 fireAnnotationRemoved(new AnnotationSetEvent(
168 AnnotationSetImpl.this,
169 AnnotationSetEvent.ANNOTATION_REMOVED,
170 getDocument(), (Annotation) res));
171 }
172 else {
173 owner.fireAnnotationRemoved(new AnnotationSetEvent(
174 AnnotationSetImpl.this,
175 AnnotationSetEvent.ANNOTATION_REMOVED,
176 getDocument(), (Annotation) res));
177 }
178 }
179 return res;
180 } //public Object remove(Object key)
181
182 static final long serialVersionUID = -4832487354063073511L;
183
184 /**
185 * The annotation set this maps is part of.
186 * This is an ugly hack in order to fix a bug: database annotation sets
187 * didn't fire annotation removed events.
188 */
189 private transient AnnotationSetImpl owner;
190
191 /**
192 * Sets the annotation set this maps is part of.
193 * This is an ugly hack in order to fix a bug: database annotation sets
194 * didn't fire annotation removed events.
195 */
196 public void setOwner(AnnotationSetImpl newOwner) {
197 this.owner = newOwner;
198 }
199 } //protected class VerboseHashMap extends HashMap
200
201 /** Get an iterator for this set */
202 public Iterator iterator() {
203 return new AnnotationSetIterator();
204 }
205
206 /** Remove an element from this set. */
207 public boolean remove(Object o) throws ClassCastException {
208 Annotation a = (Annotation) o;
209 boolean wasPresent = removeFromIdIndex(a);
210 if (wasPresent) {
211 removeFromTypeIndex(a);
212 removeFromOffsetIndex(a);
213 }
214 //fire the event
215 fireAnnotationRemoved(new AnnotationSetEvent(
216 AnnotationSetImpl.this,
217 AnnotationSetEvent.ANNOTATION_REMOVED,
218 getDocument(), a));
219
220 return wasPresent;
221 } // remove(o)
222
223
224 /** Remove from the ID index. */
225 protected boolean removeFromIdIndex(Annotation a) {
226 if (annotsById.remove(a.getId()) == null)
227 return false;
228 return true;
229 } // removeFromIdIndex(a)
230
231 /** Remove from the type index. */
232 protected void removeFromTypeIndex(Annotation a) {
233 if (annotsByType != null) {
234 AnnotationSet sameType = (AnnotationSet) annotsByType.get(a.getType());
235 if (sameType != null)
236 sameType.remove(a);
237 if (sameType.isEmpty()) // none left of this type
238 annotsByType.remove(a.getType());
239 }
240 } // removeFromTypeIndex(a)
241
242 /** Remove from the offset indices. */
243 protected void removeFromOffsetIndex(Annotation a) {
244 if (nodesByOffset != null) {
245 // knowing when a node is no longer needed would require keeping a reference
246 // count on annotations, or using a weak reference to the nodes in
247 // nodesByOffset
248 }
249 if (annotsByStartNode != null) {
250 Integer id = a.getStartNode().getId();
251 AnnotationSet starterAnnots = (AnnotationSet) annotsByStartNode.get(id);
252 starterAnnots.remove(a);
253 if (starterAnnots.isEmpty()) // no annotations start here any more
254 annotsByStartNode.remove(id);
255 }
256 if (annotsByEndNode != null) {
257 Integer id = a.getEndNode().getId();
258 AnnotationSet endingAnnots = (AnnotationSet) annotsByEndNode.get(id);
259 endingAnnots.remove(a);
260 if (endingAnnots.isEmpty()) // no annotations start here any more
261 annotsByEndNode.remove(id);
262 }
263 } // removeFromOffsetIndex(a)
264
265 /** The size of this set */
266 public int size() {
267 return annotsById.size();
268 }
269
270 /** Find annotations by id */
271 public Annotation get(Integer id) {
272 return (Annotation) annotsById.get(id);
273 } // get(id)
274
275 /** Get all annotations */
276 public AnnotationSet get() {
277 AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
278 resultSet.addAllKeepIDs(annotsById.values());
279 if (resultSet.isEmpty())
280 return null;
281 return resultSet;
282 } // get()
283
284 /** Select annotations by type */
285 public AnnotationSet get(String type) {
286 if (annotsByType == null)
287 indexByType();
288 // the aliasing that happens when returning a set directly from the
289 // types index can cause concurrent access problems; but the fix below
290 // breaks the tests....
291 //AnnotationSet newSet =
292 // new AnnotationSetImpl((Collection) annotsByType.get(type));
293 //return newSet;
294 return (AnnotationSet) annotsByType.get(type);
295 } // get(type)
296
297 /** Select annotations by a set of types. Expects a Set of String. */
298 public AnnotationSet get(Set types) throws ClassCastException {
299 if (annotsByType == null)
300 indexByType();
301 Iterator iter = types.iterator();
302 AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
303 while (iter.hasNext()) {
304 String type = (String) iter.next();
305 AnnotationSet as = (AnnotationSet) annotsByType.get(type);
306 if (as != null)
307 resultSet.addAllKeepIDs(as);
308 // need an addAllOfOneType method
309 } // while
310 if (resultSet.isEmpty())
311 return null;
312 return resultSet;
313 } // get(types)
314
315 /**
316 * Select annotations by type and features
317 *
318 * This will return an annotation set containing just those annotations of a
319 * particular type (i.e. with a particular name) and which have features with
320 * specific names and values. (It will also return annotations that have features
321 * besides those specified, but it will not return any annotations that do not
322 * have all the specified feature-value pairs.)
323 *
324 * However, if constraints contains a feature whose value is equal to
325 * gate.creole.ANNIEConstants.LOOKUP_CLASS_FEATURE_NAME (which is normally
326 * "class"), then GATE will attempt to match that feature using an ontology
327 * which it will try to retreive from a feature on the both the annotation
328 * and in constraints. If these do not return identical ontologies, or if
329 * either the annotation or constraints does not contain an ontology, then
330 * matching will fail, and the annotation will not be added. In summary,
331 * this method will not work normally for features with the name "class".
332 *
333 * @param type The name of the annotations to return.
334 * @param constraints A feature map containing all of the feature value pairs
335 * that the annotation must have in order for them to be returned.
336 * @return An annotation set containing only those annotations with the given
337 * name and which have the specified set of feature-value pairs.
338 */
339 public AnnotationSet get(String type, FeatureMap constraints) {
340 if (annotsByType == null)
341 indexByType();
342 AnnotationSet typeSet = get(type);
343 if (typeSet == null)
344 return null;
345 AnnotationSet resultSet = new AnnotationSetImpl(doc);
346 Iterator iter = typeSet.iterator();
347 while (iter.hasNext()) {
348 Annotation a = (Annotation) iter.next();
349 // we check for matching constraints by simple equality. a
350 // feature map satisfies the constraints if it contains all the
351 // key/value pairs from the constraints map
352
353 // if (a.getFeatures().entrySet().containsAll(constraints.entrySet()))
354 if( a.getFeatures().subsumes(constraints))
355 resultSet.add(a);
356 } // while
357 if (resultSet.isEmpty())
358 return null;
359 return resultSet;
360 } // get(type, constraints)
361
362 /** Select annotations by type and feature names */
363 public AnnotationSet get(String type, Set featureNames) {
364 if (annotsByType == null)
365 indexByType();
366 AnnotationSet typeSet = null;
367 if (type != null) {
368 //if a type is provided, try finding annotations of this type
369 typeSet = get(type);
370 //if none exist, then return coz nothing left to do
371 if (typeSet == null)
372 return null;
373 }
374 AnnotationSet resultSet = new AnnotationSetImpl(doc);
375 Iterator iter = null;
376 if (type != null)
377 iter = typeSet.iterator();
378 else
379 iter = annotsById.values().iterator();
380 while (iter.hasNext()) {
381 Annotation a = (Annotation) iter.next();
382 // we check for matching constraints by simple equality. a
383 // feature map satisfies the constraints if it contains all the
384 // key/value pairs from the constraints map
385 if (a.getFeatures().keySet().containsAll(featureNames))
386 resultSet.add(a);
387 } // while
388 if (resultSet.isEmpty())
389 return null;
390 return resultSet;
391 } // get(type, featureNames)
392
393 /** Select annotations by offset. This returns the set of annotations
394 * whose start node is the least such that it is less than or equal
395 * to offset. If a positional index doesn't exist it is created.
396 * If there are no nodes at or beyond the offset param then it will return
397 * null.
398 */
399 public AnnotationSet get(Long offset) {
400 if (annotsByStartNode == null)
401 indexByStartOffset();
402 // find the next node at or after offset; get the annots starting there
403 Node nextNode = (Node) nodesByOffset.getNextOf(offset);
404 if (nextNode == null) // no nodes at or beyond this offset
405 return null;
406 AnnotationSet res = (AnnotationSet) annotsByStartNode.get(nextNode.getId());
407 //get ready for next test
408 nextNode = (Node) nodesByOffset.getNextOf(new Long(offset.longValue() + 1));
409 //skip all the nodes that have no starting annotations
410 while (res == null && nextNode != null) {
411 res = (AnnotationSet) annotsByStartNode.get(nextNode.getId());
412 //get ready for next test
413 nextNode = (Node) nodesByOffset.getNextOf(
414 new Long(nextNode.getOffset().longValue() + 1)
415 );
416 }
417 //res it either null (no suitable node found) or the correct result
418 return res;
419 } // get(offset)
420
421 /**
422 * Select annotations by offset. This returns the set of annotations
423 * that overlap totaly or partially with the interval defined by the two
424 * provided offsets.The result will include all the annotations that either:
425 * <ul>
426 * <li>start before the start offset and end strictly after it</li>
427 * <li>OR</li>
428 * <li>start at a position between the start and the end offsets</li>
429 */
430 public AnnotationSet get(Long startOffset, Long endOffset) {
431 //the result will include all the annotations that either:
432 //-start before the start offset and end strictly after it
433 //or
434 //-start at a position between the start and the end offsets
435 if (annotsByStartNode == null)
436 indexByStartOffset();
437 AnnotationSetImpl resultSet = new AnnotationSetImpl(doc);
438 Iterator nodesIter;
439 Iterator annotsIter;
440 Node currentNode;
441 Annotation currentAnnot;
442 //find all the annots that start strictly before the start offset and end
443 //strictly after it
444 nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
445 while (nodesIter.hasNext()) {
446 currentNode = (Node) nodesIter.next();
447 Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
448 if (fromPoint != null) {
449 annotsIter = (fromPoint).iterator();
450 while (annotsIter.hasNext()) {
451 currentAnnot = (Annotation) annotsIter.next();
452 if (currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0) {
453 resultSet.add(currentAnnot);
454 }
455 }
456 }
457 }
458 //find all the annots that start at or after the start offset but strictly
459 //before the end offset
460 nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
461 while (nodesIter.hasNext()) {
462 currentNode = (Node) nodesIter.next();
463 Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
464 if (fromPoint != null)
465 resultSet.addAllKeepIDs(fromPoint);
466 }
467 return resultSet;
468 } //get(startOfset, endOffset)
469
470 /**
471 * Select annotations by offset. This returns the set of annotations
472 * that overlap strictly with the interval defined by the two
473 * provided offsets.The result will include all the annotations that
474 * start at the start offset and end strictly at the end offset
475 */
476 public AnnotationSet getStrict(Long startOffset, Long endOffset) {
477 //the result will include all the annotations that
478 //start at the start offset and end strictly at the end offset
479 if (annotsByStartNode == null)
480 indexByStartOffset();
481 AnnotationSet resultSet = new AnnotationSetImpl(doc);
482 Iterator annotsIter;
483 Node currentNode;
484 Annotation currentAnnot;
485 //find all the annots that start at the start offset
486 currentNode = (Node) nodesByOffset.get(startOffset);
487 if (currentNode != null) {
488 Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
489 if (fromPoint != null) {
490 annotsIter = fromPoint.iterator();
491 while (annotsIter.hasNext()) {
492 currentAnnot = (Annotation) annotsIter.next();
493 if (currentAnnot.getEndNode().getOffset().compareTo(endOffset) == 0) {
494 resultSet.add(currentAnnot);
495 } // if
496 } // while
497 } // if
498 } // if
499 return resultSet;
500 } //getStrict(startOfset, endOffset)
501
502 /**
503 * Select annotations by offset. This returns the set of annotations
504 * of the given type
505 * that overlap totaly or partially with the interval defined by the two
506 * provided offsets.The result will include all the annotations that either:
507 * <ul>
508 * <li>start before the start offset and end strictly after it</li>
509 * <li>OR</li>
510 * <li>start at a position between the start and the end offsets</li>
511 */
512 public AnnotationSet get(String neededType, Long startOffset, Long endOffset) {
513 //the result will include all the annotations that either:
514 //-start before the start offset and end strictly after it
515 //or
516 //-start at a position between the start and the end offsets
517 if (annotsByStartNode == null)
518 indexByStartOffset();
519 AnnotationSet resultSet = new AnnotationSetImpl(doc);
520 Iterator nodesIter;
521 Iterator annotsIter;
522 Node currentNode;
523 Annotation currentAnnot;
524 //find all the annots that start strictly before the start offset and end
525 //strictly after it
526 nodesIter = nodesByOffset.headMap(startOffset).values().iterator();
527 while (nodesIter.hasNext()) {
528 currentNode = (Node) nodesIter.next();
529 Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
530 if (fromPoint != null) {
531 annotsIter = (fromPoint).iterator();
532 while (annotsIter.hasNext()) {
533 currentAnnot = (Annotation) annotsIter.next();
534 if (currentAnnot.getType().equals(neededType) &&
535 currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0
536 ) {
537 resultSet.add(currentAnnot);
538 } //if
539 } //while
540 }
541 }
542 //find all the annots that start at or after the start offset but strictly
543 //before the end offset
544 nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
545 while (nodesIter.hasNext()) {
546 currentNode = (Node) nodesIter.next();
547 Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
548 if (fromPoint != null) {
549 annotsIter = (fromPoint).iterator();
550 while (annotsIter.hasNext()) {
551 currentAnnot = (Annotation) annotsIter.next();
552 if (currentAnnot.getType().equals(neededType)) {
553 resultSet.add(currentAnnot);
554 } //if
555 } //while
556 } //if
557 }
558 return resultSet;
559 } //get(type, startOfset, endOffset)
560
561 /** Select annotations by type, features and offset */
562 public AnnotationSet get(String type, FeatureMap constraints, Long offset) {
563 // select by offset
564 AnnotationSet nextAnnots = (AnnotationSet) get(offset);
565 if (nextAnnots == null)
566 return null;
567 // select by type and constraints from the next annots
568 return nextAnnots.get(type, constraints);
569 } // get(type, constraints, offset)
570
571 /**
572 * Select annotations by offset that
573 * start at a position between the start and end before the end offset
574 */
575 public AnnotationSet getContained(Long startOffset, Long endOffset) {
576 //the result will include all the annotations that either:
577 //start at a position between the start and end before the end offsets
578 if (annotsByStartNode == null)
579 indexByStartOffset();
580 AnnotationSet resultSet = new AnnotationSetImpl(doc);
581 Iterator nodesIter;
582 Node currentNode;
583 //find all the annots that start at or after the start offset but strictly
584 //before the end offset
585 nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator();
586 while (nodesIter.hasNext()) {
587 currentNode = (Node) nodesIter.next();
588 Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId());
589 if (fromPoint == null)
590 continue;
591 //loop through the annotations and find only those that
592 //also end before endOffset
593 Iterator annotIter = fromPoint.iterator();
594 while (annotIter.hasNext()) {
595 Annotation annot = (Annotation) annotIter.next();
596 if (annot.getEndNode().getOffset().compareTo(endOffset) <= 0)
597 resultSet.add(annot);
598 }
599 }
600 return resultSet;
601 } //get(startOfset, endOffset)
602
603 /** Get the node with the smallest offset */
604 public Node firstNode() {
605 indexByStartOffset();
606 if (nodesByOffset.isEmpty())
607 return null;
608 else
609 return (Node) nodesByOffset.get(nodesByOffset.firstKey());
610 } // firstNode
611
612 /** Get the node with the largest offset */
613 public Node lastNode() {
614 indexByStartOffset();
615 indexByEndOffset();
616 if (nodesByOffset.isEmpty())
617 return null;
618 else
619 return (Node) nodesByOffset.get(nodesByOffset.lastKey());
620 } // lastNode
621
622 /**
623 * Get the first node that is relevant for this annotation set and which has
624 * the offset larger than the one of the node provided.
625 */
626 public Node nextNode(Node node) {
627 indexByStartOffset();
628 indexByEndOffset();
629 return (Node) nodesByOffset.getNextOf(
630 new Long(node.getOffset().longValue() + 1)
631 );
632 }
633
634 protected static AnnotationFactory annFactory;
635
636 /**
637 * Set the annotation factory used to create annotation objects. The default
638 * factory is {@link DefaultAnnotationFactory}.
639 */
640 public static void setAnnotationFactory (AnnotationFactory newFactory) {
641 annFactory = newFactory;
642 }
643
644 static {
645 // set the default factory to always create AnnotationImpl objects
646 setAnnotationFactory(new DefaultAnnotationFactory());
647 }
648
649 /** Create and add an annotation with pre-existing nodes,
650 * and return its id
651 */
652 public Integer add(Node start, Node end, String type, FeatureMap features) {
653 // the id of the new annotation
654 Integer id = doc.getNextAnnotationId();
655 // construct an annotation
656 annFactory.createAnnotationInSet(this, id, start, end, type, features);
657 return id;
658 } // add(Node, Node, String, FeatureMap)
659
660 /** Add an existing annotation. Returns true when the set is modified. */
661 public boolean add(Object o) throws ClassCastException {
662 Annotation a = (Annotation) o;
663 Object oldValue = annotsById.put(a.getId(), a);
664 if (annotsByType != null)
665 addToTypeIndex(a);
666 if (annotsByStartNode != null || annotsByEndNode != null)
667 addToOffsetIndex(a);
668 AnnotationSetEvent evt = new AnnotationSetEvent(
669 this,
670 AnnotationSetEvent.ANNOTATION_ADDED,
671 doc, a);
672 fireAnnotationAdded(evt);
673 fireGateEvent(evt);
674 return oldValue != a;
675 } // add(o)
676
677 /**
678 * Adds multiple annotations to this set in one go.
679 * All the objects in the provided collection should be of
680 * {@link gate.Annotation} type, otherwise a ClassCastException will be
681 * thrown.
682 * The provided annotations will be used to create new annotations using the
683 * appropriate add() methods from this set. The new annotations will have
684 * different IDs from the old ones (which is required in order to preserve the
685 * uniqueness of IDs inside an annotation set).
686 * @param c a collection of annotations
687 * @return <tt>true</tt> if the set has been modified as a result of this
688 * call.
689 */
690 public boolean addAll(Collection c) {
691 Iterator annIter = c.iterator();
692 boolean changed = false;
693 while (annIter.hasNext()) {
694 Annotation a = (Annotation) annIter.next();
695 try {
696 add(a.getStartNode().getOffset(),
697 a.getEndNode().getOffset(),
698 a.getType(),
699 a.getFeatures());
700 changed = true;
701 }
702 catch (InvalidOffsetException ioe) {
703 throw new IllegalArgumentException(ioe.toString());
704 }
705 }
706 return changed;
707 }
708
709 /**
710 * Adds multiple annotations to this set in one go.
711 * All the objects in the provided collection should be of
712 * {@link gate.Annotation} type, otherwise a ClassCastException will be
713 * thrown.
714 * This method does not create copies of the annotations like addAll() does
715 * but simply adds the new annotations to the set.
716 * It is intended to be used solely by annotation sets in order to construct
717 * the results for various get(...) methods.
718 * @param c a collection of annotations
719 * @return <tt>true</tt> if the set has been modified as a result of this
720 * call.
721 */
722 protected boolean addAllKeepIDs(Collection c) {
723 Iterator annIter = c.iterator();
724 boolean changed = false;
725 while (annIter.hasNext()) {
726 Annotation a = (Annotation) annIter.next();
727 changed |= add(a);
728 }
729 return changed;
730 }
731
732 /** Create and add an annotation and return its id */
733 public Integer add(
734 Long start, Long end, String type, FeatureMap features
735 ) throws InvalidOffsetException {
736 // are the offsets valid?
737 if (!doc.isValidOffsetRange(start, end))
738 throw new InvalidOffsetException();
739 // the set has to be indexed by position in order to add, as we need
740 // to find out if nodes need creating or if they exist already
741 if (nodesByOffset == null) {
742 indexByStartOffset();
743 indexByEndOffset();
744 }
745 // find existing nodes if appropriate nodes don't already exist, create them
746 Node startNode = (Node) nodesByOffset.getNextOf(start);
747 if (startNode == null || !startNode.getOffset().equals(start))
748 startNode = new NodeImpl(doc.getNextNodeId(), start);
749 Node endNode = null;
750 if (start.equals(end))
751 endNode = startNode;
752 else
753 endNode = (Node) nodesByOffset.getNextOf(end);
754 if (endNode == null || !endNode.getOffset().equals(end))
755 endNode = new NodeImpl(doc.getNextNodeId(), end);
756 // delegate to the method that adds annotations with existing nodes
757 return add(startNode, endNode, type, features);
758 } // add(start, end, type, features)
759
760 /** Create and add an annotation from database read data
761 * In this case the id is already known being previously fetched from the
762 * database
763 */
764 public void add(
765 Integer id, Long start, Long end, String type, FeatureMap features
766 ) throws InvalidOffsetException {
767 // are the offsets valid?
768 if (!doc.isValidOffsetRange(start, end))
769 throw new InvalidOffsetException();
770 // the set has to be indexed by position in order to add, as we need
771 // to find out if nodes need creating or if they exist already
772 if (nodesByOffset == null) {
773 indexByStartOffset();
774 indexByEndOffset();
775 }
776 // find existing nodes if appropriate nodes don't already exist, create them
777 Node startNode = (Node) nodesByOffset.getNextOf(start);
778 if (startNode == null || !startNode.getOffset().equals(start))
779 startNode = new NodeImpl(doc.getNextNodeId(), start);
780 Node endNode = null;
781 if (start.equals(end))
782 endNode = startNode;
783 else
784 endNode = (Node) nodesByOffset.getNextOf(end);
785 if (endNode == null || !endNode.getOffset().equals(end))
786 endNode = new NodeImpl(doc.getNextNodeId(), end);
787 // construct an annotation
788 annFactory.createAnnotationInSet(this, id, startNode, endNode, type, features);
789 } // add(id, start, end, type, features)
790
791 /** Construct the positional index. */
792 protected void indexByType() {
793 if (annotsByType != null)
794 return;
795 annotsByType = new HashMap(Gate.HASH_STH_SIZE);
796 Iterator annotIter = annotsById.values().iterator();
797 while (annotIter.hasNext())
798 addToTypeIndex( (Annotation) annotIter.next());
799 } // indexByType()
800
801 /** Construct the positional indices for annotation start */
802 protected void indexByStartOffset() {
803 if (annotsByStartNode != null) return;
804 if (nodesByOffset == null) nodesByOffset = new RBTreeMap();
805 annotsByStartNode = new HashMap(Gate.HASH_STH_SIZE);
806 Iterator annotIter = annotsById.values().iterator();
807 while (annotIter.hasNext())
808 addToStartOffsetIndex( (Annotation) annotIter.next());
809 } // indexByStartOffset()
810
811 /** Construct the positional indices for annotation end */
812 protected void indexByEndOffset() {
813 if (annotsByEndNode != null)
814 return;
815 if (nodesByOffset == null)
816 nodesByOffset = new RBTreeMap();
817 annotsByEndNode = new HashMap(Gate.HASH_STH_SIZE);
818 Iterator annotIter = annotsById.values().iterator();
819 while (annotIter.hasNext())
820 addToEndOffsetIndex( (Annotation) annotIter.next());
821 } // indexByEndOffset()
822
823 /** Add an annotation to the type index. Does nothing if the index
824 * doesn't exist.
825 */
826 void addToTypeIndex(Annotation a) {
827 if (annotsByType == null)
828 return;
829 String type = a.getType();
830 AnnotationSet sameType = (AnnotationSet) annotsByType.get(type);
831 if (sameType == null) {
832 sameType = new AnnotationSetImpl(doc);
833 annotsByType.put(type, sameType);
834 }
835 sameType.add(a);
836 } // addToTypeIndex(a)
837
838 /** Add an annotation to the offset indices. Does nothing if they
839 * don't exist.
840 */
841 void addToOffsetIndex(Annotation a) {
842 addToStartOffsetIndex(a);
843 addToEndOffsetIndex(a);
844 } // addToOffsetIndex(a)
845
846 /** Add an annotation to the start offset index. Does nothing if the
847 * index doesn't exist.
848 */
849 void addToStartOffsetIndex(Annotation a) {
850 Node startNode = a.getStartNode();
851 Long start = startNode.getOffset();
852 // add a's nodes to the offset index
853 if (nodesByOffset != null)
854 nodesByOffset.put(start, startNode);
855 // if there's no appropriate index give up
856 if (annotsByStartNode == null)
857 return;
858 // get the annotations that start at the same node, or create new set
859 AnnotationSet thisNodeAnnots =
860 (AnnotationSet) annotsByStartNode.get(startNode.getId());
861 if (thisNodeAnnots == null) {
862 thisNodeAnnots = new AnnotationSetImpl(doc);
863 annotsByStartNode.put(startNode.getId(), thisNodeAnnots);
864 }
865 // add to the annots listed for a's start node
866 thisNodeAnnots.add(a);
867 } // addToStartOffsetIndex(a)
868
869 /** Add an annotation to the end offset index. Does nothing if the
870 * index doesn't exist.
871 */
872 void addToEndOffsetIndex(Annotation a) {
873 Node endNode = a.getEndNode();
874 Long end = endNode.getOffset();
875 // add a's nodes to the offset index
876 if (nodesByOffset != null)
877 nodesByOffset.put(end, endNode);
878 // if there's no appropriate index give up
879 if (annotsByEndNode == null)
880 return;
881 // get the annotations that start at the same node, or create new set
882 AnnotationSet thisNodeAnnots =
883 (AnnotationSet) annotsByEndNode.get(endNode.getId());
884 if (thisNodeAnnots == null) {
885 thisNodeAnnots = new AnnotationSetImpl(doc);
886 annotsByEndNode.put(endNode.getId(), thisNodeAnnots);
887 }
888 // add to the annots listed for a's start node
889 thisNodeAnnots.add(a);
890 } // addToEndOffsetIndex(a)
891
892 /** Propagate changes to the document content. Has, unfortunately,
893 * to be public, to allow DocumentImpls to get at it. Oh for a
894 * "friend" declaration. Doesn't throw InvalidOffsetException as
895 * DocumentImpl is the only client, and that checks the offsets
896 * before calling this method.
897 */
898 public void edit(Long start, Long end, DocumentContent replacement) {
899 //make sure we have the indices computed
900 indexByStartOffset();
901 indexByEndOffset();
902 if(end.compareTo(start) > 0){
903 //get the nodes that need to be processed (the nodes internal to the
904 //removed section plus the marginal ones
905 List affectedNodes = new ArrayList(nodesByOffset.subMap(start,
906 new Long(end.longValue() + 1)).values());
907 //if we have more than 1 node we need to delete all apart from the first
908 //and move the annotations so that they refer to the one we keep (the first)
909 NodeImpl firstNode = null;
910 if (!affectedNodes.isEmpty()) {
911 firstNode = (NodeImpl) affectedNodes.get(0);
912 List startingAnnotations = new ArrayList();
913 List endingAnnotations = new ArrayList();
914 for (int i = 1; i < affectedNodes.size(); i++) {
915 Node aNode = (Node) affectedNodes.get(i);
916 //save the annotations
917 AnnotationSet annSet = (AnnotationSet) annotsByStartNode.get(aNode.
918 getId());
919 if (annSet != null)
920 startingAnnotations.addAll(annSet);
921 annSet = (AnnotationSet) annotsByEndNode.get(aNode.getId());
922 if (annSet != null)
923 endingAnnotations.addAll(annSet);
924 //remove the node
925 nodesByOffset.remove(aNode.getOffset());
926 annotsByStartNode.remove(aNode);
927 annotsByEndNode.remove(aNode);
928 }
929 //modify the annotations so they point to the saved node
930 Iterator annIter = startingAnnotations.iterator();
931 while (annIter.hasNext()) {
932 AnnotationImpl anAnnot = (AnnotationImpl) annIter.next();
933 anAnnot.start = firstNode;
934 addToStartOffsetIndex(anAnnot);
935 }
936 annIter = endingAnnotations.iterator();
937 while (annIter.hasNext()) {
938 AnnotationImpl anAnnot = (AnnotationImpl) annIter.next();
939 anAnnot.end = firstNode;
940 addToEndOffsetIndex(anAnnot);
941 }
942 //repair the first node
943 //remove from offset index
944 nodesByOffset.remove(firstNode.getOffset());
945 //change the offset for the saved node
946 firstNode.setOffset(start);
947 //add back to the offset index
948 nodesByOffset.put(firstNode.getOffset(), firstNode);
949 }
950 }
951
952 //now handle the insert and/or update the rest of the nodes' position
953 //get the user selected behaviour (defaults to append)
954 boolean shouldPrepend = Gate.getUserConfig().
955 getBoolean(GateConstants.DOCEDIT_INSERT_PREPEND).booleanValue();
956
957 long s = start.longValue(), e = end.longValue();
958 long rlen = // length of the replacement value
959 ( (replacement == null) ? 0 : replacement.size().longValue());
960
961 // update the offsets and the index by offset for the rest of the nodes
962 List nodesAfterReplacement = new ArrayList(
963 nodesByOffset.tailMap(start).values());
964
965 //remove from the index by offset
966 Iterator nodesAfterReplacementIter = nodesAfterReplacement.iterator();
967 while (nodesAfterReplacementIter.hasNext()) {
968 NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
969 nodesByOffset.remove(n.getOffset());
970 }
971 //change the offsets
972 nodesAfterReplacementIter = nodesAfterReplacement.iterator();
973 while (nodesAfterReplacementIter.hasNext()) {
974 NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
975 long oldOffset = n.getOffset().longValue();
976 //by default we move all nodes back
977 long newOffset = oldOffset - (e - s) + rlen;
978 //for the first node we need behave differently
979 if (oldOffset == s){
980 //the first offset never moves back
981 if(newOffset < s) newOffset = s;
982 //if we're prepending we don't move forward
983 if(shouldPrepend) newOffset = s;
984 }
985 n.setOffset(new Long(newOffset));
986 }
987 //add back to the index by offset with the new offsets
988 nodesAfterReplacementIter = nodesAfterReplacement.iterator();
989 while (nodesAfterReplacementIter.hasNext()) {
990 NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next();
991 nodesByOffset.put(n.getOffset(), n);
992 }
993
994 // //rebuild the indices with the new offsets
995 // nodesByOffset = null;
996 // annotsByStartNode = null;
997 // annotsByEndNode = null;
998 // indexByStartOffset();
999 // indexByEndOffset();
1000 } // edit(start,end,replacement)
1001
1002 /** Get the name of this set. */
1003 public String getName() {
1004 return name;
1005 }
1006
1007 /** Get the document this set is attached to. */
1008 public Document getDocument() {
1009 return doc;
1010 }
1011
1012 /** Get a set of java.lang.String objects representing all the annotation
1013 * types present in this annotation set.
1014 */
1015 public Set getAllTypes() {
1016 indexByType();
1017 return annotsByType.keySet();
1018 }
1019
1020 /**
1021 *
1022 * @return a clone of this set.
1023 * @throws CloneNotSupportedException
1024 */
1025 public Object clone() throws CloneNotSupportedException {
1026 return super.clone();
1027 }
1028
1029 /**
1030 *
1031 * @param l
1032 */
1033 public synchronized void removeAnnotationSetListener(AnnotationSetListener l) {
1034 if (annotationSetListeners != null && annotationSetListeners.contains(l)) {
1035 Vector v = (Vector) annotationSetListeners.clone();
1036 v.removeElement(l);
1037 annotationSetListeners = v;
1038 }
1039 }
1040
1041 /**
1042 *
1043 * @param l
1044 */
1045 public synchronized void addAnnotationSetListener(AnnotationSetListener l) {
1046 Vector v = annotationSetListeners == null ? new Vector(2) :
1047 (Vector) annotationSetListeners.clone();
1048 if (!v.contains(l)) {
1049 v.addElement(l);
1050 annotationSetListeners = v;
1051 }
1052 }
1053
1054 /** String representation of the set */
1055 /*public String toString() {
1056 // annotsById
1057 SortedSet sortedAnnots = new TreeSet();
1058 sortedAnnots.addAll(annotsById.values());
1059 String aBI = sortedAnnots.toString();
1060 // annotsByType
1061 StringBuffer buf = new StringBuffer();
1062 Iterator iter = annotsByType.iterator();
1063 while(iter.hasNext()) {
1064 HashMap thisType = iter.next().entrySet();
1065 sortedAnnots.clear();
1066 sortedAnnots.addAll(thisType.);
1067 buf.append("[type: " +
1068 }
1069 return
1070 "AnnotationSetImpl: " +
1071 "name=" + name +
1072 // "; doc.getURL()=" + doc +
1073 "; annotsById=" + aBI +
1074 // "; annotsByType=" + aBT +
1075 "; "
1076 ;
1077 } // toString()
1078 */
1079// public int hashCode() {
1080// int hash = 0;
1081// Iterator i = this.iterator();
1082// while (i.hasNext()) {
1083// Annotation annot = (Annotation)i.next();
1084// if ( annot != null)
1085// hash += annot.hashCode();
1086// }
1087// int nameHash = (name == null ? 0 : name.hashCode());
1088// //int docHash = (doc == null ? 0 : doc.hashCode());
1089//
1090// return hash ^ nameHash;// ^ docHash;
1091// }
1092 /** The name of this set */
1093 String name = null;
1094 /** The document this set belongs to */
1095 DocumentImpl doc;
1096 /** Maps annotation ids (Integers) to Annotations */
1097 protected HashMap annotsById;
1098 /** Maps annotation types (Strings) to AnnotationSets */
1099 Map annotsByType = null;
1100 /** Maps offsets (Longs) to nodes */
1101 RBTreeMap nodesByOffset = null;
1102 /** Maps node ids (Integers) to AnnotationSets representing those
1103 * annotations that start from that node
1104 */
1105 Map annotsByStartNode;
1106 /** Maps node ids (Integers) to AnnotationSets representing those
1107 * annotations that end at that node
1108 */
1109 Map annotsByEndNode;
1110 protected transient Vector annotationSetListeners;
1111 private transient Vector gateListeners;
1112 /**
1113 *
1114 * @param e
1115 */
1116 protected void fireAnnotationAdded(AnnotationSetEvent e) {
1117 if (annotationSetListeners != null) {
1118 Vector listeners = annotationSetListeners;
1119 int count = listeners.size();
1120 for (int i = 0; i < count; i++) {
1121 ( (AnnotationSetListener) listeners.elementAt(i)).annotationAdded(e);
1122 }
1123 }
1124 }
1125
1126 /**
1127 *
1128 * @param e
1129 */
1130 protected void fireAnnotationRemoved(AnnotationSetEvent e) {
1131 if (annotationSetListeners != null) {
1132 Vector listeners = annotationSetListeners;
1133 int count = listeners.size();
1134 for (int i = 0; i < count; i++) {
1135 ( (AnnotationSetListener) listeners.elementAt(i)).annotationRemoved(e);
1136 }
1137 }
1138 }
1139
1140 /**
1141 *
1142 * @param l
1143 */
1144 public synchronized void removeGateListener(GateListener l) {
1145 if (gateListeners != null && gateListeners.contains(l)) {
1146 Vector v = (Vector) gateListeners.clone();
1147 v.removeElement(l);
1148 gateListeners = v;
1149 }
1150 }
1151
1152 /**
1153 *
1154 * @param l
1155 */
1156 public synchronized void addGateListener(GateListener l) {
1157 Vector v = gateListeners == null ? new Vector(2) :
1158 (Vector) gateListeners.clone();
1159 if (!v.contains(l)) {
1160 v.addElement(l);
1161 gateListeners = v;
1162 }
1163 }
1164
1165 /**
1166 *
1167 * @param e
1168 */
1169 protected void fireGateEvent(GateEvent e) {
1170 if (gateListeners != null) {
1171 Vector listeners = gateListeners;
1172 int count = listeners.size();
1173 for (int i = 0; i < count; i++) {
1174 ( (GateListener) listeners.elementAt(i)).processGateEvent(e);
1175 }
1176 }
1177 }
1178
1179 /** Freeze the serialization UID. */
1180 static final long serialVersionUID = 1479426765310434166L;
1181} // AnnotationSetImpl
1182