|
AnnotationSetImpl |
|
1 /* 2 3 * AnnotationSetImpl.java 4 5 * 6 7 * Copyright (c) 1998-2001, The University of Sheffield. 8 9 * 10 11 * This file is part of GATE (see http://gate.ac.uk/), and is free 12 13 * software, licenced under the GNU Library General Public License, 14 15 * Version 2, June 1991 (in the distribution as file licence.html, 16 17 * and also available at http://gate.ac.uk/gate/licence.html). 18 19 * 20 21 * Hamish Cunningham, 7/Feb/2000 22 23 * 24 25 * Developer notes: 26 27 * --- 28 29 * 30 31 * the addToIndex... and indexBy... methods could be refactored as I'm 32 33 * sure they can be made simpler 34 35 * 36 37 * every set to which annotation will be added has to have positional 38 39 * indexing, so that we can find or create the nodes on the new annotations 40 41 * 42 43 * note that annotations added anywhere other than sets that are 44 45 * stored on the document will not get stored anywhere... 46 47 * 48 49 * nodes aren't doing anything useful now. needs some interface that allows 50 51 * their creation, defaulting to no coterminous duplicates, but allowing such 52 53 * if required 54 55 * 56 57 * $Id: AnnotationSetImpl.java,v 1.78 2003/06/25 16:04:04 marin Exp $ 58 59 */ 60 61 62 63 package gate.annotation; 64 65 66 67 import java.util.*; 68 69 import gate.util.*; 70 71 72 73 import gate.*; 74 75 import gate.corpora.*; 76 77 import gate.event.*; 78 79 80 81 82 83 /** Implementation of AnnotationSet. Has a number of indices, all bar one 84 85 * of which are null by default and are only constructed when asked 86 87 * for. Has lots of get methods with various selection criteria; these 88 89 * return views into the set, which are nonetheless valid sets in 90 91 * their own right (but will not necesarily be fully indexed). 92 93 * Has a name, which is null by default; clients of Document can 94 95 * request named AnnotationSets if they so desire. Has a reference to the 96 97 * Document it is attached to. Contrary to Collections convention, 98 99 * there is no no-arg constructor, as this would leave the set in 100 101 * an inconsistent state. 102 103 * <P> 104 105 * There are five indices: annotation by id, annotations by type, annotations 106 107 * by start/end node and nodes by offset. The last three jointly provide 108 109 * positional indexing; construction of these is triggered by 110 111 * indexByStart/EndOffset(), 112 113 * or by calling a get method that selects on offset. The type 114 115 * index is triggered by indexByType(), or calling a get method that selects 116 117 * on type. The id index is always present. 118 119 */ 120 121 public class AnnotationSetImpl 122 123 extends AbstractSet 124 125 implements AnnotationSet 126 127 { 128 129 /** Debug flag */ 130 131 private static final boolean DEBUG = false; 132 133 134 135 /** Construction from Document. */ 136 137 public AnnotationSetImpl(Document doc) { 138 139 annotsById = new VerboseHashMap(); 140 141 this.doc = (DocumentImpl) doc; 142 143 } // construction from document 144 145 146 147 /** Construction from Document and name. */ 148 149 public AnnotationSetImpl(Document doc, String name) { 150 151 this(doc); 152 153 this.name = name; 154 155 } // construction from document and name 156 157 158 159 /** Construction from Collection (which must be an AnnotationSet) */ 160 161 //<<<dam: speedup constructor 162 163 /* 164 165 public AnnotationSetImpl(Collection c) throws ClassCastException { 166 167 this(((AnnotationSet) c).getDocument()); 168 169 addAll(c); 170 171 } // construction from collection 172 173 */ 174 175 //===dam: now 176 177 /** Construction from Collection (which must be an AnnotationSet) */ 178 179 public AnnotationSetImpl(Collection c) throws ClassCastException { 180 181 this(((AnnotationSet) c).getDocument()); 182 183 184 185 if (c instanceof AnnotationSetImpl) 186 187 { 188 189 AnnotationSetImpl theC = (AnnotationSetImpl)c; 190 191 annotsById = (HashMap)theC.annotsById.clone(); 192 193 if(theC.annotsByEndNode != null) 194 195 { 196 197 annotsByEndNode = (Map)((HashMap)theC.annotsByEndNode).clone(); 198 199 annotsByStartNode = (Map)((HashMap)theC.annotsByStartNode).clone(); 200 201 } 202 203 if (theC.annotsByType != null) 204 205 annotsByType = (Map)((HashMap)theC.annotsByType).clone(); 206 207 if (theC.nodesByOffset != null) 208 209 { 210 211 nodesByOffset = (RBTreeMap)theC.nodesByOffset.clone(); 212 213 } 214 215 216 217 } else 218 219 addAll(c); 220 221 } // construction from collection 222 223 //>>>dam: end 224 225 226 227 /** This inner class serves as the return value from the iterator() 228 229 * method. 230 231 */ 232 233 class AnnotationSetIterator implements Iterator { 234 235 236 237 private Iterator iter; 238 239 240 241 protected Annotation lastNext = null; 242 243 244 245 AnnotationSetIterator() { iter = annotsById.values().iterator(); } 246 247 248 249 public boolean hasNext() { return iter.hasNext(); } 250 251 252 253 public Object next() { return (lastNext = (Annotation) iter.next());} 254 255 256 257 public void remove() { 258 259 // this takes care of the ID index 260 261 iter.remove(); 262 263 //that's the second way of removing annotations from a set 264 265 //apart from calling remove() on the set itself 266 267 fireAnnotationRemoved(new AnnotationSetEvent( 268 269 AnnotationSetImpl.this, 270 271 AnnotationSetEvent.ANNOTATION_REMOVED, 272 273 getDocument(), (Annotation)lastNext)); 274 275 276 277 // remove from type index 278 279 removeFromTypeIndex(lastNext); 280 281 282 283 // remove from offset indices 284 285 removeFromOffsetIndex(lastNext); 286 287 288 289 } // remove() 290 291 292 293 }; // AnnotationSetIterator 294 295 296 297 /** 298 299 * Class used for the indexById structure. This is a {@link java.util.HashMap} 300 301 * that fires events when elements are removed. 302 303 */ 304 305 public class VerboseHashMap extends HashMap{ 306 307 308 309 VerboseHashMap() { 310 311 super(Gate.HASH_STH_SIZE); 312 313 } //contructor 314 315 316 317 public Object remove(Object key){ 318 319 Object res = super.remove(key); 320 321 if(res != null) { 322 323 if(owner == null){ 324 325 fireAnnotationRemoved(new AnnotationSetEvent( 326 327 AnnotationSetImpl.this, 328 329 AnnotationSetEvent.ANNOTATION_REMOVED, 330 331 getDocument(), (Annotation)res)); 332 333 }else{ 334 335 owner.fireAnnotationRemoved(new AnnotationSetEvent( 336 337 AnnotationSetImpl.this, 338 339 AnnotationSetEvent.ANNOTATION_REMOVED, 340 341 getDocument(), (Annotation) res)); 342 343 } 344 345 } 346 347 return res; 348 349 }//public Object remove(Object key) 350 351 static final long serialVersionUID = -4832487354063073511L; 352 353 354 355 /** 356 357 * The annotation set this maps is part of. 358 359 * This is an ugly hack in order to fix a bug: database annotation sets 360 361 * didn't fire annotation removed events. 362 363 */ 364 365 private transient AnnotationSetImpl owner; 366 367 /** 368 369 * Sets the annotation set this maps is part of. 370 371 * This is an ugly hack in order to fix a bug: database annotation sets 372 373 * didn't fire annotation removed events. 374 375 */ 376 377 public void setOwner(AnnotationSetImpl newOwner){ 378 379 this.owner = newOwner; 380 381 } 382 383 384 385 }//protected class VerboseHashMap extends HashMap 386 387 388 389 /** Get an iterator for this set */ 390 391 public Iterator iterator() { return new AnnotationSetIterator(); } 392 393 394 395 /** Remove an element from this set. */ 396 397 public boolean remove(Object o) throws ClassCastException { 398 399 400 401 Annotation a = (Annotation) o; 402 403 404 405 boolean wasPresent = removeFromIdIndex(a); 406 407 if(wasPresent){ 408 409 removeFromTypeIndex(a); 410 411 removeFromOffsetIndex(a); 412 413 } 414 415 return wasPresent; 416 417 } // remove(o) 418 419 420 421 /** Remove from the ID index. */ 422 423 protected boolean removeFromIdIndex(Annotation a) { 424 425 if(annotsById.remove(a.getId()) == null) 426 427 return false; 428 429 430 431 return true; 432 433 } // removeFromIdIndex(a) 434 435 436 437 /** Remove from the type index. */ 438 439 protected void removeFromTypeIndex(Annotation a) { 440 441 if(annotsByType != null) { 442 443 444 445 AnnotationSet sameType = (AnnotationSet) annotsByType.get(a.getType()); 446 447 448 449 if(sameType != null) sameType.remove(a); 450 451 452 453 if(sameType.isEmpty()) // none left of this type 454 455 annotsByType.remove(a.getType()); 456 457 } 458 459 } // removeFromTypeIndex(a) 460 461 462 463 /** Remove from the offset indices. */ 464 465 protected void removeFromOffsetIndex(Annotation a) { 466 467 if(nodesByOffset != null) { 468 469 // knowing when a node is no longer needed would require keeping a reference 470 471 // count on annotations, or using a weak reference to the nodes in 472 473 // nodesByOffset 474 475 } 476 477 478 479 if(annotsByStartNode != null) { 480 481 Integer id = a.getStartNode().getId(); 482 483 AnnotationSet starterAnnots = (AnnotationSet) annotsByStartNode.get(id); 484 485 starterAnnots.remove(a); 486 487 if(starterAnnots.isEmpty()) // no annotations start here any more 488 489 annotsByStartNode.remove(id); 490 491 } 492 493 494 495 if(annotsByEndNode != null) { 496 497 Integer id = a.getEndNode().getId(); 498 499 AnnotationSet endingAnnots = (AnnotationSet) annotsByEndNode.get(id); 500 501 endingAnnots.remove(a); 502 503 if(endingAnnots.isEmpty()) // no annotations start here any more 504 505 annotsByEndNode.remove(id); 506 507 } 508 509 510 511 } // removeFromOffsetIndex(a) 512 513 514 515 /** The size of this set */ 516 517 public int size() { return annotsById.size(); } 518 519 520 521 /** Find annotations by id */ 522 523 public Annotation get(Integer id) { 524 525 return (Annotation) annotsById.get(id); 526 527 } // get(id) 528 529 530 531 /** Get all annotations */ 532 533 public AnnotationSet get() { 534 535 AnnotationSetImpl resultSet = new AnnotationSetImpl(doc); 536 537 Iterator iter = annotsById.values().iterator(); 538 539 resultSet.addAllKeepIDs(annotsById.values()); 540 541 if(resultSet.isEmpty()) 542 543 return null; 544 545 return resultSet; 546 547 } // get() 548 549 550 551 /** Select annotations by type */ 552 553 public AnnotationSet get(String type) { 554 555 if(annotsByType == null) indexByType(); 556 557 558 559 // the aliasing that happens when returning a set directly from the 560 561 // types index can cause concurrent access problems; but the fix below 562 563 // breaks the tests.... 564 565 //AnnotationSet newSet = 566 567 // new AnnotationSetImpl((Collection) annotsByType.get(type)); 568 569 //return newSet; 570 571 572 573 return (AnnotationSet) annotsByType.get(type); 574 575 } // get(type) 576 577 578 579 /** Select annotations by a set of types. Expects a Set of String. */ 580 581 public AnnotationSet get(Set types) throws ClassCastException { 582 583 584 585 if(annotsByType == null) indexByType(); 586 587 588 589 Iterator iter = types.iterator(); 590 591 AnnotationSetImpl resultSet = new AnnotationSetImpl(doc); 592 593 594 595 while(iter.hasNext()) { 596 597 String type = (String) iter.next(); 598 599 AnnotationSet as = (AnnotationSet) annotsByType.get(type); 600 601 if(as != null) 602 603 resultSet.addAllKeepIDs(as); 604 605 // need an addAllOfOneType method 606 607 } // while 608 609 610 611 if(resultSet.isEmpty()) 612 613 return null; 614 615 return resultSet; 616 617 } // get(types) 618 619 620 621 /** Select annotations by type and features */ 622 623 public AnnotationSet get(String type, FeatureMap constraints) { 624 625 if(annotsByType == null) indexByType(); 626 627 628 629 AnnotationSet typeSet = get(type); 630 631 if(typeSet == null) 632 633 return null; 634 635 AnnotationSet resultSet = new AnnotationSetImpl(doc); 636 637 638 639 Iterator iter = typeSet.iterator(); 640 641 while(iter.hasNext()) { 642 643 Annotation a = (Annotation) iter.next(); 644 645 646 647 // we check for matching constraints by simple equality. a 648 649 // feature map satisfies the constraints if it contains all the 650 651 // key/value pairs from the constraints map 652 653 if( a.getFeatures().entrySet().containsAll( constraints.entrySet() ) ) 654 655 resultSet.add(a); 656 657 } // while 658 659 660 661 if(resultSet.isEmpty()) 662 663 return null; 664 665 return resultSet; 666 667 } // get(type, constraints) 668 669 670 671 /** Select annotations by type and feature names */ 672 673 public AnnotationSet get(String type, Set featureNames) { 674 675 if(annotsByType == null) indexByType(); 676 677 678 679 AnnotationSet typeSet= null; 680 681 if (type != null) { 682 683 //if a type is provided, try finding annotations of this type 684 685 typeSet = get(type); 686 687 //if none exist, then return coz nothing left to do 688 689 if(typeSet == null) 690 691 return null; 692 693 } 694 695 696 697 AnnotationSet resultSet = new AnnotationSetImpl(doc); 698 699 700 701 Iterator iter = null; 702 703 if (type != null) 704 705 iter = typeSet.iterator(); 706 707 else 708 709 iter = annotsById.values().iterator(); 710 711 712 713 while(iter.hasNext()) { 714 715 Annotation a = (Annotation) iter.next(); 716 717 718 719 // we check for matching constraints by simple equality. a 720 721 // feature map satisfies the constraints if it contains all the 722 723 // key/value pairs from the constraints map 724 725 if( a.getFeatures().keySet().containsAll( featureNames ) ) 726 727 resultSet.add(a); 728 729 } // while 730 731 732 733 if(resultSet.isEmpty()) 734 735 return null; 736 737 return resultSet; 738 739 } // get(type, featureNames) 740 741 742 743 /** Select annotations by offset. This returns the set of annotations 744 745 * whose start node is the least such that it is less than or equal 746 747 * to offset. If a positional index doesn't exist it is created. 748 749 * If there are no nodes at or beyond the offset param then it will return 750 751 * null. 752 753 */ 754 755 public AnnotationSet get(Long offset) { 756 757 if(annotsByStartNode == null) indexByStartOffset(); 758 759 760 761 // find the next node at or after offset; get the annots starting there 762 763 Node nextNode = (Node) nodesByOffset.getNextOf(offset); 764 765 if(nextNode == null) // no nodes at or beyond this offset 766 767 return null; 768 769 770 771 AnnotationSet res = (AnnotationSet) annotsByStartNode.get(nextNode.getId()); 772 773 774 775 //get ready for next test 776 777 nextNode = (Node) nodesByOffset.getNextOf(new Long(offset.longValue() + 1)); 778 779 780 781 //skip all the nodes that have no starting annotations 782 783 while(res == null && nextNode != null){ 784 785 res = (AnnotationSet) annotsByStartNode.get(nextNode.getId()); 786 787 788 789 //get ready for next test 790 791 nextNode = (Node) nodesByOffset.getNextOf( 792 793 new Long(nextNode.getOffset().longValue() + 1) 794 795 ); 796 797 } 798 799 800 801 //res it either null (no suitable node found) or the correct result 802 803 return res; 804 805 } // get(offset) 806 807 808 809 /** 810 811 * Select annotations by offset. This returns the set of annotations 812 813 * that overlap totaly or partially with the interval defined by the two 814 815 * provided offsets.The result will include all the annotations that either: 816 817 * <ul> 818 819 * <li>start before the start offset and end strictly after it</li> 820 821 * <li>OR</li> 822 823 * <li>start at a position between the start and the end offsets</li> 824 825 */ 826 827 public AnnotationSet get(Long startOffset, Long endOffset) { 828 829 //the result will include all the annotations that either: 830 831 //-start before the start offset and end strictly after it 832 833 //or 834 835 //-start at a position between the start and the end offsets 836 837 if(annotsByStartNode == null) indexByStartOffset(); 838 839 AnnotationSetImpl resultSet = new AnnotationSetImpl(doc); 840 841 Iterator nodesIter; 842 843 Iterator annotsIter; 844 845 Node currentNode; 846 847 Annotation currentAnnot; 848 849 //find all the annots that start strictly before the start offset and end 850 851 //strictly after it 852 853 nodesIter = nodesByOffset.headMap(startOffset).values().iterator(); 854 855 while(nodesIter.hasNext()){ 856 857 currentNode = (Node)nodesIter.next(); 858 859 Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId()); 860 861 if(fromPoint != null){ 862 863 annotsIter = (fromPoint).iterator(); 864 865 while(annotsIter.hasNext()){ 866 867 currentAnnot = (Annotation)annotsIter.next(); 868 869 if(currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0){ 870 871 resultSet.add(currentAnnot); 872 873 } 874 875 } 876 877 } 878 879 } 880 881 //find all the annots that start at or after the start offset but strictly 882 883 //before the end offset 884 885 nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator(); 886 887 while(nodesIter.hasNext()){ 888 889 currentNode = (Node)nodesIter.next(); 890 891 Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId()); 892 893 if(fromPoint != null) resultSet.addAllKeepIDs(fromPoint); 894 895 } 896 897 return resultSet; 898 899 }//get(startOfset, endOffset) 900 901 902 903 904 905 /** 906 907 * Select annotations by offset. This returns the set of annotations 908 909 * that overlap strictly with the interval defined by the two 910 911 * provided offsets.The result will include all the annotations that 912 913 * start at the start offset and end strictly at the end offset 914 915 */ 916 917 public AnnotationSet getStrict(Long startOffset, Long endOffset) { 918 919 //the result will include all the annotations that 920 921 //start at the start offset and end strictly at the end offset 922 923 if(annotsByStartNode == null) indexByStartOffset(); 924 925 AnnotationSet resultSet = new AnnotationSetImpl(doc); 926 927 Iterator nodesIter; 928 929 Iterator annotsIter; 930 931 Node currentNode; 932 933 Annotation currentAnnot; 934 935 //find all the annots that start at the start offset 936 937 currentNode = (Node) nodesByOffset.get(startOffset); 938 939 if(currentNode != null) { 940 941 Set fromPoint = (Set) annotsByStartNode.get(currentNode.getId()); 942 943 if (fromPoint != null) { 944 945 annotsIter = fromPoint.iterator(); 946 947 while (annotsIter.hasNext()) { 948 949 currentAnnot = (Annotation) annotsIter.next(); 950 951 if(currentAnnot.getEndNode().getOffset().compareTo(endOffset) == 0){ 952 953 resultSet.add(currentAnnot); 954 955 } // if 956 957 } // while 958 959 } // if 960 961 } // if 962 963 return resultSet; 964 965 }//getStrict(startOfset, endOffset) 966 967 968 969 /** 970 971 * Select annotations by offset. This returns the set of annotations 972 973 * of the given type 974 975 * that overlap totaly or partially with the interval defined by the two 976 977 * provided offsets.The result will include all the annotations that either: 978 979 * <ul> 980 981 * <li>start before the start offset and end strictly after it</li> 982 983 * <li>OR</li> 984 985 * <li>start at a position between the start and the end offsets</li> 986 987 */ 988 989 public AnnotationSet get(String neededType, Long startOffset, Long endOffset) { 990 991 //the result will include all the annotations that either: 992 993 //-start before the start offset and end strictly after it 994 995 //or 996 997 //-start at a position between the start and the end offsets 998 999 if(annotsByStartNode == null) indexByStartOffset(); 1000 1001 AnnotationSet resultSet = new AnnotationSetImpl(doc); 1002 1003 Iterator nodesIter; 1004 1005 Iterator annotsIter; 1006 1007 Node currentNode; 1008 1009 Annotation currentAnnot; 1010 1011 //find all the annots that start strictly before the start offset and end 1012 1013 //strictly after it 1014 1015 nodesIter = nodesByOffset.headMap(startOffset).values().iterator(); 1016 1017 while(nodesIter.hasNext()){ 1018 1019 currentNode = (Node)nodesIter.next(); 1020 1021 Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId()); 1022 1023 if(fromPoint != null){ 1024 1025 annotsIter = (fromPoint).iterator(); 1026 1027 while(annotsIter.hasNext()){ 1028 1029 currentAnnot = (Annotation)annotsIter.next(); 1030 1031 if(currentAnnot.getType().equals(neededType) && 1032 1033 currentAnnot.getEndNode().getOffset().compareTo(startOffset) > 0 1034 1035 ) { 1036 1037 resultSet.add(currentAnnot); 1038 1039 }//if 1040 1041 }//while 1042 1043 } 1044 1045 } 1046 1047 //find all the annots that start at or after the start offset but strictly 1048 1049 //before the end offset 1050 1051 nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator(); 1052 1053 while(nodesIter.hasNext()){ 1054 1055 currentNode = (Node)nodesIter.next(); 1056 1057 Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId()); 1058 1059 if(fromPoint != null) { 1060 1061 annotsIter = (fromPoint).iterator(); 1062 1063 while(annotsIter.hasNext()){ 1064 1065 currentAnnot = (Annotation)annotsIter.next(); 1066 1067 if(currentAnnot.getType().equals(neededType)) { 1068 1069 resultSet.add(currentAnnot); 1070 1071 }//if 1072 1073 }//while 1074 1075 } //if 1076 1077 } 1078 1079 return resultSet; 1080 1081 }//get(type, startOfset, endOffset) 1082 1083 1084 1085 1086 1087 /** Select annotations by type, features and offset */ 1088 1089 public AnnotationSet get(String type, FeatureMap constraints, Long offset) { 1090 1091 1092 1093 // select by offset 1094 1095 AnnotationSet nextAnnots = (AnnotationSet) get(offset); 1096 1097 1098 1099 if(nextAnnots == null) return null; 1100 1101 1102 1103 // select by type and constraints from the next annots 1104 1105 return nextAnnots.get(type, constraints); 1106 1107 1108 1109 } // get(type, constraints, offset) 1110 1111 1112 1113 /** 1114 1115 * Select annotations by offset that 1116 1117 * start at a position between the start and end before the end offset 1118 1119 */ 1120 1121 public AnnotationSet getContained(Long startOffset, Long endOffset) { 1122 1123 //the result will include all the annotations that either: 1124 1125 //start at a position between the start and end before the end offsets 1126 1127 if(annotsByStartNode == null) indexByStartOffset(); 1128 1129 AnnotationSet resultSet = new AnnotationSetImpl(doc); 1130 1131 Iterator nodesIter; 1132 1133 Iterator annotsIter; 1134 1135 Node currentNode; 1136 1137 Annotation currentAnnot; 1138 1139 //find all the annots that start at or after the start offset but strictly 1140 1141 //before the end offset 1142 1143 nodesIter = nodesByOffset.subMap(startOffset, endOffset).values().iterator(); 1144 1145 while(nodesIter.hasNext()){ 1146 1147 currentNode = (Node)nodesIter.next(); 1148 1149 Set fromPoint = (Set)annotsByStartNode.get(currentNode.getId()); 1150 1151 if (fromPoint == null) continue; 1152 1153 //loop through the annotations and find only those that 1154 1155 //also end before endOffset 1156 1157 Iterator annotIter = fromPoint.iterator(); 1158 1159 while (annotIter.hasNext()) { 1160 1161 Annotation annot = (Annotation) annotIter.next(); 1162 1163 if (annot.getEndNode().getOffset().compareTo(endOffset) <= 0) 1164 1165 resultSet.add(annot); 1166 1167 } 1168 1169 } 1170 1171 return resultSet; 1172 1173 }//get(startOfset, endOffset) 1174 1175 1176 1177 1178 1179 1180 1181 /** Get the node with the smallest offset */ 1182 1183 public Node firstNode() { 1184 1185 indexByStartOffset(); 1186 1187 if(nodesByOffset.isEmpty()) return null; 1188 1189 else return (Node) nodesByOffset.get(nodesByOffset.firstKey()); 1190 1191 } // firstNode 1192 1193 1194 1195 /** Get the node with the largest offset */ 1196 1197 public Node lastNode() { 1198 1199 1200 1201 indexByStartOffset(); 1202 1203 indexByEndOffset(); 1204 1205 1206 1207 if(nodesByOffset.isEmpty())return null; 1208 1209 else return (Node) nodesByOffset.get(nodesByOffset.lastKey()); 1210 1211 1212 1213 } // lastNode 1214 1215 1216 1217 /** 1218 1219 * Get the first node that is relevant for this annotation set and which has 1220 1221 * the offset larger than the one of the node provided. 1222 1223 */ 1224 1225 public Node nextNode(Node node) { 1226 1227 indexByStartOffset(); 1228 1229 indexByEndOffset(); 1230 1231 return (Node)nodesByOffset.getNextOf( 1232 1233 new Long(node.getOffset().longValue() + 1) 1234 1235 ); 1236 1237 } 1238 1239 1240 1241 /** Create and add an annotation with pre-existing nodes, 1242 1243 * and return its id 1244 1245 */ 1246 1247 public Integer add(Node start, Node end, String type, FeatureMap features) { 1248 1249 1250 1251 // the id of the new annotation 1252 1253 Integer id = doc.getNextAnnotationId(); 1254 1255 1256 1257 // construct an annotation 1258 1259 Annotation a = new AnnotationImpl(id, start, end, type, features); 1260 1261 1262 1263 // delegate to the method that adds existing annotations 1264 1265 add(a); 1266 1267 1268 1269 return id; 1270 1271 } // add(Node, Node, String, FeatureMap) 1272 1273 1274 1275 /** Add an existing annotation. Returns true when the set is modified. */ 1276 1277 public boolean add(Object o) throws ClassCastException { 1278 1279 Annotation a = (Annotation) o; 1280 1281 Object oldValue = annotsById.put(a.getId(), a); 1282 1283 if (annotsByType != null) 1284 1285 addToTypeIndex(a); 1286 1287 if (annotsByStartNode != null || annotsByEndNode != null) 1288 1289 addToOffsetIndex(a); 1290 1291 AnnotationSetEvent evt = new AnnotationSetEvent( 1292 1293 this, 1294 1295 AnnotationSetEvent.ANNOTATION_ADDED, 1296 1297 doc, a); 1298 1299 fireAnnotationAdded(evt); 1300 1301 fireGateEvent(evt); 1302 1303 return oldValue != a; 1304 1305 } // add(o) 1306 1307 1308 1309 /** 1310 1311 * Adds multiple annotations to this set in one go. 1312 1313 * All the objects in the provided collection should be of 1314 1315 * {@link gate.Annotation} type, otherwise a ClassCastException will be 1316 1317 * thrown. 1318 1319 * The provided annotations will be used to create new annotations using the 1320 1321 * appropriate add() methods from this set. The new annotations will have 1322 1323 * different IDs from the old ones (which is required in order to preserve the 1324 1325 * uniqueness of IDs inside an annotation set). 1326 1327 * @param c a collection of annotations 1328 1329 * @return <tt>true</tt> if the set has been modified as a result of this 1330 1331 * call. 1332 1333 */ 1334 1335 public boolean addAll(Collection c){ 1336 1337 Iterator annIter = c.iterator(); 1338 1339 boolean changed = false; 1340 1341 while(annIter.hasNext()){ 1342 1343 Annotation a = (Annotation)annIter.next(); 1344 1345 try{ 1346 1347 add(a.getStartNode().getOffset(), 1348 1349 a.getEndNode().getOffset(), 1350 1351 a.getType(), 1352 1353 a.getFeatures()); 1354 1355 changed = true; 1356 1357 }catch(InvalidOffsetException ioe){ 1358 1359 throw new IllegalArgumentException(ioe.toString()); 1360 1361 } 1362 1363 } 1364 1365 return changed; 1366 1367 } 1368 1369 1370 1371 /** 1372 1373 * Adds multiple annotations to this set in one go. 1374 1375 * All the objects in the provided collection should be of 1376 1377 * {@link gate.Annotation} type, otherwise a ClassCastException will be 1378 1379 * thrown. 1380 1381 * This method does not create copies of the annotations like addAll() does 1382 1383 * but simply adds the new annotations to the set. 1384 1385 * It is intended to be used solely by annotation sets in order to construct 1386 1387 * the results for various get(...) methods. 1388 1389 * @param c a collection of annotations 1390 1391 * @return <tt>true</tt> if the set has been modified as a result of this 1392 1393 * call. 1394 1395 */ 1396 1397 protected boolean addAllKeepIDs(Collection c){ 1398 1399 Iterator annIter = c.iterator(); 1400 1401 boolean changed = false; 1402 1403 while(annIter.hasNext()){ 1404 1405 Annotation a = (Annotation)annIter.next(); 1406 1407 changed |= add(a); 1408 1409 } 1410 1411 return changed; 1412 1413 } 1414 1415 1416 1417 /** Create and add an annotation and return its id */ 1418 1419 public Integer add( 1420 1421 Long start, Long end, String type, FeatureMap features 1422 1423 ) throws InvalidOffsetException { 1424 1425 1426 1427 // are the offsets valid? 1428 1429 if(! doc.isValidOffsetRange(start, end)) 1430 1431 throw new InvalidOffsetException(); 1432 1433 1434 1435 // the set has to be indexed by position in order to add, as we need 1436 1437 // to find out if nodes need creating or if they exist already 1438 1439 if(nodesByOffset == null) { 1440 1441 indexByStartOffset(); 1442 1443 indexByEndOffset(); 1444 1445 } 1446 1447 1448 1449 // find existing nodes if appropriate nodes don't already exist, create them 1450 1451 Node startNode = (Node) nodesByOffset.getNextOf(start); 1452 1453 if(startNode == null || ! startNode.getOffset().equals(start)) 1454 1455 startNode = new NodeImpl(doc.getNextNodeId(), start); 1456 1457 1458 1459 Node endNode = null; 1460 1461 if(start.equals(end)) 1462 1463 endNode = startNode; 1464 1465 else 1466 1467 endNode = (Node) nodesByOffset.getNextOf(end); 1468 1469 1470 1471 if(endNode == null || ! endNode.getOffset().equals(end)) 1472 1473 endNode = new NodeImpl(doc.getNextNodeId(), end); 1474 1475 1476 1477 // delegate to the method that adds annotations with existing nodes 1478 1479 return add(startNode, endNode, type, features); 1480 1481 } // add(start, end, type, features) 1482 1483 1484 1485 /** Create and add an annotation from database read data 1486 1487 * In this case the id is already known being previously fetched from the 1488 1489 * database 1490 1491 */ 1492 1493 public void add( 1494 1495 Integer id, Long start, Long end, String type, FeatureMap features 1496 1497 ) throws InvalidOffsetException { 1498 1499 1500 1501 // are the offsets valid? 1502 1503 if(! doc.isValidOffsetRange(start, end)) 1504 1505 throw new InvalidOffsetException(); 1506 1507 1508 1509 // the set has to be indexed by position in order to add, as we need 1510 1511 // to find out if nodes need creating or if they exist already 1512 1513 if(nodesByOffset == null){ 1514 1515 indexByStartOffset(); 1516 1517 indexByEndOffset(); 1518 1519 } 1520 1521 1522 1523 // find existing nodes if appropriate nodes don't already exist, create them 1524 1525 Node startNode = (Node) nodesByOffset.getNextOf(start); 1526 1527 if(startNode == null || ! startNode.getOffset().equals(start)) 1528 1529 startNode = new NodeImpl(doc.getNextNodeId(), start); 1530 1531 1532 1533 Node endNode = null; 1534 1535 if(start.equals(end)) 1536 1537 endNode = startNode; 1538 1539 else 1540 1541 endNode = (Node) nodesByOffset.getNextOf(end); 1542 1543 1544 1545 if(endNode == null || ! endNode.getOffset().equals(end)) 1546 1547 endNode = new NodeImpl(doc.getNextNodeId(), end); 1548 1549 1550 1551 // construct an annotation 1552 1553 Annotation a = new AnnotationImpl(id, startNode, endNode, type, features); 1554 1555 add(a); 1556 1557 1558 1559 } // add(id, start, end, type, features) 1560 1561 1562 1563 /** Construct the positional index. */ 1564 1565 protected void indexByType() { 1566 1567 1568 1569 if(annotsByType != null) return; 1570 1571 1572 1573 annotsByType = new HashMap(Gate.HASH_STH_SIZE); 1574 1575 1576 1577 Annotation a; 1578 1579 Iterator annotIter = annotsById.values().iterator(); 1580 1581 1582 1583 while (annotIter.hasNext()) 1584 1585 addToTypeIndex( (Annotation) annotIter.next() ); 1586 1587 1588 1589 } // indexByType() 1590 1591 1592 1593 /** Construct the positional indices for annotation start */ 1594 1595 protected void indexByStartOffset() { 1596 1597 1598 1599 if(annotsByStartNode != null) return; 1600 1601 1602 1603 if(nodesByOffset == null) 1604 1605 nodesByOffset = new RBTreeMap(); 1606 1607 annotsByStartNode = new HashMap(Gate.HASH_STH_SIZE); 1608 1609 1610 1611 Annotation a; 1612 1613 Iterator annotIter = annotsById.values().iterator(); 1614 1615 1616 1617 while(annotIter.hasNext()) 1618 1619 addToStartOffsetIndex( (Annotation) annotIter.next() ); 1620 1621 1622 1623 } // indexByStartOffset() 1624 1625 1626 1627 /** Construct the positional indices for annotation end */ 1628 1629 protected void indexByEndOffset() { 1630 1631 1632 1633 if(annotsByEndNode != null) return; 1634 1635 1636 1637 if(nodesByOffset == null) 1638 1639 nodesByOffset = new RBTreeMap(); 1640 1641 annotsByEndNode = new HashMap(Gate.HASH_STH_SIZE); 1642 1643 1644 1645 Annotation a; 1646 1647 Iterator annotIter = annotsById.values().iterator(); 1648 1649 1650 1651 while(annotIter.hasNext()) 1652 1653 addToEndOffsetIndex( (Annotation) annotIter.next() ); 1654 1655 1656 1657 } // indexByEndOffset() 1658 1659 1660 1661 /** Add an annotation to the type index. Does nothing if the index 1662 1663 * doesn't exist. 1664 1665 */ 1666 1667 void addToTypeIndex(Annotation a) { 1668 1669 if(annotsByType == null) return; 1670 1671 1672 1673 String type = a.getType(); 1674 1675 AnnotationSet sameType = (AnnotationSet) annotsByType.get(type); 1676 1677 1678 1679 if(sameType == null) { 1680 1681 sameType = new AnnotationSetImpl(doc); 1682 1683 annotsByType.put(type, sameType); 1684 1685 } 1686 1687 sameType.add(a); 1688 1689 } // addToTypeIndex(a) 1690 1691 1692 1693 /** Add an annotation to the offset indices. Does nothing if they 1694 1695 * don't exist. 1696 1697 */ 1698 1699 void addToOffsetIndex(Annotation a) { 1700 1701 addToStartOffsetIndex(a); 1702 1703 addToEndOffsetIndex(a); 1704 1705 } // addToOffsetIndex(a) 1706 1707 1708 1709 /** Add an annotation to the start offset index. Does nothing if the 1710 1711 * index doesn't exist. 1712 1713 */ 1714 1715 void addToStartOffsetIndex(Annotation a) { 1716 1717 Node startNode = a.getStartNode(); 1718 1719 Node endNode = a.getEndNode(); 1720 1721 Long start = startNode.getOffset(); 1722 1723 Long end = endNode.getOffset(); 1724 1725 1726 1727 // add a's nodes to the offset index 1728 1729 if(nodesByOffset != null) 1730 1731 nodesByOffset.put(start, startNode); 1732 1733 1734 1735 // if there's no appropriate index give up 1736 1737 if(annotsByStartNode == null) return; 1738 1739 1740 1741 // get the annotations that start at the same node, or create new set 1742 1743 AnnotationSet thisNodeAnnots = 1744 1745 (AnnotationSet) annotsByStartNode.get(startNode.getId()); 1746 1747 1748 1749 if(thisNodeAnnots == null) { 1750 1751 thisNodeAnnots = new AnnotationSetImpl(doc); 1752 1753 annotsByStartNode.put(startNode.getId(), thisNodeAnnots); 1754 1755 } 1756 1757 // add to the annots listed for a's start node 1758 1759 thisNodeAnnots.add(a); 1760 1761 1762 1763 } // addToStartOffsetIndex(a) 1764 1765 1766 1767 /** Add an annotation to the end offset index. Does nothing if the 1768 1769 * index doesn't exist. 1770 1771 */ 1772 1773 void addToEndOffsetIndex(Annotation a) { 1774 1775 Node startNode = a.getStartNode(); 1776 1777 Node endNode = a.getEndNode(); 1778 1779 Long start = startNode.getOffset(); 1780 1781 Long end = endNode.getOffset(); 1782 1783 1784 1785 // add a's nodes to the offset index 1786 1787 if(nodesByOffset != null) nodesByOffset.put(end, endNode); 1788 1789 1790 1791 // if there's no appropriate index give up 1792 1793 if(annotsByEndNode == null) 1794 1795 return; 1796 1797 1798 1799 // get the annotations that start at the same node, or create new set 1800 1801 AnnotationSet thisNodeAnnots = 1802 1803 (AnnotationSet) annotsByEndNode.get(endNode.getId()); 1804 1805 1806 1807 if(thisNodeAnnots == null) { 1808 1809 thisNodeAnnots = new AnnotationSetImpl(doc); 1810 1811 annotsByEndNode.put(endNode.getId(), thisNodeAnnots); 1812 1813 } 1814 1815 // add to the annots listed for a's start node 1816 1817 thisNodeAnnots.add(a); 1818 1819 1820 1821 } // addToEndOffsetIndex(a) 1822 1823 1824 1825 /** Propagate changes to the document content. Has, unfortunately, 1826 1827 * to be public, to allow DocumentImpls to get at it. Oh for a 1828 1829 * "friend" declaration. Doesn't thow InvalidOffsetException as 1830 1831 * DocumentImpl is the only client, and that checks the offsets 1832 1833 * before calling this method. 1834 1835 */ 1836 1837 public void edit(Long start, Long end, DocumentContent replacement) { 1838 1839 long s = start.longValue(), e = end.longValue(); 1840 1841 long rlen = // length of the replacement value 1842 1843 ( (replacement == null) ? 0 : replacement.size().longValue() ); 1844 1845 1846 1847 indexByStartOffset(); 1848 1849 indexByEndOffset(); 1850 1851 1852 1853 Iterator replacedAreaNodesIter = 1854 1855 nodesByOffset.subMap(start, end).values().iterator(); 1856 1857 while(replacedAreaNodesIter.hasNext()) { 1858 1859 Node n = (Node) replacedAreaNodesIter.next(); 1860 1861 1862 1863 // remove from nodes map 1864 1865// if(true) 1866 1867// throw new LazyProgrammerException("this next call tries to remove " + 1868 1869// "from a map based on the value; note index is key; also note that " + 1870 1871// "some nodes may have no index...."); 1872 1873 1874 1875//There is at most one node at any given location so removing is safe. 1876 1877//Also note that unrooted nodes have never been implemented so all nodes have 1878 1879//offset 1880 1881 nodesByOffset.remove(n.getOffset()); 1882 1883 1884 1885 // remove annots that start at this node 1886 1887 AnnotationSet invalidatedAnnots = 1888 1889 (AnnotationSet) annotsByStartNode.get(n.getId()); 1890 1891 if(invalidatedAnnots != null) 1892 1893 removeAll(invalidatedAnnots); 1894 1895 1896 1897 // remove annots that end at this node 1898 1899 invalidatedAnnots = 1900 1901 (AnnotationSet) annotsByEndNode.get(n.getId()); 1902 1903 if(invalidatedAnnots != null) 1904 1905 removeAll(invalidatedAnnots); 1906 1907 } // loop over replaced area nodes 1908 1909 1910 1911 // update the offsets of the other nodes 1912 1913 Iterator nodesAfterReplacementIter = 1914 1915 nodesByOffset.tailMap(end).values().iterator(); 1916 1917 while(nodesAfterReplacementIter.hasNext()) { 1918 1919 NodeImpl n = (NodeImpl) nodesAfterReplacementIter.next(); 1920 1921 long oldOffset = n.getOffset().longValue(); 1922 1923 1924 1925 n.setOffset(new Long( oldOffset - ( (e-s) - rlen ) )); 1926 1927 } // loop over nodes after replacement area 1928 1929 1930 1931 } // edit(start,end,replacement) 1932 1933 1934 1935 /** Get the name of this set. */ 1936 1937 public String getName() { return name; } 1938 1939 1940 1941 /** Get the document this set is attached to. */ 1942 1943 public Document getDocument() { return doc; } 1944 1945 1946 1947 /** Get a set of java.lang.String objects representing all the annotation 1948 1949 * types present in this annotation set. 1950 1951 */ 1952 1953 public Set getAllTypes() { 1954 1955 indexByType(); 1956 1957 return annotsByType.keySet(); 1958 1959 } 1960 1961 1962 1963 /** 1964 1965 * 1966 1967 * @return 1968 1969 * @throws CloneNotSupportedException 1970 1971 */ 1972 1973 public Object clone() throws CloneNotSupportedException{ 1974 1975 return super.clone(); 1976 1977 } 1978 1979 /** 1980 1981 * 1982 1983 * @param l 1984 1985 */ 1986 1987 public synchronized void removeAnnotationSetListener(AnnotationSetListener l) { 1988 1989 if (annotationSetListeners != null && annotationSetListeners.contains(l)) { 1990 1991 Vector v = (Vector) annotationSetListeners.clone(); 1992 1993 v.removeElement(l); 1994 1995 annotationSetListeners = v; 1996 1997 } 1998 1999 } 2000 2001 /** 2002 2003 * 2004 2005 * @param l 2006 2007 */ 2008 2009 public synchronized void addAnnotationSetListener(AnnotationSetListener l) { 2010 2011 Vector v = annotationSetListeners == null ? new Vector(2) : (Vector) annotationSetListeners.clone(); 2012 2013 if (!v.contains(l)) { 2014 2015 v.addElement(l); 2016 2017 annotationSetListeners = v; 2018 2019 } 2020 2021 } 2022 2023 /** String representation of the set */ 2024 2025 /*public String toString() { 2026 2027 2028 2029 // annotsById 2030 2031 SortedSet sortedAnnots = new TreeSet(); 2032 2033 sortedAnnots.addAll(annotsById.values()); 2034 2035 String aBI = sortedAnnots.toString(); 2036 2037 2038 2039 // annotsByType 2040 2041 2042 2043 StringBuffer buf = new StringBuffer(); 2044 2045 Iterator iter = annotsByType.iterator(); 2046 2047 while(iter.hasNext()) { 2048 2049 HashMap thisType = iter.next().entrySet(); 2050 2051 sortedAnnots.clear(); 2052 2053 sortedAnnots.addAll(thisType.); 2054 2055 buf.append("[type: " + 2056 2057 } 2058 2059 2060 2061 2062 2063 return 2064 2065 "AnnotationSetImpl: " + 2066 2067 "name=" + name + 2068 2069 // "; doc.getURL()=" + doc + 2070 2071 "; annotsById=" + aBI + 2072 2073 // "; annotsByType=" + aBT + 2074 2075 "; " 2076 2077 ; 2078 2079 } // toString() 2080 2081 */ 2082 2083 2084 2085// public int hashCode() { 2086 2087// int hash = 0; 2088 2089// Iterator i = this.iterator(); 2090 2091// while (i.hasNext()) { 2092 2093// Annotation annot = (Annotation)i.next(); 2094 2095// if ( annot != null) 2096 2097// hash += annot.hashCode(); 2098 2099// } 2100 2101// int nameHash = (name == null ? 0 : name.hashCode()); 2102 2103// //int docHash = (doc == null ? 0 : doc.hashCode()); 2104 2105// 2106 2107// return hash ^ nameHash;// ^ docHash; 2108 2109// } 2110 2111 2112 2113 /** The name of this set */ 2114 2115 String name = null; 2116 2117 2118 2119 /** The document this set belongs to */ 2120 2121 DocumentImpl doc; 2122 2123 2124 2125 /** Maps annotation ids (Integers) to Annotations */ 2126 2127 protected HashMap annotsById; 2128 2129 2130 2131 /** Maps annotation types (Strings) to AnnotationSets */ 2132 2133 Map annotsByType = null; 2134 2135 2136 2137 /** Maps offsets (Longs) to nodes */ 2138 2139 RBTreeMap nodesByOffset = null; 2140 2141 2142 2143 /** Maps node ids (Integers) to AnnotationSets representing those 2144 2145 * annotations that start from that node 2146 2147 */ 2148 2149 Map annotsByStartNode; 2150 2151 2152 2153 /** Maps node ids (Integers) to AnnotationSets representing those 2154 2155 * annotations that end at that node 2156 2157 */ 2158 2159 Map annotsByEndNode; 2160 2161 protected transient Vector annotationSetListeners; 2162 2163 private transient Vector gateListeners; 2164 2165 /** 2166 2167 * 2168 2169 * @param e 2170 2171 */ 2172 2173 protected void fireAnnotationAdded(AnnotationSetEvent e) { 2174 2175 if (annotationSetListeners != null) { 2176 2177 Vector listeners = annotationSetListeners; 2178 2179 int count = listeners.size(); 2180 2181 for (int i = 0; i < count; i++) { 2182 2183 ((AnnotationSetListener) listeners.elementAt(i)).annotationAdded(e); 2184 2185 } 2186 2187 } 2188 2189 } 2190 2191 /** 2192 2193 * 2194 2195 * @param e 2196 2197 */ 2198 2199 protected void fireAnnotationRemoved(AnnotationSetEvent e) { 2200 2201 if (annotationSetListeners != null) { 2202 2203 Vector listeners = annotationSetListeners; 2204 2205 int count = listeners.size(); 2206 2207 for (int i = 0; i < count; i++) { 2208 2209 ((AnnotationSetListener) listeners.elementAt(i)).annotationRemoved(e); 2210 2211 } 2212 2213 } 2214 2215 } 2216 2217 /** 2218 2219 * 2220 2221 * @param l 2222 2223 */ 2224 2225 public synchronized void removeGateListener(GateListener l) { 2226 2227 if (gateListeners != null && gateListeners.contains(l)) { 2228 2229 Vector v = (Vector) gateListeners.clone(); 2230 2231 v.removeElement(l); 2232 2233 gateListeners = v; 2234 2235 } 2236 2237 } 2238 2239 /** 2240 2241 * 2242 2243 * @param l 2244 2245 */ 2246 2247 public synchronized void addGateListener(GateListener l) { 2248 2249 Vector v = gateListeners == null ? new Vector(2) : (Vector) gateListeners.clone(); 2250 2251 if (!v.contains(l)) { 2252 2253 v.addElement(l); 2254 2255 gateListeners = v; 2256 2257 } 2258 2259 } 2260 2261 /** 2262 2263 * 2264 2265 * @param e 2266 2267 */ 2268 2269 protected void fireGateEvent(GateEvent e) { 2270 2271 if (gateListeners != null) { 2272 2273 Vector listeners = gateListeners; 2274 2275 int count = listeners.size(); 2276 2277 for (int i = 0; i < count; i++) { 2278 2279 ((GateListener) listeners.elementAt(i)).processGateEvent(e); 2280 2281 } 2282 2283 } 2284 2285 } 2286 2287 2288 2289 /** Freeze the serialization UID. */ 2290 2291 static final long serialVersionUID = 1479426765310434166L; 2292 2293} // AnnotationSetImpl 2294 2295
|
AnnotationSetImpl |
|