Utils.java
0001 /*
0002  *  Utils.java
0003  *
0004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
0005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0008  *  software, licenced under the GNU Library General Public License,
0009  *  Version 2, June 1991 (in the distribution annotationSet file licence.html,
0010  *  and also available at http://gate.ac.uk/gate/licence.html).
0011  *
0012  *  Johann Petrak, 2010-02-05
0013  *
0014  */
0015 
0016 package gate;
0017 
0018 import gate.annotation.AnnotationSetImpl;
0019 import gate.annotation.ImmutableAnnotationSetImpl;
0020 import gate.creole.ConditionalSerialController;
0021 import gate.creole.RunningStrategy;
0022 import gate.util.FeatureBearer;
0023 import gate.util.GateRuntimeException;
0024 import gate.util.InvalidOffsetException;
0025 import gate.util.OffsetComparator;
0026 
0027 import java.io.File;
0028 import java.util.ArrayList;
0029 import java.util.Arrays;
0030 import java.util.Collection;
0031 import java.util.Collections;
0032 import java.util.HashSet;
0033 import java.util.Iterator;
0034 import java.util.List;
0035 import java.util.Map;
0036 import java.util.Set;
0037 import java.util.regex.Matcher;
0038 import java.util.regex.Pattern;
0039 
0040 import org.apache.log4j.Level;
0041 import org.apache.log4j.Logger;
0042 
0043 /**
0044  * Various utility methods to make often-needed tasks more easy and
0045  * using up less code.  In Java code (or JAPE grammars) you may wish to
0046  <code>import static gate.Utils.*</code> to access these methods without
0047  * having to qualify them with a class name.  In Groovy code, this class can be
0048  * used as a category to inject each utility method into the class of its first
0049  * argument, e.g.
0050  <pre>
0051  * Document doc = // ...
0052  * Annotation ann = // ...
0053  * use(gate.Utils) {
0054  *   println "Annotation has ${ann.length()} characters"
0055  *   println "and covers the string \"${doc.stringFor(ann)}\""
0056  * }
0057  </pre>
0058  *
0059  @author Johann Petrak, Ian Roberts
0060  */
0061 public class Utils {
0062   /**
0063    * Return the length of the document content covered by an Annotation as an
0064    * int -- if the content is too long for an int, the method will throw
0065    * a GateRuntimeException. Use getLengthLong(SimpleAnnotation ann) if
0066    * this situation could occur.
0067    @param ann the annotation for which to determine the length
0068    @return the length of the document content covered by this annotation.
0069    */
0070   public static int length(SimpleAnnotation ann) {
0071     long len = lengthLong(ann);
0072     if (len > java.lang.Integer.MAX_VALUE) {
0073       throw new GateRuntimeException(
0074               "Length of annotation too big to be returned as an int: "+len);
0075     else {
0076       return (int)len;
0077     }
0078   }
0079 
0080   /**
0081    * Return the length of the document content covered by an Annotation as a
0082    * long.
0083    @param ann the annotation for which to determine the length
0084    @return the length of the document content covered by this annotation.
0085    */
0086   public static long lengthLong(SimpleAnnotation ann) {
0087     return ann.getEndNode().getOffset() -
0088        ann.getStartNode().getOffset();
0089   }
0090 
0091   /**
0092    * Return the length of the document as an
0093    * int -- if the content is too long for an int, the method will throw a
0094    * GateRuntimeException. Use getLengthLong(Document doc) if
0095    * this situation could occur.
0096    @param doc the document for which to determine the length
0097    @return the length of the document content.
0098    */
0099   public static int length(Document doc) {
0100     long len = doc.getContent().size();
0101     if (len > java.lang.Integer.MAX_VALUE) {
0102       throw new GateRuntimeException(
0103               "Length of document too big to be returned as an int: "+len);
0104     else {
0105       return (int)len;
0106     }
0107   }
0108 
0109   /**
0110    * Return the length of the document as a long.
0111    @param doc the document for which to determine the length
0112    @return the length of the document content.
0113    */
0114   public static long lengthLong(Document doc) {
0115     return doc.getContent().size();
0116   }
0117 
0118   /**
0119    * Return the DocumentContent corresponding to the annotation.
0120    <p>
0121    * Note: the DocumentContent object returned will also contain the
0122    * original content which can be accessed using the getOriginalContent()
0123    * method.
0124    @param doc the document from which to extract the content
0125    @param ann the annotation for which to return the content.
0126    @return a DocumentContent representing the content spanned by the annotation.
0127    */
0128   public static DocumentContent contentFor(
0129           SimpleDocument doc, SimpleAnnotation ann) {
0130     try {
0131       return doc.getContent().getContent(
0132               ann.getStartNode().getOffset(),
0133               ann.getEndNode().getOffset());
0134     catch(gate.util.InvalidOffsetException ex) {
0135       throw new GateRuntimeException(ex.getMessage());
0136     }
0137   }
0138 
0139   /**
0140    * Return the document text as a String corresponding to the annotation.
0141    @param doc the document from which to extract the document text
0142    @param ann the annotation for which to return the text.
0143    @return a String representing the text content spanned by the annotation.
0144    */
0145   public static String stringFor(
0146           Document doc, SimpleAnnotation ann) {
0147     try {
0148       return doc.getContent().getContent(
0149               ann.getStartNode().getOffset(),
0150               ann.getEndNode().getOffset()).toString();
0151     catch(gate.util.InvalidOffsetException ex) {
0152       throw new GateRuntimeException(ex.getMessage(),ex);
0153     }
0154   }
0155 
0156   
0157   /**
0158    * Return the cleaned document text as a String corresponding to the annotation.
0159    * (Delete leading and trailing whitespace; normalize 
0160    * internal whitespace to single spaces.)
0161    @param doc the document from which to extract the document text
0162    @param ann the annotation for which to return the text.
0163    @return a String representing the text content spanned by the annotation.
0164    */
0165   public static String cleanStringFor(Document doc, SimpleAnnotation ann) {
0166     return cleanString(stringFor(doc, ann));
0167   }
0168   
0169   /**
0170    * Returns the document text between the provided offsets.
0171    @param doc the document from which to extract the document text
0172    @param start the start offset 
0173    @param end the end offset
0174    @return document text between the provided offsets
0175    */
0176   public static String stringFor(
0177           Document doc, Long start, Long end) {
0178     try {
0179       return doc.getContent().getContent(
0180               start,
0181               end).toString();
0182     catch(gate.util.InvalidOffsetException ex) {
0183       throw new GateRuntimeException(ex.getMessage());
0184     }
0185   }
0186 
0187   
0188   /**
0189    * Return the cleaned document text between the provided offsets.
0190    * (Delete leading and trailing whitespace; normalize 
0191    * internal whitespace to single spaces.)
0192    @param doc the document from which to extract the document text
0193    @param start the start offset 
0194    @param end the end offset
0195    @return document text between the provided offsets
0196    */
0197   public static String cleanStringFor(Document doc, Long start, Long end) {
0198     return cleanString(stringFor(doc, start, end));
0199   }
0200   
0201   /**
0202    * Return the DocumentContent covered by the given annotation set.
0203    <p>
0204    * Note: the DocumentContent object returned will also contain the
0205    * original content which can be accessed using the getOriginalContent()
0206    * method.
0207    @param doc the document from which to extract the content
0208    @param anns the annotation set for which to return the content.
0209    @return a DocumentContent representing the content spanned by the
0210    * annotation set.
0211    */
0212   public static DocumentContent contentFor(
0213           SimpleDocument doc, AnnotationSet anns) {
0214     try {
0215       return doc.getContent().getContent(
0216               anns.firstNode().getOffset(),
0217               anns.lastNode().getOffset());
0218     catch(gate.util.InvalidOffsetException ex) {
0219       throw new GateRuntimeException(ex.getMessage());
0220     }
0221   }
0222 
0223   /**
0224    * Return the document text as a String covered by the given annotation set.
0225    @param doc the document from which to extract the document text
0226    @param anns the annotation set for which to return the text.
0227    @return a String representing the text content spanned by the annotation
0228    * set.
0229    */
0230   public static String stringFor(
0231           Document doc, AnnotationSet anns) {
0232     try {
0233       return doc.getContent().getContent(
0234               anns.firstNode().getOffset(),
0235               anns.lastNode().getOffset()).toString();
0236     catch(gate.util.InvalidOffsetException ex) {
0237       throw new GateRuntimeException(ex.getMessage());
0238     }
0239   }
0240 
0241   /**
0242    * Return the cleaned document text as a String covered by the given annotation set.
0243    * (Delete leading and trailing whitespace; normalize 
0244    * internal whitespace to single spaces.)
0245    @param doc the document from which to extract the document text
0246    @param anns the annotation set for which to return the text.
0247    @return a String representing the text content spanned by the annotation
0248    * set.
0249    */
0250   public static String cleanStringFor(Document doc, AnnotationSet anns) {
0251     return cleanString(stringFor(doc, anns));
0252   }
0253   
0254   /**
0255    * Return a cleaned version of the input String. (Delete leading and trailing
0256    * whitespace; normalize internal whitespace to single spaces; return an
0257    * empty String if the input contains nothing but whitespace, but null
0258    * if the input is null.)
0259    @return a cleaned version of the input String.
0260    */
0261   public static String cleanString(String input) {
0262     if (input == null) {
0263       return null;
0264     }
0265     // implied else
0266     return input.replaceAll("\\s+"" ").trim();
0267   }
0268   
0269   /**
0270    * Get the start offset of an annotation.
0271    */
0272   public static Long start(SimpleAnnotation a) {
0273     return (a.getStartNode() == nullnull : a.getStartNode().getOffset();
0274   }
0275 
0276   /**
0277    * Get the start offset of an annotation set.
0278    */
0279   public static Long start(AnnotationSet as) {
0280     return (as.firstNode() == nullnull : as.firstNode().getOffset();
0281   }
0282 
0283   /**
0284    * Get the start offset of a document (i.e. 0L).
0285    */
0286   public static Long start(SimpleDocument d) {
0287     return Long.valueOf(0L);
0288   }
0289 
0290   /**
0291    * Get the end offset of an annotation.
0292    */
0293   public static Long end(SimpleAnnotation a) {
0294     return (a.getEndNode() == nullnull : a.getEndNode().getOffset();
0295   }
0296 
0297   /**
0298    * Get the end offset of an annotation set.
0299    */
0300   public static Long end(AnnotationSet as) {
0301     return (as.lastNode() == nullnull : as.lastNode().getOffset();
0302   }
0303 
0304   /**
0305    * Get the end offset of a document.
0306    */
0307   public static Long end(SimpleDocument d) {
0308     return d.getContent().size();
0309   }
0310 
0311   /**
0312    * Return a the subset of annotations from the given annotation set
0313    * that start exactly at the given offset.
0314    *
0315    @param annotationSet the set of annotations from which to select
0316    @param atOffset the offset where the annoation to be returned should start
0317    @return an annotation set containing all the annotations from the original
0318    * set that start at the given offset
0319    */
0320   public static AnnotationSet getAnnotationsAtOffset(
0321           AnnotationSet annotationSet, Long atOffset) {
0322     // this returns all annotations that start at this atOffset OR AFTER!
0323     AnnotationSet tmp = annotationSet.get(atOffset);
0324     // so lets filter ...
0325     AnnotationSet ret = new AnnotationSetImpl(annotationSet.getDocument());
0326     Iterator<Annotation> it = tmp.iterator();
0327     while(it.hasNext()) {
0328       Annotation ann = it.next();
0329       if(ann.getStartNode().getOffset().equals(atOffset)) {
0330         ret.add(ann);
0331       }
0332     }
0333     return ret;
0334   }
0335 
0336   /**
0337    * Get all the annotations from the source annotation set that lie within
0338    * the range of the containing annotation.
0339    
0340    @param sourceAnnotationSet the annotation set from which to select
0341    @param containingAnnotation the annotation whose range must contain the
0342    * selected annotations
0343    @return the AnnotationSet containing all annotations fully contained in
0344    * the offset range of the containingAnnotation
0345    */
0346   public static AnnotationSet getContainedAnnotations(
0347     AnnotationSet sourceAnnotationSet,
0348     Annotation containingAnnotation) {
0349     return getContainedAnnotations(sourceAnnotationSet,containingAnnotation,"");
0350   }
0351 
0352   /**
0353    * Get all the annotations of type targetType
0354    * from the source annotation set that lie within
0355    * the range of the containing annotation.
0356    *
0357    @param sourceAnnotationSet the annotation set from which to select
0358    @param containingAnnotation the annotation whose range must contain the
0359    @param targetType the type the selected annotations must have. If the
0360    * empty string, no filtering on type is done.
0361    @return the AnnotationSet containing all annotations fully contained in
0362    * the offset range of the containingAnnotation
0363    */
0364   public static AnnotationSet getContainedAnnotations(
0365     AnnotationSet sourceAnnotationSet,
0366     Annotation containingAnnotation,
0367     String targetType) {
0368     if(targetType.equals("")) {
0369       return sourceAnnotationSet.getContained(
0370         containingAnnotation.getStartNode().getOffset(),
0371         containingAnnotation.getEndNode().getOffset());
0372     else {
0373       return sourceAnnotationSet.getContained(
0374         containingAnnotation.getStartNode().getOffset(),
0375         containingAnnotation.getEndNode().getOffset()).get(targetType);
0376     }
0377   }
0378 
0379   /**
0380    * Get all the annotations from the source annotation set that lie within
0381    * the range of the containing annotation set, i.e. within the offset range
0382    * between the start of the first annotation in the containing set and the
0383    * end of the last annotation in the annotation set. If the containing
0384    * annotation set is empty, an empty set is returned.
0385    *
0386    @param sourceAnnotationSet the annotation set from which to select
0387    @param containingAnnotationSet the annotation set whose range must contain
0388    * the selected annotations
0389    @return the AnnotationSet containing all annotations fully contained in
0390    * the offset range of the containingAnnotationSet
0391    */
0392   public static AnnotationSet getContainedAnnotations(
0393     AnnotationSet sourceAnnotationSet,
0394     AnnotationSet containingAnnotationSet) {
0395     return getContainedAnnotations(sourceAnnotationSet,containingAnnotationSet,"");
0396   }
0397 
0398   /**
0399    * Get all the annotations from the source annotation set with a type equal to
0400    * targetType that lie within
0401    * the range of the containing annotation set, i.e. within the offset range
0402    * between the start of the first annotation in the containing set and the
0403    * end of the last annotation in the annotation set. If the containing
0404    * annotation set is empty, an empty set is returned.
0405    *
0406    @param sourceAnnotationSet the annotation set from which to select
0407    @param containingAnnotationSet the annotation set whose range must contain
0408    * the selected annotations
0409    @param targetType the type the selected annotations must have
0410    @return the AnnotationSet containing all annotations fully contained in
0411    * the offset range of the containingAnnotationSet
0412    */
0413   public static AnnotationSet getContainedAnnotations(
0414     AnnotationSet sourceAnnotationSet,
0415     AnnotationSet containingAnnotationSet,
0416     String targetType) {
0417     if(containingAnnotationSet.isEmpty() || sourceAnnotationSet.isEmpty()) {
0418       return Factory.createImmutableAnnotationSet(sourceAnnotationSet.getDocument()null);
0419     }
0420     if(targetType.equals("")) {
0421       return sourceAnnotationSet.getContained(
0422         containingAnnotationSet.firstNode().getOffset(),
0423         containingAnnotationSet.lastNode().getOffset());
0424     else {
0425       return sourceAnnotationSet.getContained(
0426         containingAnnotationSet.firstNode().getOffset(),
0427         containingAnnotationSet.lastNode().getOffset()).get(targetType);
0428     }
0429   }
0430 
0431   
0432   /**
0433    * Get all the annotations from the source annotation set that cover
0434    * the range of the specified annotation.
0435    
0436    @param sourceAnnotationSet the annotation set from which to select
0437    @param coveredAnnotation the annotation whose range must equal or lie within
0438    * the selected annotations
0439    @return the AnnotationSet containing all annotations that fully cover
0440    * the offset range of the coveredAnnotation
0441    */
0442   public static AnnotationSet getCoveringAnnotations(
0443     AnnotationSet sourceAnnotationSet,
0444     Annotation coveredAnnotation) {
0445     return getCoveringAnnotations(sourceAnnotationSet,coveredAnnotation,"");
0446   }
0447 
0448   /**
0449    * Get all the annotations of type targetType
0450    * from the source annotation set that cover
0451    * the range of the specified annotation.
0452    *
0453    @param sourceAnnotationSet the annotation set from which to select
0454    @param coveredAnnotation the annotation whose range must be covered
0455    @param targetType the type the selected annotations must have. If the
0456    * empty string, no filtering on type is done.
0457    @return the AnnotationSet containing all annotations that fully cover
0458    * the offset range of the coveredAnnotation
0459    */
0460   public static AnnotationSet getCoveringAnnotations(
0461     AnnotationSet sourceAnnotationSet,
0462     Annotation coveredAnnotation,
0463     String targetType) {
0464     return sourceAnnotationSet.getCovering(targetType,
0465         coveredAnnotation.getStartNode().getOffset(),
0466         coveredAnnotation.getEndNode().getOffset());
0467   }
0468 
0469   /**
0470    * Get all the annotations from the source annotation set that cover
0471    * the range of the specified annotation set. If the covered
0472    * annotation set is empty, an empty set is returned.
0473    *
0474    @param sourceAnnotationSet the annotation set from which to select
0475    @param coveredAnnotationSet the annotation set whose range must be covered by
0476    * the selected annotations
0477    @return the AnnotationSet containing all annotations that fully cover
0478    * the offset range of the containingAnnotationSet
0479    */
0480   public static AnnotationSet getCoveringAnnotations(
0481     AnnotationSet sourceAnnotationSet,
0482     AnnotationSet coveredAnnotationSet) {
0483     return getCoveringAnnotations(sourceAnnotationSet,coveredAnnotationSet,"");
0484   }
0485 
0486   /**
0487    * Get all the annotations from the source annotation set with a type equal to
0488    * targetType that cover
0489    * the range of the specified annotation set. If the specified
0490    * annotation set is empty, an empty set is returned.
0491    *
0492    @param sourceAnnotationSet the annotation set from which to select
0493    @param coveredAnnotationSet the annotation set whose range must
0494    * be covered by the selected annotations
0495    @param targetType the type the selected annotations must have
0496    @return the AnnotationSet containing all annotations that fully cover
0497    * the offset range of the containingAnnotationSet
0498    */
0499   public static AnnotationSet getCoveringAnnotations(
0500     AnnotationSet sourceAnnotationSet,
0501     AnnotationSet coveredAnnotationSet,
0502     String targetType) {
0503     if(coveredAnnotationSet.isEmpty() || sourceAnnotationSet.isEmpty()) {
0504       return Factory.createImmutableAnnotationSet(sourceAnnotationSet.getDocument()null);
0505     }
0506     return sourceAnnotationSet.getCovering(targetType,
0507         coveredAnnotationSet.firstNode().getOffset(),
0508         coveredAnnotationSet.lastNode().getOffset());
0509   }
0510 
0511   
0512   
0513   
0514   /**
0515    * Get all the annotations from the source annotation set that
0516    * partly or totally overlap
0517    * the range of the specified annotation.
0518    
0519    @param sourceAnnotationSet the annotation set from which to select
0520    @param overlappedAnnotation the annotation whose range the selected
0521    * annotations must overlap
0522    @return the AnnotationSet containing all annotations that fully cover
0523    * the offset range of the coveredAnnotation
0524    */
0525   public static AnnotationSet getOverlappingAnnotations(
0526     AnnotationSet sourceAnnotationSet,
0527     Annotation overlappedAnnotation) {
0528     return getOverlappingAnnotations(sourceAnnotationSet,overlappedAnnotation,"");
0529   }
0530 
0531   /**
0532    * Get all the annotations of type targetType
0533    * from the source annotation set that partly or totally overlap
0534    * the range of the specified annotation.
0535    *
0536    @param sourceAnnotationSet the annotation set from which to select
0537    @param overlappedAnnotation the annotation whose range the selected
0538    * annotations must overlap
0539    @param targetType the type the selected annotations must have. If the
0540    * empty string, no filtering on type is done.
0541    @return the AnnotationSet containing all annotations that fully cover
0542    * the offset range of the coveredAnnotation
0543    */
0544   public static AnnotationSet getOverlappingAnnotations(
0545     AnnotationSet sourceAnnotationSet,
0546     Annotation overlappedAnnotation,
0547     String targetType) {
0548     
0549     
0550     if ( (targetType == null|| targetType.isEmpty()) {
0551       return sourceAnnotationSet.get(overlappedAnnotation.getStartNode().getOffset(),
0552           overlappedAnnotation.getEndNode().getOffset());
0553     }
0554     
0555     return sourceAnnotationSet.get(targetType,
0556         overlappedAnnotation.getStartNode().getOffset(),
0557         overlappedAnnotation.getEndNode().getOffset());
0558   }
0559 
0560   /**
0561    * Get all the annotations from the source annotation set that overlap
0562    * the range of the specified annotation set. If the overlapped
0563    * annotation set is empty, an empty set is returned.
0564    *
0565    @param sourceAnnotationSet the annotation set from which to select
0566    @param overlappedAnnotationSet the annotation set whose range must
0567    * be overlapped by the selected annotations
0568    @return the AnnotationSet containing all annotations that fully cover
0569    * the offset range of the containingAnnotationSet
0570    */
0571   public static AnnotationSet getOverlappingAnnotations(
0572     AnnotationSet sourceAnnotationSet,
0573     AnnotationSet overlappedAnnotationSet) {
0574     return getOverlappingAnnotations(sourceAnnotationSet,overlappedAnnotationSet,"");
0575   }
0576 
0577   
0578   /**
0579    * Get all the annotations from the source annotation set with a type equal to
0580    * targetType that partly or completely overlap the range of the specified 
0581    * annotation set. If the specified annotation set is empty, an empty 
0582    * set is returned.
0583    *
0584    @param sourceAnnotationSet the annotation set from which to select
0585    @param overlappedAnnotationSet the annotation set whose range must
0586    * be overlapped by the selected annotations
0587    @param targetType the type the selected annotations must have
0588    @return the AnnotationSet containing all annotations that partly or fully
0589    * overlap the offset range of the containingAnnotationSet
0590    */
0591   public static AnnotationSet getOverlappingAnnotations(
0592     AnnotationSet sourceAnnotationSet,
0593     AnnotationSet overlappedAnnotationSet,
0594     String targetType) {
0595     if(overlappedAnnotationSet.isEmpty() || sourceAnnotationSet.isEmpty()) {
0596       return Factory.createImmutableAnnotationSet(sourceAnnotationSet.getDocument()null);
0597     }
0598     
0599     if ( (targetType == null|| targetType.isEmpty()) {
0600       return sourceAnnotationSet.get(overlappedAnnotationSet.firstNode().getOffset(),
0601           overlappedAnnotationSet.lastNode().getOffset());
0602     }
0603     
0604     return sourceAnnotationSet.get(targetType,
0605         overlappedAnnotationSet.firstNode().getOffset(),
0606         overlappedAnnotationSet.lastNode().getOffset());
0607   }
0608 
0609 
0610   /**
0611    * Return a List containing the annotations in the given annotation set, in
0612    * document order (i.e. increasing order of start offset).
0613    *
0614    @param as the annotation set
0615    @return a list containing the annotations from <code>as</code> in document
0616    * order.
0617    */
0618   public static List<Annotation> inDocumentOrder(AnnotationSet as) {
0619     List<Annotation> ret = new ArrayList<Annotation>();
0620     if(as != null) {
0621       ret.addAll(as);
0622       Collections.sort(ret, OFFSET_COMPARATOR);
0623     }
0624     return ret;
0625   }
0626 
0627   /**
0628    * A single instance of {@link OffsetComparator} that can be used by any code
0629    * that requires one.
0630    */
0631   public static final OffsetComparator OFFSET_COMPARATOR =
0632           new OffsetComparator();
0633 
0634   /**
0635    * Create a feature map from an array of values.  The array must have an even
0636    * number of items, alternating keys and values i.e. [key1, value1, key2,
0637    * value2, ...].
0638    *
0639    @param values an even number of items, alternating keys and values.
0640    @return a feature map containing the given items.
0641    */
0642   public static FeatureMap featureMap(Object... values) {
0643     FeatureMap fm = Factory.newFeatureMap();
0644     if(values != null) {
0645       for(int i = 0; i < values.length; i++) {
0646         fm.put(values[i], values[++i]);
0647       }
0648     }
0649     return fm;
0650   }
0651 
0652   /**
0653    * Create a feature map from an existing map (typically one that does not
0654    * itself implement FeatureMap).
0655    *
0656    @param map the map to convert.
0657    @return a new FeatureMap containing the same mappings as the source map.
0658    */
0659   public static FeatureMap toFeatureMap(Map<?,?> map) {
0660     FeatureMap fm = Factory.newFeatureMap();
0661     fm.putAll(map);
0662     return fm;
0663   }
0664   
0665   /** 
0666    * This method can be used to check if a ProcessingResource has 
0667    * a chance to be run in the given controller with the current settings.
0668    <p>
0669    * That means that for a non-conditional controller, the method will return
0670    * true if the PR is part of the controller. For a conditional controller,
0671    * the method will return true if it is part of the controller and at least
0672    * once (if the same PR is contained multiple times) it is not disabled.
0673    
0674    @param controller 
0675    @param pr
0676    @return true or false depending on the conditions explained above.
0677    */
0678   public static boolean isEnabled(Controller controller, ProcessingResource pr) {
0679     Collection<ProcessingResource> prs = controller.getPRs();
0680     if(!prs.contains(pr)) {
0681       return false;
0682     }
0683     if(controller instanceof ConditionalSerialController) {
0684       Collection<RunningStrategy> rss = 
0685         ((ConditionalSerialController)controller).getRunningStrategies();
0686       for(RunningStrategy rs : rss) {
0687         // if we find at least one occurrence of the PR that is not disabled
0688         // return true
0689         if(rs.getPR().equals(pr&& 
0690            rs.getRunMode() != RunningStrategy.RUN_NEVER) {
0691           return true;
0692         }
0693       }
0694       // if we get here, no occurrence of the PR has found or none that
0695       // is not disabled, so return false
0696       return false;
0697     }
0698     return true;
0699   }
0700   
0701   /**
0702    * Return the running strategy of the PR in the controller, if the controller
0703    * is a conditional controller. If the controller is not a conditional 
0704    * controller, null is returned. If the controller is a conditional controller
0705    * and the PR is contained multiple times, the running strategy for the 
0706    * first occurrence the is found is returned.
0707    
0708    @param controller
0709    @param pr
0710    @return A RunningStrategy object or null
0711    */
0712   public static RunningStrategy getRunningStrategy(Controller controller,
0713           ProcessingResource pr) {
0714     if(controller instanceof ConditionalSerialController) {
0715       Collection<RunningStrategy> rss = 
0716         ((ConditionalSerialController)controller).getRunningStrategies();
0717       for(RunningStrategy rs : rss) {
0718         if(rs.getPR() == pr) {
0719           return rs;
0720         }
0721       
0722     }
0723     return null;
0724   }
0725   
0726   /**
0727    * Issue a message to the log but only if the same message has not
0728    * been logged already in the same GATE session.
0729    * This is intended for explanations or warnings that should not be 
0730    * repeated every time the same situation occurs.
0731    
0732    @param logger - the logger instance to use
0733    @param level  - the severity level for the message
0734    @param message - the message itself
0735    */
0736   public static void logOnce (Logger logger, Level level, String message) {
0737     if(!alreadyLoggedMessages.contains(message)) { 
0738       logger.log(level, message);
0739       alreadyLoggedMessages.add(message);
0740     }
0741   }
0742 
0743   /**
0744    * Check if a message has already been logged or shown. This does not log
0745    * or show anything but only stores the message as one that has been shown
0746    * already if necessary and returns if the message has been shown or not.
0747    *
0748    @param message - the message that should only be logged or shown once
0749    @return - true if the message has already been logged or checked with
0750    * this method.
0751    *
0752    */
0753    public static boolean isLoggedOnce(String message) {
0754      boolean isThere = alreadyLoggedMessages.contains(message);
0755      if(!isThere) {
0756        alreadyLoggedMessages.add(message);
0757      }
0758      return isThere;
0759    }
0760 
0761   private static final Set<String> alreadyLoggedMessages = 
0762     Collections.synchronizedSet(new HashSet<String>());
0763 
0764   
0765   /**
0766    * Returns the only annotation that annset is expected to contains, throws an
0767    * exception if there is not exactly one annotation. This is useful when a
0768    * binding set is expected to contain exactly one interesting annotation.
0769    
0770    @param annset the annotation set that is expected to contain exactly one annotation  
0771    @return the one annotation or throws an exception if there are 0 or more than one annotations
0772    
0773    */
0774   public static Annotation getOnlyAnn(AnnotationSet annset) {
0775     if (annset.size() != 1) {
0776       throw new GateRuntimeException(
0777           "Annotation set does not contain exactly 1 annotation but "
0778               + annset.size());
0779     else {
0780       return annset.iterator().next();
0781     }
0782   }
0783 
0784   /**
0785    * Add a new annotation to the output annotation set outSet, spanning the same
0786    * region as spanSet, and having the given type and feature map. The start and
0787    * end nodes of the new annotation will be new nodes. This method will convert
0788    * the checked InvalidOffsetException that can be raised by 
0789    * AnnotationSet.add to a GateRuntimeException.
0790    
0791    @param outSet the annotation set where the new annotation will be added
0792    @param spanSet an annotation set representing the span of the new annotation
0793    @param type the annotation type of the new annotation
0794    @param fm the feature map to use for the new annotation
0795    @return Returns the Id of the added annotation
0796    */
0797   public static Integer addAnn(AnnotationSet outSet, AnnotationSet spanSet,
0798       String type, FeatureMap fm) {
0799     try {
0800       return outSet.add(start(spanSet), end(spanSet), type, fm);
0801     catch (InvalidOffsetException ex) {
0802       throw new GateRuntimeException("Offset error when trying to add new annotation: ", ex);
0803     }
0804   }
0805 
0806   /**
0807    * Add a new annotation to the output annotation set outSet, spanning the 
0808    * given offset range, and having the given type and feature map. The start and
0809    * end nodes of the new annotation will be new nodes. This method will convert
0810    * the checked InvalidOffsetException that can be raised by 
0811    * AnnotationSet.add to a GateRuntimeException.
0812    
0813    @param outSet outSet the annotation set where the new annotation will be added
0814    @param startOffset the start offset of the new annotation
0815    @param endOffset the end offset of the new annotation
0816    @param type the annotation type of the new annotation
0817    @param fm the feature map to use for the new annotation
0818    @return Returns the Id of the added annotation
0819    */
0820   public static Integer addAnn(AnnotationSet outSet, long startOffset, long endOffset,
0821       String type, FeatureMap fm) {
0822     try {
0823       return outSet.add(startOffset, endOffset, type, fm);
0824     catch (InvalidOffsetException ex) {
0825       throw new GateRuntimeException("Offset error when trying to add new annotation: ", ex);
0826     }
0827   }
0828 
0829   /**
0830    * Add a new annotation to the output annotation set outSet, covering the same
0831    * region as the annotation spanAnn, and having the given type and feature map. The start and
0832    * end nodes of the new annotation will be new nodes. This method will convert
0833    * the checked InvalidOffsetException that can be raised by 
0834    * AnnotationSet.add to a GateRuntimeException.
0835    
0836    @param outSet the annotation set where the new annotation will be added
0837    @param spanAnn an annotation representing the span of the new annotation
0838    @param type the annotation type of the new annotation
0839    @param fm the feature map to use for the new annotation
0840    @return Returns the Id of the added annotation
0841    */
0842   public static Integer addAnn(AnnotationSet outSet, Annotation spanAnn,
0843       String type, FeatureMap fm) {
0844     try {
0845       return outSet.add(start(spanAnn), end(spanAnn), type, fm);
0846     catch (InvalidOffsetException ex) {
0847       throw new GateRuntimeException("Offset error adding new annotation: ", ex);
0848     }
0849   }
0850   
0851   static private Pattern nsQNamePattern = Pattern.compile("^(.*:)(.+)$");
0852   /**
0853    * Expand both namespace prefixes and base-uris, if possible.
0854    * This will expand the String toExpand according to the following rules:
0855    <ul>
0856    <li>if toExpand is a qName and does start with a name prefix in the form
0857    * "somens:" or ":", then the name prefix is looked up in the prefixes
0858    * map and replaced with the URI prefix found there. If the prefix could not
0859    * be found a GateRuntimeException is thrown.
0860    <li>if toExpand does not start with a name prefix, the entry with
0861    * an empty string as the key is retrieved from the prefixes map and 
0862    * used as a baseURI: the result is the baseURI and the toExpand String
0863    * concatenated. If no entry with an empty string is found in the map, a 
0864    * GateRuntimeException is thrown.   * 
0865    </ul>
0866    
0867    * This method can therefore be used to expand both base uris and namespaces.
0868    <p>
0869    * If the map only contains a basename uri (if the only entry is for the
0870    * empty string key) then name space prefixes are not checked: in this 
0871    * case, the toExpand string may contain an unescaped colon. 
0872    * If the map does not contain a basename URI (if there is no entry for the
0873    * empty string key) then all toExpand strings are expected to be qNames.
0874    <p>
0875    * NOTE: the name prefixes in the prefixes map must include the trailing colon!
0876    
0877    @param toExpand the URI portion to expand as a String
0878    @param prefixes a map from name prefixes to URI prefixes
0879    @return a String with name prefixes or base URI expanded 
0880    */
0881   public static String expandUriString(String toExpand, Map<String,String> prefixes ) {
0882     // lets see if we have a basename entry in the map
0883     String baseUri = prefixes.get("");
0884     // if there is a baseURI and it is the only entry, just prefix toExpand with
0885     // it, no matter what
0886     if(baseUri != null && prefixes.size() == 1) {
0887       return baseUri+toExpand;
0888     }
0889     
0890     // if the toExpand string starts with .*:, interpret this as the name space
0891     Matcher m = nsQNamePattern.matcher(toExpand);
0892     if (m.matches()) {
0893       String prefix = m.group(1);
0894       String lname = m.group(2);
0895       String uriPrefix = prefixes.get(prefix);
0896       if(uriPrefix == null) {
0897         throw new GateRuntimeException("name prefix not found in prefix map for  "+toExpand);
0898       else {
0899         return uriPrefix+lname;
0900       }      
0901     else {
0902       // this is not a qName, try to expand with the baseURI
0903       if(baseUri == null) {
0904         throw new GateRuntimeException("No base Uri in prefix map for "+toExpand);
0905       else {
0906         return baseUri + toExpand;
0907       }
0908     }
0909   }
0910   /**
0911    * Compact an URI String using base URI and namespace prefixes.
0912    * The prefixes map, which maps name prefixes of the form "ns:" or the empty
0913    * string to URI prefixes is searched for the first URI prefix in the value
0914    * set that matches the beginning of the uriString. The corresponding name prefix
0915    * is then used to replace that URI prefix.
0916    * In order to control which URI prefix is matched first if the map contains
0917    * several prefixes which can all match some URIs, a LinkedHashMap can be 
0918    * used so that the first matching URI prefix will be deterministic.
0919    *  
0920    @param uriString a full URI String that should get shortened using prefix names or a base URI
0921    @param prefixes a map containing name prefixes mapped to URI prefixes (same as for expandUriString)
0922    @return a shortened URI where the URI prefix is replaced with a prefix name or the empty string
0923    */
0924   public static String shortenUriString(String uriString, Map<String, String> prefixes) {
0925     // get the URI prefixes 
0926     Set<String> namePrefixes = prefixes.keySet();
0927     String uriPrefix = "";
0928     String namePrefix = "";
0929     for(String np : namePrefixes) {
0930       String uri = prefixes.get(np);
0931       if(uriString.startsWith(uri)) {
0932         uriPrefix = uri;
0933         namePrefix = np;
0934         break;
0935       }
0936     }
0937     if(uriPrefix.equals("")) {
0938       throw new GateRuntimeException("No prefix found in prefixes map for "+uriString);
0939     }
0940     return namePrefix + uriString.substring(uriPrefix.length());
0941   }
0942   
0943   /**
0944    * Get all the annotations from the source annotation set that start and end 
0945    * at exactly the same offsets as the given annotation set.
0946    
0947    @param source the annotation set from which to select
0948    @param coextSet the annotation set from which to take the start and end offsets
0949    @return the AnnotationSet containing all annotations exactly coextensive with coextSet
0950    */
0951   public static AnnotationSet getCoextensiveAnnotations(AnnotationSet source, AnnotationSet coextSet) {
0952     return getCoextensiveAnnotationsWorker(source, null, start(coextSet), end(coextSet));
0953   }
0954   /**
0955    * Get all the annotations from the source annotation set that start and end 
0956    * at exactly the same offsets as the given annotation set and are of the 
0957    * specified type.
0958    
0959    @param source the annotation set from which to select
0960    @param coextSet the annotation set from which to take the start and end offsets
0961    @param type the desired annotation type of the annotations to return
0962    @return the AnnotationSet containing all annotations exactly coextensive with coextSet and of the
0963    * specified type
0964    */
0965   public static AnnotationSet getCoextensiveAnnotations(AnnotationSet source, AnnotationSet coextSet, String type) {
0966     return getCoextensiveAnnotationsWorker(source, type, start(coextSet), end(coextSet));
0967   }
0968   /**
0969    * Get all the annotations from the source annotation set that start and end 
0970    * at exactly the same offsets as the given annotation.
0971    
0972    @param source the annotation set from which to select
0973    @param coextAnn the annotation from which to take the start and end offsets
0974    @return the AnnotationSet containing all annotations exactly coextensive with coextAnn
0975    */
0976   public static AnnotationSet getCoextensiveAnnotations(AnnotationSet source, Annotation coextAnn) {
0977     return getCoextensiveAnnotationsWorker(source, null, start(coextAnn), end(coextAnn));
0978   }
0979   /**
0980    * Get all the annotations from the source annotation set that start and end 
0981    * at exactly the same offsets as the given annotation and have the specified type.
0982    
0983    @param source the annotation set from which to select
0984    @param coextAnn the annotation from which to take the start and end offsets
0985    @return the AnnotationSet containing all annotations exactly coextensive with coextAnn and 
0986    * having the specified type.
0987    */
0988   public static AnnotationSet getCoextensiveAnnotations(AnnotationSet source, Annotation coextAnn, String type) {
0989     return getCoextensiveAnnotationsWorker(source, type, start(coextAnn), end(coextAnn));
0990   }
0991 
0992   private static AnnotationSet getCoextensiveAnnotationsWorker(AnnotationSet source,
0993       String type, long start, long end) {
0994     if (source instanceof gate.annotation.AnnotationSetImpl) {
0995       AnnotationSet ret = ((AnnotationSetImplsource).getStrict(start,
0996           end);
0997       if (type != null) {
0998         return ret.get(type);
0999       else {
1000         return ret;
1001       }
1002     else {
1003       AnnotationSet annset = source.getContained(start, end);
1004       List<Annotation> annotationsToAdd = new ArrayList<Annotation>();
1005       for (Annotation ann : annset) {
1006         if (start(ann== start && end(ann== end) {
1007           if (type == null || ann.getType().equals(type)) {
1008             annotationsToAdd.add(ann);
1009           }
1010         }
1011       }
1012       return Factory.createImmutableAnnotationSet(source.getDocument(), annotationsToAdd);
1013     }
1014   }
1015   
1016   /**
1017    * This will replace all occurrences of variables of the form $env{name}, 
1018    * $prop{name}, $doc{featname}, $pr_parm{inputAS} or $$env{name} etc in a String.
1019    
1020    * The source for replacing the variable can be  environment variables,
1021    * system properties, or arbitrary maps or resources specified when calling
1022    * the method.
1023    <p>
1024    * Examples:
1025    <ul>
1026    <li><code>replaceVariablesInString("text $env{GATE_HOME} more text")</code>:
1027    * returns "text /path/to/gate more text" if the environment variable 
1028    * "GATE_HOME" was set to "/path/to/gate"
1029    <li><code>replaceVariablesInString("text $pr{myfeature1} more text",pr1)</code>:
1030    * returns "myvalue1" if the feature map of the processing resource pr1 
1031    * contains an entry with key "myfeature" and value "myvalue"
1032    <li><code>replaceVariablesInString("text ${somekey} more text",map1,map2,resource1,map3)</code>:
1033    *  this will
1034    * find the value of an entry with key "somekey" in the first Map object specified
1035    * in the parameter list of the method.
1036    </ul>
1037    <p>
1038    * The possible sources for finding values for a variable are:
1039    <ul>
1040    <li><code>System.getenv()</code>: for variables of the form $env{name}
1041    <li><code>System.getProperties()</code>: for variables of the form $prop{name}
1042    <li><code>Resource</code>: the feature map of any resource which is specified in the 
1043    * list of objects is used for variables of the form $resource{name} or 
1044    * for variables of the form $corpus{name} if the resource is a corpus, for
1045    * $pr{name} if the resource is a processing resource and so on. If the 
1046    * resource is a processing resource its 
1047    <li><code>FeatureMap</code> or <code>Map</code>: any feature map or 
1048    * Map which can be used to look up String keys can be specified
1049    * as a source and will be used for variables of the form ${name}.
1050    </ul>
1051    <p>
1052    * The value substituted is converted to a string using the toString() 
1053    * method of whatever object is stored in the map. If the value returned
1054    * by Map.get(key) is null, no substitution is carried out and the 
1055    * variable is left unchanged in the string.
1056    <p>
1057    * The following variable constructs are supported:
1058    <ul>
1059    <li>$env{name} will be replaced with the value from the environment variables map 
1060    * from System.getenv() and nothing else.
1061    <li>$prop{name} will be replaced with the value of the properties map 
1062    * from System.getProperties() and nothing else.
1063    <li>$controller{name} will be replaced with the value of a feature from the FeatureMap
1064    * of the first resource of type Controller found in the argument list.
1065    <li>$corpus{name} will be replaced with the value of a feature from the FeatureMap
1066    * of the first resource of type Corpus found in the argument list.
1067    <li>$pr{name} will be replaced with the value of a feature from the FeatureMap
1068    * of the first resource of type ProcessingResource found in the argument list.
1069    <li>$pr_parm{name} will be replaced with the value of the parameter 'name'
1070    * of the first resource of type ProcessingResource found in the argument list.
1071    * This can be especially useful to replace a variable in one parameter with
1072    * the value of another, potentially hidden, parameter of the same PR.
1073    <li>$doc{name} will be replaced with the value of a feature from the FeatureMap
1074    * of the first resource of type Document found in the argument list.
1075    <li>$resource{name} will be replaced with the value of a feature from the FeatureMap
1076    * of the first resource of type Resource found in the argument list.
1077    </ul>
1078    <p>
1079    * If two dollar characters are used instead of one, the replacement string
1080    * will in turn be subject to replacement, e.g. $$env{abc} could get replaced
1081    * with the replacement string '$corpus{f1}' which would in turn get replaced
1082    * with the value of the feature 'f1' from the feature set of the first 
1083    * corpus in the parameter list that has a value for that feature.
1084    *  
1085    */
1086   @SuppressWarnings("unchecked")
1087   public static String replaceVariablesInString(
1088           String string, Object... sources)
1089   {
1090     // shortcut for strings where no replacement is possible (minimum content 
1091     // would have to be $pr{x}
1092     if(string == null || string.isEmpty() || string.length() 6) { return string; }
1093     Matcher matcher = varnamePattern.matcher(string);
1094     int findFrom = 0;
1095     int lastEnd = 0;
1096     StringBuilder sb = new StringBuilder(string.length()*2);
1097     while(findFrom < string.length() && matcher.find(findFrom)) {
1098       String dollars = matcher.group(1);
1099       String type = matcher.group(2);
1100       String varname = matcher.group(3);
1101       int matchStart = matcher.start();
1102       // whenever we have found something, we can immediately move the part
1103       // from the last end of match to the new start of match to the 
1104       // return string, that is just unmodified string ...
1105       // But only if the length is > 0
1106       if((matchStart - lastEnd0) {
1107         sb.append(string.substring(lastEnd,matchStart));
1108       }
1109       lastEnd = matcher.end();
1110       Object value = null;
1111       // for each match we find, go through all the sources in the order
1112       // listed and if the type of the source matches the requested type
1113       // then try to look the variable up. If we find something use it and
1114       // finish looking, otherwise continue until all sources have been 
1115       // exhausted. 
1116       // A variable where no value has been found anywhere is not replaced.
1117       // If a variable got replaced and it was a variable that started with
1118       // two dollar signs, then the replacement value is first getting 
1119       // recursively replaced too.
1120       if(type.equals("env")) {
1121         value = System.getenv().get(varname);
1122       else if(type.equals("prop")) {
1123         value = System.getProperties().get(varname);
1124       else {
1125         for(Object source : sources) {
1126           if(type.isEmpty()) { // an empty variable type matches only maps from the sources
1127             if(source instanceof Map) {
1128               value = ((Map<String,?>)source).get(varname);
1129             }
1130           else if(type.equals("pr"&& (source instanceof ProcessingResource)) {
1131             value = ((FeatureBearer)source).getFeatures().get(varname);
1132           else if(type.equals("pr_parm"&& (source instanceof ProcessingResource)) {
1133             try {
1134               value = ((ProcessingResource)source).getParameterValue(varname);
1135             catch(Exception ex) {
1136               // do nothing, leave the value null
1137             }
1138           else if(type.equals("doc"&& (source instanceof Document)) {
1139             value = ((FeatureBearer)source).getFeatures().get(varname);
1140           else if(type.equals("controller"&& (source instanceof Controller)) {
1141             value = ((FeatureBearer)source).getFeatures().get(varname);
1142           else if(type.equals("corpus"&& (source instanceof Corpus)) {
1143             value = ((FeatureBearer)source).getFeatures().get(varname);
1144           else if(type.equals("resource"&& (source instanceof Resource)) {
1145             value = ((FeatureBearer)source).getFeatures().get(varname);
1146           }
1147           if(value != null) {
1148             break;
1149           }
1150         // for source : sources
1151       }
1152       // only do anything at all if we found a value for this parameter
1153       if(value != null) {
1154         String replacement = value.toString();
1155         // if we had double-dollars, first do the recursive replacement ...
1156         if(dollars.equals("$$")) {
1157           replacement = replaceVariablesInString(replacement, sources);
1158         }
1159         sb.append(replacement);
1160       else {
1161         sb.append(matcher.group());
1162         // the first character after the match
1163       }
1164       findFrom = matcher.end();
1165     // while matcher.find ...
1166     // if we have some unmatched string left over, append it too
1167     if(lastEnd < string.length()) {
1168       sb.append(string.substring(lastEnd));
1169     }
1170     return sb.toString();    
1171   }
1172   private static final Pattern varnamePattern = Pattern.compile("(\\$\\$?)([a-zA-Z]*)\\{([^}]+)\\}");
1173   
1174   /**
1175    * Load a plugin from the default GATE plugins directory.
1176    
1177    * This will load the plugin with the specified directory name from the
1178    * default GATE plugins path, if GATE knows its own location. 
1179    
1180    @param dirName The directory name of the plugin within the standard GATE plugins directory.
1181    */
1182   public static void loadPlugin(String dirName) {
1183     File gatehome = Gate.getGateHome();
1184     if(gatehome == null) {
1185       throw new GateRuntimeException("Cannot load Plugin, Gate home location not known");
1186     }
1187     File pluginDir = new File(new File(gatehome,"plugins"),dirName);
1188     loadPlugin(pluginDir);
1189   }
1190   
1191   /**
1192    * Load a plugin from the specified directory.
1193    
1194    * This will load the plugin from the directory path specified as a File object.
1195    
1196    */
1197   public static void loadPlugin(File pluginDir) {
1198     try {
1199       Gate.getCreoleRegister().registerDirectories(pluginDir.toURI().toURL());
1200     catch (Exception ex) {
1201       throw new GateRuntimeException("Could not register plugin directory "+pluginDir,ex);
1202     }
1203   }
1204   
1205   /**
1206    * Return the given set with the given annotations removed.
1207    
1208    * This returns a new immutable annotation set, which contains all the annotations from origSet
1209    * except the given annotations. The removal is not based on equality but on the id of the 
1210    * annotation: an annotation in origSet which has the same id as the annotation except is removed
1211    * in the returned set.
1212    <p>
1213    * NOTE: Annotation ids are only unique within a document, so you should never mix annotations
1214    * from different documents when using this method!
1215    
1216    @param origSet The annotation set from which to remove the given annotation
1217    @param except The annotation to remove from the given set
1218    @return A new immutable annotation set with the given annotation removed from the original set
1219    */
1220   public static AnnotationSet minus(AnnotationSet origSet, Annotation... except) {
1221     return minus(origSet, Arrays.asList(except));
1222   }
1223  
1224   /**
1225    * Return the given set with the given annotations removed.
1226    
1227    * This returns a new immutable annotation set, which contains all the annotations from origSet
1228    * except the annotations given in the collection of exceptions. 
1229    * The removal is not based on equality but on the id of the 
1230    * annotations: an annotation in origSet which has the same id as an annotation from the exceptions
1231    * is removed in the returned set.
1232    <p>
1233    * NOTE: Annotation ids are only unique within a document, so you should never mix annotations
1234    * from different documents when using this method!
1235    
1236    @param origSet The annotation set from which to remove the given exceptions
1237    @param exceptions The annotations to remove from the given set
1238    @return A new immutable annotation set with the exceptions removed from the original set
1239    */
1240   public static AnnotationSet minus(AnnotationSet origSet, Collection<Annotation> exceptions) {
1241     Set<Integer> ids = new HashSet<Integer>();
1242     for(Annotation exception : exceptions) {
1243       ids.add(exception.getId());
1244     }
1245     List<Annotation> tmp = new ArrayList<Annotation>();
1246     for(Annotation ann : origSet) {
1247       if(!ids.contains(ann.getId())) {
1248         tmp.add(ann);
1249       }
1250     }
1251     return new ImmutableAnnotationSetImpl(origSet.getDocument(),tmp);
1252   }
1253  
1254   /**
1255    * Return the given set with the given annotations added.
1256    
1257    * This returns a new immutable annotation set, which contains all the annotations from origSet
1258    * plus the given annotations to add. The addition is not based on equality but on the id of the 
1259    * annotations: any new annotation is added if its annotation id differs from all the ids
1260    * already in the set.
1261    <p>
1262    * NOTE: Annotation ids are only unique within a document, so you should never mix annotations
1263    * from different documents when using this method!
1264    
1265    @param origSet The annotation set from which to remove the given exceptions
1266    @param toAdd The annotations to add to the given set
1267    @return A new immutable annotation set with the given annotations added
1268    */
1269   public static AnnotationSet plus(AnnotationSet origSet, Annotation... toAdd) {
1270     return plus(origSet,Arrays.asList(toAdd));
1271   }
1272   
1273   /**
1274    * Return the given set with the given annotations added.
1275    
1276    * This returns a new immutable annotation set, which contains all the annotations from origSet
1277    * plus the given annotations added. The addition is not based on equality but on the id of the 
1278    * annotations: any new annotation is added if its annotation id differs from all the ids
1279    * already in the set.
1280    <p>
1281    * NOTE: Annotation ids are only unique within a document, so you should never mix annotations
1282    * from different documents when using this method!
1283    
1284    @param origSet The annotation set from which to remove the given exceptions
1285    @param toAdd A collection of annotations to add to the original set
1286    @return A new immutable annotation set with the annotations from the collection added.
1287    */
1288   public static AnnotationSet plus(AnnotationSet origSet, Collection<Annotation> toAdd) {
1289     Set<Integer> ids = new HashSet<Integer>();
1290     for(Annotation orig : origSet) {
1291       ids.add(orig.getId());
1292     }
1293     List<Annotation> tmp = new ArrayList<Annotation>();
1294     tmp.addAll(origSet);
1295     for(Annotation ann : toAdd) {
1296       if(!ids.contains(ann.getId())) {
1297         tmp.add(ann);
1298       }
1299     }
1300     return new ImmutableAnnotationSetImpl(origSet.getDocument(),tmp);    
1301   }
1302   
1303   
1304   /**
1305    * Return the subset from the original set that matches one of the given annotations.
1306    
1307    * This returns a new immutable annotation set, which contains all the annotations from origSet
1308    * which are also among the annotations given. The check for matching annotations is not based 
1309    * on equality but on the id of the 
1310    * annotations: an annotation from the original set is included in the returned set if its 
1311    * annotation id matches the annotation id of any of the annotations given.
1312    <p>
1313    * NOTE: Annotation ids are only unique within a document, so you should never mix annotations
1314    * from different documents when using this method!
1315    
1316    @param origSet The annotation set from which to select only the given annotations.
1317    @param others the given annotations
1318    @return A new immutable annotation set with the interesection between original set and given annotations
1319    */
1320   public static AnnotationSet intersect(AnnotationSet origSet, Annotation... others) {
1321     return intersect(origSet,Arrays.asList(others));
1322   }
1323   
1324   public static AnnotationSet intersect(AnnotationSet origSet, Collection<Annotation> others) {
1325     if(others.isEmpty()) {
1326       return new ImmutableAnnotationSetImpl(origSet.getDocument(),null);
1327     }
1328     Set<Integer> ids = new HashSet<Integer>();
1329     for(Annotation other : others) {
1330       ids.add(other.getId());
1331     }
1332     List<Annotation> tmp = new ArrayList<Annotation>();
1333     for(Annotation ann : origSet) {
1334       if(ids.contains(ann.getId())) {
1335         tmp.add(ann);
1336       }
1337     }
1338     return new ImmutableAnnotationSetImpl(origSet.getDocument(),tmp);    
1339   }
1340   
1341 }