pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.4.0 / lucene / src / java / org / apache / lucene / index / DirectoryReader.java
diff --git a/lucene-java-3.4.0/lucene/src/java/org/apache/lucene/index/DirectoryReader.java b/lucene-java-3.4.0/lucene/src/java/org/apache/lucene/index/DirectoryReader.java
deleted file mode 100644 (file)
index 44b3903..0000000
+++ /dev/null
@@ -1,1338 +0,0 @@
-package org.apache.lucene.index;
-
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.FieldSelector;
-import org.apache.lucene.search.Similarity;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.Lock;
-import org.apache.lucene.store.LockObtainFailedException;
-import org.apache.lucene.util.MapBackedSet;
-
-/** 
- * An IndexReader which reads indexes with multiple segments.
- */
-class DirectoryReader extends IndexReader implements Cloneable {
-  protected Directory directory;
-  protected boolean readOnly;
-
-  IndexWriter writer;
-
-  private IndexDeletionPolicy deletionPolicy;
-  private Lock writeLock;
-  private final SegmentInfos segmentInfos;
-  private boolean stale;
-  private final int termInfosIndexDivisor;
-
-  private boolean rollbackHasChanges;
-
-  private SegmentReader[] subReaders;
-  private int[] starts;                           // 1st docno for each segment
-  private Map<String,byte[]> normsCache = new HashMap<String,byte[]>();
-  private int maxDoc = 0;
-  private int numDocs = -1;
-  private boolean hasDeletions = false;
-
-  // Max version in index as of when we opened; this can be
-  // > our current segmentInfos version in case we were
-  // opened on a past IndexCommit:
-  private long maxIndexVersion;
-
-  private final boolean applyAllDeletes;
-
-  static IndexReader open(final Directory directory, final IndexDeletionPolicy deletionPolicy, final IndexCommit commit, final boolean readOnly,
-                          final int termInfosIndexDivisor) throws CorruptIndexException, IOException {
-    return (IndexReader) new SegmentInfos.FindSegmentsFile(directory) {
-      @Override
-      protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
-        SegmentInfos infos = new SegmentInfos();
-        infos.read(directory, segmentFileName);
-        if (readOnly)
-          return new ReadOnlyDirectoryReader(directory, infos, deletionPolicy, termInfosIndexDivisor, null);
-        else
-          return new DirectoryReader(directory, infos, deletionPolicy, false, termInfosIndexDivisor, null);
-      }
-    }.run(commit);
-  }
-
-  /** Construct reading the named set of readers. */
-  DirectoryReader(Directory directory, SegmentInfos sis, IndexDeletionPolicy deletionPolicy, boolean readOnly, int termInfosIndexDivisor,
-                  Collection<ReaderFinishedListener> readerFinishedListeners) throws IOException {
-    this.directory = directory;
-    this.readOnly = readOnly;
-    this.segmentInfos = sis;
-    this.deletionPolicy = deletionPolicy;
-    this.termInfosIndexDivisor = termInfosIndexDivisor;
-
-    if (readerFinishedListeners == null) {
-      this.readerFinishedListeners = new MapBackedSet<ReaderFinishedListener>(new ConcurrentHashMap<ReaderFinishedListener,Boolean>());
-    } else {
-      this.readerFinishedListeners = readerFinishedListeners;
-    }
-    applyAllDeletes = false;
-
-    // To reduce the chance of hitting FileNotFound
-    // (and having to retry), we open segments in
-    // reverse because IndexWriter merges & deletes
-    // the newest segments first.
-
-    SegmentReader[] readers = new SegmentReader[sis.size()];
-    for (int i = sis.size()-1; i >= 0; i--) {
-      boolean success = false;
-      try {
-        readers[i] = SegmentReader.get(readOnly, sis.info(i), termInfosIndexDivisor);
-        readers[i].readerFinishedListeners = this.readerFinishedListeners;
-        success = true;
-      } finally {
-        if (!success) {
-          // Close all readers we had opened:
-          for(i++;i<sis.size();i++) {
-            try {
-              readers[i].close();
-            } catch (Throwable ignore) {
-              // keep going - we want to clean up as much as possible
-            }
-          }
-        }
-      }
-    }
-
-    initialize(readers);
-  }
-
-  // Used by near real-time search
-  DirectoryReader(IndexWriter writer, SegmentInfos infos, int termInfosIndexDivisor, boolean applyAllDeletes) throws IOException {
-    this.directory = writer.getDirectory();
-    this.readOnly = true;
-    this.applyAllDeletes = applyAllDeletes;       // saved for reopen
-
-    this.termInfosIndexDivisor = termInfosIndexDivisor;
-    readerFinishedListeners = writer.getReaderFinishedListeners();
-
-    // IndexWriter synchronizes externally before calling
-    // us, which ensures infos will not change; so there's
-    // no need to process segments in reverse order
-    final int numSegments = infos.size();
-
-    List<SegmentReader> readers = new ArrayList<SegmentReader>();
-    final Directory dir = writer.getDirectory();
-
-    segmentInfos = (SegmentInfos) infos.clone();
-    int infosUpto = 0;
-    for (int i=0;i<numSegments;i++) {
-      boolean success = false;
-      try {
-        final SegmentInfo info = infos.info(i);
-        assert info.dir == dir;
-        final SegmentReader reader = writer.readerPool.getReadOnlyClone(info, true, termInfosIndexDivisor);
-        if (reader.numDocs() > 0 || writer.getKeepFullyDeletedSegments()) {
-          reader.readerFinishedListeners = readerFinishedListeners;
-          readers.add(reader);
-          infosUpto++;
-        } else {
-          reader.close();
-          segmentInfos.remove(infosUpto);
-        }
-        success = true;
-      } finally {
-        if (!success) {
-          // Close all readers we had opened:
-          for(SegmentReader reader : readers) {
-            try {
-              reader.close();
-            } catch (Throwable ignore) {
-              // keep going - we want to clean up as much as possible
-            }
-          }
-        }
-      }
-    }
-
-    this.writer = writer;
-
-    initialize(readers.toArray(new SegmentReader[readers.size()]));
-  }
-
-  /** This constructor is only used for {@link #reopen()} */
-  DirectoryReader(Directory directory, SegmentInfos infos, SegmentReader[] oldReaders, int[] oldStarts,
-                  Map<String,byte[]> oldNormsCache, boolean readOnly, boolean doClone, int termInfosIndexDivisor,
-                  Collection<ReaderFinishedListener> readerFinishedListeners) throws IOException {
-    this.directory = directory;
-    this.readOnly = readOnly;
-    this.segmentInfos = infos;
-    this.termInfosIndexDivisor = termInfosIndexDivisor;
-    assert readerFinishedListeners != null;
-    this.readerFinishedListeners = readerFinishedListeners;
-    applyAllDeletes = false;
-
-    // we put the old SegmentReaders in a map, that allows us
-    // to lookup a reader using its segment name
-    Map<String,Integer> segmentReaders = new HashMap<String,Integer>();
-
-    if (oldReaders != null) {
-      // create a Map SegmentName->SegmentReader
-      for (int i = 0; i < oldReaders.length; i++) {
-        segmentReaders.put(oldReaders[i].getSegmentName(), Integer.valueOf(i));
-      }
-    }
-    
-    SegmentReader[] newReaders = new SegmentReader[infos.size()];
-    
-    // remember which readers are shared between the old and the re-opened
-    // DirectoryReader - we have to incRef those readers
-    boolean[] readerShared = new boolean[infos.size()];
-    
-    for (int i = infos.size() - 1; i>=0; i--) {
-      // find SegmentReader for this segment
-      Integer oldReaderIndex = segmentReaders.get(infos.info(i).name);
-      if (oldReaderIndex == null) {
-        // this is a new segment, no old SegmentReader can be reused
-        newReaders[i] = null;
-      } else {
-        // there is an old reader for this segment - we'll try to reopen it
-        newReaders[i] = oldReaders[oldReaderIndex.intValue()];
-      }
-
-      boolean success = false;
-      try {
-        SegmentReader newReader;
-        if (newReaders[i] == null || infos.info(i).getUseCompoundFile() != newReaders[i].getSegmentInfo().getUseCompoundFile()) {
-
-          // We should never see a totally new segment during cloning
-          assert !doClone;
-
-          // this is a new reader; in case we hit an exception we can close it safely
-          newReader = SegmentReader.get(readOnly, infos.info(i), termInfosIndexDivisor);
-          newReader.readerFinishedListeners = readerFinishedListeners;
-        } else {
-          newReader = newReaders[i].reopenSegment(infos.info(i), doClone, readOnly);
-          assert newReader.readerFinishedListeners == readerFinishedListeners;
-        }
-        if (newReader == newReaders[i]) {
-          // this reader will be shared between the old and the new one,
-          // so we must incRef it
-          readerShared[i] = true;
-          newReader.incRef();
-        } else {
-          readerShared[i] = false;
-          newReaders[i] = newReader;
-        }
-        success = true;
-      } finally {
-        if (!success) {
-          for (i++; i < infos.size(); i++) {
-            if (newReaders[i] != null) {
-              try {
-                if (!readerShared[i]) {
-                  // this is a new subReader that is not used by the old one,
-                  // we can close it
-                  newReaders[i].close();
-                } else {
-                  // this subReader is also used by the old reader, so instead
-                  // closing we must decRef it
-                  newReaders[i].decRef();
-                }
-              } catch (IOException ignore) {
-                // keep going - we want to clean up as much as possible
-              }
-            }
-          }
-        }
-      }
-    }    
-    
-    // initialize the readers to calculate maxDoc before we try to reuse the old normsCache
-    initialize(newReaders);
-    
-    // try to copy unchanged norms from the old normsCache to the new one
-    if (oldNormsCache != null) {
-      for (Map.Entry<String,byte[]> entry: oldNormsCache.entrySet()) {
-        String field = entry.getKey();
-        if (!hasNorms(field)) {
-          continue;
-        }
-
-        byte[] oldBytes = entry.getValue();
-
-        byte[] bytes = new byte[maxDoc()];
-
-        for (int i = 0; i < subReaders.length; i++) {
-          Integer oldReaderIndex = segmentReaders.get(subReaders[i].getSegmentName());
-
-          // this SegmentReader was not re-opened, we can copy all of its norms 
-          if (oldReaderIndex != null &&
-               (oldReaders[oldReaderIndex.intValue()] == subReaders[i] 
-                 || oldReaders[oldReaderIndex.intValue()].norms.get(field) == subReaders[i].norms.get(field))) {
-            // we don't have to synchronize here: either this constructor is called from a SegmentReader,
-            // in which case no old norms cache is present, or it is called from MultiReader.reopen(),
-            // which is synchronized
-            System.arraycopy(oldBytes, oldStarts[oldReaderIndex.intValue()], bytes, starts[i], starts[i+1] - starts[i]);
-          } else {
-            subReaders[i].norms(field, bytes, starts[i]);
-          }
-        }
-
-        normsCache.put(field, bytes);      // update cache
-      }
-    }
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public String toString() {
-    final StringBuilder buffer = new StringBuilder();
-    if (hasChanges) {
-      buffer.append("*");
-    }
-    buffer.append(getClass().getSimpleName());
-    buffer.append('(');
-    final String segmentsFile = segmentInfos.getCurrentSegmentFileName();
-    if (segmentsFile != null) {
-      buffer.append(segmentsFile);
-    }
-    if (writer != null) {
-      buffer.append(":nrt");
-    }
-    for(int i=0;i<subReaders.length;i++) {
-      buffer.append(' ');
-      buffer.append(subReaders[i]);
-    }
-    buffer.append(')');
-    return buffer.toString();
-  }
-
-  private void initialize(SegmentReader[] subReaders) throws IOException {
-    this.subReaders = subReaders;
-    starts = new int[subReaders.length + 1];    // build starts array
-    for (int i = 0; i < subReaders.length; i++) {
-      starts[i] = maxDoc;
-      maxDoc += subReaders[i].maxDoc();      // compute maxDocs
-
-      if (subReaders[i].hasDeletions())
-        hasDeletions = true;
-    }
-    starts[subReaders.length] = maxDoc;
-
-    if (!readOnly) {
-      maxIndexVersion = SegmentInfos.readCurrentVersion(directory);
-    }
-  }
-
-  @Override
-  public final synchronized Object clone() {
-    try {
-      return clone(readOnly); // Preserve current readOnly
-    } catch (Exception ex) {
-      throw new RuntimeException(ex);
-    }
-  }
-
-  @Override
-  public final synchronized IndexReader clone(boolean openReadOnly) throws CorruptIndexException, IOException {
-    DirectoryReader newReader = doReopen((SegmentInfos) segmentInfos.clone(), true, openReadOnly);
-
-    if (this != newReader) {
-      newReader.deletionPolicy = deletionPolicy;
-    }
-    newReader.writer = writer;
-    // If we're cloning a non-readOnly reader, move the
-    // writeLock (if there is one) to the new reader:
-    if (!openReadOnly && writeLock != null) {
-      // In near real-time search, reader is always readonly
-      assert writer == null;
-      newReader.writeLock = writeLock;
-      newReader.hasChanges = hasChanges;
-      newReader.hasDeletions = hasDeletions;
-      writeLock = null;
-      hasChanges = false;
-    }
-    assert newReader.readerFinishedListeners != null;
-
-    return newReader;
-  }
-
-  @Override
-  public final IndexReader reopen() throws CorruptIndexException, IOException {
-    // Preserve current readOnly
-    return doReopen(readOnly, null);
-  }
-
-  @Override
-  public final IndexReader reopen(boolean openReadOnly) throws CorruptIndexException, IOException {
-    return doReopen(openReadOnly, null);
-  }
-
-  @Override
-  public final IndexReader reopen(final IndexCommit commit) throws CorruptIndexException, IOException {
-    return doReopen(true, commit);
-  }
-
-  private final IndexReader doReopenFromWriter(boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
-    assert readOnly;
-
-    if (!openReadOnly) {
-      throw new IllegalArgumentException("a reader obtained from IndexWriter.getReader() can only be reopened with openReadOnly=true (got false)");
-    }
-
-    if (commit != null) {
-      throw new IllegalArgumentException("a reader obtained from IndexWriter.getReader() cannot currently accept a commit");
-    }
-
-    // TODO: right now we *always* make a new reader; in
-    // the future we could have write make some effort to
-    // detect that no changes have occurred
-    IndexReader reader = writer.getReader(applyAllDeletes);
-    reader.readerFinishedListeners = readerFinishedListeners;
-    return reader;
-  }
-
-  private IndexReader doReopen(final boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
-    ensureOpen();
-
-    assert commit == null || openReadOnly;
-
-    // If we were obtained by writer.getReader(), re-ask the
-    // writer to get a new reader.
-    if (writer != null) {
-      return doReopenFromWriter(openReadOnly, commit);
-    } else {
-      return doReopenNoWriter(openReadOnly, commit);
-    }
-  }
-
-  private synchronized IndexReader doReopenNoWriter(final boolean openReadOnly, IndexCommit commit) throws CorruptIndexException, IOException {
-
-    if (commit == null) {
-      if (hasChanges) {
-        // We have changes, which means we are not readOnly:
-        assert readOnly == false;
-        // and we hold the write lock:
-        assert writeLock != null;
-        // so no other writer holds the write lock, which
-        // means no changes could have been done to the index:
-        assert isCurrent();
-
-        if (openReadOnly) {
-          return clone(openReadOnly);
-        } else {
-          return this;
-        }
-      } else if (isCurrent()) {
-        if (openReadOnly != readOnly) {
-          // Just fallback to clone
-          return clone(openReadOnly);
-        } else {
-          return this;
-        }
-      }
-    } else {
-      if (directory != commit.getDirectory())
-        throw new IOException("the specified commit does not match the specified Directory");
-      if (segmentInfos != null && commit.getSegmentsFileName().equals(segmentInfos.getCurrentSegmentFileName())) {
-        if (readOnly != openReadOnly) {
-          // Just fallback to clone
-          return clone(openReadOnly);
-        } else {
-          return this;
-        }
-      }
-    }
-
-    return (IndexReader) new SegmentInfos.FindSegmentsFile(directory) {
-      @Override
-      protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
-        SegmentInfos infos = new SegmentInfos();
-        infos.read(directory, segmentFileName);
-        return doReopen(infos, false, openReadOnly);
-      }
-    }.run(commit);
-  }
-
-  private synchronized DirectoryReader doReopen(SegmentInfos infos, boolean doClone, boolean openReadOnly) throws CorruptIndexException, IOException {
-    DirectoryReader reader;
-    if (openReadOnly) {
-      reader = new ReadOnlyDirectoryReader(directory, infos, subReaders, starts, normsCache, doClone, termInfosIndexDivisor, readerFinishedListeners);
-    } else {
-      reader = new DirectoryReader(directory, infos, subReaders, starts, normsCache, false, doClone, termInfosIndexDivisor, readerFinishedListeners);
-    }
-    return reader;
-  }
-
-  /** Version number when this IndexReader was opened. */
-  @Override
-  public long getVersion() {
-    ensureOpen();
-    return segmentInfos.getVersion();
-  }
-
-  @Override
-  public TermFreqVector[] getTermFreqVectors(int n) throws IOException {
-    ensureOpen();
-    int i = readerIndex(n);        // find segment num
-    return subReaders[i].getTermFreqVectors(n - starts[i]); // dispatch to segment
-  }
-
-  @Override
-  public TermFreqVector getTermFreqVector(int n, String field)
-      throws IOException {
-    ensureOpen();
-    int i = readerIndex(n);        // find segment num
-    return subReaders[i].getTermFreqVector(n - starts[i], field);
-  }
-
-
-  @Override
-  public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
-    ensureOpen();
-    int i = readerIndex(docNumber);        // find segment num
-    subReaders[i].getTermFreqVector(docNumber - starts[i], field, mapper);
-  }
-
-  @Override
-  public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
-    ensureOpen();
-    int i = readerIndex(docNumber);        // find segment num
-    subReaders[i].getTermFreqVector(docNumber - starts[i], mapper);
-  }
-
-  /**
-   * Checks is the index is optimized (if it has a single segment and no deletions)
-   * @return <code>true</code> if the index is optimized; <code>false</code> otherwise
-   */
-  @Override
-  public boolean isOptimized() {
-    ensureOpen();
-    return segmentInfos.size() == 1 && !hasDeletions();
-  }
-
-  @Override
-  public int numDocs() {
-    // Don't call ensureOpen() here (it could affect performance)
-
-    // NOTE: multiple threads may wind up init'ing
-    // numDocs... but that's harmless
-    if (numDocs == -1) {        // check cache
-      int n = 0;                // cache miss--recompute
-      for (int i = 0; i < subReaders.length; i++)
-        n += subReaders[i].numDocs();      // sum from readers
-      numDocs = n;
-    }
-    return numDocs;
-  }
-
-  @Override
-  public int maxDoc() {
-    // Don't call ensureOpen() here (it could affect performance)
-    return maxDoc;
-  }
-
-  // inherit javadoc
-  @Override
-  public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
-    ensureOpen();
-    int i = readerIndex(n);                          // find segment num
-    return subReaders[i].document(n - starts[i], fieldSelector);    // dispatch to segment reader
-  }
-
-  @Override
-  public boolean isDeleted(int n) {
-    // Don't call ensureOpen() here (it could affect performance)
-    final int i = readerIndex(n);                           // find segment num
-    return subReaders[i].isDeleted(n - starts[i]);    // dispatch to segment reader
-  }
-
-  @Override
-  public boolean hasDeletions() {
-    // Don't call ensureOpen() here (it could affect performance)
-    return hasDeletions;
-  }
-
-  @Override
-  protected void doDelete(int n) throws CorruptIndexException, IOException {
-    numDocs = -1;                             // invalidate cache
-    int i = readerIndex(n);                   // find segment num
-    subReaders[i].deleteDocument(n - starts[i]);      // dispatch to segment reader
-    hasDeletions = true;
-  }
-
-  @Override
-  protected void doUndeleteAll() throws CorruptIndexException, IOException {
-    for (int i = 0; i < subReaders.length; i++)
-      subReaders[i].undeleteAll();
-
-    hasDeletions = false;
-    numDocs = -1;                                 // invalidate cache
-  }
-
-  private int readerIndex(int n) {    // find reader for doc n:
-    return readerIndex(n, this.starts, this.subReaders.length);
-  }
-  
-  final static int readerIndex(int n, int[] starts, int numSubReaders) {    // find reader for doc n:
-    int lo = 0;                                      // search starts array
-    int hi = numSubReaders - 1;                  // for first element less
-
-    while (hi >= lo) {
-      int mid = (lo + hi) >>> 1;
-      int midValue = starts[mid];
-      if (n < midValue)
-        hi = mid - 1;
-      else if (n > midValue)
-        lo = mid + 1;
-      else {                                      // found a match
-        while (mid+1 < numSubReaders && starts[mid+1] == midValue) {
-          mid++;                                  // scan to last match
-        }
-        return mid;
-      }
-    }
-    return hi;
-  }
-
-  @Override
-  public boolean hasNorms(String field) throws IOException {
-    ensureOpen();
-    for (int i = 0; i < subReaders.length; i++) {
-      if (subReaders[i].hasNorms(field)) return true;
-    }
-    return false;
-  }
-
-  @Override
-  public synchronized byte[] norms(String field) throws IOException {
-    ensureOpen();
-    byte[] bytes = normsCache.get(field);
-    if (bytes != null)
-      return bytes;          // cache hit
-    if (!hasNorms(field))
-      return null;
-
-    bytes = new byte[maxDoc()];
-    for (int i = 0; i < subReaders.length; i++)
-      subReaders[i].norms(field, bytes, starts[i]);
-    normsCache.put(field, bytes);      // update cache
-    return bytes;
-  }
-
-  @Override
-  public synchronized void norms(String field, byte[] result, int offset)
-    throws IOException {
-    ensureOpen();
-    byte[] bytes = normsCache.get(field);
-    if (bytes==null && !hasNorms(field)) {
-      Arrays.fill(result, offset, result.length, Similarity.getDefault().encodeNormValue(1.0f));
-    } else if (bytes != null) {                           // cache hit
-      System.arraycopy(bytes, 0, result, offset, maxDoc());
-    } else {
-      for (int i = 0; i < subReaders.length; i++) {      // read from segments
-        subReaders[i].norms(field, result, offset + starts[i]);
-      }
-    }
-  }
-
-  @Override
-  protected void doSetNorm(int n, String field, byte value)
-    throws CorruptIndexException, IOException {
-    synchronized (normsCache) {
-      normsCache.remove(field);                         // clear cache      
-    }
-    int i = readerIndex(n);                           // find segment num
-    subReaders[i].setNorm(n-starts[i], field, value); // dispatch
-  }
-
-  @Override
-  public TermEnum terms() throws IOException {
-    ensureOpen();
-    if (subReaders.length == 1) {
-      // Optimize single segment case:
-      return subReaders[0].terms();
-    } else {
-      return new MultiTermEnum(this, subReaders, starts, null);
-    }
-  }
-
-  @Override
-  public TermEnum terms(Term term) throws IOException {
-    ensureOpen();
-    if (subReaders.length == 1) {
-      // Optimize single segment case:
-      return subReaders[0].terms(term);
-    } else {
-      return new MultiTermEnum(this, subReaders, starts, term);
-    }
-  }
-
-  @Override
-  public int docFreq(Term t) throws IOException {
-    ensureOpen();
-    int total = 0;          // sum freqs in segments
-    for (int i = 0; i < subReaders.length; i++)
-      total += subReaders[i].docFreq(t);
-    return total;
-  }
-
-  @Override
-  public TermDocs termDocs() throws IOException {
-    ensureOpen();
-    if (subReaders.length == 1) {
-      // Optimize single segment case:
-      return subReaders[0].termDocs();
-    } else {
-      return new MultiTermDocs(this, subReaders, starts);
-    }
-  }
-
-  @Override
-  public TermDocs termDocs(Term term) throws IOException {
-    ensureOpen();
-    if (subReaders.length == 1) {
-      // Optimize single segment case:
-      return subReaders[0].termDocs(term);
-    } else {
-      return super.termDocs(term);
-    }
-  }
-
-  @Override
-  public TermPositions termPositions() throws IOException {
-    ensureOpen();
-    if (subReaders.length == 1) {
-      // Optimize single segment case:
-      return subReaders[0].termPositions();
-    } else {
-      return new MultiTermPositions(this, subReaders, starts);
-    }
-  }
-
-  /**
-   * Tries to acquire the WriteLock on this directory. this method is only valid if this IndexReader is directory
-   * owner.
-   *
-   * @throws StaleReaderException  if the index has changed since this reader was opened
-   * @throws CorruptIndexException if the index is corrupt
-   * @throws org.apache.lucene.store.LockObtainFailedException
-   *                               if another writer has this index open (<code>write.lock</code> could not be
-   *                               obtained)
-   * @throws IOException           if there is a low-level IO error
-   */
-  @Override
-  protected void acquireWriteLock() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
-
-    if (readOnly) {
-      // NOTE: we should not reach this code w/ the core
-      // IndexReader classes; however, an external subclass
-      // of IndexReader could reach this.
-      ReadOnlySegmentReader.noWrite();
-    }
-
-    if (segmentInfos != null) {
-      ensureOpen();
-      if (stale)
-        throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
-
-      if (writeLock == null) {
-        Lock writeLock = directory.makeLock(IndexWriter.WRITE_LOCK_NAME);
-        if (!writeLock.obtain(IndexWriterConfig.WRITE_LOCK_TIMEOUT)) // obtain write lock
-          throw new LockObtainFailedException("Index locked for write: " + writeLock);
-        this.writeLock = writeLock;
-
-        // we have to check whether index has changed since this reader was opened.
-        // if so, this reader is no longer valid for
-        // deletion
-        if (SegmentInfos.readCurrentVersion(directory) > maxIndexVersion) {
-          stale = true;
-          this.writeLock.release();
-          this.writeLock = null;
-          throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
-        }
-      }
-    }
-  }
-
-  /**
-   * Commit changes resulting from delete, undeleteAll, or setNorm operations
-   * <p/>
-   * If an exception is hit, then either no changes or all changes will have been committed to the index (transactional
-   * semantics).
-   *
-   * @throws IOException if there is a low-level IO error
-   */
-  @Override
-  protected void doCommit(Map<String,String> commitUserData) throws IOException {
-    if (hasChanges) {
-      segmentInfos.setUserData(commitUserData);
-      // Default deleter (for backwards compatibility) is
-      // KeepOnlyLastCommitDeleter:
-      IndexFileDeleter deleter = new IndexFileDeleter(directory,
-                                                      deletionPolicy == null ? new KeepOnlyLastCommitDeletionPolicy() : deletionPolicy,
-                                                      segmentInfos, null, null);
-      segmentInfos.updateGeneration(deleter.getLastSegmentInfos());
-      segmentInfos.changed();
-
-      // Checkpoint the state we are about to change, in
-      // case we have to roll back:
-      startCommit();
-
-      final List<SegmentInfo> rollbackSegments = segmentInfos.createBackupSegmentInfos(false);
-
-      boolean success = false;
-      try {
-        for (int i = 0; i < subReaders.length; i++)
-          subReaders[i].commit();
-
-        // Remove segments that contain only 100% deleted
-        // docs:
-        segmentInfos.pruneDeletedSegments();
-
-        // Sync all files we just wrote
-        directory.sync(segmentInfos.files(directory, false));
-        segmentInfos.commit(directory);
-        success = true;
-      } finally {
-
-        if (!success) {
-
-          // Rollback changes that were made to
-          // SegmentInfos but failed to get [fully]
-          // committed.  This way this reader instance
-          // remains consistent (matched to what's
-          // actually in the index):
-          rollbackCommit();
-
-          // Recompute deletable files & remove them (so
-          // partially written .del files, etc, are
-          // removed):
-          deleter.refresh();
-
-          // Restore all SegmentInfos (in case we pruned some)
-          segmentInfos.rollbackSegmentInfos(rollbackSegments);
-        }
-      }
-
-      // Have the deleter remove any now unreferenced
-      // files due to this commit:
-      deleter.checkpoint(segmentInfos, true);
-      deleter.close();
-
-      maxIndexVersion = segmentInfos.getVersion();
-
-      if (writeLock != null) {
-        writeLock.release();  // release write lock
-        writeLock = null;
-      }
-    }
-    hasChanges = false;
-  }
-
-  void startCommit() {
-    rollbackHasChanges = hasChanges;
-    for (int i = 0; i < subReaders.length; i++) {
-      subReaders[i].startCommit();
-    }
-  }
-
-  void rollbackCommit() {
-    hasChanges = rollbackHasChanges;
-    for (int i = 0; i < subReaders.length; i++) {
-      subReaders[i].rollbackCommit();
-    }
-  }
-
-  @Override
-  public Map<String,String> getCommitUserData() {
-    ensureOpen();
-    return segmentInfos.getUserData();
-  }
-
-  @Override
-  public boolean isCurrent() throws CorruptIndexException, IOException {
-    ensureOpen();
-    if (writer == null || writer.isClosed()) {
-      // we loaded SegmentInfos from the directory
-      return SegmentInfos.readCurrentVersion(directory) == segmentInfos.getVersion();
-    } else {
-      return writer.nrtIsCurrent(segmentInfos);
-    }
-  }
-
-  @Override
-  protected synchronized void doClose() throws IOException {
-    IOException ioe = null;
-    normsCache = null;
-    for (int i = 0; i < subReaders.length; i++) {
-      // try to close each reader, even if an exception is thrown
-      try {
-        subReaders[i].decRef();
-      } catch (IOException e) {
-        if (ioe == null) ioe = e;
-      }
-    }
-
-    if (writer != null) {
-      // Since we just closed, writer may now be able to
-      // delete unused files:
-      writer.deleteUnusedFiles();
-    }
-
-    // throw the first exception
-    if (ioe != null) throw ioe;
-  }
-
-  @Override
-  public Collection<String> getFieldNames (IndexReader.FieldOption fieldNames) {
-    ensureOpen();
-    return getFieldNames(fieldNames, this.subReaders);
-  }
-  
-  static Collection<String> getFieldNames (IndexReader.FieldOption fieldNames, IndexReader[] subReaders) {
-    // maintain a unique set of field names
-    Set<String> fieldSet = new HashSet<String>();
-    for (IndexReader reader : subReaders) {
-      Collection<String> names = reader.getFieldNames(fieldNames);
-      fieldSet.addAll(names);
-    }
-    return fieldSet;
-  } 
-  
-  @Override
-  public IndexReader[] getSequentialSubReaders() {
-    return subReaders;
-  }
-
-  /** Returns the directory this index resides in. */
-  @Override
-  public Directory directory() {
-    // Don't ensureOpen here -- in certain cases, when a
-    // cloned/reopened reader needs to commit, it may call
-    // this method on the closed original reader
-    return directory;
-  }
-
-  @Override
-  public int getTermInfosIndexDivisor() {
-    return termInfosIndexDivisor;
-  }
-
-  /**
-   * Expert: return the IndexCommit that this reader has opened.
-   * <p/>
-   * @lucene.experimental
-   */
-  @Override
-  public IndexCommit getIndexCommit() throws IOException {
-    return new ReaderCommit(segmentInfos, directory);
-  }
-
-  /** @see org.apache.lucene.index.IndexReader#listCommits */
-  public static Collection<IndexCommit> listCommits(Directory dir) throws IOException {
-    final String[] files = dir.listAll();
-
-    List<IndexCommit> commits = new ArrayList<IndexCommit>();
-
-    SegmentInfos latest = new SegmentInfos();
-    latest.read(dir);
-    final long currentGen = latest.getGeneration();
-
-    commits.add(new ReaderCommit(latest, dir));
-
-    for(int i=0;i<files.length;i++) {
-
-      final String fileName = files[i];
-
-      if (fileName.startsWith(IndexFileNames.SEGMENTS) &&
-          !fileName.equals(IndexFileNames.SEGMENTS_GEN) &&
-          SegmentInfos.generationFromSegmentsFileName(fileName) < currentGen) {
-
-        SegmentInfos sis = new SegmentInfos();
-        try {
-          // IOException allowed to throw there, in case
-          // segments_N is corrupt
-          sis.read(dir, fileName);
-        } catch (FileNotFoundException fnfe) {
-          // LUCENE-948: on NFS (and maybe others), if
-          // you have writers switching back and forth
-          // between machines, it's very likely that the
-          // dir listing will be stale and will claim a
-          // file segments_X exists when in fact it
-          // doesn't.  So, we catch this and handle it
-          // as if the file does not exist
-          sis = null;
-        }
-
-        if (sis != null)
-          commits.add(new ReaderCommit(sis, dir));
-      }
-    }
-
-    // Ensure that the commit points are sorted in ascending order.
-    Collections.sort(commits);
-
-    return commits;
-  }
-
-  private static final class ReaderCommit extends IndexCommit {
-    private String segmentsFileName;
-    Collection<String> files;
-    Directory dir;
-    long generation;
-    long version;
-    final boolean isOptimized;
-    final Map<String,String> userData;
-
-    ReaderCommit(SegmentInfos infos, Directory dir) throws IOException {
-      segmentsFileName = infos.getCurrentSegmentFileName();
-      this.dir = dir;
-      userData = infos.getUserData();
-      files = Collections.unmodifiableCollection(infos.files(dir, true));
-      version = infos.getVersion();
-      generation = infos.getGeneration();
-      isOptimized = infos.size() == 1 && !infos.info(0).hasDeletions();
-    }
-
-    @Override
-    public String toString() {
-      return "DirectoryReader.ReaderCommit(" + segmentsFileName + ")";
-    }
-
-    @Override
-    public boolean isOptimized() {
-      return isOptimized;
-    }
-
-    @Override
-    public String getSegmentsFileName() {
-      return segmentsFileName;
-    }
-
-    @Override
-    public Collection<String> getFileNames() {
-      return files;
-    }
-
-    @Override
-    public Directory getDirectory() {
-      return dir;
-    }
-
-    @Override
-    public long getVersion() {
-      return version;
-    }
-
-    @Override
-    public long getGeneration() {
-      return generation;
-    }
-
-    @Override
-    public boolean isDeleted() {
-      return false;
-    }
-
-    @Override
-    public Map<String,String> getUserData() {
-      return userData;
-    }
-
-    @Override
-    public void delete() {
-      throw new UnsupportedOperationException("This IndexCommit does not support deletions");
-    }
-  }
-
-  static class MultiTermEnum extends TermEnum {
-    IndexReader topReader; // used for matching TermEnum to TermDocs
-    private SegmentMergeQueue queue;
-  
-    private Term term;
-    private int docFreq;
-    final SegmentMergeInfo[] matchingSegments; // null terminated array of matching segments
-
-    public MultiTermEnum(IndexReader topReader, IndexReader[] readers, int[] starts, Term t)
-      throws IOException {
-      this.topReader = topReader;
-      queue = new SegmentMergeQueue(readers.length);
-      matchingSegments = new SegmentMergeInfo[readers.length+1];
-      for (int i = 0; i < readers.length; i++) {
-        IndexReader reader = readers[i];
-        TermEnum termEnum;
-  
-        if (t != null) {
-          termEnum = reader.terms(t);
-        } else
-          termEnum = reader.terms();
-  
-        SegmentMergeInfo smi = new SegmentMergeInfo(starts[i], termEnum, reader);
-        smi.ord = i;
-        if (t == null ? smi.next() : termEnum.term() != null)
-          queue.add(smi);          // initialize queue
-        else
-          smi.close();
-      }
-  
-      if (t != null && queue.size() > 0) {
-        next();
-      }
-    }
-  
-    @Override
-    public boolean next() throws IOException {
-      for (int i=0; i<matchingSegments.length; i++) {
-        SegmentMergeInfo smi = matchingSegments[i];
-        if (smi==null) break;
-        if (smi.next())
-          queue.add(smi);
-        else
-          smi.close(); // done with segment
-      }
-      
-      int numMatchingSegments = 0;
-      matchingSegments[0] = null;
-
-      SegmentMergeInfo top = queue.top();
-
-      if (top == null) {
-        term = null;
-        return false;
-      }
-  
-      term = top.term;
-      docFreq = 0;
-  
-      while (top != null && term.compareTo(top.term) == 0) {
-        matchingSegments[numMatchingSegments++] = top;
-        queue.pop();
-        docFreq += top.termEnum.docFreq();    // increment freq
-        top = queue.top();
-      }
-
-      matchingSegments[numMatchingSegments] = null;
-      return true;
-    }
-  
-    @Override
-    public Term term() {
-      return term;
-    }
-  
-    @Override
-    public int docFreq() {
-      return docFreq;
-    }
-  
-    @Override
-    public void close() throws IOException {
-      queue.close();
-    }
-  }
-
-  static class MultiTermDocs implements TermDocs {
-    IndexReader topReader;  // used for matching TermEnum to TermDocs
-    protected IndexReader[] readers;
-    protected int[] starts;
-    protected Term term;
-  
-    protected int base = 0;
-    protected int pointer = 0;
-  
-    private TermDocs[] readerTermDocs;
-    protected TermDocs current;              // == readerTermDocs[pointer]
-
-    private MultiTermEnum tenum;  // the term enum used for seeking... can be null
-    int matchingSegmentPos;  // position into the matching segments from tenum
-    SegmentMergeInfo smi;     // current segment mere info... can be null
-
-    public MultiTermDocs(IndexReader topReader, IndexReader[] r, int[] s) {
-      this.topReader = topReader;
-      readers = r;
-      starts = s;
-  
-      readerTermDocs = new TermDocs[r.length];
-    }
-
-    public int doc() {
-      return base + current.doc();
-    }
-    public int freq() {
-      return current.freq();
-    }
-  
-    public void seek(Term term) {
-      this.term = term;
-      this.base = 0;
-      this.pointer = 0;
-      this.current = null;
-      this.tenum = null;
-      this.smi = null;
-      this.matchingSegmentPos = 0;
-    }
-  
-    public void seek(TermEnum termEnum) throws IOException {
-      seek(termEnum.term());
-      if (termEnum instanceof MultiTermEnum) {
-        tenum = (MultiTermEnum)termEnum;
-        if (topReader != tenum.topReader)
-          tenum = null;
-      }
-    }
-  
-    public boolean next() throws IOException {
-      for(;;) {
-        if (current!=null && current.next()) {
-          return true;
-        }
-        else if (pointer < readers.length) {
-          if (tenum != null) {
-            smi = tenum.matchingSegments[matchingSegmentPos++];
-            if (smi==null) {
-              pointer = readers.length;
-              return false;
-            }
-            pointer = smi.ord;
-          }
-          base = starts[pointer];
-          current = termDocs(pointer++);
-        } else {
-          return false;
-        }
-      }
-    }
-  
-    /** Optimized implementation. */
-    public int read(final int[] docs, final int[] freqs) throws IOException {
-      while (true) {
-        while (current == null) {
-          if (pointer < readers.length) {      // try next segment
-            if (tenum != null) {
-              smi = tenum.matchingSegments[matchingSegmentPos++];
-              if (smi==null) {
-                pointer = readers.length;
-                return 0;
-              }
-              pointer = smi.ord;
-            }
-            base = starts[pointer];
-            current = termDocs(pointer++);
-          } else {
-            return 0;
-          }
-        }
-        int end = current.read(docs, freqs);
-        if (end == 0) {          // none left in segment
-          current = null;
-        } else {            // got some
-          final int b = base;        // adjust doc numbers
-          for (int i = 0; i < end; i++)
-           docs[i] += b;
-          return end;
-        }
-      }
-    }
-  
-   /* A Possible future optimization could skip entire segments */ 
-    public boolean skipTo(int target) throws IOException {
-      for(;;) {
-        if (current != null && current.skipTo(target-base)) {
-          return true;
-        } else if (pointer < readers.length) {
-          if (tenum != null) {
-            SegmentMergeInfo smi = tenum.matchingSegments[matchingSegmentPos++];
-            if (smi==null) {
-              pointer = readers.length;
-              return false;
-            }
-            pointer = smi.ord;
-          }
-          base = starts[pointer];
-          current = termDocs(pointer++);
-        } else
-          return false;
-      }
-    }
-  
-    private TermDocs termDocs(int i) throws IOException {
-      TermDocs result = readerTermDocs[i];
-      if (result == null)
-        result = readerTermDocs[i] = termDocs(readers[i]);
-      if (smi != null) {
-        assert(smi.ord == i);
-        assert(smi.termEnum.term().equals(term));
-        result.seek(smi.termEnum);
-      } else {
-        result.seek(term);
-      }
-      return result;
-    }
-  
-    protected TermDocs termDocs(IndexReader reader)
-      throws IOException {
-      return term==null ? reader.termDocs(null) : reader.termDocs();
-    }
-  
-    public void close() throws IOException {
-      for (int i = 0; i < readerTermDocs.length; i++) {
-        if (readerTermDocs[i] != null)
-          readerTermDocs[i].close();
-      }
-    }
-  }
-
-  static class MultiTermPositions extends MultiTermDocs implements TermPositions {
-    public MultiTermPositions(IndexReader topReader, IndexReader[] r, int[] s) {
-      super(topReader,r,s);
-    }
-  
-    @Override
-    protected TermDocs termDocs(IndexReader reader) throws IOException {
-      return reader.termPositions();
-    }
-  
-    public int nextPosition() throws IOException {
-      return ((TermPositions)current).nextPosition();
-    }
-    
-    public int getPayloadLength() {
-      return ((TermPositions)current).getPayloadLength();
-    }
-     
-    public byte[] getPayload(byte[] data, int offset) throws IOException {
-      return ((TermPositions)current).getPayload(data, offset);
-    }
-  
-  
-    // TODO: Remove warning after API has been finalized
-    public boolean isPayloadAvailable() {
-      return ((TermPositions) current).isPayloadAvailable();
-    }
-  }
-}