add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / src / java / org / apache / lucene / index / DirectoryReader.java
1 package org.apache.lucene.index;
2
3 /**
4  * Licensed to the Apache Software Foundation (ASF) under one or more
5  * contributor license agreements.  See the NOTICE file distributed with
6  * this work for additional information regarding copyright ownership.
7  * The ASF licenses this file to You under the Apache License, Version 2.0
8  * (the "License"); you may not use this file except in compliance with
9  * the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.concurrent.ConcurrentHashMap;
33
34 import org.apache.lucene.document.Document;
35 import org.apache.lucene.document.FieldSelector;
36 import org.apache.lucene.search.Similarity;
37 import org.apache.lucene.store.Directory;
38 import org.apache.lucene.store.Lock;
39 import org.apache.lucene.store.LockObtainFailedException;
40 import org.apache.lucene.util.MapBackedSet;
41
42 /** 
43  * An IndexReader which reads indexes with multiple segments.
44  */
45 class DirectoryReader extends IndexReader implements Cloneable {
46   protected Directory directory;
47   protected boolean readOnly;
48
49   IndexWriter writer;
50
51   private IndexDeletionPolicy deletionPolicy;
52   private Lock writeLock;
53   private final SegmentInfos segmentInfos;
54   private boolean stale;
55   private final int termInfosIndexDivisor;
56
57   private boolean rollbackHasChanges;
58
59   private SegmentReader[] subReaders;
60   private int[] starts;                           // 1st docno for each segment
61   private Map<String,byte[]> normsCache = new HashMap<String,byte[]>();
62   private int maxDoc = 0;
63   private int numDocs = -1;
64   private boolean hasDeletions = false;
65
66   // Max version in index as of when we opened; this can be
67   // > our current segmentInfos version in case we were
68   // opened on a past IndexCommit:
69   private long maxIndexVersion;
70
71   private final boolean applyAllDeletes;
72
73   static IndexReader open(final Directory directory, final IndexDeletionPolicy deletionPolicy, final IndexCommit commit, final boolean readOnly,
74                           final int termInfosIndexDivisor) throws CorruptIndexException, IOException {
75     return (IndexReader) new SegmentInfos.FindSegmentsFile(directory) {
76       @Override
77       protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
78         SegmentInfos infos = new SegmentInfos();
79         infos.read(directory, segmentFileName);
80         if (readOnly)
81           return new ReadOnlyDirectoryReader(directory, infos, deletionPolicy, termInfosIndexDivisor, null);
82         else
83           return new DirectoryReader(directory, infos, deletionPolicy, false, termInfosIndexDivisor, null);
84       }
85     }.run(commit);
86   }
87
88   /** Construct reading the named set of readers. */
89   DirectoryReader(Directory directory, SegmentInfos sis, IndexDeletionPolicy deletionPolicy, boolean readOnly, int termInfosIndexDivisor,
90                   Collection<ReaderFinishedListener> readerFinishedListeners) throws IOException {
91     this.directory = directory;
92     this.readOnly = readOnly;
93     this.segmentInfos = sis;
94     this.deletionPolicy = deletionPolicy;
95     this.termInfosIndexDivisor = termInfosIndexDivisor;
96
97     if (readerFinishedListeners == null) {
98       this.readerFinishedListeners = new MapBackedSet<ReaderFinishedListener>(new ConcurrentHashMap<ReaderFinishedListener,Boolean>());
99     } else {
100       this.readerFinishedListeners = readerFinishedListeners;
101     }
102     applyAllDeletes = false;
103
104     // To reduce the chance of hitting FileNotFound
105     // (and having to retry), we open segments in
106     // reverse because IndexWriter merges & deletes
107     // the newest segments first.
108
109     SegmentReader[] readers = new SegmentReader[sis.size()];
110     for (int i = sis.size()-1; i >= 0; i--) {
111       boolean success = false;
112       try {
113         readers[i] = SegmentReader.get(readOnly, sis.info(i), termInfosIndexDivisor);
114         readers[i].readerFinishedListeners = this.readerFinishedListeners;
115         success = true;
116       } finally {
117         if (!success) {
118           // Close all readers we had opened:
119           for(i++;i<sis.size();i++) {
120             try {
121               readers[i].close();
122             } catch (Throwable ignore) {
123               // keep going - we want to clean up as much as possible
124             }
125           }
126         }
127       }
128     }
129
130     initialize(readers);
131   }
132
133   // Used by near real-time search
134   DirectoryReader(IndexWriter writer, SegmentInfos infos, int termInfosIndexDivisor, boolean applyAllDeletes) throws IOException {
135     this.directory = writer.getDirectory();
136     this.readOnly = true;
137     this.applyAllDeletes = applyAllDeletes;       // saved for reopen
138
139     this.termInfosIndexDivisor = termInfosIndexDivisor;
140     readerFinishedListeners = writer.getReaderFinishedListeners();
141
142     // IndexWriter synchronizes externally before calling
143     // us, which ensures infos will not change; so there's
144     // no need to process segments in reverse order
145     final int numSegments = infos.size();
146
147     List<SegmentReader> readers = new ArrayList<SegmentReader>();
148     final Directory dir = writer.getDirectory();
149
150     segmentInfos = (SegmentInfos) infos.clone();
151     int infosUpto = 0;
152     for (int i=0;i<numSegments;i++) {
153       boolean success = false;
154       try {
155         final SegmentInfo info = infos.info(i);
156         assert info.dir == dir;
157         final SegmentReader reader = writer.readerPool.getReadOnlyClone(info, true, termInfosIndexDivisor);
158         if (reader.numDocs() > 0 || writer.getKeepFullyDeletedSegments()) {
159           reader.readerFinishedListeners = readerFinishedListeners;
160           readers.add(reader);
161           infosUpto++;
162         } else {
163           reader.close();
164           segmentInfos.remove(infosUpto);
165         }
166         success = true;
167       } finally {
168         if (!success) {
169           // Close all readers we had opened:
170           for(SegmentReader reader : readers) {
171             try {
172               reader.close();
173             } catch (Throwable ignore) {
174               // keep going - we want to clean up as much as possible
175             }
176           }
177         }
178       }
179     }
180
181     this.writer = writer;
182
183     initialize(readers.toArray(new SegmentReader[readers.size()]));
184   }
185
186   /** This constructor is only used for {@link #reopen()} */
187   DirectoryReader(Directory directory, SegmentInfos infos, SegmentReader[] oldReaders, int[] oldStarts,
188                   Map<String,byte[]> oldNormsCache, boolean readOnly, boolean doClone, int termInfosIndexDivisor,
189                   Collection<ReaderFinishedListener> readerFinishedListeners) throws IOException {
190     this.directory = directory;
191     this.readOnly = readOnly;
192     this.segmentInfos = infos;
193     this.termInfosIndexDivisor = termInfosIndexDivisor;
194     assert readerFinishedListeners != null;
195     this.readerFinishedListeners = readerFinishedListeners;
196     applyAllDeletes = false;
197
198     // we put the old SegmentReaders in a map, that allows us
199     // to lookup a reader using its segment name
200     Map<String,Integer> segmentReaders = new HashMap<String,Integer>();
201
202     if (oldReaders != null) {
203       // create a Map SegmentName->SegmentReader
204       for (int i = 0; i < oldReaders.length; i++) {
205         segmentReaders.put(oldReaders[i].getSegmentName(), Integer.valueOf(i));
206       }
207     }
208     
209     SegmentReader[] newReaders = new SegmentReader[infos.size()];
210     
211     // remember which readers are shared between the old and the re-opened
212     // DirectoryReader - we have to incRef those readers
213     boolean[] readerShared = new boolean[infos.size()];
214     
215     for (int i = infos.size() - 1; i>=0; i--) {
216       // find SegmentReader for this segment
217       Integer oldReaderIndex = segmentReaders.get(infos.info(i).name);
218       if (oldReaderIndex == null) {
219         // this is a new segment, no old SegmentReader can be reused
220         newReaders[i] = null;
221       } else {
222         // there is an old reader for this segment - we'll try to reopen it
223         newReaders[i] = oldReaders[oldReaderIndex.intValue()];
224       }
225
226       boolean success = false;
227       try {
228         SegmentReader newReader;
229         if (newReaders[i] == null || infos.info(i).getUseCompoundFile() != newReaders[i].getSegmentInfo().getUseCompoundFile()) {
230
231           // We should never see a totally new segment during cloning
232           assert !doClone;
233
234           // this is a new reader; in case we hit an exception we can close it safely
235           newReader = SegmentReader.get(readOnly, infos.info(i), termInfosIndexDivisor);
236           newReader.readerFinishedListeners = readerFinishedListeners;
237         } else {
238           newReader = newReaders[i].reopenSegment(infos.info(i), doClone, readOnly);
239           assert newReader.readerFinishedListeners == readerFinishedListeners;
240         }
241         if (newReader == newReaders[i]) {
242           // this reader will be shared between the old and the new one,
243           // so we must incRef it
244           readerShared[i] = true;
245           newReader.incRef();
246         } else {
247           readerShared[i] = false;
248           newReaders[i] = newReader;
249         }
250         success = true;
251       } finally {
252         if (!success) {
253           for (i++; i < infos.size(); i++) {
254             if (newReaders[i] != null) {
255               try {
256                 if (!readerShared[i]) {
257                   // this is a new subReader that is not used by the old one,
258                   // we can close it
259                   newReaders[i].close();
260                 } else {
261                   // this subReader is also used by the old reader, so instead
262                   // closing we must decRef it
263                   newReaders[i].decRef();
264                 }
265               } catch (IOException ignore) {
266                 // keep going - we want to clean up as much as possible
267               }
268             }
269           }
270         }
271       }
272     }    
273     
274     // initialize the readers to calculate maxDoc before we try to reuse the old normsCache
275     initialize(newReaders);
276     
277     // try to copy unchanged norms from the old normsCache to the new one
278     if (oldNormsCache != null) {
279       for (Map.Entry<String,byte[]> entry: oldNormsCache.entrySet()) {
280         String field = entry.getKey();
281         if (!hasNorms(field)) {
282           continue;
283         }
284
285         byte[] oldBytes = entry.getValue();
286
287         byte[] bytes = new byte[maxDoc()];
288
289         for (int i = 0; i < subReaders.length; i++) {
290           Integer oldReaderIndex = segmentReaders.get(subReaders[i].getSegmentName());
291
292           // this SegmentReader was not re-opened, we can copy all of its norms 
293           if (oldReaderIndex != null &&
294                (oldReaders[oldReaderIndex.intValue()] == subReaders[i] 
295                  || oldReaders[oldReaderIndex.intValue()].norms.get(field) == subReaders[i].norms.get(field))) {
296             // we don't have to synchronize here: either this constructor is called from a SegmentReader,
297             // in which case no old norms cache is present, or it is called from MultiReader.reopen(),
298             // which is synchronized
299             System.arraycopy(oldBytes, oldStarts[oldReaderIndex.intValue()], bytes, starts[i], starts[i+1] - starts[i]);
300           } else {
301             subReaders[i].norms(field, bytes, starts[i]);
302           }
303         }
304
305         normsCache.put(field, bytes);      // update cache
306       }
307     }
308   }
309
310   /** {@inheritDoc} */
311   @Override
312   public String toString() {
313     final StringBuilder buffer = new StringBuilder();
314     if (hasChanges) {
315       buffer.append("*");
316     }
317     buffer.append(getClass().getSimpleName());
318     buffer.append('(');
319     final String segmentsFile = segmentInfos.getCurrentSegmentFileName();
320     if (segmentsFile != null) {
321       buffer.append(segmentsFile);
322     }
323     if (writer != null) {
324       buffer.append(":nrt");
325     }
326     for(int i=0;i<subReaders.length;i++) {
327       buffer.append(' ');
328       buffer.append(subReaders[i]);
329     }
330     buffer.append(')');
331     return buffer.toString();
332   }
333
334   private void initialize(SegmentReader[] subReaders) throws IOException {
335     this.subReaders = subReaders;
336     starts = new int[subReaders.length + 1];    // build starts array
337     for (int i = 0; i < subReaders.length; i++) {
338       starts[i] = maxDoc;
339       maxDoc += subReaders[i].maxDoc();      // compute maxDocs
340
341       if (subReaders[i].hasDeletions())
342         hasDeletions = true;
343     }
344     starts[subReaders.length] = maxDoc;
345
346     if (!readOnly) {
347       maxIndexVersion = SegmentInfos.readCurrentVersion(directory);
348     }
349   }
350
351   @Override
352   public final synchronized Object clone() {
353     try {
354       return clone(readOnly); // Preserve current readOnly
355     } catch (Exception ex) {
356       throw new RuntimeException(ex);
357     }
358   }
359
360   @Override
361   public final synchronized IndexReader clone(boolean openReadOnly) throws CorruptIndexException, IOException {
362     DirectoryReader newReader = doReopen((SegmentInfos) segmentInfos.clone(), true, openReadOnly);
363
364     if (this != newReader) {
365       newReader.deletionPolicy = deletionPolicy;
366     }
367     newReader.writer = writer;
368     // If we're cloning a non-readOnly reader, move the
369     // writeLock (if there is one) to the new reader:
370     if (!openReadOnly && writeLock != null) {
371       // In near real-time search, reader is always readonly
372       assert writer == null;
373       newReader.writeLock = writeLock;
374       newReader.hasChanges = hasChanges;
375       newReader.hasDeletions = hasDeletions;
376       writeLock = null;
377       hasChanges = false;
378     }
379     assert newReader.readerFinishedListeners != null;
380
381     return newReader;
382   }
383
384   @Override
385   public final IndexReader reopen() throws CorruptIndexException, IOException {
386     // Preserve current readOnly
387     return doReopen(readOnly, null);
388   }
389
390   @Override
391   public final IndexReader reopen(boolean openReadOnly) throws CorruptIndexException, IOException {
392     return doReopen(openReadOnly, null);
393   }
394
395   @Override
396   public final IndexReader reopen(final IndexCommit commit) throws CorruptIndexException, IOException {
397     return doReopen(true, commit);
398   }
399
400   private final IndexReader doReopenFromWriter(boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
401     assert readOnly;
402
403     if (!openReadOnly) {
404       throw new IllegalArgumentException("a reader obtained from IndexWriter.getReader() can only be reopened with openReadOnly=true (got false)");
405     }
406
407     if (commit != null) {
408       throw new IllegalArgumentException("a reader obtained from IndexWriter.getReader() cannot currently accept a commit");
409     }
410
411     // TODO: right now we *always* make a new reader; in
412     // the future we could have write make some effort to
413     // detect that no changes have occurred
414     IndexReader reader = writer.getReader(applyAllDeletes);
415     reader.readerFinishedListeners = readerFinishedListeners;
416     return reader;
417   }
418
419   private IndexReader doReopen(final boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
420     ensureOpen();
421
422     assert commit == null || openReadOnly;
423
424     // If we were obtained by writer.getReader(), re-ask the
425     // writer to get a new reader.
426     if (writer != null) {
427       return doReopenFromWriter(openReadOnly, commit);
428     } else {
429       return doReopenNoWriter(openReadOnly, commit);
430     }
431   }
432
433   private synchronized IndexReader doReopenNoWriter(final boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
434
435     if (commit == null) {
436       if (hasChanges) {
437         // We have changes, which means we are not readOnly:
438         assert readOnly == false;
439         // and we hold the write lock:
440         assert writeLock != null;
441         // so no other writer holds the write lock, which
442         // means no changes could have been done to the index:
443         assert isCurrent();
444
445         if (openReadOnly) {
446           return clone(openReadOnly);
447         } else {
448           return this;
449         }
450       } else if (isCurrent()) {
451         if (openReadOnly != readOnly) {
452           // Just fallback to clone
453           return clone(openReadOnly);
454         } else {
455           return this;
456         }
457       }
458     } else {
459       if (directory != commit.getDirectory())
460         throw new IOException("the specified commit does not match the specified Directory");
461       if (segmentInfos != null && commit.getSegmentsFileName().equals(segmentInfos.getCurrentSegmentFileName())) {
462         if (readOnly != openReadOnly) {
463           // Just fallback to clone
464           return clone(openReadOnly);
465         } else {
466           return this;
467         }
468       }
469     }
470
471     return (IndexReader) new SegmentInfos.FindSegmentsFile(directory) {
472       @Override
473       protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
474         SegmentInfos infos = new SegmentInfos();
475         infos.read(directory, segmentFileName);
476         return doReopen(infos, false, openReadOnly);
477       }
478     }.run(commit);
479   }
480
481   private synchronized DirectoryReader doReopen(SegmentInfos infos, boolean doClone, boolean openReadOnly) throws CorruptIndexException, IOException {
482     DirectoryReader reader;
483     if (openReadOnly) {
484       reader = new ReadOnlyDirectoryReader(directory, infos, subReaders, starts, normsCache, doClone, termInfosIndexDivisor, readerFinishedListeners);
485     } else {
486       reader = new DirectoryReader(directory, infos, subReaders, starts, normsCache, false, doClone, termInfosIndexDivisor, readerFinishedListeners);
487     }
488     return reader;
489   }
490
491   /** Version number when this IndexReader was opened. */
492   @Override
493   public long getVersion() {
494     ensureOpen();
495     return segmentInfos.getVersion();
496   }
497
498   @Override
499   public TermFreqVector[] getTermFreqVectors(int n) throws IOException {
500     ensureOpen();
501     int i = readerIndex(n);        // find segment num
502     return subReaders[i].getTermFreqVectors(n - starts[i]); // dispatch to segment
503   }
504
505   @Override
506   public TermFreqVector getTermFreqVector(int n, String field)
507       throws IOException {
508     ensureOpen();
509     int i = readerIndex(n);        // find segment num
510     return subReaders[i].getTermFreqVector(n - starts[i], field);
511   }
512
513
514   @Override
515   public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
516     ensureOpen();
517     int i = readerIndex(docNumber);        // find segment num
518     subReaders[i].getTermFreqVector(docNumber - starts[i], field, mapper);
519   }
520
521   @Override
522   public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
523     ensureOpen();
524     int i = readerIndex(docNumber);        // find segment num
525     subReaders[i].getTermFreqVector(docNumber - starts[i], mapper);
526   }
527
528   /**
529    * Checks is the index is optimized (if it has a single segment and no deletions)
530    * @return <code>true</code> if the index is optimized; <code>false</code> otherwise
531    */
532   @Override
533   public boolean isOptimized() {
534     ensureOpen();
535     return segmentInfos.size() == 1 && !hasDeletions();
536   }
537
538   @Override
539   public int numDocs() {
540     // Don't call ensureOpen() here (it could affect performance)
541
542     // NOTE: multiple threads may wind up init'ing
543     // numDocs... but that's harmless
544     if (numDocs == -1) {        // check cache
545       int n = 0;                // cache miss--recompute
546       for (int i = 0; i < subReaders.length; i++)
547         n += subReaders[i].numDocs();      // sum from readers
548       numDocs = n;
549     }
550     return numDocs;
551   }
552
553   @Override
554   public int maxDoc() {
555     // Don't call ensureOpen() here (it could affect performance)
556     return maxDoc;
557   }
558
559   // inherit javadoc
560   @Override
561   public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
562     ensureOpen();
563     int i = readerIndex(n);                          // find segment num
564     return subReaders[i].document(n - starts[i], fieldSelector);    // dispatch to segment reader
565   }
566
567   @Override
568   public boolean isDeleted(int n) {
569     // Don't call ensureOpen() here (it could affect performance)
570     final int i = readerIndex(n);                           // find segment num
571     return subReaders[i].isDeleted(n - starts[i]);    // dispatch to segment reader
572   }
573
574   @Override
575   public boolean hasDeletions() {
576     // Don't call ensureOpen() here (it could affect performance)
577     return hasDeletions;
578   }
579
580   @Override
581   protected void doDelete(int n) throws CorruptIndexException, IOException {
582     numDocs = -1;                             // invalidate cache
583     int i = readerIndex(n);                   // find segment num
584     subReaders[i].deleteDocument(n - starts[i]);      // dispatch to segment reader
585     hasDeletions = true;
586   }
587
588   @Override
589   protected void doUndeleteAll() throws CorruptIndexException, IOException {
590     for (int i = 0; i < subReaders.length; i++)
591       subReaders[i].undeleteAll();
592
593     hasDeletions = false;
594     numDocs = -1;                                 // invalidate cache
595   }
596
597   private int readerIndex(int n) {    // find reader for doc n:
598     return readerIndex(n, this.starts, this.subReaders.length);
599   }
600   
601   final static int readerIndex(int n, int[] starts, int numSubReaders) {    // find reader for doc n:
602     int lo = 0;                                      // search starts array
603     int hi = numSubReaders - 1;                  // for first element less
604
605     while (hi >= lo) {
606       int mid = (lo + hi) >>> 1;
607       int midValue = starts[mid];
608       if (n < midValue)
609         hi = mid - 1;
610       else if (n > midValue)
611         lo = mid + 1;
612       else {                                      // found a match
613         while (mid+1 < numSubReaders && starts[mid+1] == midValue) {
614           mid++;                                  // scan to last match
615         }
616         return mid;
617       }
618     }
619     return hi;
620   }
621
622   @Override
623   public boolean hasNorms(String field) throws IOException {
624     ensureOpen();
625     for (int i = 0; i < subReaders.length; i++) {
626       if (subReaders[i].hasNorms(field)) return true;
627     }
628     return false;
629   }
630
631   @Override
632   public synchronized byte[] norms(String field) throws IOException {
633     ensureOpen();
634     byte[] bytes = normsCache.get(field);
635     if (bytes != null)
636       return bytes;          // cache hit
637     if (!hasNorms(field))
638       return null;
639
640     bytes = new byte[maxDoc()];
641     for (int i = 0; i < subReaders.length; i++)
642       subReaders[i].norms(field, bytes, starts[i]);
643     normsCache.put(field, bytes);      // update cache
644     return bytes;
645   }
646
647   @Override
648   public synchronized void norms(String field, byte[] result, int offset)
649     throws IOException {
650     ensureOpen();
651     byte[] bytes = normsCache.get(field);
652     if (bytes==null && !hasNorms(field)) {
653       Arrays.fill(result, offset, result.length, Similarity.getDefault().encodeNormValue(1.0f));
654     } else if (bytes != null) {                           // cache hit
655       System.arraycopy(bytes, 0, result, offset, maxDoc());
656     } else {
657       for (int i = 0; i < subReaders.length; i++) {      // read from segments
658         subReaders[i].norms(field, result, offset + starts[i]);
659       }
660     }
661   }
662
663   @Override
664   protected void doSetNorm(int n, String field, byte value)
665     throws CorruptIndexException, IOException {
666     synchronized (normsCache) {
667       normsCache.remove(field);                         // clear cache      
668     }
669     int i = readerIndex(n);                           // find segment num
670     subReaders[i].setNorm(n-starts[i], field, value); // dispatch
671   }
672
673   @Override
674   public TermEnum terms() throws IOException {
675     ensureOpen();
676     if (subReaders.length == 1) {
677       // Optimize single segment case:
678       return subReaders[0].terms();
679     } else {
680       return new MultiTermEnum(this, subReaders, starts, null);
681     }
682   }
683
684   @Override
685   public TermEnum terms(Term term) throws IOException {
686     ensureOpen();
687     if (subReaders.length == 1) {
688       // Optimize single segment case:
689       return subReaders[0].terms(term);
690     } else {
691       return new MultiTermEnum(this, subReaders, starts, term);
692     }
693   }
694
695   @Override
696   public int docFreq(Term t) throws IOException {
697     ensureOpen();
698     int total = 0;          // sum freqs in segments
699     for (int i = 0; i < subReaders.length; i++)
700       total += subReaders[i].docFreq(t);
701     return total;
702   }
703
704   @Override
705   public TermDocs termDocs() throws IOException {
706     ensureOpen();
707     if (subReaders.length == 1) {
708       // Optimize single segment case:
709       return subReaders[0].termDocs();
710     } else {
711       return new MultiTermDocs(this, subReaders, starts);
712     }
713   }
714
715   @Override
716   public TermDocs termDocs(Term term) throws IOException {
717     ensureOpen();
718     if (subReaders.length == 1) {
719       // Optimize single segment case:
720       return subReaders[0].termDocs(term);
721     } else {
722       return super.termDocs(term);
723     }
724   }
725
726   @Override
727   public TermPositions termPositions() throws IOException {
728     ensureOpen();
729     if (subReaders.length == 1) {
730       // Optimize single segment case:
731       return subReaders[0].termPositions();
732     } else {
733       return new MultiTermPositions(this, subReaders, starts);
734     }
735   }
736
737   /**
738    * Tries to acquire the WriteLock on this directory. this method is only valid if this IndexReader is directory
739    * owner.
740    *
741    * @throws StaleReaderException  if the index has changed since this reader was opened
742    * @throws CorruptIndexException if the index is corrupt
743    * @throws org.apache.lucene.store.LockObtainFailedException
744    *                               if another writer has this index open (<code>write.lock</code> could not be
745    *                               obtained)
746    * @throws IOException           if there is a low-level IO error
747    */
748   @Override
749   protected void acquireWriteLock() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
750
751     if (readOnly) {
752       // NOTE: we should not reach this code w/ the core
753       // IndexReader classes; however, an external subclass
754       // of IndexReader could reach this.
755       ReadOnlySegmentReader.noWrite();
756     }
757
758     if (segmentInfos != null) {
759       ensureOpen();
760       if (stale)
761         throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
762
763       if (writeLock == null) {
764         Lock writeLock = directory.makeLock(IndexWriter.WRITE_LOCK_NAME);
765         if (!writeLock.obtain(IndexWriterConfig.WRITE_LOCK_TIMEOUT)) // obtain write lock
766           throw new LockObtainFailedException("Index locked for write: " + writeLock);
767         this.writeLock = writeLock;
768
769         // we have to check whether index has changed since this reader was opened.
770         // if so, this reader is no longer valid for
771         // deletion
772         if (SegmentInfos.readCurrentVersion(directory) > maxIndexVersion) {
773           stale = true;
774           this.writeLock.release();
775           this.writeLock = null;
776           throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
777         }
778       }
779     }
780   }
781
782   /**
783    * Commit changes resulting from delete, undeleteAll, or setNorm operations
784    * <p/>
785    * If an exception is hit, then either no changes or all changes will have been committed to the index (transactional
786    * semantics).
787    *
788    * @throws IOException if there is a low-level IO error
789    */
790   @Override
791   protected void doCommit(Map<String,String> commitUserData) throws IOException {
792     if (hasChanges) {
793       segmentInfos.setUserData(commitUserData);
794       // Default deleter (for backwards compatibility) is
795       // KeepOnlyLastCommitDeleter:
796       IndexFileDeleter deleter = new IndexFileDeleter(directory,
797                                                       deletionPolicy == null ? new KeepOnlyLastCommitDeletionPolicy() : deletionPolicy,
798                                                       segmentInfos, null, null);
799       segmentInfos.updateGeneration(deleter.getLastSegmentInfos());
800       segmentInfos.changed();
801
802       // Checkpoint the state we are about to change, in
803       // case we have to roll back:
804       startCommit();
805
806       final List<SegmentInfo> rollbackSegments = segmentInfos.createBackupSegmentInfos(false);
807
808       boolean success = false;
809       try {
810         for (int i = 0; i < subReaders.length; i++)
811           subReaders[i].commit();
812
813         // Remove segments that contain only 100% deleted
814         // docs:
815         segmentInfos.pruneDeletedSegments();
816
817         // Sync all files we just wrote
818         directory.sync(segmentInfos.files(directory, false));
819         segmentInfos.commit(directory);
820         success = true;
821       } finally {
822
823         if (!success) {
824
825           // Rollback changes that were made to
826           // SegmentInfos but failed to get [fully]
827           // committed.  This way this reader instance
828           // remains consistent (matched to what's
829           // actually in the index):
830           rollbackCommit();
831
832           // Recompute deletable files & remove them (so
833           // partially written .del files, etc, are
834           // removed):
835           deleter.refresh();
836
837           // Restore all SegmentInfos (in case we pruned some)
838           segmentInfos.rollbackSegmentInfos(rollbackSegments);
839         }
840       }
841
842       // Have the deleter remove any now unreferenced
843       // files due to this commit:
844       deleter.checkpoint(segmentInfos, true);
845       deleter.close();
846
847       maxIndexVersion = segmentInfos.getVersion();
848
849       if (writeLock != null) {
850         writeLock.release();  // release write lock
851         writeLock = null;
852       }
853     }
854     hasChanges = false;
855   }
856
857   void startCommit() {
858     rollbackHasChanges = hasChanges;
859     for (int i = 0; i < subReaders.length; i++) {
860       subReaders[i].startCommit();
861     }
862   }
863
864   void rollbackCommit() {
865     hasChanges = rollbackHasChanges;
866     for (int i = 0; i < subReaders.length; i++) {
867       subReaders[i].rollbackCommit();
868     }
869   }
870
871   @Override
872   public Map<String,String> getCommitUserData() {
873     ensureOpen();
874     return segmentInfos.getUserData();
875   }
876
877   @Override
878   public boolean isCurrent() throws CorruptIndexException, IOException {
879     ensureOpen();
880     if (writer == null || writer.isClosed()) {
881       // we loaded SegmentInfos from the directory
882       return SegmentInfos.readCurrentVersion(directory) == segmentInfos.getVersion();
883     } else {
884       return writer.nrtIsCurrent(segmentInfos);
885     }
886   }
887
888   @Override
889   protected synchronized void doClose() throws IOException {
890     IOException ioe = null;
891     normsCache = null;
892     for (int i = 0; i < subReaders.length; i++) {
893       // try to close each reader, even if an exception is thrown
894       try {
895         subReaders[i].decRef();
896       } catch (IOException e) {
897         if (ioe == null) ioe = e;
898       }
899     }
900
901     if (writer != null) {
902       // Since we just closed, writer may now be able to
903       // delete unused files:
904       writer.deleteUnusedFiles();
905     }
906
907     // throw the first exception
908     if (ioe != null) throw ioe;
909   }
910
911   @Override
912   public Collection<String> getFieldNames (IndexReader.FieldOption fieldNames) {
913     ensureOpen();
914     return getFieldNames(fieldNames, this.subReaders);
915   }
916   
917   static Collection<String> getFieldNames (IndexReader.FieldOption fieldNames, IndexReader[] subReaders) {
918     // maintain a unique set of field names
919     Set<String> fieldSet = new HashSet<String>();
920     for (IndexReader reader : subReaders) {
921       Collection<String> names = reader.getFieldNames(fieldNames);
922       fieldSet.addAll(names);
923     }
924     return fieldSet;
925   } 
926   
927   @Override
928   public IndexReader[] getSequentialSubReaders() {
929     return subReaders;
930   }
931
932   /** Returns the directory this index resides in. */
933   @Override
934   public Directory directory() {
935     // Don't ensureOpen here -- in certain cases, when a
936     // cloned/reopened reader needs to commit, it may call
937     // this method on the closed original reader
938     return directory;
939   }
940
941   @Override
942   public int getTermInfosIndexDivisor() {
943     return termInfosIndexDivisor;
944   }
945
946   /**
947    * Expert: return the IndexCommit that this reader has opened.
948    * <p/>
949    * @lucene.experimental
950    */
951   @Override
952   public IndexCommit getIndexCommit() throws IOException {
953     return new ReaderCommit(segmentInfos, directory);
954   }
955
956   /** @see org.apache.lucene.index.IndexReader#listCommits */
957   public static Collection<IndexCommit> listCommits(Directory dir) throws IOException {
958     final String[] files = dir.listAll();
959
960     List<IndexCommit> commits = new ArrayList<IndexCommit>();
961
962     SegmentInfos latest = new SegmentInfos();
963     latest.read(dir);
964     final long currentGen = latest.getGeneration();
965
966     commits.add(new ReaderCommit(latest, dir));
967
968     for(int i=0;i<files.length;i++) {
969
970       final String fileName = files[i];
971
972       if (fileName.startsWith(IndexFileNames.SEGMENTS) &&
973           !fileName.equals(IndexFileNames.SEGMENTS_GEN) &&
974           SegmentInfos.generationFromSegmentsFileName(fileName) < currentGen) {
975
976         SegmentInfos sis = new SegmentInfos();
977         try {
978           // IOException allowed to throw there, in case
979           // segments_N is corrupt
980           sis.read(dir, fileName);
981         } catch (FileNotFoundException fnfe) {
982           // LUCENE-948: on NFS (and maybe others), if
983           // you have writers switching back and forth
984           // between machines, it's very likely that the
985           // dir listing will be stale and will claim a
986           // file segments_X exists when in fact it
987           // doesn't.  So, we catch this and handle it
988           // as if the file does not exist
989           sis = null;
990         }
991
992         if (sis != null)
993           commits.add(new ReaderCommit(sis, dir));
994       }
995     }
996
997     // Ensure that the commit points are sorted in ascending order.
998     Collections.sort(commits);
999
1000     return commits;
1001   }
1002
1003   private static final class ReaderCommit extends IndexCommit {
1004     private String segmentsFileName;
1005     Collection<String> files;
1006     Directory dir;
1007     long generation;
1008     long version;
1009     final boolean isOptimized;
1010     final Map<String,String> userData;
1011
1012     ReaderCommit(SegmentInfos infos, Directory dir) throws IOException {
1013       segmentsFileName = infos.getCurrentSegmentFileName();
1014       this.dir = dir;
1015       userData = infos.getUserData();
1016       files = Collections.unmodifiableCollection(infos.files(dir, true));
1017       version = infos.getVersion();
1018       generation = infos.getGeneration();
1019       isOptimized = infos.size() == 1 && !infos.info(0).hasDeletions();
1020     }
1021
1022     @Override
1023     public String toString() {
1024       return "DirectoryReader.ReaderCommit(" + segmentsFileName + ")";
1025     }
1026
1027     @Override
1028     public boolean isOptimized() {
1029       return isOptimized;
1030     }
1031
1032     @Override
1033     public String getSegmentsFileName() {
1034       return segmentsFileName;
1035     }
1036
1037     @Override
1038     public Collection<String> getFileNames() {
1039       return files;
1040     }
1041
1042     @Override
1043     public Directory getDirectory() {
1044       return dir;
1045     }
1046
1047     @Override
1048     public long getVersion() {
1049       return version;
1050     }
1051
1052     @Override
1053     public long getGeneration() {
1054       return generation;
1055     }
1056
1057     @Override
1058     public boolean isDeleted() {
1059       return false;
1060     }
1061
1062     @Override
1063     public Map<String,String> getUserData() {
1064       return userData;
1065     }
1066
1067     @Override
1068     public void delete() {
1069       throw new UnsupportedOperationException("This IndexCommit does not support deletions");
1070     }
1071   }
1072
1073   static class MultiTermEnum extends TermEnum {
1074     IndexReader topReader; // used for matching TermEnum to TermDocs
1075     private SegmentMergeQueue queue;
1076   
1077     private Term term;
1078     private int docFreq;
1079     final SegmentMergeInfo[] matchingSegments; // null terminated array of matching segments
1080
1081     public MultiTermEnum(IndexReader topReader, IndexReader[] readers, int[] starts, Term t)
1082       throws IOException {
1083       this.topReader = topReader;
1084       queue = new SegmentMergeQueue(readers.length);
1085       matchingSegments = new SegmentMergeInfo[readers.length+1];
1086       for (int i = 0; i < readers.length; i++) {
1087         IndexReader reader = readers[i];
1088         TermEnum termEnum;
1089   
1090         if (t != null) {
1091           termEnum = reader.terms(t);
1092         } else
1093           termEnum = reader.terms();
1094   
1095         SegmentMergeInfo smi = new SegmentMergeInfo(starts[i], termEnum, reader);
1096         smi.ord = i;
1097         if (t == null ? smi.next() : termEnum.term() != null)
1098           queue.add(smi);          // initialize queue
1099         else
1100           smi.close();
1101       }
1102   
1103       if (t != null && queue.size() > 0) {
1104         next();
1105       }
1106     }
1107   
1108     @Override
1109     public boolean next() throws IOException {
1110       for (int i=0; i<matchingSegments.length; i++) {
1111         SegmentMergeInfo smi = matchingSegments[i];
1112         if (smi==null) break;
1113         if (smi.next())
1114           queue.add(smi);
1115         else
1116           smi.close(); // done with segment
1117       }
1118       
1119       int numMatchingSegments = 0;
1120       matchingSegments[0] = null;
1121
1122       SegmentMergeInfo top = queue.top();
1123
1124       if (top == null) {
1125         term = null;
1126         return false;
1127       }
1128   
1129       term = top.term;
1130       docFreq = 0;
1131   
1132       while (top != null && term.compareTo(top.term) == 0) {
1133         matchingSegments[numMatchingSegments++] = top;
1134         queue.pop();
1135         docFreq += top.termEnum.docFreq();    // increment freq
1136         top = queue.top();
1137       }
1138
1139       matchingSegments[numMatchingSegments] = null;
1140       return true;
1141     }
1142   
1143     @Override
1144     public Term term() {
1145       return term;
1146     }
1147   
1148     @Override
1149     public int docFreq() {
1150       return docFreq;
1151     }
1152   
1153     @Override
1154     public void close() throws IOException {
1155       queue.close();
1156     }
1157   }
1158
1159   static class MultiTermDocs implements TermDocs {
1160     IndexReader topReader;  // used for matching TermEnum to TermDocs
1161     protected IndexReader[] readers;
1162     protected int[] starts;
1163     protected Term term;
1164   
1165     protected int base = 0;
1166     protected int pointer = 0;
1167   
1168     private TermDocs[] readerTermDocs;
1169     protected TermDocs current;              // == readerTermDocs[pointer]
1170
1171     private MultiTermEnum tenum;  // the term enum used for seeking... can be null
1172     int matchingSegmentPos;  // position into the matching segments from tenum
1173     SegmentMergeInfo smi;     // current segment mere info... can be null
1174
1175     public MultiTermDocs(IndexReader topReader, IndexReader[] r, int[] s) {
1176       this.topReader = topReader;
1177       readers = r;
1178       starts = s;
1179   
1180       readerTermDocs = new TermDocs[r.length];
1181     }
1182
1183     public int doc() {
1184       return base + current.doc();
1185     }
1186     public int freq() {
1187       return current.freq();
1188     }
1189   
1190     public void seek(Term term) {
1191       this.term = term;
1192       this.base = 0;
1193       this.pointer = 0;
1194       this.current = null;
1195       this.tenum = null;
1196       this.smi = null;
1197       this.matchingSegmentPos = 0;
1198     }
1199   
1200     public void seek(TermEnum termEnum) throws IOException {
1201       seek(termEnum.term());
1202       if (termEnum instanceof MultiTermEnum) {
1203         tenum = (MultiTermEnum)termEnum;
1204         if (topReader != tenum.topReader)
1205           tenum = null;
1206       }
1207     }
1208   
1209     public boolean next() throws IOException {
1210       for(;;) {
1211         if (current!=null && current.next()) {
1212           return true;
1213         }
1214         else if (pointer < readers.length) {
1215           if (tenum != null) {
1216             smi = tenum.matchingSegments[matchingSegmentPos++];
1217             if (smi==null) {
1218               pointer = readers.length;
1219               return false;
1220             }
1221             pointer = smi.ord;
1222           }
1223           base = starts[pointer];
1224           current = termDocs(pointer++);
1225         } else {
1226           return false;
1227         }
1228       }
1229     }
1230   
1231     /** Optimized implementation. */
1232     public int read(final int[] docs, final int[] freqs) throws IOException {
1233       while (true) {
1234         while (current == null) {
1235           if (pointer < readers.length) {      // try next segment
1236             if (tenum != null) {
1237               smi = tenum.matchingSegments[matchingSegmentPos++];
1238               if (smi==null) {
1239                 pointer = readers.length;
1240                 return 0;
1241               }
1242               pointer = smi.ord;
1243             }
1244             base = starts[pointer];
1245             current = termDocs(pointer++);
1246           } else {
1247             return 0;
1248           }
1249         }
1250         int end = current.read(docs, freqs);
1251         if (end == 0) {          // none left in segment
1252           current = null;
1253         } else {            // got some
1254           final int b = base;        // adjust doc numbers
1255           for (int i = 0; i < end; i++)
1256            docs[i] += b;
1257           return end;
1258         }
1259       }
1260     }
1261   
1262    /* A Possible future optimization could skip entire segments */ 
1263     public boolean skipTo(int target) throws IOException {
1264       for(;;) {
1265         if (current != null && current.skipTo(target-base)) {
1266           return true;
1267         } else if (pointer < readers.length) {
1268           if (tenum != null) {
1269             SegmentMergeInfo smi = tenum.matchingSegments[matchingSegmentPos++];
1270             if (smi==null) {
1271               pointer = readers.length;
1272               return false;
1273             }
1274             pointer = smi.ord;
1275           }
1276           base = starts[pointer];
1277           current = termDocs(pointer++);
1278         } else
1279           return false;
1280       }
1281     }
1282   
1283     private TermDocs termDocs(int i) throws IOException {
1284       TermDocs result = readerTermDocs[i];
1285       if (result == null)
1286         result = readerTermDocs[i] = termDocs(readers[i]);
1287       if (smi != null) {
1288         assert(smi.ord == i);
1289         assert(smi.termEnum.term().equals(term));
1290         result.seek(smi.termEnum);
1291       } else {
1292         result.seek(term);
1293       }
1294       return result;
1295     }
1296   
1297     protected TermDocs termDocs(IndexReader reader)
1298       throws IOException {
1299       return term==null ? reader.termDocs(null) : reader.termDocs();
1300     }
1301   
1302     public void close() throws IOException {
1303       for (int i = 0; i < readerTermDocs.length; i++) {
1304         if (readerTermDocs[i] != null)
1305           readerTermDocs[i].close();
1306       }
1307     }
1308   }
1309
1310   static class MultiTermPositions extends MultiTermDocs implements TermPositions {
1311     public MultiTermPositions(IndexReader topReader, IndexReader[] r, int[] s) {
1312       super(topReader,r,s);
1313     }
1314   
1315     @Override
1316     protected TermDocs termDocs(IndexReader reader) throws IOException {
1317       return reader.termPositions();
1318     }
1319   
1320     public int nextPosition() throws IOException {
1321       return ((TermPositions)current).nextPosition();
1322     }
1323     
1324     public int getPayloadLength() {
1325       return ((TermPositions)current).getPayloadLength();
1326     }
1327      
1328     public byte[] getPayload(byte[] data, int offset) throws IOException {
1329       return ((TermPositions)current).getPayload(data, offset);
1330     }
1331   
1332   
1333     // TODO: Remove warning after API has been finalized
1334     public boolean isPayloadAvailable() {
1335       return ((TermPositions) current).isPayloadAvailable();
1336     }
1337   }
1338 }