add --shared
[pylucene.git] / lucene-java-3.4.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. subReader.reopen() 
95    * returned a new instance != subReader), 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#reopen()}).
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   public synchronized IndexReader reopen() throws CorruptIndexException, IOException {
112     return doReopen(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 doReopen(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 same one (this) if
137    *   reopen/clone is not necessary
138    * @throws CorruptIndexException
139    * @throws IOException
140    */
141   protected IndexReader doReopen(boolean doClone) throws CorruptIndexException, IOException {
142     ensureOpen();
143     
144     boolean reopened = false;
145     IndexReader[] newSubReaders = new IndexReader[subReaders.length];
146     
147     boolean success = false;
148     try {
149       for (int i = 0; i < subReaders.length; i++) {
150         if (doClone)
151           newSubReaders[i] = (IndexReader) subReaders[i].clone();
152         else
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]) {
157           reopened = true;
158         }
159       }
160       success = true;
161     } finally {
162       if (!success && reopened) {
163         for (int i = 0; i < newSubReaders.length; i++) {
164           if (newSubReaders[i] != subReaders[i]) {
165             try {
166               newSubReaders[i].close();
167             } catch (IOException ignore) {
168               // keep going - we want to clean up as much as possible
169             }
170           }
171         }
172       }
173     }
174
175     if (reopened) {
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;
181         }
182       }
183       MultiReader mr = new MultiReader(newSubReaders);
184       mr.decrefOnClose = newDecrefOnClose;
185       return mr;
186     } else {
187       return this;
188     }
189   }
190
191   @Override
192   public TermFreqVector[] getTermFreqVectors(int n) throws IOException {
193     ensureOpen();
194     int i = readerIndex(n);        // find segment num
195     return subReaders[i].getTermFreqVectors(n - starts[i]); // dispatch to segment
196   }
197
198   @Override
199   public TermFreqVector getTermFreqVector(int n, String field)
200       throws IOException {
201     ensureOpen();
202     int i = readerIndex(n);        // find segment num
203     return subReaders[i].getTermFreqVector(n - starts[i], field);
204   }
205
206
207   @Override
208   public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
209     ensureOpen();
210     int i = readerIndex(docNumber);        // find segment num
211     subReaders[i].getTermFreqVector(docNumber - starts[i], field, mapper);
212   }
213
214   @Override
215   public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
216     ensureOpen();
217     int i = readerIndex(docNumber);        // find segment num
218     subReaders[i].getTermFreqVector(docNumber - starts[i], mapper);
219   }
220
221   @Override
222   public boolean isOptimized() {
223     return false;
224   }
225   
226   @Override
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
235       numDocs = n;
236     }
237     return numDocs;
238   }
239
240   @Override
241   public int maxDoc() {
242     // Don't call ensureOpen() here (it could affect performance)
243     return maxDoc;
244   }
245
246   // inherit javadoc
247   @Override
248   public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
249     ensureOpen();
250     int i = readerIndex(n);                          // find segment num
251     return subReaders[i].document(n - starts[i], fieldSelector);    // dispatch to segment reader
252   }
253
254   @Override
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
259   }
260
261   @Override
262   public boolean hasDeletions() {
263     // Don't call ensureOpen() here (it could affect performance)
264     return hasDeletions;
265   }
266
267   @Override
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
272     hasDeletions = true;
273   }
274
275   @Override
276   protected void doUndeleteAll() throws CorruptIndexException, IOException {
277     for (int i = 0; i < subReaders.length; i++)
278       subReaders[i].undeleteAll();
279
280     hasDeletions = false;
281     numDocs = -1;                                 // invalidate cache
282   }
283
284   private int readerIndex(int n) {    // find reader for doc n:
285     return DirectoryReader.readerIndex(n, this.starts, this.subReaders.length);
286   }
287   
288   @Override
289   public boolean hasNorms(String field) throws IOException {
290     ensureOpen();
291     for (int i = 0; i < subReaders.length; i++) {
292       if (subReaders[i].hasNorms(field)) return true;
293     }
294     return false;
295   }
296   
297   @Override
298   public synchronized byte[] norms(String field) throws IOException {
299     ensureOpen();
300     byte[] bytes = normsCache.get(field);
301     if (bytes != null)
302       return bytes;          // cache hit
303     if (!hasNorms(field))
304       return null;
305
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
310     return bytes;
311   }
312
313   @Override
314   public synchronized void norms(String field, byte[] result, int offset)
315     throws IOException {
316     ensureOpen();
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]);
320
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());
325     } else {
326       for (int i = 0; i < subReaders.length; i++) {     // read from segments
327         subReaders[i].norms(field, result, offset + starts[i]);
328       }
329     }
330   }
331
332   @Override
333   protected void doSetNorm(int n, String field, byte value)
334     throws CorruptIndexException, IOException {
335     synchronized (normsCache) {
336       normsCache.remove(field);                         // clear cache
337     }
338     int i = readerIndex(n);                           // find segment num
339     subReaders[i].setNorm(n-starts[i], field, value); // dispatch
340   }
341
342   @Override
343   public TermEnum terms() throws IOException {
344     ensureOpen();
345     if (subReaders.length == 1) {
346       // Optimize single segment case:
347       return subReaders[0].terms();
348     } else {
349       return new MultiTermEnum(this, subReaders, starts, null);
350     }
351   }
352
353   @Override
354   public TermEnum terms(Term term) throws IOException {
355     ensureOpen();
356     if (subReaders.length == 1) {
357       // Optimize single segment case:
358       return subReaders[0].terms(term);
359     } else {
360       return new MultiTermEnum(this, subReaders, starts, term);
361     }
362   }
363
364   @Override
365   public int docFreq(Term t) throws IOException {
366     ensureOpen();
367     int total = 0;          // sum freqs in segments
368     for (int i = 0; i < subReaders.length; i++)
369       total += subReaders[i].docFreq(t);
370     return total;
371   }
372
373   @Override
374   public TermDocs termDocs() throws IOException {
375     ensureOpen();
376     if (subReaders.length == 1) {
377       // Optimize single segment case:
378       return subReaders[0].termDocs();
379     } else {
380       return new MultiTermDocs(this, subReaders, starts);
381     }
382   }
383
384   @Override
385   public TermDocs termDocs(Term term) throws IOException {
386     ensureOpen();
387     if (subReaders.length == 1) {
388       // Optimize single segment case:
389       return subReaders[0].termDocs(term);
390     } else {
391       return super.termDocs(term);
392     }
393   }
394
395   @Override
396   public TermPositions termPositions() throws IOException {
397     ensureOpen();
398     if (subReaders.length == 1) {
399       // Optimize single segment case:
400       return subReaders[0].termPositions();
401     } else {
402       return new MultiTermPositions(this, subReaders, starts);
403     }
404   }
405
406   @Override
407   protected void doCommit(Map<String,String> commitUserData) throws IOException {
408     for (int i = 0; i < subReaders.length; i++)
409       subReaders[i].commit(commitUserData);
410   }
411
412   @Override
413   protected synchronized void doClose() throws IOException {
414     for (int i = 0; i < subReaders.length; i++) {
415       if (decrefOnClose[i]) {
416         subReaders[i].decRef();
417       } else {
418         subReaders[i].close();
419       }
420     }
421   }
422   
423   @Override
424   public Collection<String> getFieldNames (IndexReader.FieldOption fieldNames) {
425     ensureOpen();
426     return DirectoryReader.getFieldNames(fieldNames, this.subReaders);
427   }  
428   
429   /**
430    * Checks recursively if all subreaders are up to date. 
431    */
432   @Override
433   public boolean isCurrent() throws CorruptIndexException, IOException {
434     for (int i = 0; i < subReaders.length; i++) {
435       if (!subReaders[i].isCurrent()) {
436         return false;
437       }
438     }
439     
440     // all subreaders are up to date
441     return true;
442   }
443   
444   /** Not implemented.
445    * @throws UnsupportedOperationException
446    */
447   @Override
448   public long getVersion() {
449     throw new UnsupportedOperationException("MultiReader does not support this method.");
450   }
451   
452   @Override
453   public IndexReader[] getSequentialSubReaders() {
454     return subReaders;
455   }
456
457   @Override
458   public void addReaderFinishedListener(ReaderFinishedListener listener) {
459     super.addReaderFinishedListener(listener);
460     for(IndexReader sub : subReaders) {
461       sub.addReaderFinishedListener(listener);
462     }
463   }
464
465   @Override
466   public void removeReaderFinishedListener(ReaderFinishedListener listener) {
467     super.removeReaderFinishedListener(listener);
468     for(IndexReader sub : subReaders) {
469       sub.removeReaderFinishedListener(listener);
470     }
471   }
472 }