1 package org.apache.lucene.index;
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
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;
32 import java.util.concurrent.ConcurrentHashMap;
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;
43 * An IndexReader which reads indexes with multiple segments.
45 class DirectoryReader extends IndexReader implements Cloneable {
46 protected Directory directory;
47 protected boolean readOnly;
51 private IndexDeletionPolicy deletionPolicy;
52 private Lock writeLock;
53 private final SegmentInfos segmentInfos;
54 private boolean stale;
55 private final int termInfosIndexDivisor;
57 private boolean rollbackHasChanges;
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;
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;
71 private final boolean applyAllDeletes;
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) {
77 protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
78 SegmentInfos infos = new SegmentInfos();
79 infos.read(directory, segmentFileName);
81 return new ReadOnlyDirectoryReader(directory, infos, deletionPolicy, termInfosIndexDivisor, null);
83 return new DirectoryReader(directory, infos, deletionPolicy, false, termInfosIndexDivisor, null);
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;
97 if (readerFinishedListeners == null) {
98 this.readerFinishedListeners = new MapBackedSet<ReaderFinishedListener>(new ConcurrentHashMap<ReaderFinishedListener,Boolean>());
100 this.readerFinishedListeners = readerFinishedListeners;
102 applyAllDeletes = false;
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.
109 SegmentReader[] readers = new SegmentReader[sis.size()];
110 for (int i = sis.size()-1; i >= 0; i--) {
111 boolean success = false;
113 readers[i] = SegmentReader.get(readOnly, sis.info(i), termInfosIndexDivisor);
114 readers[i].readerFinishedListeners = this.readerFinishedListeners;
118 // Close all readers we had opened:
119 for(i++;i<sis.size();i++) {
122 } catch (Throwable ignore) {
123 // keep going - we want to clean up as much as possible
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
139 this.termInfosIndexDivisor = termInfosIndexDivisor;
140 readerFinishedListeners = writer.getReaderFinishedListeners();
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();
147 List<SegmentReader> readers = new ArrayList<SegmentReader>();
148 final Directory dir = writer.getDirectory();
150 segmentInfos = (SegmentInfos) infos.clone();
152 for (int i=0;i<numSegments;i++) {
153 boolean success = false;
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;
164 segmentInfos.remove(infosUpto);
169 // Close all readers we had opened:
170 for(SegmentReader reader : readers) {
173 } catch (Throwable ignore) {
174 // keep going - we want to clean up as much as possible
181 this.writer = writer;
183 initialize(readers.toArray(new SegmentReader[readers.size()]));
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;
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>();
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));
209 SegmentReader[] newReaders = new SegmentReader[infos.size()];
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()];
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;
222 // there is an old reader for this segment - we'll try to reopen it
223 newReaders[i] = oldReaders[oldReaderIndex.intValue()];
226 boolean success = false;
228 SegmentReader newReader;
229 if (newReaders[i] == null || infos.info(i).getUseCompoundFile() != newReaders[i].getSegmentInfo().getUseCompoundFile()) {
231 // We should never see a totally new segment during cloning
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;
238 newReader = newReaders[i].reopenSegment(infos.info(i), doClone, readOnly);
239 assert newReader.readerFinishedListeners == readerFinishedListeners;
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;
247 readerShared[i] = false;
248 newReaders[i] = newReader;
253 for (i++; i < infos.size(); i++) {
254 if (newReaders[i] != null) {
256 if (!readerShared[i]) {
257 // this is a new subReader that is not used by the old one,
259 newReaders[i].close();
261 // this subReader is also used by the old reader, so instead
262 // closing we must decRef it
263 newReaders[i].decRef();
265 } catch (IOException ignore) {
266 // keep going - we want to clean up as much as possible
274 // initialize the readers to calculate maxDoc before we try to reuse the old normsCache
275 initialize(newReaders);
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)) {
285 byte[] oldBytes = entry.getValue();
287 byte[] bytes = new byte[maxDoc()];
289 for (int i = 0; i < subReaders.length; i++) {
290 Integer oldReaderIndex = segmentReaders.get(subReaders[i].getSegmentName());
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]);
301 subReaders[i].norms(field, bytes, starts[i]);
305 normsCache.put(field, bytes); // update cache
312 public String toString() {
313 final StringBuilder buffer = new StringBuilder();
317 buffer.append(getClass().getSimpleName());
319 final String segmentsFile = segmentInfos.getCurrentSegmentFileName();
320 if (segmentsFile != null) {
321 buffer.append(segmentsFile);
323 if (writer != null) {
324 buffer.append(":nrt");
326 for(int i=0;i<subReaders.length;i++) {
328 buffer.append(subReaders[i]);
331 return buffer.toString();
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++) {
339 maxDoc += subReaders[i].maxDoc(); // compute maxDocs
341 if (subReaders[i].hasDeletions())
344 starts[subReaders.length] = maxDoc;
347 maxIndexVersion = SegmentInfos.readCurrentVersion(directory);
352 public final synchronized Object clone() {
354 return clone(readOnly); // Preserve current readOnly
355 } catch (Exception ex) {
356 throw new RuntimeException(ex);
361 public final synchronized IndexReader clone(boolean openReadOnly) throws CorruptIndexException, IOException {
362 DirectoryReader newReader = doReopen((SegmentInfos) segmentInfos.clone(), true, openReadOnly);
364 if (this != newReader) {
365 newReader.deletionPolicy = deletionPolicy;
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;
379 assert newReader.readerFinishedListeners != null;
385 public final IndexReader reopen() throws CorruptIndexException, IOException {
386 // Preserve current readOnly
387 return doReopen(readOnly, null);
391 public final IndexReader reopen(boolean openReadOnly) throws CorruptIndexException, IOException {
392 return doReopen(openReadOnly, null);
396 public final IndexReader reopen(final IndexCommit commit) throws CorruptIndexException, IOException {
397 return doReopen(true, commit);
400 private final IndexReader doReopenFromWriter(boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
404 throw new IllegalArgumentException("a reader obtained from IndexWriter.getReader() can only be reopened with openReadOnly=true (got false)");
407 if (commit != null) {
408 throw new IllegalArgumentException("a reader obtained from IndexWriter.getReader() cannot currently accept a commit");
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;
419 private IndexReader doReopen(final boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
422 assert commit == null || openReadOnly;
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);
429 return doReopenNoWriter(openReadOnly, commit);
433 private synchronized IndexReader doReopenNoWriter(final boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
435 if (commit == null) {
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:
446 return clone(openReadOnly);
450 } else if (isCurrent()) {
451 if (openReadOnly != readOnly) {
452 // Just fallback to clone
453 return clone(openReadOnly);
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);
471 return (IndexReader) new SegmentInfos.FindSegmentsFile(directory) {
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);
481 private synchronized DirectoryReader doReopen(SegmentInfos infos, boolean doClone, boolean openReadOnly) throws CorruptIndexException, IOException {
482 DirectoryReader reader;
484 reader = new ReadOnlyDirectoryReader(directory, infos, subReaders, starts, normsCache, doClone, termInfosIndexDivisor, readerFinishedListeners);
486 reader = new DirectoryReader(directory, infos, subReaders, starts, normsCache, false, doClone, termInfosIndexDivisor, readerFinishedListeners);
491 /** Version number when this IndexReader was opened. */
493 public long getVersion() {
495 return segmentInfos.getVersion();
499 public TermFreqVector[] getTermFreqVectors(int n) throws IOException {
501 int i = readerIndex(n); // find segment num
502 return subReaders[i].getTermFreqVectors(n - starts[i]); // dispatch to segment
506 public TermFreqVector getTermFreqVector(int n, String field)
509 int i = readerIndex(n); // find segment num
510 return subReaders[i].getTermFreqVector(n - starts[i], field);
515 public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
517 int i = readerIndex(docNumber); // find segment num
518 subReaders[i].getTermFreqVector(docNumber - starts[i], field, mapper);
522 public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
524 int i = readerIndex(docNumber); // find segment num
525 subReaders[i].getTermFreqVector(docNumber - starts[i], mapper);
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
533 public boolean isOptimized() {
535 return segmentInfos.size() == 1 && !hasDeletions();
539 public int numDocs() {
540 // Don't call ensureOpen() here (it could affect performance)
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
554 public int maxDoc() {
555 // Don't call ensureOpen() here (it could affect performance)
561 public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
563 int i = readerIndex(n); // find segment num
564 return subReaders[i].document(n - starts[i], fieldSelector); // dispatch to segment reader
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
575 public boolean hasDeletions() {
576 // Don't call ensureOpen() here (it could affect performance)
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
589 protected void doUndeleteAll() throws CorruptIndexException, IOException {
590 for (int i = 0; i < subReaders.length; i++)
591 subReaders[i].undeleteAll();
593 hasDeletions = false;
594 numDocs = -1; // invalidate cache
597 private int readerIndex(int n) { // find reader for doc n:
598 return readerIndex(n, this.starts, this.subReaders.length);
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
606 int mid = (lo + hi) >>> 1;
607 int midValue = starts[mid];
610 else if (n > midValue)
612 else { // found a match
613 while (mid+1 < numSubReaders && starts[mid+1] == midValue) {
614 mid++; // scan to last match
623 public boolean hasNorms(String field) throws IOException {
625 for (int i = 0; i < subReaders.length; i++) {
626 if (subReaders[i].hasNorms(field)) return true;
632 public synchronized byte[] norms(String field) throws IOException {
634 byte[] bytes = normsCache.get(field);
636 return bytes; // cache hit
637 if (!hasNorms(field))
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
648 public synchronized void norms(String field, byte[] result, int offset)
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());
657 for (int i = 0; i < subReaders.length; i++) { // read from segments
658 subReaders[i].norms(field, result, offset + starts[i]);
664 protected void doSetNorm(int n, String field, byte value)
665 throws CorruptIndexException, IOException {
666 synchronized (normsCache) {
667 normsCache.remove(field); // clear cache
669 int i = readerIndex(n); // find segment num
670 subReaders[i].setNorm(n-starts[i], field, value); // dispatch
674 public TermEnum terms() throws IOException {
676 if (subReaders.length == 1) {
677 // Optimize single segment case:
678 return subReaders[0].terms();
680 return new MultiTermEnum(this, subReaders, starts, null);
685 public TermEnum terms(Term term) throws IOException {
687 if (subReaders.length == 1) {
688 // Optimize single segment case:
689 return subReaders[0].terms(term);
691 return new MultiTermEnum(this, subReaders, starts, term);
696 public int docFreq(Term t) throws IOException {
698 int total = 0; // sum freqs in segments
699 for (int i = 0; i < subReaders.length; i++)
700 total += subReaders[i].docFreq(t);
705 public TermDocs termDocs() throws IOException {
707 if (subReaders.length == 1) {
708 // Optimize single segment case:
709 return subReaders[0].termDocs();
711 return new MultiTermDocs(this, subReaders, starts);
716 public TermDocs termDocs(Term term) throws IOException {
718 if (subReaders.length == 1) {
719 // Optimize single segment case:
720 return subReaders[0].termDocs(term);
722 return super.termDocs(term);
727 public TermPositions termPositions() throws IOException {
729 if (subReaders.length == 1) {
730 // Optimize single segment case:
731 return subReaders[0].termPositions();
733 return new MultiTermPositions(this, subReaders, starts);
738 * Tries to acquire the WriteLock on this directory. this method is only valid if this IndexReader is directory
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
746 * @throws IOException if there is a low-level IO error
749 protected void acquireWriteLock() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
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();
758 if (segmentInfos != null) {
761 throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
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;
769 // we have to check whether index has changed since this reader was opened.
770 // if so, this reader is no longer valid for
772 if (SegmentInfos.readCurrentVersion(directory) > maxIndexVersion) {
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");
783 * Commit changes resulting from delete, undeleteAll, or setNorm operations
785 * If an exception is hit, then either no changes or all changes will have been committed to the index (transactional
788 * @throws IOException if there is a low-level IO error
791 protected void doCommit(Map<String,String> commitUserData) throws IOException {
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();
802 // Checkpoint the state we are about to change, in
803 // case we have to roll back:
806 final List<SegmentInfo> rollbackSegments = segmentInfos.createBackupSegmentInfos(false);
808 boolean success = false;
810 for (int i = 0; i < subReaders.length; i++)
811 subReaders[i].commit();
813 // Remove segments that contain only 100% deleted
815 segmentInfos.pruneDeletedSegments();
817 // Sync all files we just wrote
818 directory.sync(segmentInfos.files(directory, false));
819 segmentInfos.commit(directory);
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):
832 // Recompute deletable files & remove them (so
833 // partially written .del files, etc, are
837 // Restore all SegmentInfos (in case we pruned some)
838 segmentInfos.rollbackSegmentInfos(rollbackSegments);
842 // Have the deleter remove any now unreferenced
843 // files due to this commit:
844 deleter.checkpoint(segmentInfos, true);
847 maxIndexVersion = segmentInfos.getVersion();
849 if (writeLock != null) {
850 writeLock.release(); // release write lock
858 rollbackHasChanges = hasChanges;
859 for (int i = 0; i < subReaders.length; i++) {
860 subReaders[i].startCommit();
864 void rollbackCommit() {
865 hasChanges = rollbackHasChanges;
866 for (int i = 0; i < subReaders.length; i++) {
867 subReaders[i].rollbackCommit();
872 public Map<String,String> getCommitUserData() {
874 return segmentInfos.getUserData();
878 public boolean isCurrent() throws CorruptIndexException, IOException {
880 if (writer == null || writer.isClosed()) {
881 // we loaded SegmentInfos from the directory
882 return SegmentInfos.readCurrentVersion(directory) == segmentInfos.getVersion();
884 return writer.nrtIsCurrent(segmentInfos);
889 protected synchronized void doClose() throws IOException {
890 IOException ioe = null;
892 for (int i = 0; i < subReaders.length; i++) {
893 // try to close each reader, even if an exception is thrown
895 subReaders[i].decRef();
896 } catch (IOException e) {
897 if (ioe == null) ioe = e;
901 if (writer != null) {
902 // Since we just closed, writer may now be able to
903 // delete unused files:
904 writer.deleteUnusedFiles();
907 // throw the first exception
908 if (ioe != null) throw ioe;
912 public Collection<String> getFieldNames (IndexReader.FieldOption fieldNames) {
914 return getFieldNames(fieldNames, this.subReaders);
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);
928 public IndexReader[] getSequentialSubReaders() {
932 /** Returns the directory this index resides in. */
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
942 public int getTermInfosIndexDivisor() {
943 return termInfosIndexDivisor;
947 * Expert: return the IndexCommit that this reader has opened.
949 * @lucene.experimental
952 public IndexCommit getIndexCommit() throws IOException {
953 return new ReaderCommit(segmentInfos, directory);
956 /** @see org.apache.lucene.index.IndexReader#listCommits */
957 public static Collection<IndexCommit> listCommits(Directory dir) throws IOException {
958 final String[] files = dir.listAll();
960 List<IndexCommit> commits = new ArrayList<IndexCommit>();
962 SegmentInfos latest = new SegmentInfos();
964 final long currentGen = latest.getGeneration();
966 commits.add(new ReaderCommit(latest, dir));
968 for(int i=0;i<files.length;i++) {
970 final String fileName = files[i];
972 if (fileName.startsWith(IndexFileNames.SEGMENTS) &&
973 !fileName.equals(IndexFileNames.SEGMENTS_GEN) &&
974 SegmentInfos.generationFromSegmentsFileName(fileName) < currentGen) {
976 SegmentInfos sis = new SegmentInfos();
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
993 commits.add(new ReaderCommit(sis, dir));
997 // Ensure that the commit points are sorted in ascending order.
998 Collections.sort(commits);
1003 private static final class ReaderCommit extends IndexCommit {
1004 private String segmentsFileName;
1005 Collection<String> files;
1009 final boolean isOptimized;
1010 final Map<String,String> userData;
1012 ReaderCommit(SegmentInfos infos, Directory dir) throws IOException {
1013 segmentsFileName = infos.getCurrentSegmentFileName();
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();
1023 public String toString() {
1024 return "DirectoryReader.ReaderCommit(" + segmentsFileName + ")";
1028 public boolean isOptimized() {
1033 public String getSegmentsFileName() {
1034 return segmentsFileName;
1038 public Collection<String> getFileNames() {
1043 public Directory getDirectory() {
1048 public long getVersion() {
1053 public long getGeneration() {
1058 public boolean isDeleted() {
1063 public Map<String,String> getUserData() {
1068 public void delete() {
1069 throw new UnsupportedOperationException("This IndexCommit does not support deletions");
1073 static class MultiTermEnum extends TermEnum {
1074 IndexReader topReader; // used for matching TermEnum to TermDocs
1075 private SegmentMergeQueue queue;
1078 private int docFreq;
1079 final SegmentMergeInfo[] matchingSegments; // null terminated array of matching segments
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];
1091 termEnum = reader.terms(t);
1093 termEnum = reader.terms();
1095 SegmentMergeInfo smi = new SegmentMergeInfo(starts[i], termEnum, reader);
1097 if (t == null ? smi.next() : termEnum.term() != null)
1098 queue.add(smi); // initialize queue
1103 if (t != null && queue.size() > 0) {
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;
1116 smi.close(); // done with segment
1119 int numMatchingSegments = 0;
1120 matchingSegments[0] = null;
1122 SegmentMergeInfo top = queue.top();
1132 while (top != null && term.compareTo(top.term) == 0) {
1133 matchingSegments[numMatchingSegments++] = top;
1135 docFreq += top.termEnum.docFreq(); // increment freq
1139 matchingSegments[numMatchingSegments] = null;
1144 public Term term() {
1149 public int docFreq() {
1154 public void close() throws IOException {
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;
1165 protected int base = 0;
1166 protected int pointer = 0;
1168 private TermDocs[] readerTermDocs;
1169 protected TermDocs current; // == readerTermDocs[pointer]
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
1175 public MultiTermDocs(IndexReader topReader, IndexReader[] r, int[] s) {
1176 this.topReader = topReader;
1180 readerTermDocs = new TermDocs[r.length];
1184 return base + current.doc();
1187 return current.freq();
1190 public void seek(Term term) {
1194 this.current = null;
1197 this.matchingSegmentPos = 0;
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)
1209 public boolean next() throws IOException {
1211 if (current!=null && current.next()) {
1214 else if (pointer < readers.length) {
1215 if (tenum != null) {
1216 smi = tenum.matchingSegments[matchingSegmentPos++];
1218 pointer = readers.length;
1223 base = starts[pointer];
1224 current = termDocs(pointer++);
1231 /** Optimized implementation. */
1232 public int read(final int[] docs, final int[] freqs) throws IOException {
1234 while (current == null) {
1235 if (pointer < readers.length) { // try next segment
1236 if (tenum != null) {
1237 smi = tenum.matchingSegments[matchingSegmentPos++];
1239 pointer = readers.length;
1244 base = starts[pointer];
1245 current = termDocs(pointer++);
1250 int end = current.read(docs, freqs);
1251 if (end == 0) { // none left in segment
1253 } else { // got some
1254 final int b = base; // adjust doc numbers
1255 for (int i = 0; i < end; i++)
1262 /* A Possible future optimization could skip entire segments */
1263 public boolean skipTo(int target) throws IOException {
1265 if (current != null && current.skipTo(target-base)) {
1267 } else if (pointer < readers.length) {
1268 if (tenum != null) {
1269 SegmentMergeInfo smi = tenum.matchingSegments[matchingSegmentPos++];
1271 pointer = readers.length;
1276 base = starts[pointer];
1277 current = termDocs(pointer++);
1283 private TermDocs termDocs(int i) throws IOException {
1284 TermDocs result = readerTermDocs[i];
1286 result = readerTermDocs[i] = termDocs(readers[i]);
1288 assert(smi.ord == i);
1289 assert(smi.termEnum.term().equals(term));
1290 result.seek(smi.termEnum);
1297 protected TermDocs termDocs(IndexReader reader)
1298 throws IOException {
1299 return term==null ? reader.termDocs(null) : reader.termDocs();
1302 public void close() throws IOException {
1303 for (int i = 0; i < readerTermDocs.length; i++) {
1304 if (readerTermDocs[i] != null)
1305 readerTermDocs[i].close();
1310 static class MultiTermPositions extends MultiTermDocs implements TermPositions {
1311 public MultiTermPositions(IndexReader topReader, IndexReader[] r, int[] s) {
1312 super(topReader,r,s);
1316 protected TermDocs termDocs(IndexReader reader) throws IOException {
1317 return reader.termPositions();
1320 public int nextPosition() throws IOException {
1321 return ((TermPositions)current).nextPosition();
1324 public int getPayloadLength() {
1325 return ((TermPositions)current).getPayloadLength();
1328 public byte[] getPayload(byte[] data, int offset) throws IOException {
1329 return ((TermPositions)current).getPayload(data, offset);
1333 // TODO: Remove warning after API has been finalized
1334 public boolean isPayloadAvailable() {
1335 return ((TermPositions) current).isPayloadAvailable();