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    }