pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / search / FieldCacheImpl.java
1 package org.apache.lucene.search;
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.io.PrintStream;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.WeakHashMap;
27
28 import org.apache.lucene.index.IndexReader;
29 import org.apache.lucene.index.Term;
30 import org.apache.lucene.index.TermDocs;
31 import org.apache.lucene.index.TermEnum;
32 import org.apache.lucene.util.Bits;
33 import org.apache.lucene.util.FixedBitSet;
34 import org.apache.lucene.util.StringHelper;
35 import org.apache.lucene.util.FieldCacheSanityChecker;
36
37 /**
38  * Expert: The default cache implementation, storing all values in memory.
39  * A WeakHashMap is used for storage.
40  *
41  * <p>Created: May 19, 2004 4:40:36 PM
42  *
43  * @since   lucene 1.4
44  */
45 class FieldCacheImpl implements FieldCache {
46         
47   private Map<Class<?>,Cache> caches;
48   FieldCacheImpl() {
49     init();
50   }
51   private synchronized void init() {
52     caches = new HashMap<Class<?>,Cache>(9);
53     caches.put(Byte.TYPE, new ByteCache(this));
54     caches.put(Short.TYPE, new ShortCache(this));
55     caches.put(Integer.TYPE, new IntCache(this));
56     caches.put(Float.TYPE, new FloatCache(this));
57     caches.put(Long.TYPE, new LongCache(this));
58     caches.put(Double.TYPE, new DoubleCache(this));
59     caches.put(String.class, new StringCache(this));
60     caches.put(StringIndex.class, new StringIndexCache(this));
61     caches.put(DocsWithFieldCache.class, new DocsWithFieldCache(this));
62   }
63
64   public synchronized void purgeAllCaches() {
65     init();
66   }
67
68   public synchronized void purge(IndexReader r) {
69     for(Cache c : caches.values()) {
70       c.purge(r);
71     }
72   }
73   
74   public synchronized CacheEntry[] getCacheEntries() {
75     List<CacheEntry> result = new ArrayList<CacheEntry>(17);
76     for(final Map.Entry<Class<?>,Cache> cacheEntry: caches.entrySet()) {
77       final Cache cache = cacheEntry.getValue();
78       final Class<?> cacheType = cacheEntry.getKey();
79       synchronized(cache.readerCache) {
80         for (final Map.Entry<Object,Map<Entry, Object>> readerCacheEntry : cache.readerCache.entrySet()) {
81           final Object readerKey = readerCacheEntry.getKey();
82           if (readerKey == null) continue;
83           final Map<Entry, Object> innerCache = readerCacheEntry.getValue();
84           for (final Map.Entry<Entry, Object> mapEntry : innerCache.entrySet()) {
85             Entry entry = mapEntry.getKey();
86             result.add(new CacheEntryImpl(readerKey, entry.field,
87                                           cacheType, entry.custom,
88                                           mapEntry.getValue()));
89           }
90         }
91       }
92     }
93     return result.toArray(new CacheEntry[result.size()]);
94   }
95   
96   private static final class CacheEntryImpl extends CacheEntry {
97     private final Object readerKey;
98     private final String fieldName;
99     private final Class<?> cacheType;
100     private final Object custom;
101     private final Object value;
102     CacheEntryImpl(Object readerKey, String fieldName,
103                    Class<?> cacheType,
104                    Object custom,
105                    Object value) {
106         this.readerKey = readerKey;
107         this.fieldName = fieldName;
108         this.cacheType = cacheType;
109         this.custom = custom;
110         this.value = value;
111
112         // :HACK: for testing.
113 //         if (null != locale || SortField.CUSTOM != sortFieldType) {
114 //           throw new RuntimeException("Locale/sortFieldType: " + this);
115 //         }
116
117     }
118     @Override
119     public Object getReaderKey() { return readerKey; }
120     @Override
121     public String getFieldName() { return fieldName; }
122     @Override
123     public Class<?> getCacheType() { return cacheType; }
124     @Override
125     public Object getCustom() { return custom; }
126     @Override
127     public Object getValue() { return value; }
128   }
129
130   /**
131    * Hack: When thrown from a Parser (NUMERIC_UTILS_* ones), this stops
132    * processing terms and returns the current FieldCache
133    * array.
134    */
135   static final class StopFillCacheException extends RuntimeException {
136   }
137
138   final static IndexReader.ReaderFinishedListener purgeReader = new IndexReader.ReaderFinishedListener() {
139     // @Override -- not until Java 1.6
140     public void finished(IndexReader reader) {
141       FieldCache.DEFAULT.purge(reader);
142     }
143   };
144
145   /** Expert: Internal cache. */
146   abstract static class Cache {
147     Cache() {
148       this.wrapper = null;
149     }
150
151     Cache(FieldCacheImpl wrapper) {
152       this.wrapper = wrapper;
153     }
154
155     final FieldCacheImpl wrapper;
156
157     final Map<Object,Map<Entry,Object>> readerCache = new WeakHashMap<Object,Map<Entry,Object>>();
158     
159     protected abstract Object createValue(IndexReader reader, Entry key, boolean setDocsWithField)
160         throws IOException;
161
162     /** Remove this reader from the cache, if present. */
163     public void purge(IndexReader r) {
164       Object readerKey = r.getCoreCacheKey();
165       synchronized(readerCache) {
166         readerCache.remove(readerKey);
167       }
168     }
169
170     /** Sets the key to the value for the provided reader;
171      *  if the key is already set then this doesn't change it. */
172     public void put(IndexReader reader, Entry key, Object value) {
173       final Object readerKey = reader.getCoreCacheKey();
174       synchronized (readerCache) {
175         Map<Entry,Object> innerCache = readerCache.get(readerKey);
176         if (innerCache == null) {
177           // First time this reader is using FieldCache
178           innerCache = new HashMap<Entry,Object>();
179           readerCache.put(readerKey, innerCache);
180           reader.addReaderFinishedListener(purgeReader);
181         }
182         if (innerCache.get(key) == null) {
183           innerCache.put(key, value);
184         } else {
185           // Another thread beat us to it; leave the current
186           // value
187         }
188       }
189     }
190
191     public Object get(IndexReader reader, Entry key, boolean setDocsWithField) throws IOException {
192       Map<Entry,Object> innerCache;
193       Object value;
194       final Object readerKey = reader.getCoreCacheKey();
195       synchronized (readerCache) {
196         innerCache = readerCache.get(readerKey);
197         if (innerCache == null) {
198           // First time this reader is using FieldCache
199           innerCache = new HashMap<Entry,Object>();
200           readerCache.put(readerKey, innerCache);
201           reader.addReaderFinishedListener(purgeReader);
202           value = null;
203         } else {
204           value = innerCache.get(key);
205         }
206         if (value == null) {
207           value = new CreationPlaceholder();
208           innerCache.put(key, value);
209         }
210       }
211       if (value instanceof CreationPlaceholder) {
212         synchronized (value) {
213           CreationPlaceholder progress = (CreationPlaceholder) value;
214           if (progress.value == null) {
215             progress.value = createValue(reader, key, setDocsWithField);
216             synchronized (readerCache) {
217               innerCache.put(key, progress.value);
218             }
219
220             // Only check if key.custom (the parser) is
221             // non-null; else, we check twice for a single
222             // call to FieldCache.getXXX
223             if (key.custom != null && wrapper != null) {
224               final PrintStream infoStream = wrapper.getInfoStream();
225               if (infoStream != null) {
226                 printNewInsanity(infoStream, progress.value);
227               }
228             }
229           }
230           return progress.value;
231         }
232       }
233       return value;
234     }
235
236     private void printNewInsanity(PrintStream infoStream, Object value) {
237       final FieldCacheSanityChecker.Insanity[] insanities = FieldCacheSanityChecker.checkSanity(wrapper);
238       for(int i=0;i<insanities.length;i++) {
239         final FieldCacheSanityChecker.Insanity insanity = insanities[i];
240         final CacheEntry[] entries = insanity.getCacheEntries();
241         for(int j=0;j<entries.length;j++) {
242           if (entries[j].getValue() == value) {
243             // OK this insanity involves our entry
244             infoStream.println("WARNING: new FieldCache insanity created\nDetails: " + insanity.toString());
245             infoStream.println("\nStack:\n");
246             new Throwable().printStackTrace(infoStream);
247             break;
248           }
249         }
250       }
251     }
252   }
253
254   /** Expert: Every composite-key in the internal cache is of this type. */
255   static class Entry {
256     final String field;        // which Fieldable
257     final Object custom;       // which custom comparator or parser
258
259     /** Creates one of these objects for a custom comparator/parser. */
260     Entry (String field, Object custom) {
261       this.field = StringHelper.intern(field);
262       this.custom = custom;
263     }
264
265     /** Two of these are equal iff they reference the same field and type. */
266     @Override
267     public boolean equals (Object o) {
268       if (o instanceof Entry) {
269         Entry other = (Entry) o;
270         if (other.field == field) {
271           if (other.custom == null) {
272             if (custom == null) return true;
273           } else if (other.custom.equals (custom)) {
274             return true;
275           }
276         }
277       }
278       return false;
279     }
280
281     /** Composes a hashcode based on the field and type. */
282     @Override
283     public int hashCode() {
284       return field.hashCode() ^ (custom==null ? 0 : custom.hashCode());
285     }
286   }
287
288   // inherit javadocs
289   public byte[] getBytes (IndexReader reader, String field) throws IOException {
290     return getBytes(reader, field, null, false);
291   }
292
293   // inherit javadocs
294   public byte[] getBytes(IndexReader reader, String field, ByteParser parser)
295       throws IOException {
296     return getBytes(reader, field, parser, false);
297   }
298
299   public byte[] getBytes(IndexReader reader, String field, ByteParser parser, boolean setDocsWithField)
300       throws IOException {
301     return (byte[]) caches.get(Byte.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
302   }
303
304   static final class ByteCache extends Cache {
305     ByteCache(FieldCacheImpl wrapper) {
306       super(wrapper);
307     }
308
309     @Override
310     protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
311         throws IOException {
312       Entry entry = entryKey;
313       String field = entry.field;
314       ByteParser parser = (ByteParser) entry.custom;
315       if (parser == null) {
316         return wrapper.getBytes(reader, field, FieldCache.DEFAULT_BYTE_PARSER, setDocsWithField);
317       }
318       final int maxDoc = reader.maxDoc();
319       final byte[] retArray = new byte[maxDoc];
320       TermDocs termDocs = reader.termDocs();
321       TermEnum termEnum = reader.terms (new Term (field));
322       FixedBitSet docsWithField = null;
323       try {
324         do {
325           Term term = termEnum.term();
326           if (term==null || term.field() != field) break;
327           byte termval = parser.parseByte(term.text());
328           termDocs.seek (termEnum);
329           while (termDocs.next()) {
330             final int docID = termDocs.doc();
331             retArray[docID] = termval;
332             if (setDocsWithField) {
333               if (docsWithField == null) {
334                 // Lazy init
335                 docsWithField = new FixedBitSet(maxDoc);
336               }
337               docsWithField.set(docID);
338             }
339           }
340         } while (termEnum.next());
341       } catch (StopFillCacheException stop) {
342       } finally {
343         termDocs.close();
344         termEnum.close();
345       }
346       if (setDocsWithField) {
347         wrapper.setDocsWithField(reader, field, docsWithField);
348       }
349       return retArray;
350     }
351   }
352   
353   // inherit javadocs
354   public short[] getShorts (IndexReader reader, String field) throws IOException {
355     return getShorts(reader, field, null, false);
356   }
357
358   // inherit javadocs
359   public short[] getShorts(IndexReader reader, String field, ShortParser parser)
360       throws IOException {
361     return getShorts(reader, field, parser, false);
362   }
363
364   // inherit javadocs
365   public short[] getShorts(IndexReader reader, String field, ShortParser parser, boolean setDocsWithField)
366       throws IOException {
367     return (short[]) caches.get(Short.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
368   }
369
370   static final class ShortCache extends Cache {
371     ShortCache(FieldCacheImpl wrapper) {
372       super(wrapper);
373     }
374
375     @Override
376     protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
377         throws IOException {
378       Entry entry =  entryKey;
379       String field = entry.field;
380       ShortParser parser = (ShortParser) entry.custom;
381       if (parser == null) {
382         return wrapper.getShorts(reader, field, FieldCache.DEFAULT_SHORT_PARSER, setDocsWithField);
383       }
384       final int maxDoc = reader.maxDoc();
385       final short[] retArray = new short[maxDoc];
386       TermDocs termDocs = reader.termDocs();
387       TermEnum termEnum = reader.terms (new Term (field));
388       FixedBitSet docsWithField = null;
389       try {
390         do {
391           Term term = termEnum.term();
392           if (term==null || term.field() != field) break;
393           short termval = parser.parseShort(term.text());
394           termDocs.seek (termEnum);
395           while (termDocs.next()) {
396             final int docID = termDocs.doc();
397             retArray[docID] = termval;
398             if (setDocsWithField) {
399               if (docsWithField == null) {
400                 // Lazy init
401                 docsWithField = new FixedBitSet(maxDoc);
402               }
403               docsWithField.set(docID);
404             }
405           }
406         } while (termEnum.next());
407       } catch (StopFillCacheException stop) {
408       } finally {
409         termDocs.close();
410         termEnum.close();
411       }
412       if (setDocsWithField) {
413         wrapper.setDocsWithField(reader, field, docsWithField);
414       }
415       return retArray;
416     }
417   }
418   
419   // null Bits means no docs matched
420   void setDocsWithField(IndexReader reader, String field, Bits docsWithField) {
421     final int maxDoc = reader.maxDoc();
422     final Bits bits;
423     if (docsWithField == null) {
424       bits = new Bits.MatchNoBits(maxDoc);
425     } else if (docsWithField instanceof FixedBitSet) {
426       final int numSet = ((FixedBitSet) docsWithField).cardinality();
427       if (numSet >= maxDoc) {
428         // The cardinality of the BitSet is maxDoc if all documents have a value.
429         assert numSet == maxDoc;
430         bits = new Bits.MatchAllBits(maxDoc);
431       } else {
432         bits = docsWithField;
433       }
434     } else {
435       bits = docsWithField;
436     }
437     caches.get(DocsWithFieldCache.class).put(reader, new Entry(field, null), bits);
438   }
439
440   // inherit javadocs
441   public int[] getInts (IndexReader reader, String field) throws IOException {
442     return getInts(reader, field, null);
443   }
444
445   // inherit javadocs
446   public int[] getInts(IndexReader reader, String field, IntParser parser)
447       throws IOException {
448     return getInts(reader, field, parser, false);
449   }
450
451   // inherit javadocs
452   public int[] getInts(IndexReader reader, String field, IntParser parser, boolean setDocsWithField)
453       throws IOException {
454     return (int[]) caches.get(Integer.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
455   }
456
457   static final class IntCache extends Cache {
458     IntCache(FieldCacheImpl wrapper) {
459       super(wrapper);
460     }
461
462     @Override
463     protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
464         throws IOException {
465       Entry entry = entryKey;
466       String field = entry.field;
467       IntParser parser = (IntParser) entry.custom;
468       if (parser == null) {
469         try {
470           return wrapper.getInts(reader, field, DEFAULT_INT_PARSER, setDocsWithField);
471         } catch (NumberFormatException ne) {
472           return wrapper.getInts(reader, field, NUMERIC_UTILS_INT_PARSER, setDocsWithField);
473         }
474       }
475       final int maxDoc = reader.maxDoc();
476       int[] retArray = null;
477       TermDocs termDocs = reader.termDocs();
478       TermEnum termEnum = reader.terms (new Term (field));
479       FixedBitSet docsWithField = null;
480       try {
481         do {
482           Term term = termEnum.term();
483           if (term==null || term.field() != field) break;
484           int termval = parser.parseInt(term.text());
485           if (retArray == null) { // late init
486             retArray = new int[maxDoc];
487           }
488           termDocs.seek (termEnum);
489           while (termDocs.next()) {
490             final int docID = termDocs.doc();
491             retArray[docID] = termval;
492             if (setDocsWithField) {
493               if (docsWithField == null) {
494                 // Lazy init
495                 docsWithField = new FixedBitSet(maxDoc);
496               }
497               docsWithField.set(docID);
498             }
499           }
500         } while (termEnum.next());
501       } catch (StopFillCacheException stop) {
502       } finally {
503         termDocs.close();
504         termEnum.close();
505       }
506       if (setDocsWithField) {
507         wrapper.setDocsWithField(reader, field, docsWithField);
508       }
509       if (retArray == null) { // no values
510         retArray = new int[maxDoc];
511       }
512       return retArray;
513     }
514   }
515   
516   public Bits getDocsWithField(IndexReader reader, String field)
517       throws IOException {
518     return (Bits) caches.get(DocsWithFieldCache.class).get(reader, new Entry(field, null), false);
519   }
520
521   static final class DocsWithFieldCache extends Cache {
522     DocsWithFieldCache(FieldCacheImpl wrapper) {
523       super(wrapper);
524     }
525     
526     @Override
527     protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField /* ignored */)
528     throws IOException {
529       final Entry entry = entryKey;
530       final String field = entry.field;      
531       FixedBitSet res = null;
532       final TermDocs termDocs = reader.termDocs();
533       final TermEnum termEnum = reader.terms(new Term(field));
534       try {
535         do {
536           final Term term = termEnum.term();
537           if (term == null || term.field() != field) break;
538           if (res == null) // late init
539             res = new FixedBitSet(reader.maxDoc());
540           termDocs.seek(termEnum);
541           while (termDocs.next()) {
542             res.set(termDocs.doc());
543           }
544         } while (termEnum.next());
545       } finally {
546         termDocs.close();
547         termEnum.close();
548       }
549       if (res == null)
550         return new Bits.MatchNoBits(reader.maxDoc());
551       final int numSet = res.cardinality();
552       if (numSet >= reader.numDocs()) {
553         // The cardinality of the BitSet is numDocs if all documents have a value.
554         // As deleted docs are not in TermDocs, this is always true
555         assert numSet == reader.numDocs();
556         return new Bits.MatchAllBits(reader.maxDoc());
557       }
558       return res;
559     }
560   }
561
562   // inherit javadocs
563   public float[] getFloats (IndexReader reader, String field)
564     throws IOException {
565     return getFloats(reader, field, null, false);
566   }
567
568   // inherit javadocs
569   public float[] getFloats(IndexReader reader, String field, FloatParser parser)
570     throws IOException {
571     return getFloats(reader, field, parser, false);
572   }
573
574   public float[] getFloats(IndexReader reader, String field, FloatParser parser, boolean setDocsWithField)
575     throws IOException {
576
577     return (float[]) caches.get(Float.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
578   }
579
580   static final class FloatCache extends Cache {
581     FloatCache(FieldCacheImpl wrapper) {
582       super(wrapper);
583     }
584
585     @Override
586     protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
587         throws IOException {
588       Entry entry = entryKey;
589       String field = entry.field;
590       FloatParser parser = (FloatParser) entry.custom;
591       if (parser == null) {
592         try {
593           return wrapper.getFloats(reader, field, DEFAULT_FLOAT_PARSER, setDocsWithField);
594         } catch (NumberFormatException ne) {
595           return wrapper.getFloats(reader, field, NUMERIC_UTILS_FLOAT_PARSER, setDocsWithField);
596         }
597       }
598       final int maxDoc = reader.maxDoc();
599       float[] retArray = null;
600       TermDocs termDocs = reader.termDocs();
601       TermEnum termEnum = reader.terms (new Term (field));
602       FixedBitSet docsWithField = null;
603       try {
604         do {
605           Term term = termEnum.term();
606           if (term==null || term.field() != field) break;
607           float termval = parser.parseFloat(term.text());
608           if (retArray == null) { // late init
609             retArray = new float[maxDoc];
610           }
611           termDocs.seek (termEnum);
612           while (termDocs.next()) {
613             final int docID = termDocs.doc();
614             retArray[docID] = termval;
615             if (setDocsWithField) {
616               if (docsWithField == null) {
617                 // Lazy init
618                 docsWithField = new FixedBitSet(maxDoc);
619               }
620               docsWithField.set(docID);
621             }
622           }
623         } while (termEnum.next());
624       } catch (StopFillCacheException stop) {
625       } finally {
626         termDocs.close();
627         termEnum.close();
628       }
629       if (setDocsWithField) {
630         wrapper.setDocsWithField(reader, field, docsWithField);
631       }
632       if (retArray == null) { // no values
633         retArray = new float[maxDoc];
634       }
635       return retArray;
636     }
637   }
638
639   public long[] getLongs(IndexReader reader, String field) throws IOException {
640     return getLongs(reader, field, null, false);
641   }
642   
643   // inherit javadocs
644   public long[] getLongs(IndexReader reader, String field, FieldCache.LongParser parser)
645       throws IOException {
646     return getLongs(reader, field, parser, false);
647   }
648
649   // inherit javadocs
650   public long[] getLongs(IndexReader reader, String field, FieldCache.LongParser parser, boolean setDocsWithField)
651       throws IOException {
652     return (long[]) caches.get(Long.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
653   }
654
655   static final class LongCache extends Cache {
656     LongCache(FieldCacheImpl wrapper) {
657       super(wrapper);
658     }
659
660     @Override
661     protected Object createValue(IndexReader reader, Entry entry, boolean setDocsWithField)
662         throws IOException {
663       String field = entry.field;
664       FieldCache.LongParser parser = (FieldCache.LongParser) entry.custom;
665       if (parser == null) {
666         try {
667           return wrapper.getLongs(reader, field, DEFAULT_LONG_PARSER, setDocsWithField);
668         } catch (NumberFormatException ne) {
669           return wrapper.getLongs(reader, field, NUMERIC_UTILS_LONG_PARSER, setDocsWithField);
670         }
671       }
672       final int maxDoc = reader.maxDoc();
673       long[] retArray = null;
674       TermDocs termDocs = reader.termDocs();
675       TermEnum termEnum = reader.terms (new Term(field));
676       FixedBitSet docsWithField = null;
677       try {
678         do {
679           Term term = termEnum.term();
680           if (term==null || term.field() != field) break;
681           long termval = parser.parseLong(term.text());
682           if (retArray == null) { // late init
683             retArray = new long[maxDoc];
684           }
685           termDocs.seek (termEnum);
686           while (termDocs.next()) {
687             final int docID = termDocs.doc();
688             retArray[docID] = termval;
689             if (setDocsWithField) {
690               if (docsWithField == null) {
691                 // Lazy init
692                 docsWithField = new FixedBitSet(maxDoc);
693               }
694               docsWithField.set(docID);
695             }
696           }
697         } while (termEnum.next());
698       } catch (StopFillCacheException stop) {
699       } finally {
700         termDocs.close();
701         termEnum.close();
702       }
703       if (setDocsWithField) {
704         wrapper.setDocsWithField(reader, field, docsWithField);
705       }
706       if (retArray == null) { // no values
707         retArray = new long[maxDoc];
708       }
709       return retArray;
710     }
711   }
712
713   // inherit javadocs
714   public double[] getDoubles(IndexReader reader, String field)
715     throws IOException {
716     return getDoubles(reader, field, null, false);
717   }
718
719   // inherit javadocs
720   public double[] getDoubles(IndexReader reader, String field, FieldCache.DoubleParser parser)
721       throws IOException {
722     return getDoubles(reader, field, parser, false);
723   }
724
725   // inherit javadocs
726   public double[] getDoubles(IndexReader reader, String field, FieldCache.DoubleParser parser, boolean setDocsWithField)
727       throws IOException {
728     return (double[]) caches.get(Double.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
729   }
730
731   static final class DoubleCache extends Cache {
732     DoubleCache(FieldCacheImpl wrapper) {
733       super(wrapper);
734     }
735
736     @Override
737     protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
738         throws IOException {
739       Entry entry = entryKey;
740       String field = entry.field;
741       FieldCache.DoubleParser parser = (FieldCache.DoubleParser) entry.custom;
742       if (parser == null) {
743         try {
744           return wrapper.getDoubles(reader, field, DEFAULT_DOUBLE_PARSER, setDocsWithField);
745         } catch (NumberFormatException ne) {
746           return wrapper.getDoubles(reader, field, NUMERIC_UTILS_DOUBLE_PARSER, setDocsWithField);
747         }
748       }
749       final int maxDoc = reader.maxDoc();
750       double[] retArray = null;
751       TermDocs termDocs = reader.termDocs();
752       TermEnum termEnum = reader.terms (new Term (field));
753       FixedBitSet docsWithField = null;
754       try {
755         do {
756           Term term = termEnum.term();
757           if (term==null || term.field() != field) break;
758           double termval = parser.parseDouble(term.text());
759           if (retArray == null) { // late init
760             retArray = new double[maxDoc];
761           }
762           termDocs.seek (termEnum);
763           while (termDocs.next()) {
764             final int docID = termDocs.doc();
765             retArray[docID] = termval;
766             if (setDocsWithField) {
767               if (docsWithField == null) {
768                 // Lazy init
769                 docsWithField = new FixedBitSet(maxDoc);
770               }
771               docsWithField.set(docID);
772             }
773           }
774         } while (termEnum.next());
775       } catch (StopFillCacheException stop) {
776       } finally {
777         termDocs.close();
778         termEnum.close();
779       }
780       if (setDocsWithField) {
781         wrapper.setDocsWithField(reader, field, docsWithField);
782       }
783       if (retArray == null) { // no values
784         retArray = new double[maxDoc];
785       }
786       return retArray;
787     }
788   }
789
790   // inherit javadocs
791   public String[] getStrings(IndexReader reader, String field)
792       throws IOException {
793     return (String[]) caches.get(String.class).get(reader, new Entry(field, (Parser)null), false);
794   }
795
796   static final class StringCache extends Cache {
797     StringCache(FieldCacheImpl wrapper) {
798       super(wrapper);
799     }
800
801     @Override
802     protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField /* ignored */)
803         throws IOException {
804       String field = StringHelper.intern(entryKey.field);
805       final String[] retArray = new String[reader.maxDoc()];
806       TermDocs termDocs = reader.termDocs();
807       TermEnum termEnum = reader.terms (new Term (field));
808       final int termCountHardLimit = reader.maxDoc();
809       int termCount = 0;
810       try {
811         do {
812           if (termCount++ == termCountHardLimit) {
813             // app is misusing the API (there is more than
814             // one term per doc); in this case we make best
815             // effort to load what we can (see LUCENE-2142)
816             break;
817           }
818
819           Term term = termEnum.term();
820           if (term==null || term.field() != field) break;
821           String termval = term.text();
822           termDocs.seek (termEnum);
823           while (termDocs.next()) {
824             retArray[termDocs.doc()] = termval;
825           }
826         } while (termEnum.next());
827       } finally {
828         termDocs.close();
829         termEnum.close();
830       }
831       return retArray;
832     }
833   }
834
835   // inherit javadocs
836   public StringIndex getStringIndex(IndexReader reader, String field)
837       throws IOException {
838     return (StringIndex) caches.get(StringIndex.class).get(reader, new Entry(field, (Parser)null), false);
839   }
840
841   static final class StringIndexCache extends Cache {
842     StringIndexCache(FieldCacheImpl wrapper) {
843       super(wrapper);
844     }
845
846     @Override
847     protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField /* ignored */)
848         throws IOException {
849       String field = StringHelper.intern(entryKey.field);
850       final int[] retArray = new int[reader.maxDoc()];
851       String[] mterms = new String[reader.maxDoc()+1];
852       TermDocs termDocs = reader.termDocs();
853       TermEnum termEnum = reader.terms (new Term (field));
854       int t = 0;  // current term number
855
856       // an entry for documents that have no terms in this field
857       // should a document with no terms be at top or bottom?
858       // this puts them at the top - if it is changed, FieldDocSortedHitQueue
859       // needs to change as well.
860       mterms[t++] = null;
861
862       try {
863         do {
864           Term term = termEnum.term();
865           if (term==null || term.field() != field || t >= mterms.length) break;
866
867           // store term text
868           mterms[t] = term.text();
869
870           termDocs.seek (termEnum);
871           while (termDocs.next()) {
872             retArray[termDocs.doc()] = t;
873           }
874
875           t++;
876         } while (termEnum.next());
877       } finally {
878         termDocs.close();
879         termEnum.close();
880       }
881
882       if (t == 0) {
883         // if there are no terms, make the term array
884         // have a single null entry
885         mterms = new String[1];
886       } else if (t < mterms.length) {
887         // if there are less terms than documents,
888         // trim off the dead array space
889         String[] terms = new String[t];
890         System.arraycopy (mterms, 0, terms, 0, t);
891         mterms = terms;
892       }
893
894       StringIndex value = new StringIndex (retArray, mterms);
895       return value;
896     }
897   }
898
899   private volatile PrintStream infoStream;
900
901   public void setInfoStream(PrintStream stream) {
902     infoStream = stream;
903   }
904
905   public PrintStream getInfoStream() {
906     return infoStream;
907   }
908 }
909