LuceneDocument.java
001 /*
002  *  LuceneDocument.java
003  *
004  *  Niraj Aswani, 19/March/07
005  *
006  *  $Id: LuceneDocument.html,v 1.0 2007/03/19 16:22:01 niraj Exp $
007  */
008 package gate.creole.annic.lucene;
009 
010 import gate.Annotation;
011 import gate.AnnotationSet;
012 import gate.FeatureMap;
013 import gate.annotation.AnnotationSetImpl;
014 import gate.creole.annic.Constants;
015 import gate.creole.annic.apache.lucene.analysis.Token;
016 import gate.creole.annic.apache.lucene.document.Document;
017 import gate.creole.annic.apache.lucene.document.Field;
018 import gate.util.Err;
019 import gate.util.GateRuntimeException;
020 import gate.util.InvalidOffsetException;
021 import gate.util.OffsetComparator;
022 
023 import java.io.BufferedOutputStream;
024 import java.io.File;
025 import java.io.FileOutputStream;
026 import java.io.IOException;
027 import java.io.ObjectOutput;
028 import java.io.ObjectOutputStream;
029 import java.io.OutputStream;
030 import java.util.ArrayList;
031 import java.util.Arrays;
032 import java.util.Collections;
033 import java.util.HashSet;
034 import java.util.Iterator;
035 import java.util.List;
036 import java.util.Set;
037 
038 /**
039  * Given an instance of Gate Document, this class provides a method to convert
040  * it into the format that lucene can understand and can store in its indexes.
041  * This class also stores the tokenStream on the disk in order to retrieve it at
042  * the time of searching
043  
044  @author niraj
045  
046  */
047 public class LuceneDocument {
048 
049   /**
050    * Given an instance of Gate Document, it converts it into the format that
051    * lucene can understand and can store in its indexes. This method also stores
052    * the tokenStream on the disk in order to retrieve it at the time of
053    * searching
054    */
055   public List<Document> createDocuments(String corpusPersistenceID,
056     gate.Document gateDoc, String documentID,
057     List<String> annotSetsToInclude, List<String> annotSetsToExclude,
058     List<String> featuresToInclude, List<String> featuresToExclude,
059     String indexLocation, String baseTokenAnnotationType,
060     Boolean createTokensAutomatically, String indexUnitAnnotationType) {
061 
062     if(baseTokenAnnotationType != null)
063       baseTokenAnnotationType = baseTokenAnnotationType.trim();
064 
065     List<Document> toReturnBack = new ArrayList<Document>();
066     List<String> annotSetsToIndex = new ArrayList<String>();
067 
068     // by default merge set must be created
069     //boolean createMergeSet = true;
070 
071     // if user has provided annotation sets to include, we don't bother
072     // about annotation sets to exclude
073     if(annotSetsToInclude.size() 0) {
074       annotSetsToIndex = annotSetsToInclude;
075 
076       // if there's only one annotation to index, we don't need to
077       // create a MergeSet
078       //if(annotSetsToIndex.size() == 1) createMergeSet = false;
079     }
080     else if(annotSetsToExclude.size() 0) {
081       // if there were no annotation sets to include, check if user has
082       // provided any annotation sets to exclude
083       // if so, we need to index all annotation sets but provided in the
084       // annotationsetstoexclude list
085 
086       Set<String> namedAnnotSets = new HashSet<String>();
087       if(gateDoc.getNamedAnnotationSets() != null
088         && gateDoc.getNamedAnnotationSets().keySet() != null) {
089         namedAnnotSets = gateDoc.getNamedAnnotationSets().keySet();
090       }
091 
092       for(String setName : namedAnnotSets) {
093         if(annotSetsToExclude.contains(setName)) continue;
094         annotSetsToIndex.add(setName);
095       }
096 
097       if(!annotSetsToExclude.contains(Constants.DEFAULT_ANNOTATION_SET_NAME)) {
098         annotSetsToIndex.add(Constants.DEFAULT_ANNOTATION_SET_NAME);
099       }
100     }
101     else {
102       // if both annotation sets to include and annotation sets to
103       // exclude are empty
104       // we need to index all annotation sets
105       Set<String> namedAnnotSets = new HashSet<String>();
106       if(gateDoc.getNamedAnnotationSets() != null
107         && gateDoc.getNamedAnnotationSets().keySet() != null) {
108         namedAnnotSets = gateDoc.getNamedAnnotationSets().keySet();
109       }
110 
111       for(String setName : namedAnnotSets) {
112         annotSetsToIndex.add(setName);
113       }
114       annotSetsToIndex.add(Constants.DEFAULT_ANNOTATION_SET_NAME);
115     }
116 
117     // lets find out the annotation set that contains tokens in it
118     AnnotationSet baseTokenAnnotationSet = null;
119 
120     // search in annotation sets to find out which of them has the
121     // baseTokenAnnotationType annotations
122     // initially this is set to false
123     boolean searchBaseTokensInAllAnnotationSets = false;
124     boolean searchIndexUnitInAllAnnotationSets = false;
125 
126     // this variable tells whether we want to create manual tokens or
127     // not
128     boolean createManualTokens = false;
129 
130     // lets check if user's input is setName.basetokenAnnotationType
131     int index = -1;
132     if(baseTokenAnnotationType != null && baseTokenAnnotationType.length() 0)
133       index = baseTokenAnnotationType.lastIndexOf('.');
134 
135     // yes it is, find out the annotationset name and the
136     // basetokenAnnotationType
137     if(index >= 0) {
138 
139       // set name
140       String setName = baseTokenAnnotationType.substring(0, index);
141 
142       // token type
143       baseTokenAnnotationType =
144         baseTokenAnnotationType.substring(index + 1, baseTokenAnnotationType
145           .length());
146 
147       // check if user has asked to take tokens from the default
148       // annotation set
149       if(setName.equals(Constants.DEFAULT_ANNOTATION_SET_NAME))
150         baseTokenAnnotationSet =
151           gateDoc.getAnnotations().get(baseTokenAnnotationType);
152       else baseTokenAnnotationSet =
153         gateDoc.getAnnotations(setName).get(baseTokenAnnotationType);
154 
155       // here we check if the baseTokenAnnotationSet is null or its size
156       // is 0
157       // if so, we'll have to find out in all annotation sets for the
158       // base token annotation type
159       if(baseTokenAnnotationSet == null || baseTokenAnnotationSet.size() == 0) {
160         System.err.println("Base Tokens " + baseTokenAnnotationType
161           " counldn't be found under the specified annotation set " + setName
162           "\n searching them in other annotation sets");
163         searchBaseTokensInAllAnnotationSets = true;
164       }
165     }
166     else {
167 
168       // either baseTokenAnnotation type is null or user hasn't provided
169       // any annotaiton set name
170       // so we search in all annotation sets
171       searchBaseTokensInAllAnnotationSets = true;
172     }
173 
174 //    if(searchBaseTokensInAllAnnotationSets) {
175 //      System.out.println("Searching for the base token annotation type \""
176 //        + baseTokenAnnotationType + "\"in all sets");
177 //    }
178 
179     if(baseTokenAnnotationType != null && baseTokenAnnotationType.length() 0
180       && searchBaseTokensInAllAnnotationSets) {
181       // we set this to true and if we find basetokens in any of the
182       // annotationsets to index
183       // we will set this to false
184       createManualTokens = true;
185 
186       for(String aSet : annotSetsToIndex) {
187         if(aSet.equals(Constants.DEFAULT_ANNOTATION_SET_NAME)) {
188           AnnotationSet tempSet =
189             gateDoc.getAnnotations().get(baseTokenAnnotationType);
190           if(tempSet.size() 0) {
191             baseTokenAnnotationSet = tempSet;
192 //            System.out.println("found in default annotation set");
193             createManualTokens = false;
194             break;
195           }
196         }
197         else {
198           AnnotationSet tempSet =
199             gateDoc.getAnnotations(aSet).get(baseTokenAnnotationType);
200           if(tempSet.size() 0) {
201             baseTokenAnnotationSet = tempSet;
202 //            System.out.println("found in "+aSet);
203             createManualTokens = false;
204             break;
205           }
206         }
207       }
208     }
209 
210     // if baseTokenAnnotaitonType is null or an empty string
211     // we'll have to create tokens ourselves
212     if(baseTokenAnnotationType == null || baseTokenAnnotationType.length() == 0)
213       createManualTokens = true;
214 
215     // lets check if we have to create ManualTokens
216     if(createManualTokens) {
217       if(!createTokensAutomatically.booleanValue()) {
218         System.out
219           .println("Tokens couldn't be found in the document - Ignoring the document "
220             + gateDoc.getName());
221         return null;
222       }
223 
224       baseTokenAnnotationType = Constants.ANNIC_TOKEN;
225 
226       if(baseTokenAnnotationSet == null) {
227         baseTokenAnnotationSet = new AnnotationSetImpl(gateDoc);
228       }
229 
230       if(!createTokens(gateDoc, baseTokenAnnotationSet)) {
231         System.out
232           .println("Tokens couldn't be created manually - Ignoring the document "
233             + gateDoc.getName());
234         return null;
235       }
236     }
237     // by now, baseTokenAnnotationSet will not be null for sure and we
238     // know what's the baseTokenAnnotationType
239 
240     // lets find out the annotation set that contains
241     // indexUnitAnnotationType in it
242     AnnotationSet indexUnitAnnotationSet = null;
243 
244     // lets check if user has provided setName.indexUnitAnnotationType
245     index = -1;
246     if(indexUnitAnnotationType != null
247       && indexUnitAnnotationType.trim().length() 0)
248       index = indexUnitAnnotationType.lastIndexOf('.');
249 
250     // yes he has, so lets go and fethc setName and
251     // indexUnitAnnotationType
252     if(index >= 0) {
253       // setName
254       String setName = indexUnitAnnotationType.substring(0, index);
255 
256       // indexUnitAnnotationType
257       indexUnitAnnotationType =
258         indexUnitAnnotationType.substring(index + 1, indexUnitAnnotationType
259           .length());
260 
261       if(setName.equals(Constants.DEFAULT_ANNOTATION_SET_NAME))
262         indexUnitAnnotationSet =
263           gateDoc.getAnnotations().get(indexUnitAnnotationType);
264       else indexUnitAnnotationSet =
265         gateDoc.getAnnotations(setName).get(indexUnitAnnotationType);
266 
267       // here we check if the indexUnitAnnotationSet is null or its size
268       // is 0
269       // if so, we'll have to search other annotation sets
270       if(indexUnitAnnotationSet == null || indexUnitAnnotationSet.size() == 0) {
271         System.err.println("Index Unit " + indexUnitAnnotationType
272           " counldn't be found under the specified annotation set " + setName
273           "\n searching them in other annotation sets");
274         searchIndexUnitInAllAnnotationSets = true;
275       }
276     }
277     else {
278 
279       // either indexUnitAnnotationType is null or user hasn't provided
280       // the setname
281       searchIndexUnitInAllAnnotationSets = true;
282     }
283 
284     // searching in all annotation set names
285     if(indexUnitAnnotationType != null && indexUnitAnnotationType.length() 0
286       && searchIndexUnitInAllAnnotationSets) {
287       for(String aSet : annotSetsToIndex) {
288         if(aSet.equals(Constants.DEFAULT_ANNOTATION_SET_NAME)) {
289           AnnotationSet tempSet =
290             gateDoc.getAnnotations().get(indexUnitAnnotationType);
291           if(tempSet.size() 0) {
292             indexUnitAnnotationSet = tempSet;
293             break;
294           }
295         }
296         else {
297           AnnotationSet tempSet =
298             gateDoc.getAnnotations(aSet).get(indexUnitAnnotationType);
299           if(tempSet.size() 0) {
300             indexUnitAnnotationSet = tempSet;
301             break;
302           }
303         }
304       }
305     }
306 
307     // if indexUnitAnnotationSet is null, we set indexUnitAnnotationType
308     // to null as well
309     if(indexUnitAnnotationSet == null) {
310       indexUnitAnnotationType = null;
311     }
312 
313     int j = 0;
314 
315     // we maintain an annotation set that contains all annotations from
316     // all the annotation sets to be indexed
317     // however it must not contain the baseTokens or
318     // indexUnitAnnotationType annotations
319     //AnnotationSet mergedSet = null;
320 
321     for(String annotSet : annotSetsToIndex) {
322 
323       // we need to generate the Token Stream here, and send it to the
324       // GateLuceneReader
325       AnnotationSet aSetToIndex =
326         annotSet.equals(Constants.DEFAULT_ANNOTATION_SET_NAME? gateDoc
327           .getAnnotations() : gateDoc.getAnnotations(annotSet);
328 
329       Set<String> indexedFeatures = new HashSet<String>();
330       // tempBaseTokenAnnotationSet is not null
331       List<Token>[] tokenStreams =
332         getTokens(gateDoc, aSetToIndex, featuresToInclude, featuresToExclude,
333           baseTokenAnnotationType, baseTokenAnnotationSet,
334           indexUnitAnnotationType, indexUnitAnnotationSet, indexedFeatures);
335 
336       // if there was some problem inside obtaining tokens
337       // tokenStream is set to null
338       if(tokenStreams == nullreturn null;
339 
340       // this is enabled only if there are more than one annotation sets
341       // available to search in
342 //      if(createMergeSet) {
343 //        if(mergedSet == null) mergedSet = new AnnotationSetImpl(gateDoc);
344 //
345 //        // we need to merge all annotations but the
346 //        // baseTokenAnnotationType
347 //        for(String aType : aSetToIndex.getAllTypes()) {
348 //
349 //          if(aType.equals(baseTokenAnnotationType)) {
350 //            continue;
351 //          }
352 //
353 //          if(indexUnitAnnotationType != null
354 //            && aType.equals(indexUnitAnnotationType)) {
355 //            continue;
356 //          }
357 //
358 //          for(Annotation a : aSetToIndex.get(aType)) {
359 //            try {
360 //              mergedSet.add(a.getStartNode().getOffset(), a.getEndNode()
361 //                .getOffset(), a.getType(), a.getFeatures());
362 //            }
363 //            catch(InvalidOffsetException ioe) {
364 //              throw new GateRuntimeException(ioe);
365 //            }
366 //          }
367 //
368 //        }
369 //      }
370 
371       StringBuffer indexedFeaturesString = new StringBuffer();
372       for(String aFeat : indexedFeatures) {
373         indexedFeaturesString.append(aFeat + ";");
374       }
375 
376       Document[] toReturn = new Document[tokenStreams.length];
377 
378       for(int i = 0; i < tokenStreams.length; i++, j++) {
379         // make a new, empty document
380         Document doc = new Document();
381 
382         // and then create the document
383         LuceneReader reader = new LuceneReader(gateDoc, tokenStreams[i]);
384         doc.add(Field.Keyword(Constants.DOCUMENT_ID, documentID));
385         doc.add(Field.Keyword(Constants.DOCUMENT_ID_FOR_SERIALIZED_FILE,
386           documentID + "-" + j));
387         doc.add(Field.Keyword(Constants.INDEXED_FEATURES, indexedFeaturesString
388           .substring(0, indexedFeaturesString.length() 1)));
389 
390         if(corpusPersistenceID != null)
391           doc.add(Field.Keyword(Constants.CORPUS_ID, corpusPersistenceID));
392         doc.add(Field.Keyword(Constants.ANNOTATION_SET_ID, annotSet));
393 
394         doc.add(Field.Text("contents", reader));
395         // here we store token stream on the file system
396         try {
397           writeOnDisk(tokenStreams[i], documentID, documentID + "-" + j,
398             indexLocation);
399         }
400         catch(Exception e) {
401           Err.println("\nIgnoring the document : " + gateDoc.getName()
402             " since its token stream cannot be written on the disk");
403           Err.println("Reason: " + e.getMessage());
404           return null;
405         }
406 
407         // return the document
408         toReturn[i= doc;
409       }
410 
411       toReturnBack.addAll(Arrays.asList(toReturn));
412     }
413 
414 //    // once again do an index with everything merged all together
415 //    if(createMergeSet && mergedSet != null) {
416 //      Set<String> indexedFeatures = new HashSet<String>();
417 //      ArrayList<Token>[] tokenStreams =
418 //        getTokens(gateDoc, mergedSet, featuresToInclude, featuresToExclude,
419 //          baseTokenAnnotationType, baseTokenAnnotationSet,
420 //          indexUnitAnnotationType, indexUnitAnnotationSet, indexedFeatures);
421 //
422 //      if(tokenStreams == null) return null;
423 //
424 //      Document[] toReturn = new Document[tokenStreams.length];
425 //
426 //      for(int i = 0; i < tokenStreams.length; i++, j++) {
427 //        // make a new, empty document
428 //        Document doc = new Document();
429 //
430 //        // and then create the document
431 //        LuceneReader reader = new LuceneReader(gateDoc, tokenStreams[i]);
432 //        doc.add(Field.Keyword(Constants.DOCUMENT_ID, documentID));
433 //        doc.add(Field.Keyword(Constants.DOCUMENT_ID_FOR_SERIALIZED_FILE,
434 //          documentID + "-" + j));
435 //        StringBuffer indexedFeaturesString = new StringBuffer();
436 //        for(String aFeat : indexedFeatures) {
437 //          indexedFeaturesString.append(aFeat + ";");
438 //        }
439 //        doc.add(Field.Keyword(Constants.INDEXED_FEATURES, indexedFeaturesString
440 //          .substring(0, indexedFeaturesString.length() - 1)));
441 //
442 //        if(corpusPersistenceID != null)
443 //          doc.add(Field.Keyword(Constants.CORPUS_ID, corpusPersistenceID));
444 //        doc.add(Field.Keyword(Constants.ANNOTATION_SET_ID,
445 //          Constants.COMBINED_SET));
446 //
447 //        doc.add(Field.Text("contents", reader));
448 //        // here we store token stream on the file system
449 //        try {
450 //          writeOnDisk(tokenStreams[i], documentID, documentID + "-" + j,
451 //            indexLocation);
452 //        }
453 //        catch(Exception e) {
454 //          Err.println("\nIgnoring the document : " + gateDoc.getName()
455 //            + " since its token stream cannot be written on the disk");
456 //          Err.println("Reason: " + e.getMessage());
457 //          return null;
458 //        }
459 //
460 //        // return the document
461 //        toReturn[i] = doc;
462 //      }
463 //
464 //      toReturnBack.addAll(Arrays.asList(toReturn));
465 //    }
466 
467     return toReturnBack;
468   }
469 
470   private boolean createTokens(gate.Document gateDocument, AnnotationSet set) {
471     String gateContent = gateDocument.getContent().toString();
472     int start = -1;
473     for(int i = 0; i < gateContent.length(); i++) {
474       char c = gateContent.charAt(i);
475       if(Character.isWhitespace(c)) {
476         if(start != -1) {
477           FeatureMap features = gate.Factory.newFeatureMap();
478           String string = gateContent.substring(start, i);
479           if(string.trim().length() 0) {
480             features.put("string", string);
481             try {
482               set.add(new Long(start)new Long(i), Constants.ANNIC_TOKEN,
483                 features);
484             }
485             catch(InvalidOffsetException ioe) {
486               ioe.printStackTrace();
487               return false;
488             }
489           }
490           start = i + 1;
491         }
492       }
493       else {
494         if(start == -1start = i;
495       }
496     }
497     if(start == -1return false;
498     if(start < gateContent.length()) {
499       FeatureMap features = gate.Factory.newFeatureMap();
500       String string = gateContent.substring(start, gateContent.length());
501       if(string.trim().length() 0) {
502         features.put("string", string);
503         try {
504           set.add(new Long(start)new Long(gateContent.length()),
505             Constants.ANNIC_TOKEN, features);
506         }
507         catch(InvalidOffsetException ioe) {
508           ioe.printStackTrace();
509           return false;
510         }
511       }
512     }
513     return true;
514   }
515 
516   /**
517    * Some file names are not compatible to the underlying file system. This
518    * method replaces all those incompatible characters with '_'.
519    */
520   private String getCompatibleName(String name) {
521     return name.replaceAll("[\\/:\\*\\?\"<>|]""_");
522   }
523 
524   /**
525    * This method, given a tokenstream and file name, writes the tokenstream on
526    * the provided location.
527    */
528   private void writeOnDisk(List<Token> tokenStream, String folderName,
529     String fileName, String locationthrows Exception {
530 
531     // before we write it on a disk, we need to change its name to
532     // underlying file system name
533     fileName = getCompatibleName(fileName);
534     folderName = getCompatibleName(folderName);
535 
536     if(location.startsWith("file:/"))
537       location = location.substring(6, location.length());
538 
539     if(location.charAt(1!= ':'location = "/" + location;
540 
541     File locationFile = new File(location);
542     File folder = new File(locationFile, Constants.SERIALIZED_FOLDER_NAME);
543     if(!folder.exists()) {
544       folder.mkdirs();
545     }
546     if(!folder.exists()) { throw new IOException(
547       "Directory could not be created :" + folder.getAbsolutePath())}
548 
549     folder = new File(folder, folderName);
550     if(!folder.exists()) {
551       folder.mkdirs();
552     }
553 
554     if(!folder.exists()) { throw new IOException(
555       "Directory could not be created :" + folder.getAbsolutePath())}
556 
557     File outputFile = new File(folder, fileName + ".annic");
558     ObjectOutput output = null;
559     OutputStream file = new FileOutputStream(outputFile);
560     OutputStream buffer = new BufferedOutputStream(file);
561     output = new ObjectOutputStream(buffer);
562     output.writeObject(tokenStream);
563     if(output != null) {
564       output.close();
565     }
566   }
567 
568   /**
569    * Internal class used for storing the offsets of annotations.
570    
571    @author niraj
572    
573    */
574   private class OffsetGroup {
575     Long startOffset;
576 
577     Long endOffset;
578   }
579 
580   /**
581    * This method given a GATE document and other required parameters, for each
582    * annotation of type indexUnitAnnotationType creates a separate list of
583    * baseTokens underlying in it.
584    */
585   private List<Token>[] getTokens(gate.Document document,
586     AnnotationSet inputAs, List<String> featuresToInclude,
587     List<String> featuresToExclude, String baseTokenAnnotationType,
588     AnnotationSet baseTokenSet, String indexUnitAnnotationType,
589     AnnotationSet indexUnitSet, Set<String> indexedFeatures) {
590 
591     boolean excludeFeatures = false;
592     boolean includeFeatures = false;
593 
594     // if include features are provided, we donot look at the exclude
595     // features
596     if(!featuresToInclude.isEmpty()) {
597       includeFeatures = true;
598     }
599     else if(!featuresToExclude.isEmpty()) {
600       excludeFeatures = true;
601     }
602 
603     HashSet<OffsetGroup> unitOffsetsSet = new HashSet<OffsetGroup>();
604     if(indexUnitAnnotationType == null
605       || indexUnitAnnotationType.trim().length() == || indexUnitSet == null
606       || indexUnitSet.size() == 0) {
607       // the index Unit Annotation Type is not specified
608       // therefore we consider the entire document as a single unit
609       OffsetGroup group = new OffsetGroup();
610       group.startOffset = new Long(0);
611       group.endOffset = document.getContent().size();
612       unitOffsetsSet.add(group);
613     }
614     else {
615       Iterator<Annotation> iter = indexUnitSet.iterator();
616       while(iter.hasNext()) {
617         Annotation annotation = iter.next();
618         OffsetGroup group = new OffsetGroup();
619         group.startOffset = annotation.getStartNode().getOffset();
620         group.endOffset = annotation.getEndNode().getOffset();
621         unitOffsetsSet.add(group);
622       }
623     }
624 
625     Set<String> allTypes = new HashSet<String>();
626 
627     for(String aType : inputAs.getAllTypes()) {
628       if(aType.indexOf("."> -|| aType.indexOf("="> -1
629         || aType.indexOf(";"> -|| aType.indexOf(","> -1) {
630         System.err
631           .println("Annotations of type "
632             + aType
633             " cannot be indexed as the type name contains one of the ., =, or ; character");
634         continue;
635       }
636       allTypes.add(aType);
637     }
638 
639     if(baseTokenSet != null && baseTokenSet.size() 0) {
640       allTypes.remove(baseTokenAnnotationType);
641     }
642 
643     if(indexUnitSet != null && indexUnitSet.size() 0)
644       allTypes.remove(indexUnitAnnotationType);
645 
646     AnnotationSet toUseSet = new AnnotationSetImpl(document);
647     for(String type : allTypes) {
648       for(Annotation a : inputAs.get(type)) {
649         try {
650           toUseSet.add(a.getStartNode().getOffset(),
651             a.getEndNode().getOffset(), a.getType(), a.getFeatures());
652         }
653         catch(InvalidOffsetException ioe) {
654           throw new GateRuntimeException(ioe);
655         }
656       }
657     }
658 
659     
660     @SuppressWarnings({"cast","unchecked","rawtypes"})
661     List<Token> toReturn[] (List<Token>[])new List[unitOffsetsSet.size()];
662     Iterator<OffsetGroup> iter = unitOffsetsSet.iterator();
663     int counter = 0;
664     while(iter.hasNext()) {
665       OffsetGroup group = iter.next();
666       List<Token> newTokens = new ArrayList<Token>();
667       List<Annotation> tokens =
668         new ArrayList<Annotation>(toUseSet.getContained(group.startOffset,
669           group.endOffset));
670 
671       // add tokens from the baseTokenSet
672       if(baseTokenSet != null && baseTokenSet.size() != 0) {
673         tokens.addAll(baseTokenSet.getContained(group.startOffset,
674           group.endOffset));
675       }
676 
677       if(tokens == null || tokens.size() == 0return null;
678 
679       Collections.sort(tokens, new OffsetComparator());
680 
681       int position = -1;
682       for(int i = 0; i < tokens.size(); i++) {
683         byte inc = 1;
684         Annotation annot = tokens.get(i);
685         String type = annot.getType();
686         // if the feature is specified in featuresToExclude -exclude it
687         if(excludeFeatures && featuresToExclude.contains(type)) continue;
688 
689         // if the feature is not sepcified in the include features -
690         // exclude it
691         if(includeFeatures && !featuresToInclude.contains(type)) continue;
692 
693         int startOffset = annot.getStartNode().getOffset().intValue();
694         int endOffset = annot.getEndNode().getOffset().intValue();
695         String text =
696           document.getContent().toString().substring(startOffset, endOffset);
697         if(text == null) {
698           continue;
699         }
700 
701 
702         Token token1 = new Token(type, startOffset, endOffset, "*");
703 
704         // each token has four values
705         // String, int, int, String
706         // we add extra info of position
707         if(i > 0) {
708           if(annot.getStartNode().getOffset().longValue() == tokens.get(i - 1)
709             .getStartNode().getOffset().longValue()) {
710             token1.setPositionIncrement(0);
711             inc = 0;
712           }
713         }
714 
715         position += inc;
716         token1.setPosition(position);
717         newTokens.add(token1);
718 
719         if(!type.equals(baseTokenAnnotationType)
720           || (annot.getFeatures().get("string"== null)) {
721           // we need to create one string feature for this
722           Token tk1 = new Token(text, startOffset, endOffset, type + ".string");
723           indexedFeatures.add(type + ".string");
724           tk1.setPositionIncrement(0);
725           tk1.setPosition(position);
726           newTokens.add(tk1);
727         }
728 
729         // now find out the features and add them
730         FeatureMap features = annot.getFeatures();
731         Iterator<Object> fIter = features.keySet().iterator();
732         while(fIter.hasNext()) {
733           String type1 = fIter.next().toString();
734           // if the feature is specified in featuresToExclude -exclude
735           // it
736           if(excludeFeatures && featuresToExclude.contains(type + "." + type1)) {
737             continue;
738           }
739 
740           // if the feature is not sepcified in the include features -
741           // exclude it
742           if(includeFeatures && !featuresToInclude.contains(type + "." + type1))
743             continue;
744 
745           Object tempText = features.get(type1);
746           if(tempText == nullcontinue;
747 
748           String text1 = tempText.toString();
749           // we need to qualify the type names
750           // for each annotation type feature we add AT.Feature=="**" to be able
751           // to search for it
752           // to calculate stats
753 
754           Token tempToken =
755             new Token(text1, startOffset, endOffset, type + "." + type1);
756           indexedFeatures.add(type + "." + type1);
757           tempToken.setPositionIncrement(0);
758           tempToken.setPosition(position);
759           newTokens.add(tempToken);
760 
761           Token onlyATFeature =
762             new Token(type + "." + type1, startOffset, endOffset, "**");
763           onlyATFeature.setPosition(position);
764           onlyATFeature.setPositionIncrement(0);
765           newTokens.add(onlyATFeature);
766 
767         }
768       }
769       toReturn[counter= newTokens;
770       counter++;
771     }
772     return toReturn;
773   }
774 }