pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / index / MultiReader.java
1 package org.apache.lucene.index;
2
3 /**
4  * Licensed to the Apache Software Foundation (ASF) under one or more
5  * contributor license agreements.  See the NOTICE file distributed with
6  * this work for additional information regarding copyright ownership.
7  * The ASF licenses this file to You under the Apache License, Version 2.0
8  * (the "License"); you may not use this file except in compliance with
9  * the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 import java.io.IOException;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.concurrent.ConcurrentHashMap;
26
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;
34
35 /** An IndexReader which reads multiple indexes, appending
36  * their content. */
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;
45   
46  /**
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
52   */
53   public MultiReader(IndexReader... subReaders) {
54     initialize(subReaders, true);
55   }
56
57   /**
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
64    */
65   public MultiReader(IndexReader[] subReaders, boolean closeSubReaders) {
66     initialize(subReaders, closeSubReaders);
67   }
68   
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++) {
74       starts[i] = maxDoc;
75       maxDoc += subReaders[i].maxDoc();      // compute maxDocs
76
77       if (!closeSubReaders) {
78         subReaders[i].incRef();
79         decrefOnClose[i] = true;
80       } else {
81         decrefOnClose[i] = false;
82       }
83       
84       if (subReaders[i].hasDeletions())
85         hasDeletions = true;
86     }
87     starts[subReaders.length] = maxDoc;
88     readerFinishedListeners = new MapBackedSet<ReaderFinishedListener>(new ConcurrentHashMap<ReaderFinishedListener,Boolean>());
89   }
90   
91   /**
92    * Tries to reopen the subreaders.
93    * <br>
94    * If one or more subreaders could be re-opened (i. e. IndexReader.openIfChanged(subReader) 
95    * returned a new instance), then a new MultiReader instance 
96    * is returned, otherwise this instance is returned.
97    * <p>
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#openIfChanged}).
102    * <p>
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.
106    * 
107    * @throws CorruptIndexException if the index is corrupt
108    * @throws IOException if there is a low-level IO error 
109    */
110   @Override
111   protected synchronized IndexReader doOpenIfChanged() throws CorruptIndexException, IOException {
112     return doOpenIfChanged(false);
113   }
114   
115   /**
116    * Clones the subreaders.
117    * (see {@link IndexReader#clone()}).
118    * <br>
119    * <p>
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.
123    */
124   @Override
125   public synchronized Object clone() {
126     try {
127       return doOpenIfChanged(true);
128     } catch (Exception ex) {
129       throw new RuntimeException(ex);
130     }
131   }
132   
133   /**
134    * If clone is true then we clone each of the subreaders
135    * @param doClone
136    * @return New IndexReader, or null if open/clone is not necessary
137    * @throws CorruptIndexException
138    * @throws IOException
139    */
140   protected IndexReader doOpenIfChanged(boolean doClone) throws CorruptIndexException, IOException {
141     ensureOpen();
142     
143     boolean changed = false;
144     IndexReader[] newSubReaders = new IndexReader[subReaders.length];
145     
146     boolean success = false;
147     try {
148       for (int i = 0; i < subReaders.length; i++) {
149         if (doClone) {
150           newSubReaders[i] = (IndexReader) subReaders[i].clone();
151           changed = true;
152         } else {
153           final IndexReader newSubReader = IndexReader.openIfChanged(subReaders[i]);
154           if (newSubReader != null) {
155             newSubReaders[i] = newSubReader;
156             changed = true;
157           } else {
158             newSubReaders[i] = subReaders[i];
159           }
160         }
161       }
162       success = true;
163     } finally {
164       if (!success && changed) {
165         for (int i = 0; i < newSubReaders.length; i++) {
166           if (newSubReaders[i] != subReaders[i]) {
167             try {
168               newSubReaders[i].close();
169             } catch (IOException ignore) {
170               // keep going - we want to clean up as much as possible
171             }
172           }
173         }
174       }
175     }
176
177     if (changed) {
178       boolean[] newDecrefOnClose = new boolean[subReaders.length];
179       for (int i = 0; i < subReaders.length; i++) {
180         if (newSubReaders[i] == subReaders[i]) {
181           newSubReaders[i].incRef();
182           newDecrefOnClose[i] = true;
183         }
184       }
185       MultiReader mr = new MultiReader(newSubReaders);
186       mr.decrefOnClose = newDecrefOnClose;
187       return mr;
188     } else {
189       return null;
190     }
191   }
192
193   @Override
194   public TermFreqVector[] getTermFreqVectors(int n) throws IOException {
195     ensureOpen();
196     int i = readerIndex(n);        // find segment num
197     return subReaders[i].getTermFreqVectors(n - starts[i]); // dispatch to segment
198   }
199
200   @Override
201   public TermFreqVector getTermFreqVector(int n, String field)
202       throws IOException {
203     ensureOpen();
204     int i = readerIndex(n);        // find segment num
205     return subReaders[i].getTermFreqVector(n - starts[i], field);
206   }
207
208
209   @Override
210   public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
211     ensureOpen();
212     int i = readerIndex(docNumber);        // find segment num
213     subReaders[i].getTermFreqVector(docNumber - starts[i], field, mapper);
214   }
215
216   @Override
217   public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
218     ensureOpen();
219     int i = readerIndex(docNumber);        // find segment num
220     subReaders[i].getTermFreqVector(docNumber - starts[i], mapper);
221   }
222
223   @Deprecated
224   @Override
225   public boolean isOptimized() {
226     ensureOpen();
227     return false;
228   }
229
230   @Override
231   public int numDocs() {
232     // Don't call ensureOpen() here (it could affect performance)
233     // NOTE: multiple threads may wind up init'ing
234     // numDocs... but that's harmless
235     if (numDocs == -1) {        // check cache
236       int n = 0;                // cache miss--recompute
237       for (int i = 0; i < subReaders.length; i++)
238         n += subReaders[i].numDocs();      // sum from readers
239       numDocs = n;
240     }
241     return numDocs;
242   }
243
244   @Override
245   public int maxDoc() {
246     // Don't call ensureOpen() here (it could affect performance)
247     return maxDoc;
248   }
249
250   // inherit javadoc
251   @Override
252   public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
253     ensureOpen();
254     int i = readerIndex(n);                          // find segment num
255     return subReaders[i].document(n - starts[i], fieldSelector);    // dispatch to segment reader
256   }
257
258   @Override
259   public boolean isDeleted(int n) {
260     // Don't call ensureOpen() here (it could affect performance)
261     int i = readerIndex(n);                           // find segment num
262     return subReaders[i].isDeleted(n - starts[i]);    // dispatch to segment reader
263   }
264
265   @Override
266   public boolean hasDeletions() {
267     ensureOpen();
268     return hasDeletions;
269   }
270
271   @Override
272   protected void doDelete(int n) throws CorruptIndexException, IOException {
273     numDocs = -1;                             // invalidate cache
274     int i = readerIndex(n);                   // find segment num
275     subReaders[i].deleteDocument(n - starts[i]);      // dispatch to segment reader
276     hasDeletions = true;
277   }
278
279   @Override
280   protected void doUndeleteAll() throws CorruptIndexException, IOException {
281     for (int i = 0; i < subReaders.length; i++)
282       subReaders[i].undeleteAll();
283
284     hasDeletions = false;
285     numDocs = -1;                                 // invalidate cache
286   }
287
288   private int readerIndex(int n) {    // find reader for doc n:
289     return DirectoryReader.readerIndex(n, this.starts, this.subReaders.length);
290   }
291   
292   @Override
293   public boolean hasNorms(String field) throws IOException {
294     ensureOpen();
295     for (int i = 0; i < subReaders.length; i++) {
296       if (subReaders[i].hasNorms(field)) return true;
297     }
298     return false;
299   }
300   
301   @Override
302   public synchronized byte[] norms(String field) throws IOException {
303     ensureOpen();
304     byte[] bytes = normsCache.get(field);
305     if (bytes != null)
306       return bytes;          // cache hit
307     if (!hasNorms(field))
308       return null;
309
310     bytes = new byte[maxDoc()];
311     for (int i = 0; i < subReaders.length; i++)
312       subReaders[i].norms(field, bytes, starts[i]);
313     normsCache.put(field, bytes);      // update cache
314     return bytes;
315   }
316
317   @Override
318   public synchronized void norms(String field, byte[] result, int offset)
319     throws IOException {
320     ensureOpen();
321     byte[] bytes = normsCache.get(field);
322     for (int i = 0; i < subReaders.length; i++)      // read from segments
323       subReaders[i].norms(field, result, offset + starts[i]);
324
325     if (bytes==null && !hasNorms(field)) {
326       Arrays.fill(result, offset, result.length, Similarity.getDefault().encodeNormValue(1.0f));
327     } else if (bytes != null) {                         // cache hit
328       System.arraycopy(bytes, 0, result, offset, maxDoc());
329     } else {
330       for (int i = 0; i < subReaders.length; i++) {     // read from segments
331         subReaders[i].norms(field, result, offset + starts[i]);
332       }
333     }
334   }
335
336   @Override
337   protected void doSetNorm(int n, String field, byte value)
338     throws CorruptIndexException, IOException {
339     synchronized (normsCache) {
340       normsCache.remove(field);                         // clear cache
341     }
342     int i = readerIndex(n);                           // find segment num
343     subReaders[i].setNorm(n-starts[i], field, value); // dispatch
344   }
345
346   @Override
347   public TermEnum terms() throws IOException {
348     ensureOpen();
349     if (subReaders.length == 1) {
350       // Optimize single segment case:
351       return subReaders[0].terms();
352     } else {
353       return new MultiTermEnum(this, subReaders, starts, null);
354     }
355   }
356
357   @Override
358   public TermEnum terms(Term term) throws IOException {
359     ensureOpen();
360     if (subReaders.length == 1) {
361       // Optimize single segment case:
362       return subReaders[0].terms(term);
363     } else {
364       return new MultiTermEnum(this, subReaders, starts, term);
365     }
366   }
367
368   @Override
369   public int docFreq(Term t) throws IOException {
370     ensureOpen();
371     int total = 0;          // sum freqs in segments
372     for (int i = 0; i < subReaders.length; i++)
373       total += subReaders[i].docFreq(t);
374     return total;
375   }
376
377   @Override
378   public TermDocs termDocs() throws IOException {
379     ensureOpen();
380     if (subReaders.length == 1) {
381       // Optimize single segment case:
382       return subReaders[0].termDocs();
383     } else {
384       return new MultiTermDocs(this, subReaders, starts);
385     }
386   }
387
388   @Override
389   public TermDocs termDocs(Term term) throws IOException {
390     ensureOpen();
391     if (subReaders.length == 1) {
392       // Optimize single segment case:
393       return subReaders[0].termDocs(term);
394     } else {
395       return super.termDocs(term);
396     }
397   }
398
399   @Override
400   public TermPositions termPositions() throws IOException {
401     ensureOpen();
402     if (subReaders.length == 1) {
403       // Optimize single segment case:
404       return subReaders[0].termPositions();
405     } else {
406       return new MultiTermPositions(this, subReaders, starts);
407     }
408   }
409
410   @Override
411   protected void doCommit(Map<String,String> commitUserData) throws IOException {
412     for (int i = 0; i < subReaders.length; i++)
413       subReaders[i].commit(commitUserData);
414   }
415
416   @Override
417   protected synchronized void doClose() throws IOException {
418     for (int i = 0; i < subReaders.length; i++) {
419       if (decrefOnClose[i]) {
420         subReaders[i].decRef();
421       } else {
422         subReaders[i].close();
423       }
424     }
425   }
426   
427   @Override
428   public Collection<String> getFieldNames (IndexReader.FieldOption fieldNames) {
429     ensureOpen();
430     return DirectoryReader.getFieldNames(fieldNames, this.subReaders);
431   }  
432   
433   /**
434    * Checks recursively if all subreaders are up to date. 
435    */
436   @Override
437   public boolean isCurrent() throws CorruptIndexException, IOException {
438     ensureOpen();
439     for (int i = 0; i < subReaders.length; i++) {
440       if (!subReaders[i].isCurrent()) {
441         return false;
442       }
443     }
444     
445     // all subreaders are up to date
446     return true;
447   }
448   
449   /** Not implemented.
450    * @throws UnsupportedOperationException
451    */
452   @Override
453   public long getVersion() {
454     throw new UnsupportedOperationException("MultiReader does not support this method.");
455   }
456   
457   @Override
458   public IndexReader[] getSequentialSubReaders() {
459     return subReaders;
460   }
461
462   @Override
463   public void addReaderFinishedListener(ReaderFinishedListener listener) {
464     super.addReaderFinishedListener(listener);
465     for(IndexReader sub : subReaders) {
466       sub.addReaderFinishedListener(listener);
467     }
468   }
469
470   @Override
471   public void removeReaderFinishedListener(ReaderFinishedListener listener) {
472     super.removeReaderFinishedListener(listener);
473     for(IndexReader sub : subReaders) {
474       sub.removeReaderFinishedListener(listener);
475     }
476   }
477 }