IndexReader.java
001 package gate.creole.annic.apache.lucene.index;
002 
003 /**
004  * Copyright 2004 The Apache Software Foundation
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 
019 import java.io.IOException;
020 import java.io.File;
021 import java.util.Collection;
022 
023 import gate.creole.annic.apache.lucene.store.Directory;
024 import gate.creole.annic.apache.lucene.store.FSDirectory;
025 import gate.creole.annic.apache.lucene.store.Lock;
026 import gate.creole.annic.apache.lucene.document.Document;
027 import gate.creole.annic.apache.lucene.document.Field;          // for javadoc
028 import gate.creole.annic.apache.lucene.search.Similarity;
029 
030 /** IndexReader is an abstract class, providing an interface for accessing an
031  index.  Search of an index is done entirely through this abstract interface,
032  so that any subclass which implements it is searchable.
033 
034  <p> Concrete subclasses of IndexReader are usually constructed with a call to
035  the static method {@link #open}.
036 
037  <p> For efficiency, in this API documents are often referred to via
038  <i>document numbers</i>, non-negative integers which each name a unique
039  document in the index.  These document numbers are ephemeral--they may change
040  as documents are added to and deleted from an index.  Clients should thus not
041  rely on a given document having the same number between sessions.
042 
043  @author Doug Cutting
044  @version $Id: IndexReader.java 529 2004-10-05 11:55:26Z niraj $
045 */
046 @SuppressWarnings("rawtypes")
047 public abstract class IndexReader {
048 
049   /**
050    * Constructor used if IndexReader is not owner of its directory.
051    * This is used for IndexReaders that are used within other IndexReaders that take care or locking directories.
052    *
053    @param directory Directory where IndexReader files reside.
054    */
055   protected IndexReader(Directory directory) {
056     this.directory = directory;
057     segmentInfos = null;
058     directoryOwner = false;
059     closeDirectory = false;
060     stale = false;
061     hasChanges = false;
062     writeLock = null;
063   }
064 
065   /**
066    * Constructor used if IndexReader is owner of its directory.
067    * If IndexReader is owner of its directory, it locks its directory in case of write operations.
068    *
069    @param directory Directory where IndexReader files reside.
070    @param segmentInfos Used for write-l
071    @param closeDirectory
072    */
073   IndexReader(Directory directory, SegmentInfos segmentInfos, boolean closeDirectory) {
074     this.directory = directory;
075     this.segmentInfos = segmentInfos;
076     directoryOwner = true;
077     this.closeDirectory = closeDirectory;
078     stale = false;
079     hasChanges = false;
080     writeLock = null;
081   }
082 
083   final private Directory directory;
084 
085   final private boolean directoryOwner;
086   final private SegmentInfos segmentInfos;
087   private Lock writeLock;
088   private boolean stale;
089   private boolean hasChanges;
090 
091   final private boolean closeDirectory;
092 
093   /** Returns an IndexReader reading the index in an FSDirectory in the named
094    path. */
095   public static IndexReader open(String paththrows IOException {
096     return open(FSDirectory.getDirectory(path, false)true);
097   }
098 
099   /** Returns an IndexReader reading the index in an FSDirectory in the named
100    path. */
101   public static IndexReader open(File paththrows IOException {
102     return open(FSDirectory.getDirectory(path, false)true);
103   }
104 
105   /** Returns an IndexReader reading the index in the given Directory. */
106   public static IndexReader open(final Directory directorythrows IOException {
107     return open(directory, false);
108   }
109 
110   private static IndexReader open(final Directory directory, final boolean closeDirectorythrows IOException {
111     synchronized (directory) {        // in- & inter-process sync
112       return (IndexReader)new Lock.With(
113           directory.makeLock(IndexWriter.COMMIT_LOCK_NAME),
114           IndexWriter.COMMIT_LOCK_TIMEOUT) {
115           @Override
116           public Object doBody() throws IOException {
117             SegmentInfos infos = new SegmentInfos();
118             infos.read(directory);
119             if (infos.size() == 1) {      // index is optimized
120               return new SegmentReader(infos, infos.info(0), closeDirectory);
121             else {
122               IndexReader[] readers = new IndexReader[infos.size()];
123               for (int i = 0; i < infos.size(); i++)
124                 readers[inew SegmentReader(infos.info(i));
125               return new MultiReader(directory, infos, closeDirectory, readers);
126             }
127           }
128         }.run();
129     }
130   }
131 
132   /** Returns the directory this index resides in. */
133   public Directory directory() { return directory; }
134 
135   /**
136    * Returns the time the index in the named directory was last modified.
137    *
138    <p>Synchronization of IndexReader and IndexWriter instances is
139    * no longer done via time stamps of the segments file since the time resolution
140    * depends on the hardware platform. Instead, a version number is maintained
141    * within the segments file, which is incremented everytime when the index is
142    * changed.</p>
143    *
144    @deprecated  Replaced by {@link #getCurrentVersion(String)}
145    * */
146   @Deprecated
147   public static long lastModified(String directorythrows IOException {
148     return lastModified(new File(directory));
149   }
150 
151   /**
152    * Returns the time the index in the named directory was last modified.
153    *
154    <p>Synchronization of IndexReader and IndexWriter instances is
155    * no longer done via time stamps of the segments file since the time resolution
156    * depends on the hardware platform. Instead, a version number is maintained
157    * within the segments file, which is incremented everytime when the index is
158    * changed.</p>
159    *
160    @deprecated  Replaced by {@link #getCurrentVersion(File)}
161    * */
162   @Deprecated
163   public static long lastModified(File directorythrows IOException {
164     return FSDirectory.fileModified(directory, "segments");
165   }
166 
167   /**
168    * Returns the time the index in the named directory was last modified.
169    *
170    <p>Synchronization of IndexReader and IndexWriter instances is
171    * no longer done via time stamps of the segments file since the time resolution
172    * depends on the hardware platform. Instead, a version number is maintained
173    * within the segments file, which is incremented everytime when the index is
174    * changed.</p>
175    *
176    @deprecated  Replaced by {@link #getCurrentVersion(Directory)}
177    * */
178   @Deprecated
179   public static long lastModified(Directory directorythrows IOException {
180     return directory.fileModified("segments");
181   }
182 
183   /**
184    * Reads version number from segments files. The version number counts the
185    * number of changes of the index.
186    *
187    @param directory where the index resides.
188    @return version number.
189    @throws IOException if segments file cannot be read
190    */
191   public static long getCurrentVersion(String directorythrows IOException {
192     return getCurrentVersion(new File(directory));
193   }
194 
195   /**
196    * Reads version number from segments files. The version number counts the
197    * number of changes of the index.
198    *
199    @param directory where the index resides.
200    @return version number.
201    @throws IOException if segments file cannot be read
202    */
203   public static long getCurrentVersion(File directorythrows IOException {
204     Directory dir = FSDirectory.getDirectory(directory, false);
205     long version = getCurrentVersion(dir);
206     dir.close();
207     return version;
208   }
209 
210   /**
211    * Reads version number from segments files. The version number counts the
212    * number of changes of the index.
213    *
214    @param directory where the index resides.
215    @return version number.
216    @throws IOException if segments file cannot be read.
217    */
218   public static long getCurrentVersion(Directory directorythrows IOException {
219     return SegmentInfos.readCurrentVersion(directory);
220   }
221 
222   /** Return an array of term frequency vectors for the specified document.
223    *  The array contains a vector for each vectorized field in the document.
224    *  Each vector contains terms and frequencies for all terms
225    *  in a given vectorized field.
226    *  If no such fields existed, the method returns null.
227    *
228    @see Field#isTermVectorStored()
229    */
230   abstract public TermFreqVector[] getTermFreqVectors(int docNumber)
231           throws IOException;
232 
233   /** Return a term frequency vector for the specified document and field. The
234    *  vector returned contains terms and frequencies for those terms in
235    *  the specified field of this document, if the field had storeTermVector
236    *  flag set.  If the flag was not set, the method returns null.
237    *
238    @see Field#isTermVectorStored()
239    */
240   abstract public TermFreqVector getTermFreqVector(int docNumber, String field)
241           throws IOException;
242 
243   /**
244    * Returns <code>true</code> if an index exists at the specified directory.
245    * If the directory does not exist or if there is no index in it.
246    <code>false</code> is returned.
247    @param  directory the directory to check for an index
248    @return <code>true</code> if an index exists; <code>false</code> otherwise
249    */
250   public static boolean indexExists(String directory) {
251     return (new File(directory, "segments")).exists();
252   }
253 
254   /**
255    * Returns <code>true</code> if an index exists at the specified directory.
256    * If the directory does not exist or if there is no index in it.
257    @param  directory the directory to check for an index
258    @return <code>true</code> if an index exists; <code>false</code> otherwise
259    */
260   public static boolean indexExists(File directory) {
261     return (new File(directory, "segments")).exists();
262   }
263 
264   /**
265    * Returns <code>true</code> if an index exists at the specified directory.
266    * If the directory does not exist or if there is no index in it.
267    @param  directory the directory to check for an index
268    @return <code>true</code> if an index exists; <code>false</code> otherwise
269    @throws IOException if there is a problem with accessing the index
270    */
271   public static boolean indexExists(Directory directorythrows IOException {
272     return directory.fileExists("segments");
273   }
274 
275   /** Returns the number of documents in this index. */
276   public abstract int numDocs();
277 
278   /** Returns one greater than the largest possible document number.
279    This may be used to, e.g., determine how big to allocate an array which
280    will have an element for every document number in an index.
281    */
282   public abstract int maxDoc();
283 
284   /** Returns the stored fields of the <code>n</code><sup>th</sup>
285    <code>Document</code> in this index. */
286   public abstract Document document(int nthrows IOException;
287 
288   /** Returns true if document <i>n</i> has been deleted */
289   public abstract boolean isDeleted(int n);
290 
291   /** Returns true if any documents have been deleted */
292   public abstract boolean hasDeletions();
293 
294   /** Returns the byte-encoded normalization factor for the named field of
295    * every document.  This is used by the search code to score documents.
296    *
297    @see Field#setBoost(float)
298    */
299   public abstract byte[] norms(String fieldthrows IOException;
300 
301   /** Reads the byte-encoded normalization factor for the named field of every
302    *  document.  This is used by the search code to score documents.
303    *
304    @see Field#setBoost(float)
305    */
306   public abstract void norms(String field, byte[] bytes, int offset)
307     throws IOException;
308 
309   /** Expert: Resets the normalization factor for the named field of the named
310    * document.  The norm represents the product of the field's {@link
311    * Field#setBoost(float) boost} and its {@link Similarity#lengthNorm(String,
312           * int) length normalization}.  Thus, to preserve the length normalization
313    * values when resetting this, one should base the new value upon the old.
314    *
315    @see #norms(String)
316    @see Similarity#decodeNorm(byte)
317    */
318   public final synchronized  void setNorm(int doc, String field, byte value)
319           throws IOException{
320     if(directoryOwner)
321       aquireWriteLock();
322     doSetNorm(doc, field, value);
323     hasChanges = true;
324   }
325 
326   /** Implements setNorm in subclass.*/
327   protected abstract void doSetNorm(int doc, String field, byte value)
328           throws IOException;
329 
330   /** Expert: Resets the normalization factor for the named field of the named
331    * document.
332    *
333    @see #norms(String)
334    @see Similarity#decodeNorm(byte)
335    */
336   public void setNorm(int doc, String field, float value)
337           throws IOException {
338     setNorm(doc, field, Similarity.encodeNorm(value));
339   }
340 
341 
342   /** Returns an enumeration of all the terms in the index.
343    The enumeration is ordered by Term.compareTo().  Each term
344    is greater than all that precede it in the enumeration.
345    */
346   public abstract TermEnum terms() throws IOException;
347 
348   /** Returns an enumeration of all terms after a given term.
349    The enumeration is ordered by Term.compareTo().  Each term
350    is greater than all that precede it in the enumeration.
351    */
352   public abstract TermEnum terms(Term tthrows IOException;
353 
354   /** Returns the number of documents containing the term <code>t</code>. */
355   public abstract int docFreq(Term tthrows IOException;
356 
357   /** Returns an enumeration of all the documents which contain
358    <code>term</code>. For each document, the document number, the frequency of
359    the term in that document is also provided, for use in search scoring.
360    Thus, this method implements the mapping:
361    <p><ul>
362    Term &nbsp;&nbsp; =&gt; &nbsp;&nbsp; &lt;docNum, freq&gt;<sup>*</sup>
363    </ul>
364    <p>The enumeration is ordered by document number.  Each document number
365    is greater than all that precede it in the enumeration.
366    */
367   public TermDocs termDocs(Term termthrows IOException {
368     TermDocs termDocs = termDocs();
369     termDocs.seek(term);
370     return termDocs;
371   }
372 
373   /** Returns an unpositioned {@link TermDocs} enumerator. */
374   public abstract TermDocs termDocs() throws IOException;
375 
376   /** Returns an enumeration of all the documents which contain
377    <code>term</code>.  For each document, in addition to the document number
378    and frequency of the term in that document, a list of all of the ordinal
379    positions of the term in the document is available.  Thus, this method
380    implements the mapping:
381 
382    <p><ul>
383    Term &nbsp;&nbsp; =&gt; &nbsp;&nbsp; &lt;docNum, freq,
384    &lt;pos<sub>1</sub>, pos<sub>2</sub>, ...
385    pos<sub>freq-1</sub>&gt;
386    &gt;<sup>*</sup>
387    </ul>
388    <p> This positional information faciliates phrase and proximity searching.
389    <p>The enumeration is ordered by document number.  Each document number is
390    greater than all that precede it in the enumeration.
391    */
392   public TermPositions termPositions(Term termthrows IOException {
393     TermPositions termPositions = termPositions();
394     termPositions.seek(term);
395     return termPositions;
396   }
397 
398   /** Returns an unpositioned {@link TermPositions} enumerator. */
399   public abstract TermPositions termPositions() throws IOException;
400 
401   /**
402    * Trys to acquire the WriteLock on this directory.
403    * this method is only valid if this IndexReader is directory owner.
404    *
405    @throws IOException If WriteLock cannot be acquired.
406    */
407   private void aquireWriteLock() throws IOException {
408     if (stale)
409       throw new IOException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
410 
411     if (writeLock == null) {
412       Lock writeLock = directory.makeLock(IndexWriter.WRITE_LOCK_NAME);
413       if (!writeLock.obtain(IndexWriter.WRITE_LOCK_TIMEOUT)) // obtain write lock
414         throw new IOException("Index locked for write: " + writeLock);
415       this.writeLock = writeLock;
416 
417       // we have to check whether index has changed since this reader was opened.
418       // if so, this reader is no longer valid for deletion
419       if (SegmentInfos.readCurrentVersion(directory> segmentInfos.getVersion()) {
420         stale = true;
421         this.writeLock.release();
422         this.writeLock = null;
423         throw new IOException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
424       }
425     }
426   }
427 
428   /** Deletes the document numbered <code>docNum</code>.  Once a document is
429    deleted it will not appear in TermDocs or TermPostitions enumerations.
430    Attempts to read its field with the {@link #document}
431    method will result in an error.  The presence of this document may still be
432    reflected in the {@link #docFreq} statistic, though
433    this will be corrected eventually as the index is further modified.
434    */
435   public final synchronized void delete(int docNumthrows IOException {
436     if(directoryOwner)
437       aquireWriteLock();
438     doDelete(docNum);
439     hasChanges = true;
440   }
441 
442   /** Implements deletion of the document numbered <code>docNum</code>.
443    * Applications should call {@link #delete(int)} or {@link #delete(Term)}.
444    */
445   protected abstract void doDelete(int docNumthrows IOException;
446 
447   /** Deletes all documents containing <code>term</code>.
448    This is useful if one uses a document field to hold a unique ID string for
449    the document.  Then to delete such a document, one merely constructs a
450    term with the appropriate field and the unique ID string as its text and
451    passes it to this method.  Returns the number of documents deleted.
452    */
453   public final int delete(Term termthrows IOException {
454     TermDocs docs = termDocs(term);
455     if (docs == nullreturn 0;
456     int n = 0;
457     try {
458       while (docs.next()) {
459         delete(docs.doc());
460         n++;
461       }
462     finally {
463       docs.close();
464     }
465     return n;
466   }
467 
468   /** Undeletes all documents currently marked as deleted in this index.*/
469   public final synchronized void undeleteAll() throws IOException{
470     if(directoryOwner)
471       aquireWriteLock();
472     doUndeleteAll();
473     hasChanges = true;
474   }
475 
476   /** Implements actual undeleteAll() in subclass. */
477   protected abstract void doUndeleteAll() throws IOException;
478 
479   /**
480    * Commit changes resulting from delete, undeleteAll, or setNorm operations
481    *
482    @throws IOException
483    */
484   protected final synchronized void commit() throws IOException{
485     if(hasChanges){
486       if(directoryOwner){
487         synchronized (directory) {      // in- & inter-process sync
488            new Lock.With(directory.makeLock(IndexWriter.COMMIT_LOCK_NAME),
489                    IndexWriter.COMMIT_LOCK_TIMEOUT) {
490              @Override
491             public Object doBody() throws IOException {
492                doCommit();
493                segmentInfos.write(directory);
494                return null;
495              }
496            }.run();
497          }
498         if (writeLock != null) {
499           writeLock.release();  // release write lock
500           writeLock = null;
501         }
502       }
503       else
504         doCommit();
505     }
506     hasChanges = false;
507   }
508 
509   /** Implements commit. */
510   protected abstract void doCommit() throws IOException;
511 
512   /**
513    * Closes files associated with this index.
514    * Also saves any new deletions to disk.
515    * No other methods should be called after this has been called.
516    */
517   public final synchronized void close() throws IOException {
518     commit();
519     doClose();
520     if(closeDirectory)
521       directory.close();
522   }
523 
524   /** Implements close. */
525   protected abstract void doClose() throws IOException;
526 
527   /** Release the write lock, if needed. */
528   @Override
529   protected final void finalize() throws IOException {
530     if (writeLock != null) {
531       writeLock.release();                        // release write lock
532       writeLock = null;
533     }
534   }
535 
536   /**
537    * Returns a list of all unique field names that exist in the index pointed
538    * to by this IndexReader.
539    @return Collection of Strings indicating the names of the fields
540    @throws IOException if there is a problem with accessing the index
541    */
542   public abstract Collection getFieldNames() throws IOException;
543 
544   /**
545    * Returns a list of all unique field names that exist in the index pointed
546    * to by this IndexReader.  The boolean argument specifies whether the fields
547    * returned are indexed or not.
548    @param indexed <code>true</code> if only indexed fields should be returned;
549    *                <code>false</code> if only unindexed fields should be returned.
550    @return Collection of Strings indicating the names of the fields
551    @throws IOException if there is a problem with accessing the index
552    */
553   public abstract Collection getFieldNames(boolean indexedthrows IOException;
554 
555   /**
556    *
557    @param storedTermVector if true, returns only Indexed fields that have term vector info,
558    *                        else only indexed fields without term vector info
559    @return Collection of Strings indicating the names of the fields
560    */
561   public abstract Collection getIndexedFieldNames(boolean storedTermVector);
562 
563   /**
564    * Returns <code>true</code> iff the index in the named directory is
565    * currently locked.
566    @param directory the directory to check for a lock
567    @throws IOException if there is a problem with accessing the index
568    */
569   public static boolean isLocked(Directory directorythrows IOException {
570     return
571             directory.makeLock(IndexWriter.WRITE_LOCK_NAME).isLocked() ||
572             directory.makeLock(IndexWriter.COMMIT_LOCK_NAME).isLocked();
573 
574   }
575 
576   /**
577    * Returns <code>true</code> iff the index in the named directory is
578    * currently locked.
579    @param directory the directory to check for a lock
580    @throws IOException if there is a problem with accessing the index
581    */
582   public static boolean isLocked(String directorythrows IOException {
583     Directory dir = FSDirectory.getDirectory(directory, false);
584     boolean result = isLocked(dir);
585     dir.close();
586     return result;
587   }
588 
589   /**
590    * Forcibly unlocks the index in the named directory.
591    <P>
592    * Caution: this should only be used by failure recovery code,
593    * when it is known that no other process nor thread is in fact
594    * currently accessing this index.
595    */
596   public static void unlock(Directory directorythrows IOException {
597     directory.makeLock(IndexWriter.WRITE_LOCK_NAME).release();
598     directory.makeLock(IndexWriter.COMMIT_LOCK_NAME).release();
599   }
600 }