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