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.IOException;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.HashMap;
25 import java.util.concurrent.ConcurrentHashMap;
27 import org.apache.lucene.document.Document;
28 import org.apache.lucene.document.FieldSelector;
29 import org.apache.lucene.index.DirectoryReader.MultiTermDocs;
30 import org.apache.lucene.index.DirectoryReader.MultiTermEnum;
31 import org.apache.lucene.index.DirectoryReader.MultiTermPositions;
32 import org.apache.lucene.search.Similarity;
33 import org.apache.lucene.util.MapBackedSet;
35 /** An IndexReader which reads multiple indexes, appending
37 public class MultiReader extends IndexReader implements Cloneable {
38 protected IndexReader[] subReaders;
39 private int[] starts; // 1st docno for each segment
40 private boolean[] decrefOnClose; // remember which subreaders to decRef on close
41 private Map<String,byte[]> normsCache = new HashMap<String,byte[]>();
42 private int maxDoc = 0;
43 private int numDocs = -1;
44 private boolean hasDeletions = false;
47 * <p>Construct a MultiReader aggregating the named set of (sub)readers.
48 * Directory locking for delete, undeleteAll, and setNorm operations is
49 * left to the subreaders. </p>
50 * <p>Note that all subreaders are closed if this Multireader is closed.</p>
51 * @param subReaders set of (sub)readers
53 public MultiReader(IndexReader... subReaders) {
54 initialize(subReaders, true);
58 * <p>Construct a MultiReader aggregating the named set of (sub)readers.
59 * Directory locking for delete, undeleteAll, and setNorm operations is
60 * left to the subreaders. </p>
61 * @param closeSubReaders indicates whether the subreaders should be closed
62 * when this MultiReader is closed
63 * @param subReaders set of (sub)readers
65 public MultiReader(IndexReader[] subReaders, boolean closeSubReaders) {
66 initialize(subReaders, closeSubReaders);
69 private void initialize(IndexReader[] subReaders, boolean closeSubReaders) {
70 this.subReaders = subReaders.clone();
71 starts = new int[subReaders.length + 1]; // build starts array
72 decrefOnClose = new boolean[subReaders.length];
73 for (int i = 0; i < subReaders.length; i++) {
75 maxDoc += subReaders[i].maxDoc(); // compute maxDocs
77 if (!closeSubReaders) {
78 subReaders[i].incRef();
79 decrefOnClose[i] = true;
81 decrefOnClose[i] = false;
84 if (subReaders[i].hasDeletions())
87 starts[subReaders.length] = maxDoc;
88 readerFinishedListeners = new MapBackedSet<ReaderFinishedListener>(new ConcurrentHashMap<ReaderFinishedListener,Boolean>());
92 * Tries to reopen the subreaders.
94 * If one or more subreaders could be re-opened (i. e. subReader.reopen()
95 * returned a new instance != subReader), then a new MultiReader instance
96 * is returned, otherwise this instance is returned.
98 * A re-opened instance might share one or more subreaders with the old
99 * instance. Index modification operations result in undefined behavior
100 * when performed before the old instance is closed.
101 * (see {@link IndexReader#reopen()}).
103 * If subreaders are shared, then the reference count of those
104 * readers is increased to ensure that the subreaders remain open
105 * until the last referring reader is closed.
107 * @throws CorruptIndexException if the index is corrupt
108 * @throws IOException if there is a low-level IO error
111 public synchronized IndexReader reopen() throws CorruptIndexException, IOException {
112 return doReopen(false);
116 * Clones the subreaders.
117 * (see {@link IndexReader#clone()}).
120 * If subreaders are shared, then the reference count of those
121 * readers is increased to ensure that the subreaders remain open
122 * until the last referring reader is closed.
125 public synchronized Object clone() {
127 return doReopen(true);
128 } catch (Exception ex) {
129 throw new RuntimeException(ex);
134 * If clone is true then we clone each of the subreaders
136 * @return New IndexReader, or same one (this) if
137 * reopen/clone is not necessary
138 * @throws CorruptIndexException
139 * @throws IOException
141 protected IndexReader doReopen(boolean doClone) throws CorruptIndexException, IOException {
144 boolean reopened = false;
145 IndexReader[] newSubReaders = new IndexReader[subReaders.length];
147 boolean success = false;
149 for (int i = 0; i < subReaders.length; i++) {
151 newSubReaders[i] = (IndexReader) subReaders[i].clone();
153 newSubReaders[i] = subReaders[i].reopen();
154 // if at least one of the subreaders was updated we remember that
155 // and return a new MultiReader
156 if (newSubReaders[i] != subReaders[i]) {
162 if (!success && reopened) {
163 for (int i = 0; i < newSubReaders.length; i++) {
164 if (newSubReaders[i] != subReaders[i]) {
166 newSubReaders[i].close();
167 } catch (IOException ignore) {
168 // keep going - we want to clean up as much as possible
176 boolean[] newDecrefOnClose = new boolean[subReaders.length];
177 for (int i = 0; i < subReaders.length; i++) {
178 if (newSubReaders[i] == subReaders[i]) {
179 newSubReaders[i].incRef();
180 newDecrefOnClose[i] = true;
183 MultiReader mr = new MultiReader(newSubReaders);
184 mr.decrefOnClose = newDecrefOnClose;
192 public TermFreqVector[] getTermFreqVectors(int n) throws IOException {
194 int i = readerIndex(n); // find segment num
195 return subReaders[i].getTermFreqVectors(n - starts[i]); // dispatch to segment
199 public TermFreqVector getTermFreqVector(int n, String field)
202 int i = readerIndex(n); // find segment num
203 return subReaders[i].getTermFreqVector(n - starts[i], field);
208 public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
210 int i = readerIndex(docNumber); // find segment num
211 subReaders[i].getTermFreqVector(docNumber - starts[i], field, mapper);
215 public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
217 int i = readerIndex(docNumber); // find segment num
218 subReaders[i].getTermFreqVector(docNumber - starts[i], mapper);
222 public boolean isOptimized() {
227 public int numDocs() {
228 // Don't call ensureOpen() here (it could affect performance)
229 // NOTE: multiple threads may wind up init'ing
230 // numDocs... but that's harmless
231 if (numDocs == -1) { // check cache
232 int n = 0; // cache miss--recompute
233 for (int i = 0; i < subReaders.length; i++)
234 n += subReaders[i].numDocs(); // sum from readers
241 public int maxDoc() {
242 // Don't call ensureOpen() here (it could affect performance)
248 public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
250 int i = readerIndex(n); // find segment num
251 return subReaders[i].document(n - starts[i], fieldSelector); // dispatch to segment reader
255 public boolean isDeleted(int n) {
256 // Don't call ensureOpen() here (it could affect performance)
257 int i = readerIndex(n); // find segment num
258 return subReaders[i].isDeleted(n - starts[i]); // dispatch to segment reader
262 public boolean hasDeletions() {
263 // Don't call ensureOpen() here (it could affect performance)
268 protected void doDelete(int n) throws CorruptIndexException, IOException {
269 numDocs = -1; // invalidate cache
270 int i = readerIndex(n); // find segment num
271 subReaders[i].deleteDocument(n - starts[i]); // dispatch to segment reader
276 protected void doUndeleteAll() throws CorruptIndexException, IOException {
277 for (int i = 0; i < subReaders.length; i++)
278 subReaders[i].undeleteAll();
280 hasDeletions = false;
281 numDocs = -1; // invalidate cache
284 private int readerIndex(int n) { // find reader for doc n:
285 return DirectoryReader.readerIndex(n, this.starts, this.subReaders.length);
289 public boolean hasNorms(String field) throws IOException {
291 for (int i = 0; i < subReaders.length; i++) {
292 if (subReaders[i].hasNorms(field)) return true;
298 public synchronized byte[] norms(String field) throws IOException {
300 byte[] bytes = normsCache.get(field);
302 return bytes; // cache hit
303 if (!hasNorms(field))
306 bytes = new byte[maxDoc()];
307 for (int i = 0; i < subReaders.length; i++)
308 subReaders[i].norms(field, bytes, starts[i]);
309 normsCache.put(field, bytes); // update cache
314 public synchronized void norms(String field, byte[] result, int offset)
317 byte[] bytes = normsCache.get(field);
318 for (int i = 0; i < subReaders.length; i++) // read from segments
319 subReaders[i].norms(field, result, offset + starts[i]);
321 if (bytes==null && !hasNorms(field)) {
322 Arrays.fill(result, offset, result.length, Similarity.getDefault().encodeNormValue(1.0f));
323 } else if (bytes != null) { // cache hit
324 System.arraycopy(bytes, 0, result, offset, maxDoc());
326 for (int i = 0; i < subReaders.length; i++) { // read from segments
327 subReaders[i].norms(field, result, offset + starts[i]);
333 protected void doSetNorm(int n, String field, byte value)
334 throws CorruptIndexException, IOException {
335 synchronized (normsCache) {
336 normsCache.remove(field); // clear cache
338 int i = readerIndex(n); // find segment num
339 subReaders[i].setNorm(n-starts[i], field, value); // dispatch
343 public TermEnum terms() throws IOException {
345 if (subReaders.length == 1) {
346 // Optimize single segment case:
347 return subReaders[0].terms();
349 return new MultiTermEnum(this, subReaders, starts, null);
354 public TermEnum terms(Term term) throws IOException {
356 if (subReaders.length == 1) {
357 // Optimize single segment case:
358 return subReaders[0].terms(term);
360 return new MultiTermEnum(this, subReaders, starts, term);
365 public int docFreq(Term t) throws IOException {
367 int total = 0; // sum freqs in segments
368 for (int i = 0; i < subReaders.length; i++)
369 total += subReaders[i].docFreq(t);
374 public TermDocs termDocs() throws IOException {
376 if (subReaders.length == 1) {
377 // Optimize single segment case:
378 return subReaders[0].termDocs();
380 return new MultiTermDocs(this, subReaders, starts);
385 public TermDocs termDocs(Term term) throws IOException {
387 if (subReaders.length == 1) {
388 // Optimize single segment case:
389 return subReaders[0].termDocs(term);
391 return super.termDocs(term);
396 public TermPositions termPositions() throws IOException {
398 if (subReaders.length == 1) {
399 // Optimize single segment case:
400 return subReaders[0].termPositions();
402 return new MultiTermPositions(this, subReaders, starts);
407 protected void doCommit(Map<String,String> commitUserData) throws IOException {
408 for (int i = 0; i < subReaders.length; i++)
409 subReaders[i].commit(commitUserData);
413 protected synchronized void doClose() throws IOException {
414 for (int i = 0; i < subReaders.length; i++) {
415 if (decrefOnClose[i]) {
416 subReaders[i].decRef();
418 subReaders[i].close();
424 public Collection<String> getFieldNames (IndexReader.FieldOption fieldNames) {
426 return DirectoryReader.getFieldNames(fieldNames, this.subReaders);
430 * Checks recursively if all subreaders are up to date.
433 public boolean isCurrent() throws CorruptIndexException, IOException {
434 for (int i = 0; i < subReaders.length; i++) {
435 if (!subReaders[i].isCurrent()) {
440 // all subreaders are up to date
445 * @throws UnsupportedOperationException
448 public long getVersion() {
449 throw new UnsupportedOperationException("MultiReader does not support this method.");
453 public IndexReader[] getSequentialSubReaders() {
458 public void addReaderFinishedListener(ReaderFinishedListener listener) {
459 super.addReaderFinishedListener(listener);
460 for(IndexReader sub : subReaders) {
461 sub.addReaderFinishedListener(listener);
466 public void removeReaderFinishedListener(ReaderFinishedListener listener) {
467 super.removeReaderFinishedListener(listener);
468 for(IndexReader sub : subReaders) {
469 sub.removeReaderFinishedListener(listener);