pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / index / MultiReader.java
diff --git a/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/index/MultiReader.java b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/index/MultiReader.java
new file mode 100644 (file)
index 0000000..8c6ea8f
--- /dev/null
@@ -0,0 +1,477 @@
+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.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.index.DirectoryReader.MultiTermDocs;
+import org.apache.lucene.index.DirectoryReader.MultiTermEnum;
+import org.apache.lucene.index.DirectoryReader.MultiTermPositions;
+import org.apache.lucene.search.Similarity;
+import org.apache.lucene.util.MapBackedSet;
+
+/** An IndexReader which reads multiple indexes, appending
+ * their content. */
+public class MultiReader extends IndexReader implements Cloneable {
+  protected IndexReader[] subReaders;
+  private int[] starts;                           // 1st docno for each segment
+  private boolean[] decrefOnClose;                // remember which subreaders to decRef on close
+  private Map<String,byte[]> normsCache = new HashMap<String,byte[]>();
+  private int maxDoc = 0;
+  private int numDocs = -1;
+  private boolean hasDeletions = false;
+  
+ /**
+  * <p>Construct a MultiReader aggregating the named set of (sub)readers.
+  * Directory locking for delete, undeleteAll, and setNorm operations is
+  * left to the subreaders. </p>
+  * <p>Note that all subreaders are closed if this Multireader is closed.</p>
+  * @param subReaders set of (sub)readers
+  */
+  public MultiReader(IndexReader... subReaders) {
+    initialize(subReaders, true);
+  }
+
+  /**
+   * <p>Construct a MultiReader aggregating the named set of (sub)readers.
+   * Directory locking for delete, undeleteAll, and setNorm operations is
+   * left to the subreaders. </p>
+   * @param closeSubReaders indicates whether the subreaders should be closed
+   * when this MultiReader is closed
+   * @param subReaders set of (sub)readers
+   */
+  public MultiReader(IndexReader[] subReaders, boolean closeSubReaders) {
+    initialize(subReaders, closeSubReaders);
+  }
+  
+  private void initialize(IndexReader[] subReaders, boolean closeSubReaders) {
+    this.subReaders =  subReaders.clone();
+    starts = new int[subReaders.length + 1];    // build starts array
+    decrefOnClose = new boolean[subReaders.length];
+    for (int i = 0; i < subReaders.length; i++) {
+      starts[i] = maxDoc;
+      maxDoc += subReaders[i].maxDoc();      // compute maxDocs
+
+      if (!closeSubReaders) {
+        subReaders[i].incRef();
+        decrefOnClose[i] = true;
+      } else {
+        decrefOnClose[i] = false;
+      }
+      
+      if (subReaders[i].hasDeletions())
+        hasDeletions = true;
+    }
+    starts[subReaders.length] = maxDoc;
+    readerFinishedListeners = new MapBackedSet<ReaderFinishedListener>(new ConcurrentHashMap<ReaderFinishedListener,Boolean>());
+  }
+  
+  /**
+   * Tries to reopen the subreaders.
+   * <br>
+   * If one or more subreaders could be re-opened (i. e. IndexReader.openIfChanged(subReader) 
+   * returned a new instance), then a new MultiReader instance 
+   * is returned, otherwise this instance is returned.
+   * <p>
+   * A re-opened instance might share one or more subreaders with the old 
+   * instance. Index modification operations result in undefined behavior
+   * when performed before the old instance is closed.
+   * (see {@link IndexReader#openIfChanged}).
+   * <p>
+   * If subreaders are shared, then the reference count of those
+   * readers is increased to ensure that the subreaders remain open
+   * until the last referring reader is closed.
+   * 
+   * @throws CorruptIndexException if the index is corrupt
+   * @throws IOException if there is a low-level IO error 
+   */
+  @Override
+  protected synchronized IndexReader doOpenIfChanged() throws CorruptIndexException, IOException {
+    return doOpenIfChanged(false);
+  }
+  
+  /**
+   * Clones the subreaders.
+   * (see {@link IndexReader#clone()}).
+   * <br>
+   * <p>
+   * If subreaders are shared, then the reference count of those
+   * readers is increased to ensure that the subreaders remain open
+   * until the last referring reader is closed.
+   */
+  @Override
+  public synchronized Object clone() {
+    try {
+      return doOpenIfChanged(true);
+    } catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+  
+  /**
+   * If clone is true then we clone each of the subreaders
+   * @param doClone
+   * @return New IndexReader, or null if open/clone is not necessary
+   * @throws CorruptIndexException
+   * @throws IOException
+   */
+  protected IndexReader doOpenIfChanged(boolean doClone) throws CorruptIndexException, IOException {
+    ensureOpen();
+    
+    boolean changed = false;
+    IndexReader[] newSubReaders = new IndexReader[subReaders.length];
+    
+    boolean success = false;
+    try {
+      for (int i = 0; i < subReaders.length; i++) {
+        if (doClone) {
+          newSubReaders[i] = (IndexReader) subReaders[i].clone();
+          changed = true;
+        } else {
+          final IndexReader newSubReader = IndexReader.openIfChanged(subReaders[i]);
+          if (newSubReader != null) {
+            newSubReaders[i] = newSubReader;
+            changed = true;
+          } else {
+            newSubReaders[i] = subReaders[i];
+          }
+        }
+      }
+      success = true;
+    } finally {
+      if (!success && changed) {
+        for (int i = 0; i < newSubReaders.length; i++) {
+          if (newSubReaders[i] != subReaders[i]) {
+            try {
+              newSubReaders[i].close();
+            } catch (IOException ignore) {
+              // keep going - we want to clean up as much as possible
+            }
+          }
+        }
+      }
+    }
+
+    if (changed) {
+      boolean[] newDecrefOnClose = new boolean[subReaders.length];
+      for (int i = 0; i < subReaders.length; i++) {
+        if (newSubReaders[i] == subReaders[i]) {
+          newSubReaders[i].incRef();
+          newDecrefOnClose[i] = true;
+        }
+      }
+      MultiReader mr = new MultiReader(newSubReaders);
+      mr.decrefOnClose = newDecrefOnClose;
+      return mr;
+    } else {
+      return null;
+    }
+  }
+
+  @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);
+  }
+
+  @Deprecated
+  @Override
+  public boolean isOptimized() {
+    ensureOpen();
+    return false;
+  }
+
+  @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)
+    int i = readerIndex(n);                           // find segment num
+    return subReaders[i].isDeleted(n - starts[i]);    // dispatch to segment reader
+  }
+
+  @Override
+  public boolean hasDeletions() {
+    ensureOpen();
+    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 DirectoryReader.readerIndex(n, this.starts, this.subReaders.length);
+  }
+  
+  @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);
+    for (int i = 0; i < subReaders.length; i++)      // read from segments
+      subReaders[i].norms(field, result, offset + starts[i]);
+
+    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);
+    }
+  }
+
+  @Override
+  protected void doCommit(Map<String,String> commitUserData) throws IOException {
+    for (int i = 0; i < subReaders.length; i++)
+      subReaders[i].commit(commitUserData);
+  }
+
+  @Override
+  protected synchronized void doClose() throws IOException {
+    for (int i = 0; i < subReaders.length; i++) {
+      if (decrefOnClose[i]) {
+        subReaders[i].decRef();
+      } else {
+        subReaders[i].close();
+      }
+    }
+  }
+  
+  @Override
+  public Collection<String> getFieldNames (IndexReader.FieldOption fieldNames) {
+    ensureOpen();
+    return DirectoryReader.getFieldNames(fieldNames, this.subReaders);
+  }  
+  
+  /**
+   * Checks recursively if all subreaders are up to date. 
+   */
+  @Override
+  public boolean isCurrent() throws CorruptIndexException, IOException {
+    ensureOpen();
+    for (int i = 0; i < subReaders.length; i++) {
+      if (!subReaders[i].isCurrent()) {
+        return false;
+      }
+    }
+    
+    // all subreaders are up to date
+    return true;
+  }
+  
+  /** Not implemented.
+   * @throws UnsupportedOperationException
+   */
+  @Override
+  public long getVersion() {
+    throw new UnsupportedOperationException("MultiReader does not support this method.");
+  }
+  
+  @Override
+  public IndexReader[] getSequentialSubReaders() {
+    return subReaders;
+  }
+
+  @Override
+  public void addReaderFinishedListener(ReaderFinishedListener listener) {
+    super.addReaderFinishedListener(listener);
+    for(IndexReader sub : subReaders) {
+      sub.addReaderFinishedListener(listener);
+    }
+  }
+
+  @Override
+  public void removeReaderFinishedListener(ReaderFinishedListener listener) {
+    super.removeReaderFinishedListener(listener);
+    for(IndexReader sub : subReaders) {
+      sub.removeReaderFinishedListener(listener);
+    }
+  }
+}