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 }