001 /* 002 * The contents of this file are subject to the Mozilla Public 003 * License Version 1.1 (the "License"); you may not use this file 004 * except in compliance with the License. You may obtain a copy of 005 * the License at http://www.mozilla.org/MPL/ 006 * 007 * Software distributed under the License is distributed on an "AS 008 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or 009 * implied. See the License for the specific language governing 010 * rights and limitations under the License. 011 * 012 * The Original Code is Knowtator. 013 * 014 * The Initial Developer of the Original Code is University of Colorado. 015 * Copyright (C) 2005-2008. All Rights Reserved. 016 * 017 * Knowtator was developed by the Center for Computational Pharmacology 018 * (http://compbio.uchcs.edu) at the University of Colorado Health 019 * Sciences Center School of Medicine with support from the National 020 * Library of Medicine. 021 * 022 * Current information about Knowtator can be obtained at 023 * http://knowtator.sourceforge.net/ 024 * 025 * Contributor(s): 026 * Philip V. Ogren <philip@ogren.info> (Original Author) 027 */ 028 029 package edu.uchsc.ccp.iaa; 030 031 import java.util.ArrayList; 032 import java.util.Collection; 033 import java.util.Collections; 034 import java.util.HashMap; 035 import java.util.HashSet; 036 import java.util.List; 037 import java.util.Map; 038 import java.util.Set; 039 040 import edu.uchsc.ccp.iaa.matcher.MatchResult; 041 042 public class Annotation { 043 String setName = "Set name not specified"; 044 045 String docID = "Document id not specified"; 046 047 String annotationClass; 048 049 List<Span> spans; 050 051 // key is a feature name, value is the value of the feature 052 Map<String, Set<Object>> simpleFeatures; 053 054 Map<String, Set<Annotation>> complexFeatures; 055 056 private int size = 0; 057 058 public Annotation() { 059 spans = new ArrayList<Span>(); 060 simpleFeatures = new HashMap<String, Set<Object>>(); 061 complexFeatures = new HashMap<String, Set<Annotation>>(); 062 } 063 064 public String getSetName() { 065 return setName; 066 } 067 068 public void setSetName(String setName) { 069 this.setName = setName; 070 } 071 072 public String getDocID() { 073 return docID; 074 } 075 076 public void setDocID(String docID) { 077 this.docID = docID; 078 } 079 080 public String getAnnotationClass() { 081 return annotationClass; 082 } 083 084 public void setAnnotationClass(String type) { 085 this.annotationClass = type; 086 } 087 088 public List<Span> getSpans() { 089 return Collections.unmodifiableList(spans); 090 } 091 092 public void setSpans(List<Span> spans) { 093 this.spans.clear(); 094 this.spans.addAll(spans); 095 Collections.sort(this.spans); 096 } 097 098 public void setSpan(Span span) { 099 this.spans.clear(); 100 this.spans.add(span); 101 } 102 103 public Set<String> getSimpleFeatureNames() { 104 return Collections.unmodifiableSet(simpleFeatures.keySet()); 105 } 106 107 public Set<Object> getSimpleFeatureValues(String featureName) { 108 if (simpleFeatures.get(featureName) == null) 109 return Collections.emptySet(); 110 return Collections.unmodifiableSet(simpleFeatures.get(featureName)); 111 } 112 113 public boolean isSimpleFeature(String featureName) { 114 return simpleFeatures.containsKey(featureName); 115 } 116 117 public void setSimpleFeature(String featureName, Set<Object> featureValues) { 118 if (featureValues == null) 119 return; 120 complexFeatures.remove(featureName); 121 simpleFeatures.put(featureName, new HashSet<Object>(featureValues)); 122 } 123 124 public void setSimpleFeature(String featureName, Object featureValue) { 125 if (featureValue == null) 126 return; 127 complexFeatures.remove(featureName); 128 HashSet<Object> featureValues = new HashSet<Object>(); 129 featureValues.add(featureValue); 130 simpleFeatures.put(featureName, featureValues); 131 } 132 133 public Set<String> getComplexFeatureNames() { 134 return Collections.unmodifiableSet(complexFeatures.keySet()); 135 } 136 137 public Set<Annotation> getComplexFeatureValues(String featureName) { 138 if (complexFeatures.get(featureName) == null) 139 return Collections.emptySet(); 140 return Collections.unmodifiableSet(complexFeatures.get(featureName)); 141 } 142 143 public boolean isComplexFeature(String featureName) { 144 return complexFeatures.containsKey(featureName); 145 } 146 147 public void setComplexFeature(String featureName, Set<Annotation> featureValues) { 148 simpleFeatures.remove(featureName); 149 complexFeatures.put(featureName, new HashSet<Annotation>(featureValues)); 150 } 151 152 public void setComplexFeature(String featureName, Annotation featureValue) { 153 simpleFeatures.remove(featureName); 154 HashSet<Annotation> featureValues = new HashSet<Annotation>(); 155 featureValues.add(featureValue); 156 complexFeatures.put(featureName, featureValues); 157 } 158 159 public Set<String> getFeatureNames() { 160 Set<String> featureNames = new HashSet<String>(simpleFeatures.keySet()); 161 featureNames.addAll(complexFeatures.keySet()); 162 return Collections.unmodifiableSet(featureNames); 163 } 164 165 /** 166 * @param annotation1 167 * @param annotation2 168 * @return true if the annotations have the same spans 169 */ 170 public static boolean spansMatch(Annotation annotation1, Annotation annotation2) { 171 return Span.spansMatch(annotation1.getSpans(), annotation2.getSpans()); 172 } 173 174 public static boolean spansMatch(List<Annotation> annotations) { 175 for (int i = 1; i < annotations.size(); i++) { 176 if (!spansMatch(annotations.get(0), annotations.get(i))) 177 return false; 178 } 179 return true; 180 } 181 182 /** 183 * returns true only if both annotations have the same non-null 184 * annotationClass. 185 */ 186 public static boolean classesMatch(Annotation annotation1, Annotation annotation2) { 187 String cls1 = annotation1.getAnnotationClass(); 188 String cls2 = annotation2.getAnnotationClass(); 189 190 if (cls1 == null || cls2 == null) 191 return false; 192 193 return cls1.equals(cls2); 194 } 195 196 public static boolean spansOverlap(Annotation annotation1, Annotation annotation2) { 197 return Span.intersects(annotation1.getSpans(), annotation2.getSpans()); 198 } 199 200 private static boolean compareNames(Set<String> names1, Set<String> names2) { 201 if (names1.size() != names2.size()) 202 return false; 203 for (String name : names1) { 204 if (!names2.contains(name)) 205 return false; 206 } 207 return true; 208 } 209 210 /** 211 * This method checks to see if two annotations have the same simple 212 * features but does not compare the values of the features. 213 * 214 * @param annotation1 215 * @param annotation2 216 * @return true if both annotations have the same number of simple features 217 * and they have the same names. 218 */ 219 public static boolean compareSimpleFeatureNames(Annotation annotation1, Annotation annotation2) { 220 return compareNames(annotation1.getSimpleFeatureNames(), annotation2.getSimpleFeatureNames()); 221 } 222 223 /** 224 * This method checks to see if two annotations have the same complex 225 * features but does not compare the values of the features. 226 * 227 * @param annotation1 228 * @param annotation2 229 * @return true if both annotations have the same number of complex features 230 * and they have the same names. 231 */ 232 public static boolean compareComplexFeatureNames(Annotation annotation1, Annotation annotation2) { 233 return compareNames(annotation1.getComplexFeatureNames(), annotation2.getComplexFeatureNames()); 234 } 235 236 public static boolean compareFeatureNames(Annotation annotation1, Annotation annotation2) { 237 return compareNames(annotation1.getFeatureNames(), annotation2.getFeatureNames()); 238 } 239 240 /** 241 * @return MatchResult.TRIVIAL_MATCH if both values are null, one is null 242 * and the other empty, or if both are empty <br> 243 * MatchResult.TRIVIAL_NONMATCH if one of the values is empty and 244 * they other is not, or if one values is null and the other is not 245 * null and not empty <br> 246 * MatchResult.NONTRIVIAL_NONMATCH if the sizes of the values are 247 * different. <br> 248 * MatchResult.MATCH_RESULT_UNASSIGNED is none of the above. 249 * @param values1 250 * the value of a feature (simple or complex) 251 * @param values2 252 * the value of another feature (simple or complex) 253 * @return MatchResult.TRIVIAL_MATCH, MatchResult.TRIVIAL_NONMATCH, 254 * MatchResult.NONTRIVIAL_NONMATCH, or MATCH_RESULT_UNASSIGNED 255 * @see edu.uchsc.ccp.iaa.matcher.MatchResult 256 * 257 */ 258 259 public static int trivialCompare(Set values1, Set values2) { 260 if (values1 == null && values2 == null) 261 return MatchResult.TRIVIAL_MATCH; // if both are null than it is a 262 // trivial match 263 if (values1 == null && values2.size() == 0) 264 return MatchResult.TRIVIAL_MATCH; // if one is null and the other 265 // empty, then trivial match 266 if (values2 == null && values1.size() == 0) 267 return MatchResult.TRIVIAL_MATCH; 268 if (values1 == null || values2 == null) 269 return MatchResult.TRIVIAL_NONMATCH; // if one is null and the other 270 // is not empty, then trivial 271 // nonmatch 272 if (values1.size() == 0 && values2.size() == 0) 273 return MatchResult.TRIVIAL_MATCH; // if both are empty, then trivial 274 // nonmatch 275 if (values1.size() == 0 || values2.size() == 0) 276 return MatchResult.TRIVIAL_NONMATCH; // if one is empty and the 277 // other is not, then trivial 278 // nonmatch 279 if (values1.size() != values2.size()) 280 return MatchResult.NONTRIVIAL_NONMATCH; // if neither are empty and 281 // the sizes are different, 282 // then non-trivial nonmatch 283 return MatchResult.MATCH_RESULT_UNASSIGNED; 284 } 285 286 /** 287 * 288 * @param annotation1 289 * @param annotation2 290 * @param featureName 291 * the name of the feature that will be compared between the two 292 * annotations 293 * @return MatchResult.TRIVIAL_NONMATCH if the featureName does not 294 * correspond to a simple feature in either or both of the 295 * annotations <br> 296 * the result of trivialCompare for the feature values unless that 297 * method returns MatchResult.MATCH_RESULT_UNASSIGNED. Otherwise, <br> 298 * MatchResult.NONTRIVIAL_MATCH if the values of the features are 299 * equal as defined by the equals method. <br> 300 * MatchResult.NONTRIVIAL_NONMATCH if the values are not equal as 301 * defined by the equals method. 302 * @see #trivialCompare(Set, Set) 303 */ 304 305 public static int compareSimpleFeature(Annotation annotation1, Annotation annotation2, String featureName) { 306 // if(!annotation1.isSimpleFeature(featureName) || 307 // !annotation2.isSimpleFeature(featureName)) return 308 // MatchResult.TRIVIAL_NONMATCH; 309 310 int trivialResult = trivialCompare(annotation1.getSimpleFeatureValues(featureName), annotation2 311 .getSimpleFeatureValues(featureName)); 312 if (trivialResult != MatchResult.MATCH_RESULT_UNASSIGNED) 313 return trivialResult; 314 315 Set<Object> featureValues1 = annotation1.getSimpleFeatureValues(featureName); 316 Set<Object> featureValues2 = new HashSet<Object>(annotation2.getSimpleFeatureValues(featureName)); 317 318 for (Object featureValue : featureValues1) { 319 if (!featureValues2.contains(featureValue)) { 320 return MatchResult.NONTRIVIAL_NONMATCH; 321 } 322 featureValues2.remove(featureValue); 323 } 324 325 return MatchResult.NONTRIVIAL_MATCH; 326 327 } 328 329 /** 330 * Compares all of the simple features of two annotations 331 * 332 * @param annotation1 333 * @param annotation2 334 * @return <ul> 335 * 336 * <li>TRIVIAL_NONMATCH if any of the simple features are trivial 337 * non-matches. 338 * <li>NONTRIVIAL_NONMATCH there is a non-matching simple feature 339 * and all non-matching simple features are non-trivial. 340 * <li>TRIVIAL_MATCH all simple features match and there is one 341 * simple feature that is a trivial match. 342 * <li>TRIVIAL_MATCH if there are no simple features. 343 * <li>NONTRIVIAL_MATH all simple features match and are non-trivial 344 * </ul> 345 * @see edu.uchsc.ccp.iaa.matcher.MatchResult#NONTRIVIAL_MATCH 346 * @see edu.uchsc.ccp.iaa.matcher.MatchResult#NONTRIVIAL_NONMATCH 347 * @see edu.uchsc.ccp.iaa.matcher.MatchResult#TRIVIAL_MATCH 348 * @see edu.uchsc.ccp.iaa.matcher.MatchResult#TRIVIAL_NONMATCH 349 */ 350 public static int compareSimpleFeatures(Annotation annotation1, Annotation annotation2) { 351 Set<String> featureNames = new HashSet<String>(annotation1.getSimpleFeatureNames()); 352 featureNames.addAll(annotation2.getSimpleFeatureNames()); 353 354 if (featureNames.size() == 0) 355 return MatchResult.TRIVIAL_MATCH; 356 357 return compareSimpleFeatures(annotation1, annotation2, featureNames); 358 } 359 360 /** 361 * Compares the simple features of two annotations named in featureNames 362 * 363 * @param annotation1 364 * @param annotation2 365 * @param featureNames 366 * the simple features to compare. 367 * @return <ul> 368 * <li>TRIVIAL_NONMATCH if any of the features are trivial 369 * non-matches 370 * <li>NONTRIVIAL_NONMATCH if each of the simple features that are 371 * non-matching are also non-trivial. For example, if there are five 372 * simple features being compared and 2 are trivial matches, 1 is a 373 * non-trivial match, and the other 2 are non-trivial non-matches, 374 * then NONTRIVIAL_NONMATCH will be returned. 375 * <li>TRIVIAL_MATCH if all of the features match and at least one 376 * of them is a trivial match 377 * <li>TRIVIAL_MATCH if featureNames is an empty set or null. 378 * <li>NONTRIVIAL_MATH all simple features match and are non-trivial 379 * </ul> 380 * @see edu.uchsc.ccp.iaa.Annotation#compareSimpleFeature(Annotation, 381 * Annotation, String) 382 * @see edu.uchsc.ccp.iaa.matcher.MatchResult#NONTRIVIAL_MATCH 383 * @see edu.uchsc.ccp.iaa.matcher.MatchResult#NONTRIVIAL_NONMATCH 384 * @see edu.uchsc.ccp.iaa.matcher.MatchResult#TRIVIAL_MATCH 385 * @see edu.uchsc.ccp.iaa.matcher.MatchResult#TRIVIAL_NONMATCH 386 */ 387 public static int compareSimpleFeatures(Annotation annotation1, Annotation annotation2, Set<String> featureNames) { 388 if (featureNames == null || featureNames.size() == 0) 389 return MatchResult.TRIVIAL_MATCH; 390 boolean trivialMatch = false; 391 boolean nonmatch = false; 392 393 for (String featureName : featureNames) { 394 int result = compareSimpleFeature(annotation1, annotation2, featureName); 395 if (result == MatchResult.TRIVIAL_NONMATCH) { 396 return result; 397 } else if (result == MatchResult.TRIVIAL_MATCH) { 398 trivialMatch = true; 399 } else if (result == MatchResult.NONTRIVIAL_NONMATCH) { 400 nonmatch = true; 401 } 402 } 403 if (nonmatch) 404 return MatchResult.NONTRIVIAL_NONMATCH; 405 if (trivialMatch) 406 return MatchResult.TRIVIAL_MATCH; 407 return MatchResult.NONTRIVIAL_MATCH; 408 } 409 410 /** 411 * This method compares the complex features of two annotations. A complex 412 * feature has a name and a value. The value is a set of Annotations 413 * (typically one - but can be more). The parameters to this method 414 * determine how the feature values should be compared. 415 * 416 * @param annotation1 417 * @param annotation2 418 * @param complexFeatureName 419 * the name of the feature that will be compared between the two 420 * annotations 421 * @param complexFeatureSpanComparison 422 * specifies how the spans of the feature values should be 423 * compared. The value of this parameter must be one of 424 * SPANS_OVERLAP_COMPARISON, SPANS_EXACT_COMPARISON, or 425 * IGNORE_SPANS_COMPARISON. If IGNORE_SPANS_COMPARISON is passed 426 * in, then the spans will be considered as matching. 427 * @param complexFeatureClassComparison 428 * specifies how the classes of the feature values should be 429 * compared. If true, then the classes will be compared and will 430 * be considered matched if they are the same. If false, then the 431 * classes will not be compared and will be considered as 432 * matching. 433 * @param simpleFeatureNamesOfComplexFeature 434 * specifies which simple features of the feature values should 435 * be compared. If null or an empty set is passed in, then the 436 * next parameter should probably be set to 'false'. 437 * @param trivialSimpleFeatureMatchesCauseTrivialMatch 438 * this parameter determines how a TRIVIAL_MATCH between simple 439 * features of the feature values should affect the return value 440 * of this method. If true, then a trivial match between any of 441 * the simple features of the feature values will cause 442 * TRIVIAL_MATCH (if it not a non-match) to be returned. If 443 * false, then a trivial match between any of the simple features 444 * will not have an effect on whether the return value of this 445 * method is TRIVIAL or NONTRIVIAL. 446 * 447 * @return MatchResult.TRIVIAL_NONMATCH if the featureName does not 448 * correspond to a complex feature in either or both of the 449 * annotations <br> 450 * the result of trivialCompare(Set, Set) for the feature values 451 * unless that method returns MatchResult.MATCH_RESULT_UNASSIGNED. 452 * Note that this is the only other criteria under which 453 * TRIVIAL_NONMATCH is returned. <br> 454 * MatchResult.NONTRIVIAL_MATCH if the values of the complex feature 455 * match as defined by the match parameters. <br> 456 * MatchResult.TRIVIAL_MATCH if the values of the complex feature 457 * match trivially and the parameter 458 * trivialSimpleFeatureMatchesCauseTrivialMatch is true. <br> 459 * MatchResult.NONTRIVIAL_NONMATCH if the values are not equal as 460 * defined by the match parameters. 461 * 462 */ 463 public static int compareComplexFeature(Annotation annotation1, Annotation annotation2, String complexFeatureName, 464 int complexFeatureSpanComparison, boolean complexFeatureClassComparison, 465 Set<String> simpleFeatureNamesOfComplexFeature, boolean trivialSimpleFeatureMatchesCauseTrivialMatch) { 466 // if(!annotation1.isComplexFeature(complexFeatureName) || 467 // !annotation2.isComplexFeature(complexFeatureName)) return 468 // MatchResult.TRIVIAL_NONMATCH; 469 470 Set<Annotation> featureValues1 = annotation1.getComplexFeatureValues(complexFeatureName); 471 Set<Annotation> featureValues2 = new HashSet<Annotation>(annotation2 472 .getComplexFeatureValues(complexFeatureName)); 473 474 int trivialResult = trivialCompare(featureValues1, featureValues2); 475 476 if (trivialResult != MatchResult.MATCH_RESULT_UNASSIGNED) 477 return trivialResult; 478 479 boolean trivialSimpleFeatureMatch = false; 480 for (Annotation featureValue1 : featureValues1) { 481 Annotation matchedFeature = null; 482 int matchedFeatureResult = MatchResult.MATCH_RESULT_UNASSIGNED; 483 484 for (Annotation featureValue2 : featureValues2) { 485 int result = compareAnnotations(featureValue1, featureValue2, complexFeatureSpanComparison, 486 complexFeatureClassComparison, simpleFeatureNamesOfComplexFeature); 487 if (result == MatchResult.NONTRIVIAL_MATCH) { 488 matchedFeature = featureValue2; 489 matchedFeatureResult = result; 490 break; 491 } else if (result == MatchResult.TRIVIAL_MATCH) { 492 matchedFeature = featureValue2; 493 matchedFeatureResult = result; 494 // do not break because we want to prefer NONTRIVIAL_MATCHes 495 } 496 } 497 if (matchedFeature != null) { 498 featureValues2.remove(matchedFeature); 499 if (matchedFeatureResult == MatchResult.TRIVIAL_MATCH) 500 trivialSimpleFeatureMatch = true; 501 } else { 502 return MatchResult.NONTRIVIAL_NONMATCH; 503 } 504 } 505 506 if (trivialSimpleFeatureMatch && trivialSimpleFeatureMatchesCauseTrivialMatch) { 507 return MatchResult.TRIVIAL_MATCH; 508 } 509 510 return MatchResult.NONTRIVIAL_MATCH; 511 512 } 513 514 public static final int SPANS_OVERLAP_COMPARISON = 1; 515 516 public static final int SPANS_EXACT_COMPARISON = 2; 517 518 public static final int IGNORE_SPANS_COMPARISON = 3; 519 520 /** 521 * This method compares two annotations with respect to their spans, 522 * annotation classes and simple features. 523 * 524 * @param annotation1 525 * @param annotation2 526 * @param spanComparison 527 * must be one of SPANS_OVERLAP_COMPARISON, 528 * SPANS_EXACT_COMPARISON, or IGNORE_SPANS_COMPARISON. If 529 * IGNORE_SPANS_COMPARISON is passed in, then the spans will be 530 * considered as matching. 531 * @param compareClass 532 * if true, then the classes will be compared and will be 533 * considered matched if they are the same. If false, then the 534 * classes will not be compared and will be considered as 535 * matching. 536 * @param simpleFeatureNames 537 * the simple features that will be compared. 538 * @return MatchResult.TRIVIAL_NONMATCH if the spans do not match. 539 * MatchResult.TRIVIAL_NONMATCH if the classes do not match. 540 * MatchResult.TRIVIAL_MATCH if spans and classes match and 541 * simpleFeatureNames is empty or null. If spans and classes match, 542 * then the result of compareSimpleFeatures(Annotation, Annotation, 543 * Set<String>) is returned. 544 * @see edu.uchsc.ccp.iaa.Annotation#compareSimpleFeatures(Annotation, 545 * Annotation, Set) 546 */ 547 548 public static int compareAnnotations(Annotation annotation1, Annotation annotation2, int spanComparison, 549 boolean compareClass, Set<String> simpleFeatureNames) { 550 boolean spansMatch = false; 551 boolean classesMatch = false; 552 553 if (spanComparison == SPANS_OVERLAP_COMPARISON && spansOverlap(annotation1, annotation2)) 554 spansMatch = true; 555 else if (spanComparison == SPANS_EXACT_COMPARISON && spansMatch(annotation1, annotation2)) 556 spansMatch = true; 557 else if (spanComparison == IGNORE_SPANS_COMPARISON) 558 spansMatch = true; 559 560 if (spanComparison != SPANS_OVERLAP_COMPARISON && spanComparison != SPANS_EXACT_COMPARISON 561 && spanComparison != IGNORE_SPANS_COMPARISON) 562 throw new IllegalArgumentException( 563 "The value for the parameter compareSpans is illegal. Please use one of SPANS_OVERLAP_COMPARISON, SPANS_EXACT_COMPARISON, or IGNORE_SPANS_COMPARISON."); 564 565 if (!spansMatch) 566 return MatchResult.TRIVIAL_NONMATCH; 567 568 if (compareClass && classesMatch(annotation1, annotation2)) 569 classesMatch = true; 570 else if (!compareClass) 571 classesMatch = true; 572 573 if (!classesMatch) 574 return MatchResult.TRIVIAL_NONMATCH; 575 576 return compareSimpleFeatures(annotation1, annotation2, simpleFeatureNames); 577 578 } 579 580 /** 581 * Returns the text covered by an annotation. 582 * 583 * @param annotation 584 * an annotation that has spans corresponding to extents of 585 * annotationText 586 * @param annotationText 587 * the text from which an annotation corresponds to. 588 * @param spanSeparator 589 * if more than one span exists, then this String will be 590 * inserted between each segment of text. 591 * @return the text covered by an annotation. 592 */ 593 public static String getCoveredText(Annotation annotation, String annotationText, String spanSeparator) { 594 List<Span> spans = annotation.getSpans(); 595 if (spans == null || spans.size() == 0) 596 return ""; 597 else if (spans.size() == 1) { 598 return Span.substring(annotationText, spans.get(0)); 599 } else { 600 StringBuffer sb = new StringBuffer(); 601 sb.append(Span.substring(annotationText, spans.get(0))); 602 for (int i = 1; i < spans.size(); i++) { 603 sb.append(spanSeparator); 604 sb.append(Span.substring(annotationText, spans.get(i))); 605 } 606 return sb.toString(); 607 } 608 } 609 610 /** 611 * @return the size of the span associated with the annotation. If the 612 * annotation has more than one span, then the sum of the size of 613 * the spans is returned. 614 */ 615 616 public int getSize() { 617 if (size == 0) { 618 List<Span> spans = getSpans(); 619 for (Span span : spans) { 620 size += span.getSize(); 621 } 622 return size; 623 } else 624 return size; 625 } 626 627 /** 628 * This method returns the shortest annotation - that is the annotation 629 * whose span is the shortest. If an annotation has more than one span, then 630 * its size is the sum of the size of each of its spans. 631 * 632 * @param annotations 633 * @return will only return one annotation. In the case of a tie, will 634 * return the first annotation with the smallest size encountered 635 * during iteration. Returns null if annotations is null or empty. 636 */ 637 public static Annotation getShortestAnnotation(Collection<Annotation> annotations) { 638 if (annotations == null || annotations.size() == 0) 639 return null; 640 641 Annotation shortestAnnotation = null; 642 int shortestAnnotationLength = -1; 643 644 for (Annotation annotation : annotations) { 645 int annotationSize = annotation.getSize(); 646 if (shortestAnnotationLength == -1 || annotationSize < shortestAnnotationLength) { 647 shortestAnnotation = annotation; 648 shortestAnnotationLength = annotationSize; 649 } 650 } 651 return shortestAnnotation; 652 } 653 654 /** 655 * this needs to be moved out of this class 656 * 657 * @return an html representation of the annotation 658 */ 659 public String toHTML() { 660 return toHTML(true); 661 } 662 663 public String toHTML(boolean printComplexFeatures) { 664 StringBuffer sb = new StringBuffer(); 665 sb.append("<ul><li>" + setName + "</li>"); 666 sb.append("<li>class = " + annotationClass + "</li>"); 667 sb.append("<li>spans = "); 668 for (Span span : spans) 669 sb.append(span.toString() + " "); 670 sb.append("</li>"); 671 672 if (simpleFeatures.size() > 0) { 673 for (String featureName : simpleFeatures.keySet()) { 674 sb.append("<li>" + featureName + " = <b>" + simpleFeatures.get(featureName) + "</b></li>"); 675 } 676 } 677 if (printComplexFeatures && complexFeatures.size() > 0) { 678 for (String featureName : complexFeatures.keySet()) { 679 sb.append("<li>" + featureName + " = "); 680 Set<Annotation> features = complexFeatures.get(featureName); 681 for (Annotation feature : features) { 682 sb.append(feature.toHTML(false)); 683 } 684 } 685 } 686 sb.append("</ul>"); 687 return sb.toString(); 688 } 689 690 }