1 package org.apache.lucene.search;
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.io.PrintStream;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
26 import java.util.WeakHashMap;
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;
38 * Expert: The default cache implementation, storing all values in memory.
39 * A WeakHashMap is used for storage.
41 * <p>Created: May 19, 2004 4:40:36 PM
45 class FieldCacheImpl implements FieldCache {
47 private Map<Class<?>,Cache> caches;
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));
64 public synchronized void purgeAllCaches() {
68 public synchronized void purge(IndexReader r) {
69 for(Cache c : caches.values()) {
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()));
93 return result.toArray(new CacheEntry[result.size()]);
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,
106 this.readerKey = readerKey;
107 this.fieldName = fieldName;
108 this.cacheType = cacheType;
109 this.custom = custom;
112 // :HACK: for testing.
113 // if (null != locale || SortField.CUSTOM != sortFieldType) {
114 // throw new RuntimeException("Locale/sortFieldType: " + this);
119 public Object getReaderKey() { return readerKey; }
121 public String getFieldName() { return fieldName; }
123 public Class<?> getCacheType() { return cacheType; }
125 public Object getCustom() { return custom; }
127 public Object getValue() { return value; }
131 * Hack: When thrown from a Parser (NUMERIC_UTILS_* ones), this stops
132 * processing terms and returns the current FieldCache
135 static final class StopFillCacheException extends RuntimeException {
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);
145 /** Expert: Internal cache. */
146 abstract static class Cache {
151 Cache(FieldCacheImpl wrapper) {
152 this.wrapper = wrapper;
155 final FieldCacheImpl wrapper;
157 final Map<Object,Map<Entry,Object>> readerCache = new WeakHashMap<Object,Map<Entry,Object>>();
159 protected abstract Object createValue(IndexReader reader, Entry key, boolean setDocsWithField)
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);
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);
182 if (innerCache.get(key) == null) {
183 innerCache.put(key, value);
185 // Another thread beat us to it; leave the current
191 public Object get(IndexReader reader, Entry key, boolean setDocsWithField) throws IOException {
192 Map<Entry,Object> innerCache;
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);
204 value = innerCache.get(key);
207 value = new CreationPlaceholder();
208 innerCache.put(key, value);
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);
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);
230 return progress.value;
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);
254 /** Expert: Every composite-key in the internal cache is of this type. */
256 final String field; // which Fieldable
257 final Object custom; // which custom comparator or parser
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;
265 /** Two of these are equal iff they reference the same field and type. */
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)) {
281 /** Composes a hashcode based on the field and type. */
283 public int hashCode() {
284 return field.hashCode() ^ (custom==null ? 0 : custom.hashCode());
289 public byte[] getBytes (IndexReader reader, String field) throws IOException {
290 return getBytes(reader, field, null, false);
294 public byte[] getBytes(IndexReader reader, String field, ByteParser parser)
296 return getBytes(reader, field, parser, false);
299 public byte[] getBytes(IndexReader reader, String field, ByteParser parser, boolean setDocsWithField)
301 return (byte[]) caches.get(Byte.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
304 static final class ByteCache extends Cache {
305 ByteCache(FieldCacheImpl wrapper) {
310 protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
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);
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;
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) {
335 docsWithField = new FixedBitSet(maxDoc);
337 docsWithField.set(docID);
340 } while (termEnum.next());
341 } catch (StopFillCacheException stop) {
346 if (setDocsWithField) {
347 wrapper.setDocsWithField(reader, field, docsWithField);
354 public short[] getShorts (IndexReader reader, String field) throws IOException {
355 return getShorts(reader, field, null, false);
359 public short[] getShorts(IndexReader reader, String field, ShortParser parser)
361 return getShorts(reader, field, parser, false);
365 public short[] getShorts(IndexReader reader, String field, ShortParser parser, boolean setDocsWithField)
367 return (short[]) caches.get(Short.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
370 static final class ShortCache extends Cache {
371 ShortCache(FieldCacheImpl wrapper) {
376 protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
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);
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;
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) {
401 docsWithField = new FixedBitSet(maxDoc);
403 docsWithField.set(docID);
406 } while (termEnum.next());
407 } catch (StopFillCacheException stop) {
412 if (setDocsWithField) {
413 wrapper.setDocsWithField(reader, field, docsWithField);
419 // null Bits means no docs matched
420 void setDocsWithField(IndexReader reader, String field, Bits docsWithField) {
421 final int maxDoc = reader.maxDoc();
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);
432 bits = docsWithField;
435 bits = docsWithField;
437 caches.get(DocsWithFieldCache.class).put(reader, new Entry(field, null), bits);
441 public int[] getInts (IndexReader reader, String field) throws IOException {
442 return getInts(reader, field, null);
446 public int[] getInts(IndexReader reader, String field, IntParser parser)
448 return getInts(reader, field, parser, false);
452 public int[] getInts(IndexReader reader, String field, IntParser parser, boolean setDocsWithField)
454 return (int[]) caches.get(Integer.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
457 static final class IntCache extends Cache {
458 IntCache(FieldCacheImpl wrapper) {
463 protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
465 Entry entry = entryKey;
466 String field = entry.field;
467 IntParser parser = (IntParser) entry.custom;
468 if (parser == null) {
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);
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;
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];
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) {
495 docsWithField = new FixedBitSet(maxDoc);
497 docsWithField.set(docID);
500 } while (termEnum.next());
501 } catch (StopFillCacheException stop) {
506 if (setDocsWithField) {
507 wrapper.setDocsWithField(reader, field, docsWithField);
509 if (retArray == null) { // no values
510 retArray = new int[maxDoc];
516 public Bits getDocsWithField(IndexReader reader, String field)
518 return (Bits) caches.get(DocsWithFieldCache.class).get(reader, new Entry(field, null), false);
521 static final class DocsWithFieldCache extends Cache {
522 DocsWithFieldCache(FieldCacheImpl wrapper) {
527 protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField /* ignored */)
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));
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());
544 } while (termEnum.next());
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());
563 public float[] getFloats (IndexReader reader, String field)
565 return getFloats(reader, field, null, false);
569 public float[] getFloats(IndexReader reader, String field, FloatParser parser)
571 return getFloats(reader, field, parser, false);
574 public float[] getFloats(IndexReader reader, String field, FloatParser parser, boolean setDocsWithField)
577 return (float[]) caches.get(Float.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
580 static final class FloatCache extends Cache {
581 FloatCache(FieldCacheImpl wrapper) {
586 protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
588 Entry entry = entryKey;
589 String field = entry.field;
590 FloatParser parser = (FloatParser) entry.custom;
591 if (parser == null) {
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);
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;
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];
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) {
618 docsWithField = new FixedBitSet(maxDoc);
620 docsWithField.set(docID);
623 } while (termEnum.next());
624 } catch (StopFillCacheException stop) {
629 if (setDocsWithField) {
630 wrapper.setDocsWithField(reader, field, docsWithField);
632 if (retArray == null) { // no values
633 retArray = new float[maxDoc];
639 public long[] getLongs(IndexReader reader, String field) throws IOException {
640 return getLongs(reader, field, null, false);
644 public long[] getLongs(IndexReader reader, String field, FieldCache.LongParser parser)
646 return getLongs(reader, field, parser, false);
650 public long[] getLongs(IndexReader reader, String field, FieldCache.LongParser parser, boolean setDocsWithField)
652 return (long[]) caches.get(Long.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
655 static final class LongCache extends Cache {
656 LongCache(FieldCacheImpl wrapper) {
661 protected Object createValue(IndexReader reader, Entry entry, boolean setDocsWithField)
663 String field = entry.field;
664 FieldCache.LongParser parser = (FieldCache.LongParser) entry.custom;
665 if (parser == null) {
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);
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;
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];
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) {
692 docsWithField = new FixedBitSet(maxDoc);
694 docsWithField.set(docID);
697 } while (termEnum.next());
698 } catch (StopFillCacheException stop) {
703 if (setDocsWithField) {
704 wrapper.setDocsWithField(reader, field, docsWithField);
706 if (retArray == null) { // no values
707 retArray = new long[maxDoc];
714 public double[] getDoubles(IndexReader reader, String field)
716 return getDoubles(reader, field, null, false);
720 public double[] getDoubles(IndexReader reader, String field, FieldCache.DoubleParser parser)
722 return getDoubles(reader, field, parser, false);
726 public double[] getDoubles(IndexReader reader, String field, FieldCache.DoubleParser parser, boolean setDocsWithField)
728 return (double[]) caches.get(Double.TYPE).get(reader, new Entry(field, parser), setDocsWithField);
731 static final class DoubleCache extends Cache {
732 DoubleCache(FieldCacheImpl wrapper) {
737 protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField)
739 Entry entry = entryKey;
740 String field = entry.field;
741 FieldCache.DoubleParser parser = (FieldCache.DoubleParser) entry.custom;
742 if (parser == null) {
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);
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;
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];
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) {
769 docsWithField = new FixedBitSet(maxDoc);
771 docsWithField.set(docID);
774 } while (termEnum.next());
775 } catch (StopFillCacheException stop) {
780 if (setDocsWithField) {
781 wrapper.setDocsWithField(reader, field, docsWithField);
783 if (retArray == null) { // no values
784 retArray = new double[maxDoc];
791 public String[] getStrings(IndexReader reader, String field)
793 return (String[]) caches.get(String.class).get(reader, new Entry(field, (Parser)null), false);
796 static final class StringCache extends Cache {
797 StringCache(FieldCacheImpl wrapper) {
802 protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField /* ignored */)
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();
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)
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;
826 } while (termEnum.next());
836 public StringIndex getStringIndex(IndexReader reader, String field)
838 return (StringIndex) caches.get(StringIndex.class).get(reader, new Entry(field, (Parser)null), false);
841 static final class StringIndexCache extends Cache {
842 StringIndexCache(FieldCacheImpl wrapper) {
847 protected Object createValue(IndexReader reader, Entry entryKey, boolean setDocsWithField /* ignored */)
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
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.
864 Term term = termEnum.term();
865 if (term==null || term.field() != field || t >= mterms.length) break;
868 mterms[t] = term.text();
870 termDocs.seek (termEnum);
871 while (termDocs.next()) {
872 retArray[termDocs.doc()] = t;
876 } while (termEnum.next());
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);
894 StringIndex value = new StringIndex (retArray, mterms);
899 private volatile PrintStream infoStream;
901 public void setInfoStream(PrintStream stream) {
905 public PrintStream getInfoStream() {