LuceneSearchThread.java
0001 /*
0002  *  LuceneSearchThread.java
0003  *
0004  *  Niraj Aswani, 19/March/07
0005  *
0006  *  $Id: LuceneSearchThread.html,v 1.0 2007/03/19 16:22:01 niraj Exp $
0007  */
0008 package gate.creole.annic.lucene;
0009 
0010 import java.io.BufferedInputStream;
0011 import java.io.File;
0012 import java.io.FileInputStream;
0013 import java.io.InputStream;
0014 import java.io.ObjectInput;
0015 import java.io.ObjectInputStream;
0016 import java.util.ArrayList;
0017 import java.util.HashMap;
0018 import java.util.Iterator;
0019 import java.util.List;
0020 import java.util.Map;
0021 
0022 import gate.creole.annic.Pattern;
0023 import gate.creole.annic.PatternAnnotation;
0024 import gate.creole.annic.Constants;
0025 import gate.creole.annic.SearchException;
0026 import gate.creole.annic.apache.lucene.search.Hits;
0027 import gate.creole.annic.apache.lucene.search.Query;
0028 
0029 /**
0030  * Given a boolean query, it is translated into one or more AND
0031  * normalized queries. For example: (A|B)C is translated into AC and BC.
0032  * For each such query an instance of LuceneSearchThread is created.
0033  * Here, each query is issued separately and results are submitted to
0034  * main instance of LuceneSearch.
0035  
0036  @author niraj
0037  */
0038 public class LuceneSearchThread {
0039 
0040   /**
0041    * Debug variable
0042    */
0043   private static boolean DEBUG = false;
0044 
0045   /**
0046    * Number of base token annotations to be used in context.
0047    */
0048   private int contextWindow;
0049 
0050   /**
0051    * The location of index.
0052    */
0053   private String indexLocation;
0054 
0055   /**
0056    * Instance of a QueryParser.
0057    */
0058   private QueryParser queryParser;
0059 
0060   /**
0061    * BaseTokenAnnotationType.
0062    */
0063   private String baseTokenAnnotationType;
0064 
0065   /**
0066    * Instance of the LuceneSearcher.
0067    */
0068   private LuceneSearcher luceneSearcher;
0069 
0070   /**
0071    * Indicates if searching process is finished.
0072    */
0073   public boolean finished = false;
0074 
0075   /**
0076    * Index of the serializedFileID we are currently searching for.
0077    */
0078   private int serializedFileIDIndex = 0;
0079 
0080   /**
0081    * QueryItemIndex
0082    */
0083   private int queryItemIndex = 0;
0084 
0085   /**
0086    * List of serialized Files IDs retrieved from the lucene index
0087    */
0088   private List<String> serializedFilesIDsList = new ArrayList<String>();
0089 
0090   /**
0091    * A Map that holds information about search results.
0092    */
0093   private Map<String, List<QueryItem>> searchResultInfoMap = new HashMap<String, List<QueryItem>>();
0094 
0095   /**
0096    * First term position index.
0097    */
0098   private int ftpIndex = 0;
0099 
0100   /**
0101    * Indicates if the query was success.
0102    */
0103   private boolean success = false;
0104 
0105   /**
0106    * Indicates if we've reached the end of search results.
0107    */
0108   private boolean fwdIterationEnded = false;
0109 
0110   /**
0111    * We keep track of what was the last ID of the serialized File that we visited. This is
0112    * used for optimization reasons
0113    */
0114   private String serializedFileIDInUse = null;
0115 
0116   /**
0117    * This is where we store the tokenStreamInUse
0118    */
0119   private List<gate.creole.annic.apache.lucene.analysis.Token> tokenStreamInUse = null;
0120 
0121   /**
0122    * Query
0123    */
0124   private String query = null;
0125 
0126   /**
0127    * Given a file name, it replaces the all invalid characters with '_'.
0128    */
0129   private String getCompatibleName(String name) {
0130     return name.replaceAll("[\\/:\\*\\?\"<>|]""_");
0131   }
0132 
0133   /**
0134    * This method collects the necessary information from lucene and uses
0135    * it when the next method is called
0136    
0137    @param query query supplied by the user
0138    @param patternWindow number of tokens to refer on left and right
0139    *          context
0140    @param indexLocation location of the index the searcher should
0141    *          search in
0142    @param luceneSearcher an instance of lucene search from where the
0143    *          instance of SearchThread is invoked
0144    @return true iff search was successful false otherwise
0145    */
0146   @SuppressWarnings("unchecked")
0147   public boolean search(String query, int patternWindow, String indexLocation,
0148           String corpusToSearchIn, String annotationSetToSearchIn,
0149           LuceneSearcher luceneSearcherthrows SearchException {
0150 
0151     this.query = query;
0152     this.contextWindow = patternWindow;
0153     this.indexLocation = indexLocation;
0154     this.queryParser = new QueryParser();
0155     this.luceneSearcher = luceneSearcher;
0156 
0157     /*
0158      * reset all parameters that keep track of where we are in our
0159      * searching. These parameters are used mostly to keep track of
0160      * where to start fetching the next results from
0161      */
0162     searchResultInfoMap = new HashMap<String, List<QueryItem>>();
0163     serializedFileIDIndex = 0;
0164     queryItemIndex = 0;
0165     serializedFilesIDsList = new ArrayList<String>();
0166     ftpIndex = -1;
0167     success = false;
0168     fwdIterationEnded = false;
0169 
0170     try {
0171       // first find out the location of Index
0172       String temp = "";
0173       for(int i = 0; i < indexLocation.length(); i++) {
0174         if(indexLocation.charAt(i== '\\') {
0175           temp += "/";
0176         }
0177         else {
0178           temp += indexLocation.charAt(i);
0179         }
0180       }
0181       indexLocation = temp;
0182 
0183       /*
0184        * for each different location there can be different
0185        * baseTokenAnnotationType each index will have their index
0186        * Definition file stored under the index directory so first see
0187        * if given location is a valid directory
0188        */
0189       File locationFile = new File(indexLocation);
0190       if(!locationFile.isDirectory()) {
0191         System.out.println("Skipping the invalid Index Location :"
0192                 + indexLocation);
0193         return false;
0194       }
0195 
0196       if(!indexLocation.endsWith("/")) {
0197         indexLocation += "/";
0198       }
0199 
0200       // otherwise let us read the index definition file
0201       locationFile = new File(indexLocation + "LuceneIndexDefinition.xml");
0202 
0203       // check if this file is available
0204       if(!locationFile.exists()) {
0205         System.out
0206                 .println("Index Definition file not found - Skipping the invalid Index Location :"
0207                         + indexLocation + "LuceneIndexDefinition.xml");
0208         return false;
0209       }
0210 
0211       java.io.FileReader fileReader = new java.io.FileReader(indexLocation
0212               "LuceneIndexDefinition.xml");
0213 
0214       Map<String,Object> indexInformation = null;
0215       try {
0216         // other wise read this file
0217         com.thoughtworks.xstream.XStream xstream = new com.thoughtworks.xstream.XStream(
0218                 new com.thoughtworks.xstream.io.xml.StaxDriver());
0219   
0220         // Saving was accomplished by using XML serialization of the map.
0221         indexInformation = (Map<String,Object>)xstream.fromXML(fileReader);
0222       }
0223       finally {
0224         fileReader.close();
0225       }
0226 
0227       // find out if the current index was indexed by annicIndexPR
0228       String indexedWithANNICIndexPR = (String)indexInformation
0229               .get(Constants.CORPUS_INDEX_FEATURE);
0230 
0231       if(indexedWithANNICIndexPR == null
0232               || !indexedWithANNICIndexPR
0233                       .equals(Constants.CORPUS_INDEX_FEATURE_VALUE)) {
0234         System.out
0235                 .println("This corpus was not indexed by Annic Index PR - Skipping the invalid Index");
0236         return false;
0237       }
0238 
0239       // find out the baseTokenAnnotationType name
0240       baseTokenAnnotationType = ((String)indexInformation
0241               .get(Constants.BASE_TOKEN_ANNOTATION_TYPE)).trim();
0242 
0243       int separatorIndex = baseTokenAnnotationType.lastIndexOf('.');
0244       if(separatorIndex >= 0) {
0245         baseTokenAnnotationType = baseTokenAnnotationType
0246                 .substring(separatorIndex + 1);
0247       }
0248 
0249       // create various Queries from the user's query
0250       Query[] luceneQueries = queryParser.parse("contents", query,
0251               baseTokenAnnotationType, corpusToSearchIn,
0252               annotationSetToSearchIn);
0253       if(queryParser.needValidation()) {
0254         if(DEBUGSystem.out.println("Validation enabled!");
0255       }
0256       else {
0257         if(DEBUGSystem.out.println("Validation disabled!");
0258       }
0259 
0260       // create an instance of Index Searcher
0261       LuceneIndexSearcher searcher = new LuceneIndexSearcher(indexLocation);
0262       
0263       try {
0264         // we need to iterate through one query at a time
0265         for(int luceneQueryIndex = 0; luceneQueryIndex < luceneQueries.length; luceneQueryIndex++) {
0266             
0267           /*
0268            * this call reinitializes the first Term positions arraylists
0269            * which are being used to store the results
0270            */
0271           searcher.initializeTermPositions();
0272   
0273           /*
0274            * and now execute the query result of which will be stored in
0275            * hits
0276            */
0277           Hits hits = searcher.search(luceneQueries[luceneQueryIndex]);
0278   
0279           /*
0280            * and so now find out the positions of the first terms in the
0281            * returned results. first term position is the position of the
0282            * first term in the found pattern
0283            */
0284           List<?>[] firstTermPositions = searcher.getFirstTermPositions();
0285           // if no result available, set null to our scores
0286           if(firstTermPositions[0].size() == 0) {
0287             // do nothing
0288             continue;
0289           }
0290   
0291   
0292          
0293           // iterate through each result and collect necessary
0294           // information
0295           for(int hitIndex = 0; hitIndex < hits.length(); hitIndex++) {
0296             int index = firstTermPositions[0].indexOf(new Integer(hits
0297                     .id(hitIndex)));
0298   
0299             // we fetch all the first term positions for the query
0300             // issued
0301             List<?> ftp = (List<?>)firstTermPositions[1].get(index);
0302 
0303             /*
0304              * pattern length (in terms of total number of annotations
0305              * following one other)
0306              */
0307             int patLen = ((Integer)firstTermPositions[2].get(index)).intValue();
0308   
0309             /*
0310              * and the type of query (if it has only one annotation in it,
0311              * or multiple terms following them)
0312              */
0313             int qType = ((Integer)firstTermPositions[3].get(index)).intValue();
0314   
0315             // find out the documentID
0316             String serializedFileID = hits.doc(hitIndex).get(Constants.DOCUMENT_ID_FOR_SERIALIZED_FILE);
0317             QueryItem queryItem = new QueryItem();
0318             queryItem.annotationSetName = hits.doc(hitIndex).get(
0319                     Constants.ANNOTATION_SET_ID).intern();
0320             queryItem.id = hits.id(hitIndex);
0321             queryItem.documentID = hits.doc(hitIndex).get(Constants.DOCUMENT_ID).intern();
0322             queryItem.ftp = ftp;
0323             queryItem.patLen = patLen;
0324             queryItem.qType = qType;
0325             queryItem.query = luceneQueries[luceneQueryIndex];
0326             queryItem.queryString = queryParser.getQueryString(luceneQueryIndex).intern();
0327   
0328             /*
0329              * all these information go in the top level arrayList. we
0330              * create separate arrayList for each individual document
0331              * where each element in the arrayList provides information
0332              * about different query issued over it
0333              */
0334             List<QueryItem> queryItemsList = searchResultInfoMap.get(serializedFileID);
0335             if(queryItemsList == null) {
0336               queryItemsList = new ArrayList<QueryItem>();
0337               queryItemsList.add(queryItem);
0338               searchResultInfoMap.put(serializedFileID, queryItemsList);
0339               serializedFilesIDsList.add(serializedFileID);
0340             }
0341             else {
0342 //              // before inserting we check if it is already added
0343 //              if(!doesAlreadyExist(queryItem, queryItemsList)) {
0344                 queryItemsList.add(queryItem);
0345 //              }
0346             }
0347           }
0348         }
0349       }
0350       finally {
0351         searcher.close();
0352       }
0353       // if any result possible, return true
0354       if(searchResultInfoMap.size() 0)
0355         success = true;
0356       else success = false;
0357     }
0358     catch(Exception e) {
0359       throw new SearchException(e);
0360     }
0361 
0362     return success;
0363   }
0364 
0365   /**
0366    * First term positions.
0367    */
0368   private List<?> ftp;
0369 
0370   /**
0371    * This method returns a list containing instances of Pattern
0372    
0373    @param numberOfResults the number of results to fetch
0374    @return a list of QueryResult
0375    @throws Exception
0376    */
0377   public List<Pattern> next(int numberOfResultsthrows Exception {
0378 
0379     /*
0380      * We check here, if there were no results found, we return null
0381      */
0382     if(!success) {
0383       return null;
0384     }
0385 
0386     if(fwdIterationEnded) {
0387       return null;
0388     }
0389 
0390     int noOfResultsToFetch = numberOfResults;
0391     List<Pattern> toReturn = new ArrayList<Pattern>();
0392 
0393     // iterator over one document ID
0394     for(; serializedFileIDIndex < serializedFilesIDsList.size(); serializedFileIDIndex++, queryItemIndex = 0this.ftp = null) {
0395 
0396       // deal with one document at a time
0397       String serializedFileID = serializedFilesIDsList.get(serializedFileIDIndex);
0398 
0399       // obtain the information about all queries
0400       List<QueryItem> queryItemsList = searchResultInfoMap.get(serializedFileID);
0401       if(queryItemsList.isEmpty()) continue;
0402       String folder = queryItemsList.get(0).documentID.intern();
0403       
0404       if(serializedFileIDInUse == null || !serializedFileIDInUse.equals(serializedFileID)
0405               || tokenStreamInUse == null) {
0406         serializedFileIDInUse = serializedFileID;
0407         try {
0408           // this is the first and last time we want this tokenStream
0409           // to hold information about the current document
0410           tokenStreamInUse = getTokenStreamFromDisk(indexLocation,getCompatibleName(folder),
0411                   getCompatibleName(serializedFileID));
0412         }
0413         catch(Exception e) {
0414           continue;
0415         }
0416       }
0417 
0418       // deal with one query at a time
0419       for(; queryItemIndex < queryItemsList.size(); queryItemIndex++, ftpIndex = -1this.ftp = null) {
0420         QueryItem queryItem = queryItemsList.get(queryItemIndex);
0421 
0422         /*
0423          * we've found the tokenStream and now we need to convert it
0424          * into the format we had at the time of creating index.. the
0425          * method getTokenStream(...) returns an array of arraylists
0426          * where the first object is GateAnnotations of that pattern
0427          * only second object is the position of the first token of the
0428          * actual pattern third object is the lenght of the actual
0429          * pattern
0430          */
0431         int qType = queryItem.qType;
0432         int patLen = queryItem.patLen;
0433         if(this.ftp == null) {
0434           this.ftp = queryItem.ftp;
0435         }
0436         else {
0437           qType = 1;
0438           patLen = 1;
0439         }
0440         PatternResult patternResult = getPatternResult(tokenStreamInUse,
0441                 queryItem.annotationSetName, patLen, qType, contextWindow,
0442                 queryItem.queryString, baseTokenAnnotationType,
0443                 noOfResultsToFetch);
0444 
0445         /*
0446          * if none of the found patterns is valid continue with the next
0447          * query
0448          */
0449         if(patternResult == null || patternResult.numberOfPatterns == 0)
0450           continue;
0451 
0452         /*
0453          * We've found some patterns so give its effect to
0454          * noOfResultsToFetch
0455          */
0456         if(noOfResultsToFetch != -1)
0457           noOfResultsToFetch -= patternResult.numberOfPatterns;
0458 
0459         List<Pattern> annicPatterns = createAnnicPatterns(new LuceneQueryResult(
0460                 removeUnitNumber(serializedFileID), patternResult.annotationSetName,
0461                 patternResult.firstTermPositions, patternResult.patternLegths,
0462                 queryItem.qType, patternResult.gateAnnotations,
0463                 queryItem.queryString));
0464         toReturn.addAll(annicPatterns);
0465 
0466         /*
0467          * If noOfResultsToFetch is 0, it means the search should
0468          * terminate unless and otherwise user has asked to return all
0469          * (-1)
0470          */
0471         if(numberOfResults != -&& noOfResultsToFetch == 0) {
0472           return toReturn;
0473         }
0474       }
0475     }
0476 
0477     /*
0478      * if we are out of the loop set success to false such that this
0479      * thread is closed
0480      */
0481     fwdIterationEnded = true;
0482     return toReturn;
0483   }
0484 
0485   /**
0486    * Given an object of luceneQueryResult this method for each found
0487    * pattern, converts it into the annic pattern. In other words, for
0488    * each pattern it collects the information such as annotations in
0489    * context and so on.
0490    */
0491   private List<Pattern> createAnnicPatterns(LuceneQueryResult aResult) {
0492     // get the result from search engine
0493     List<Pattern> annicPatterns = new ArrayList<Pattern>();
0494     List<?> firstTermPositions = aResult.getFirstTermPositions();
0495     if(firstTermPositions != null && firstTermPositions.size() 0) {
0496       List<Integer> patternLength = aResult.patternLength();
0497       // locate Pattern
0498       List<Pattern> pats = locatePatterns((String)aResult.getDocumentID(),
0499               aResult.getAnnotationSetName(), aResult.getGateAnnotations(),
0500               firstTermPositions, patternLength, aResult.getQuery());
0501       if(pats != null) {
0502         annicPatterns.addAll(pats);
0503       }
0504     }
0505     return annicPatterns;
0506   }
0507 
0508   /**
0509    * Locates the valid patterns in token stream and discards the invalid
0510    * first term positions returned by the lucene searcher.
0511    */
0512   private List<Pattern> locatePatterns(String docID, String annotationSetName,
0513           List<List<PatternAnnotation>> gateAnnotations,
0514           List<?> firstTermPositions, List<Integer> patternLength,
0515           String queryString) {
0516 
0517     // patterns
0518     List<Pattern> pats = new ArrayList<Pattern>();
0519     for(int i = 0; i < gateAnnotations.size(); i++) {
0520 
0521       // each element in the tokens stream is a pattern
0522       List<PatternAnnotation> annotations = gateAnnotations.get(i);
0523       if(annotations.size() == 0) {
0524         continue;
0525       }
0526       // from this annotations we need to create a text string
0527       // so lets find out the smallest and the highest offsets
0528       int smallest = Integer.MAX_VALUE;
0529       int highest = -1;
0530       for(int j = 0; j < annotations.size(); j++) {
0531         // each annotation is an instance of GateAnnotation
0532         PatternAnnotation ga = annotations.get(j);
0533         if(ga.getStartOffset() < smallest) {
0534           smallest = ga.getStartOffset();
0535         }
0536 
0537         if(ga.getEndOffset() > highest) {
0538           highest = ga.getEndOffset();
0539         }
0540       }
0541 
0542       // we have smallest and highest offsets
0543       char[] patternText = new char[highest - smallest];
0544 
0545       for(int j = 0; j < patternText.length; j++) {
0546         patternText[j' ';
0547       }
0548 
0549       // and now place the text
0550       for(int j = 0; j < annotations.size(); j++) {
0551         // each annotation is an instance of GateAnnotation
0552         PatternAnnotation ga = annotations.get(j);
0553         if(ga.getText() == null) {
0554           // this is to avoid annotations such as split
0555           continue;
0556         }
0557 
0558         for(int k = ga.getStartOffset() - smallest, m = 0; m < ga.getText()
0559                 .length()
0560                 && k < patternText.length; m++, k++) {
0561           patternText[k= ga.getText().charAt(m);
0562         }
0563 
0564         // we will initiate the annotTypes as well
0565         if(luceneSearcher.annotationTypesMap.keySet().contains(ga.getType())) {
0566           List<String> aFeatures = luceneSearcher.annotationTypesMap.get(ga
0567                   .getType());
0568           Map<String, String> features = ga.getFeatures();
0569           if(features != null) {
0570             Iterator<String> fSet = features.keySet().iterator();
0571             while(fSet.hasNext()) {
0572               String feature = fSet.next();
0573               if(!aFeatures.contains(feature)) {
0574                 aFeatures.add(feature);
0575               }
0576             }
0577           }
0578           luceneSearcher.annotationTypesMap.put(ga.getType(), aFeatures);
0579         }
0580         else {
0581           Map<String, String> features = ga.getFeatures();
0582           List<String> aFeatures = new ArrayList<String>();
0583           aFeatures.add("All");
0584           if(features != null) {
0585             aFeatures.addAll(features.keySet());
0586           }
0587           luceneSearcher.annotationTypesMap.put(ga.getType(), aFeatures);
0588         }
0589         // end of initializing annotationTypes for the comboBox
0590       }
0591 
0592       // we have the text
0593       // smallest is the textStOffset
0594       // highest is the textEndOffset
0595       // how to find the patternStartOffset
0596       int stPos = ((Integer)firstTermPositions.get(i)).intValue();
0597       int endOffset = patternLength.get(i).intValue();
0598       int patStart = Integer.MAX_VALUE;
0599 
0600       for(int j = 0; j < annotations.size(); j++) {
0601         // each annotation is an instance of GateAnnotation
0602         PatternAnnotation ga = annotations.get(j);
0603         if(ga.getPosition() == stPos) {
0604           if(ga.getStartOffset() < patStart) {
0605             patStart = ga.getStartOffset();
0606           }
0607         }
0608       }
0609 
0610       if(patStart == Integer.MAX_VALUE) {
0611         continue;
0612       }
0613 
0614       if(patStart < smallest || endOffset > highest) {
0615         continue;
0616       }
0617 
0618       // now create the pattern for this
0619       Pattern ap = new Pattern(docID, annotationSetName,
0620               new String(patternText), patStart, endOffset, smallest, highest,
0621               annotations, queryString);
0622       pats.add(ap);
0623     }
0624     return pats;
0625   }
0626 
0627   /**
0628    * Each index unit is first converted into a separate lucene document.
0629    * And a new ID with documentName and a unit number is assined to it.
0630    * But when we return results, we take the unit number out.
0631    */
0632   private String removeUnitNumber(String documentID) {
0633     int index = documentID.lastIndexOf("-");
0634     if(index == -1return documentID;
0635     return documentID.substring(0, index);
0636   }
0637 
0638   /**
0639    * This method looks on the disk to find the tokenStream
0640    */
0641   private List<gate.creole.annic.apache.lucene.analysis.Token> getTokenStreamFromDisk(
0642           String indexDirectory, String documentFolder, String documentIDthrows Exception {
0643     if(indexDirectory.startsWith("file:/"))
0644       indexDirectory = indexDirectory.substring(6, indexDirectory.length());
0645 
0646     // use buffering
0647     File folder = new File(indexDirectory, Constants.SERIALIZED_FOLDER_NAME);
0648     folder = new File(folder, documentFolder);
0649     File fileToLoad = new File(folder, documentID + ".annic");
0650     InputStream file = new FileInputStream(fileToLoad);
0651     InputStream buffer = new BufferedInputStream(file);
0652     ObjectInput input = new ObjectInputStream(buffer);
0653 
0654     // deserialize the List
0655     @SuppressWarnings("unchecked")
0656     List<gate.creole.annic.apache.lucene.analysis.Token> recoveredTokenStream = 
0657       (List<gate.creole.annic.apache.lucene.analysis.Token>)input.readObject();
0658     if(input != null) {
0659       // close "input" and its underlying streams
0660       input.close();
0661     }
0662     return recoveredTokenStream;
0663   }
0664 
0665   /**
0666    * this method takes the tokenStream as a text, the first term
0667    * positions, pattern length, queryType and patternWindow and returns
0668    * the GateAnnotations as an array for each pattern with left and
0669    * right context
0670    */
0671   private PatternResult getPatternResult(
0672           List<gate.creole.annic.apache.lucene.analysis.Token> subTokens,
0673           String annotationSetName, int patLen, int qType, int patWindow,
0674           String query, String baseTokenAnnotationType,
0675           int numberOfResultsToFetch) {
0676 
0677     /*
0678      * ok so we first see what kind of query is that two possibilities
0679      * (Phrase query or Term query) Term query is what contains only one
0680      * word to seach and Phrase query contains more than one word 1
0681      * indicates the PhraseQuery
0682      */
0683     if(qType == 1) {
0684       return getPatternResult(subTokens, annotationSetName, patLen, patWindow,
0685               query, baseTokenAnnotationType, numberOfResultsToFetch);
0686     }
0687     else {
0688       /*
0689        * where the query is Term. In term query it is possible that user
0690        * is searching for the particular annotation type (say: "Token"
0691        * or may be for text (say: "Hello") query parser converts the
0692        * annotation type query into Token == "*" and the latter to
0693        * Token.string == "Hello"
0694        */
0695 
0696       /*
0697        * the first element is text. the second element is type
0698        */
0699       String annotText = (String)ftp.get(0);
0700       String annotType = (String)ftp.get(1);
0701 
0702       // so here we search through subTokens and find out the positions
0703       List<Integer> positions = new ArrayList<Integer>();
0704       for(int j = 0; j < subTokens.size(); j++) {
0705         gate.creole.annic.apache.lucene.analysis.Token token = subTokens.get(j);
0706         String type = token.termText();
0707         String text = token.type();
0708 
0709         // if annotType == "*", the query was {AnnotType}
0710         if(annotType.equals("*")) {
0711           if(type.equals(annotText&& annotType.equals(text)) {
0712             positions.add(new Integer(token.getPosition()));
0713           }
0714         }
0715         // the query is Token == "string"
0716         else {
0717           if(annotText.equals(type&& annotType.equals(text)) {
0718             positions.add(new Integer(token.getPosition()));
0719           }
0720         }
0721       }
0722 
0723       this.ftp = positions;
0724       // we have positions here
0725       return getPatternResult(subTokens, annotationSetName, 1, patWindow,
0726               query, baseTokenAnnotationType, numberOfResultsToFetch);
0727     }
0728   }
0729 
0730   /**
0731    * This method returns the valid patterns back and the respective
0732    * GateAnnotations
0733    */
0734   @SuppressWarnings({"rawtypes""unchecked"})
0735   private PatternResult getPatternResult(
0736           List<gate.creole.annic.apache.lucene.analysis.Token> subTokens,
0737           String annotationSetName, int patLen, int patWindow, String query,
0738           String baseTokenAnnotationType, int noOfResultsToFetch) {
0739 
0740     List<List<PatternAnnotation>> tokens = new ArrayList<List<PatternAnnotation>>();
0741     List<Integer> patLens = new ArrayList<Integer>();
0742     ftpIndex++;
0743 
0744     // Phrase Query
0745     // consider only one pattern at a time
0746 
0747     // first term position index at the begining
0748     int ftpIndexATB = ftpIndex;
0749     mainForLoop: for(; ftpIndex < ftp.size()
0750             && (noOfResultsToFetch == -|| noOfResultsToFetch > 0); ftpIndex++) {
0751 
0752       // find out the position of the first term
0753       int pos = ((Integer)ftp.get(ftpIndex)).intValue();
0754 
0755       // find out the token with pos
0756       int j = 0;
0757       for(; j < subTokens.size(); j++) {
0758         gate.creole.annic.apache.lucene.analysis.Token token = subTokens.get(j);
0759         if(token.getPosition() == pos) {
0760           break;
0761         }
0762       }
0763 
0764       int counter = 0;
0765       int leftstart = -1;
0766       /*
0767        * ok so we need to go back to find out the first token of the
0768        * left context
0769        */
0770       int k = j - 1;
0771       for(; k >= 0; k--) {
0772         gate.creole.annic.apache.lucene.analysis.Token token = subTokens.get(k);
0773         if(token.getPosition() < pos
0774                 && token.termText().equals(baseTokenAnnotationType)
0775                 && token.type().equals("*")) {
0776           counter++;
0777           leftstart = token.startOffset();
0778           j = k;
0779         }
0780         if(counter == patWindow) {
0781           break;
0782         }
0783       }
0784 
0785       // j holds the start of the left context
0786 
0787       // now we want to search for the end of left context
0788       pos--;
0789       k = j;
0790 
0791       if(leftstart > -1) {
0792 
0793         boolean breakNow = false;
0794         for(; k < subTokens.size(); k++) {
0795           gate.creole.annic.apache.lucene.analysis.Token token = subTokens
0796                   .get(k);
0797           if(token.getPosition() == pos) {
0798             breakNow = true;
0799           }
0800           else {
0801             if(breakNow) {
0802               break;
0803             }
0804           }
0805         }
0806       }
0807       // now k holds the begining of the pattern
0808 
0809       // leftEnd holds the position of the last token in left context
0810       int leftEnd = leftstart == -? -: k - 1;
0811 
0812       /*
0813        * we need to validate this pattern. As a result of query, we get
0814        * the positions of the first term. We need to locate the full
0815        * pattern along with all its other annotations. This is done by
0816        * using the ValidatePattern class. This class provides a method,
0817        * which takes as arguments the query Tokens, the position in the
0818        * tokenStream from where to start searching and returns the end
0819        * offset of the last annotation in the found pattern. We then
0820        * search for this endoffset in our current tokenStream to
0821        * retrieve the wanted annotations.
0822        */
0823       int upto = -1;
0824       int tempPos = 0;
0825       if(this.queryParser.needValidation()) {
0826 
0827         try {
0828 
0829           List<String> queryTokens = luceneSearcher.getQueryTokens(query);
0830           if(queryTokens == null) {
0831             queryTokens = new QueryParser().findTokens(query);
0832             luceneSearcher.addQueryTokens(query, queryTokens);
0833           }
0834 
0835           /*
0836            * validate method returns the endoffset of the last token of
0837            * the middle pattern returns -1 if pattern could not be
0838            * located at that location
0839            */
0840           PatternValidator vp = new PatternValidator();
0841 
0842           // here k is the position where the first token should occur
0843 
0844           upto = vp.validate(queryTokens, subTokens, k, new QueryParser());
0845           if(upto == -1) {
0846             /*
0847              * if the validatePAttern class could not find the valid
0848              * pattern it returns -1 and therefore we should remove the
0849              * position of the invalid pattern
0850              */
0851             ftp.remove(ftpIndex);
0852             ftpIndex--;
0853             continue mainForLoop;
0854           }
0855           else {
0856             /*
0857              * now we need to locate the token whose endPosition is upto
0858              */
0859             int jj = leftEnd + 1;
0860             boolean breaknow = false;
0861             tempPos = subTokens.get(jj).getPosition();
0862             for(; jj < subTokens.size(); jj++) {
0863               gate.creole.annic.apache.lucene.analysis.Token token = subTokens
0864                       .get(jj);
0865               if(token.endOffset() == upto) {
0866                 tempPos = token.getPosition();
0867                 breaknow = true;
0868               }
0869               else if(breaknow) {
0870                 break;
0871               }
0872             }
0873             // we send the endoffset to our GUI class
0874             patLens.add(new Integer(upto));
0875 
0876             /*
0877              * k holds the position of the first token in right context
0878              */
0879             k = jj;
0880           }
0881         }
0882         catch(Exception e) {
0883           e.printStackTrace();
0884         }
0885       }
0886       else {
0887         /*
0888          * the query contains all tokens, which is already validated at
0889          * the time of creating query the pointer k points to the
0890          * begining of our patern we need to travel patLen into the
0891          * right direction to obtain the pattern
0892          */
0893         for(counter = 0; counter < patLen && k < subTokens.size(); k++) {
0894           gate.creole.annic.apache.lucene.analysis.Token token = subTokens
0895                   .get(k);
0896           if(token.termText().equals(baseTokenAnnotationType)
0897                   && token.type().equals("*")) {
0898             counter++;
0899             upto = token.endOffset();
0900             tempPos = token.getPosition();
0901           }
0902         }
0903         patLens.add(new Integer(upto));
0904         k++;
0905       }
0906       int maxEndOffset = upto;
0907 
0908       /*
0909        * so now search for the token with the position == tempPos + 1 in
0910        * other words search for the first term of the right context
0911        */
0912       for(; k < subTokens.size(); k++) {
0913         gate.creole.annic.apache.lucene.analysis.Token token = subTokens.get(k);
0914         if(token.getPosition() == tempPos + 1) {
0915           break;
0916         }
0917       }
0918 
0919       // and now we need to locate the right context pattern
0920       counter = 0;
0921       for(; k < subTokens.size(); k++) {
0922         gate.creole.annic.apache.lucene.analysis.Token token = subTokens.get(k);
0923         if(token.startOffset() >= upto
0924                 && token.termText().equals(baseTokenAnnotationType)
0925                 && token.type().equals("*")) {
0926           counter++;
0927           maxEndOffset = token.endOffset();
0928         }
0929         if(counter == patWindow) {
0930           break;
0931         }
0932       }
0933 
0934       // if there are any sub-tokens left
0935       if(k < subTokens.size()) {
0936         /*
0937          * now we would search for the position untill we see it having
0938          * the same position
0939          */
0940         tempPos = subTokens.get(k).getPosition();
0941 
0942         for(; k < subTokens.size(); k++) {
0943           gate.creole.annic.apache.lucene.analysis.Token token = subTokens
0944                   .get(k);
0945           if(token.getPosition() != tempPos) {
0946             break;
0947           }
0948         }
0949       }
0950 
0951       if(k >= subTokens.size()) {
0952         // we used all sub-tokens - set k to maximum size
0953         k = subTokens.size() 1;
0954       }
0955 
0956       /*
0957        * so k is the position til where we need to search for each
0958        * annotation and every feature in it at the time of creating
0959        * index were converted into separate tokens we need to convert
0960        * them back into annotations
0961        */
0962       List<PatternAnnotation> patternGateAnnotations = new ArrayList<PatternAnnotation>();
0963       PatternAnnotation ga = null;
0964       for(int m = j; m <= k; m++) {
0965         gate.creole.annic.apache.lucene.analysis.Token token = subTokens.get(m);
0966         String text = token.termText();
0967         int st = token.startOffset();
0968         int end = token.endOffset();
0969         String type = token.type();
0970         int position = token.getPosition();
0971 
0972         // if this is a new annotation Type
0973         if(type.equals("*")) {
0974           ga = new PatternAnnotation();
0975           ga.setType(text);
0976           ga.setStOffset(st);
0977           ga.setEnOffset(end);
0978           ga.setPosition(position);
0979           if(ga.getEndOffset() <= maxEndOffset) {
0980             patternGateAnnotations.add(ga);
0981           }
0982           continue;
0983         else if(type.equals("**")) {
0984           continue;
0985         }
0986 
0987         // and from here all are the features
0988         int index = type.indexOf(".");
0989         String feature = type.substring(index + 1, type.length());
0990         /*
0991          * we need to compare the type1 each annotation has string
0992          * feature in index so text will be definitely going to be
0993          * initialized
0994          */
0995         if(feature.equals("string")) {
0996           ga.setText(text);
0997         }
0998         ga.addFeature(feature, text);
0999       }
1000       tokens.add(patternGateAnnotations);
1001       if(noOfResultsToFetch != -1noOfResultsToFetch--;
1002     }
1003 
1004     if(noOfResultsToFetch == && ftpIndex < ftp.size()) ftpIndex--;
1005 
1006     // finally create an instance of PatternResult
1007     PatternResult pr = new PatternResult();
1008     pr.annotationSetName = annotationSetName;
1009     pr.gateAnnotations = tokens;
1010     pr.firstTermPositions = new ArrayList();
1011     for(int i = 0; i < pr.gateAnnotations.size(); i++) {
1012       pr.firstTermPositions.add(ftp.get(i + ftpIndexATB));
1013     }
1014     pr.patternLegths = patLens;
1015     pr.numberOfPatterns = pr.gateAnnotations.size();
1016     return pr;
1017   }
1018 
1019   /**
1020    * Inner class to store pattern results.
1021    
1022    @author niraj
1023    */
1024   private class PatternResult {
1025     int numberOfPatterns;
1026 
1027     List<List<PatternAnnotation>> gateAnnotations;
1028     
1029     String annotationSetName;
1030 
1031     @SuppressWarnings("rawtypes")
1032     List firstTermPositions;
1033 
1034     List<Integer> patternLegths;
1035   }
1036 
1037   /**
1038    * Inner class to store query Item.
1039    
1040    @author niraj
1041    
1042    */
1043   private class QueryItem {
1044     @SuppressWarnings("unused")
1045     float score;
1046 
1047     @SuppressWarnings("unused")
1048     int id;
1049 
1050     String documentID;
1051     
1052     @SuppressWarnings("rawtypes")
1053     List ftp;
1054 
1055     int patLen;
1056 
1057     int qType;
1058 
1059     @SuppressWarnings("unused")
1060     Query query;
1061 
1062     String queryString;
1063 
1064     String annotationSetName;
1065 
1066 //    public boolean equals(Object m) {
1067 //      if(m instanceof QueryItem) {
1068 //        QueryItem n = (QueryItem)m;
1069 //        // no need to compare documentID as we don't compare documents with different docIDs anyway
1070 //        return n.score == score && n.id == id && n.patLen == patLen
1071 //                && n.qType == qType && n.ftp.size() == ftp.size()
1072 //                && n.queryString.equals(queryString)
1073 //                && n.annotationSetName.equals(annotationSetName)
1074 //                && areTheyEqual(n.ftp, ftp, qType);
1075 //      }
1076 //      return false;
1077 //    }
1078   }
1079 
1080   /**
1081    * Checks if the QueryItem already exists.
1082    */
1083 //  private boolean doesAlreadyExist(QueryItem n, List<QueryItem> top) {
1084 //
1085 //    for(int i = 0; i < top.size(); i++) {
1086 //      QueryItem m = top.get(i);
1087 //      if(m.equals(n)) return true;
1088 //    }
1089 //    return false;
1090 //  }
1091 
1092   /**
1093    * Checks if two first term positions are identical. 
1094    */
1095   @SuppressWarnings({"unused""rawtypes"})
1096   private boolean areTheyEqual(List ftp, List ftp1, int qType) {
1097     if(qType == 1) {
1098       if(ftp.size() == ftp1.size()) {
1099         for(int i = 0; i < ftp.size(); i++) {
1100           int pos = ((Integer)ftp.get(i)).intValue();
1101           int pos1 = ((Integer)ftp1.get(i)).intValue();
1102           if(pos != pos1return false;
1103         }
1104         return true;
1105       }
1106       else {
1107         return false;
1108       }
1109     }
1110     else {
1111       String annotText = (String)ftp.get(0);
1112       String annotType = (String)ftp.get(1);
1113       String annotText1 = (String)ftp1.get(0);
1114       String annotType1 = (String)ftp1.get(1);
1115       return annotText1.equals(annotText&& annotType1.equals(annotType);
1116     }
1117   }
1118 
1119   /**
1120    * Gets the query.
1121    */
1122   public String getQuery() {
1123     return query;
1124   }
1125 
1126 }