package org.apache.lucene.search; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.cache.ByteValuesCreator; import org.apache.lucene.search.cache.DocTermsCreator; import org.apache.lucene.search.cache.DocTermsIndexCreator; import org.apache.lucene.search.cache.DoubleValuesCreator; import org.apache.lucene.search.cache.EntryCreator; import org.apache.lucene.search.cache.FloatValuesCreator; import org.apache.lucene.search.cache.IntValuesCreator; import org.apache.lucene.search.cache.LongValuesCreator; import org.apache.lucene.search.cache.ShortValuesCreator; import org.apache.lucene.search.cache.CachedArray.ByteValues; import org.apache.lucene.search.cache.CachedArray.DoubleValues; import org.apache.lucene.search.cache.CachedArray.FloatValues; import org.apache.lucene.search.cache.CachedArray.IntValues; import org.apache.lucene.search.cache.CachedArray.LongValues; import org.apache.lucene.search.cache.CachedArray.ShortValues; import org.apache.lucene.util.FieldCacheSanityChecker; import org.apache.lucene.util.StringHelper; /** * Expert: The default cache implementation, storing all values in memory. * A WeakHashMap is used for storage. * * <p>Created: May 19, 2004 4:40:36 PM * * @lucene.internal -- this is now public so that the tests can use reflection * to call methods. It will likely be removed without (much) notice. * * @since lucene 1.4 */ public class FieldCacheImpl implements FieldCache { // Made Public so that private Map<Class<?>,Cache> caches; FieldCacheImpl() { init(); } private synchronized void init() { caches = new HashMap<Class<?>,Cache>(7); caches.put(Byte.TYPE, new Cache<ByteValues>(this)); caches.put(Short.TYPE, new Cache<ShortValues>(this)); caches.put(Integer.TYPE, new Cache<IntValues>(this)); caches.put(Float.TYPE, new Cache<FloatValues>(this)); caches.put(Long.TYPE, new Cache<LongValues>(this)); caches.put(Double.TYPE, new Cache<DoubleValues>(this)); caches.put(DocTermsIndex.class, new Cache<DocTermsIndex>(this)); caches.put(DocTerms.class, new Cache<DocTerms>(this)); } public synchronized void purgeAllCaches() { init(); } public synchronized void purge(IndexReader r) { for(Cache c : caches.values()) { c.purge(r); } } public synchronized CacheEntry[] getCacheEntries() { List<CacheEntry> result = new ArrayList<CacheEntry>(17); for(final Map.Entry<Class<?>,Cache> cacheEntry: caches.entrySet()) { final Cache<?> cache = cacheEntry.getValue(); final Class<?> cacheType = cacheEntry.getKey(); synchronized(cache.readerCache) { for( Object readerKey : cache.readerCache.keySet() ) { Map<?, Object> innerCache = cache.readerCache.get(readerKey); for (final Map.Entry<?, Object> mapEntry : innerCache.entrySet()) { Entry entry = (Entry)mapEntry.getKey(); result.add(new CacheEntryImpl(readerKey, entry.field, cacheType, entry.creator, mapEntry.getValue())); } } } } return result.toArray(new CacheEntry[result.size()]); } private static final class CacheEntryImpl extends CacheEntry { private final Object readerKey; private final String fieldName; private final Class<?> cacheType; private final EntryCreator custom; private final Object value; CacheEntryImpl(Object readerKey, String fieldName, Class<?> cacheType, EntryCreator custom, Object value) { this.readerKey = readerKey; this.fieldName = fieldName; this.cacheType = cacheType; this.custom = custom; this.value = value; // :HACK: for testing. // if (null != locale || SortField.CUSTOM != sortFieldType) { // throw new RuntimeException("Locale/sortFieldType: " + this); // } } @Override public Object getReaderKey() { return readerKey; } @Override public String getFieldName() { return fieldName; } @Override public Class<?> getCacheType() { return cacheType; } @Override public Object getCustom() { return custom; } @Override public Object getValue() { return value; } } final static IndexReader.ReaderFinishedListener purgeReader = new IndexReader.ReaderFinishedListener() { // @Override -- not until Java 1.6 public void finished(IndexReader reader) { FieldCache.DEFAULT.purge(reader); } }; /** Expert: Internal cache. */ final static class Cache<T> { Cache() { this.wrapper = null; } Cache(FieldCache wrapper) { this.wrapper = wrapper; } final FieldCache wrapper; final Map<Object,Map<Entry<T>,Object>> readerCache = new WeakHashMap<Object,Map<Entry<T>,Object>>(); protected Object createValue(IndexReader reader, Entry entryKey) throws IOException { return entryKey.creator.create( reader ); } /** Remove this reader from the cache, if present. */ public void purge(IndexReader r) { Object readerKey = r.getCoreCacheKey(); synchronized(readerCache) { readerCache.remove(readerKey); } } @SuppressWarnings("unchecked") public Object get(IndexReader reader, Entry<T> key) throws IOException { Map<Entry<T>,Object> innerCache; Object value; final Object readerKey = reader.getCoreCacheKey(); synchronized (readerCache) { innerCache = readerCache.get(readerKey); if (innerCache == null) { // First time this reader is using FieldCache innerCache = new HashMap<Entry<T>,Object>(); readerCache.put(readerKey, innerCache); reader.addReaderFinishedListener(purgeReader); value = null; } else { value = innerCache.get(key); } if (value == null) { value = new CreationPlaceholder(); innerCache.put(key, value); } } if (value instanceof CreationPlaceholder) { synchronized (value) { CreationPlaceholder progress = (CreationPlaceholder) value; if (progress.value == null) { progress.value = createValue(reader, key); synchronized (readerCache) { innerCache.put(key, progress.value); } // Only check if key.custom (the parser) is // non-null; else, we check twice for a single // call to FieldCache.getXXX if (key.creator != null && wrapper != null) { final PrintStream infoStream = wrapper.getInfoStream(); if (infoStream != null) { printNewInsanity(infoStream, progress.value); } } } return progress.value; } } // Validate new entries if( key.creator.shouldValidate() ) { key.creator.validate( (T)value, reader); } return value; } private void printNewInsanity(PrintStream infoStream, Object value) { final FieldCacheSanityChecker.Insanity[] insanities = FieldCacheSanityChecker.checkSanity(wrapper); for(int i=0;i<insanities.length;i++) { final FieldCacheSanityChecker.Insanity insanity = insanities[i]; final CacheEntry[] entries = insanity.getCacheEntries(); for(int j=0;j<entries.length;j++) { if (entries[j].getValue() == value) { // OK this insanity involves our entry infoStream.println("WARNING: new FieldCache insanity created\nDetails: " + insanity.toString()); infoStream.println("\nStack:\n"); new Throwable().printStackTrace(infoStream); break; } } } } } /** Expert: Every composite-key in the internal cache is of this type. */ static class Entry<T> { final String field; // which Fieldable final EntryCreator<T> creator; // which custom comparator or parser /** Creates one of these objects for a custom comparator/parser. */ Entry (String field, EntryCreator<T> custom) { this.field = StringHelper.intern(field); this.creator = custom; } /** Two of these are equal iff they reference the same field and type. */ @Override public boolean equals (Object o) { if (o instanceof Entry) { Entry other = (Entry) o; if (other.field == field) { if (other.creator == null) { if (creator == null) return true; } else if (other.creator.equals (creator)) { return true; } } } return false; } /** Composes a hashcode based on the field and type. */ @Override public int hashCode() { return field.hashCode() ^ (creator==null ? 0 : creator.hashCode()); } } // inherit javadocs public byte[] getBytes (IndexReader reader, String field) throws IOException { return getBytes(reader, field, new ByteValuesCreator(field, null)).values; } // inherit javadocs public byte[] getBytes(IndexReader reader, String field, ByteParser parser) throws IOException { return getBytes(reader, field, new ByteValuesCreator(field, parser)).values; } @SuppressWarnings("unchecked") public ByteValues getBytes(IndexReader reader, String field, EntryCreator<ByteValues> creator ) throws IOException { return (ByteValues)caches.get(Byte.TYPE).get(reader, new Entry(field, creator)); } // inherit javadocs public short[] getShorts (IndexReader reader, String field) throws IOException { return getShorts(reader, field, new ShortValuesCreator(field,null)).values; } // inherit javadocs public short[] getShorts(IndexReader reader, String field, ShortParser parser) throws IOException { return getShorts(reader, field, new ShortValuesCreator(field,parser)).values; } @SuppressWarnings("unchecked") public ShortValues getShorts(IndexReader reader, String field, EntryCreator<ShortValues> creator ) throws IOException { return (ShortValues)caches.get(Short.TYPE).get(reader, new Entry(field, creator)); } // inherit javadocs public int[] getInts (IndexReader reader, String field) throws IOException { return getInts(reader, field, new IntValuesCreator( field, null )).values; } // inherit javadocs public int[] getInts(IndexReader reader, String field, IntParser parser) throws IOException { return getInts(reader, field, new IntValuesCreator( field, parser )).values; } @SuppressWarnings("unchecked") public IntValues getInts(IndexReader reader, String field, EntryCreator<IntValues> creator ) throws IOException { return (IntValues)caches.get(Integer.TYPE).get(reader, new Entry(field, creator)); } // inherit javadocs public float[] getFloats (IndexReader reader, String field) throws IOException { return getFloats(reader, field, new FloatValuesCreator( field, null ) ).values; } // inherit javadocs public float[] getFloats(IndexReader reader, String field, FloatParser parser) throws IOException { return getFloats(reader, field, new FloatValuesCreator( field, parser ) ).values; } @SuppressWarnings("unchecked") public FloatValues getFloats(IndexReader reader, String field, EntryCreator<FloatValues> creator ) throws IOException { return (FloatValues)caches.get(Float.TYPE).get(reader, new Entry(field, creator)); } public long[] getLongs(IndexReader reader, String field) throws IOException { return getLongs(reader, field, new LongValuesCreator( field, null ) ).values; } // inherit javadocs public long[] getLongs(IndexReader reader, String field, FieldCache.LongParser parser) throws IOException { return getLongs(reader, field, new LongValuesCreator( field, parser ) ).values; } @SuppressWarnings("unchecked") public LongValues getLongs(IndexReader reader, String field, EntryCreator<LongValues> creator ) throws IOException { return (LongValues)caches.get(Long.TYPE).get(reader, new Entry(field, creator)); } // inherit javadocs public double[] getDoubles(IndexReader reader, String field) throws IOException { return getDoubles(reader, field, new DoubleValuesCreator( field, null ) ).values; } // inherit javadocs public double[] getDoubles(IndexReader reader, String field, FieldCache.DoubleParser parser) throws IOException { return getDoubles(reader, field, new DoubleValuesCreator( field, parser ) ).values; } @SuppressWarnings("unchecked") public DoubleValues getDoubles(IndexReader reader, String field, EntryCreator<DoubleValues> creator ) throws IOException { return (DoubleValues)caches.get(Double.TYPE).get(reader, new Entry(field, creator)); } public DocTermsIndex getTermsIndex(IndexReader reader, String field) throws IOException { return getTermsIndex(reader, field, new DocTermsIndexCreator(field)); } public DocTermsIndex getTermsIndex(IndexReader reader, String field, boolean fasterButMoreRAM) throws IOException { return getTermsIndex(reader, field, new DocTermsIndexCreator(field, fasterButMoreRAM ? DocTermsIndexCreator.FASTER_BUT_MORE_RAM : 0)); } @SuppressWarnings("unchecked") public DocTermsIndex getTermsIndex(IndexReader reader, String field, EntryCreator<DocTermsIndex> creator) throws IOException { return (DocTermsIndex)caches.get(DocTermsIndex.class).get(reader, new Entry(field, creator)); } // TODO: this if DocTermsIndex was already created, we // should share it... public DocTerms getTerms(IndexReader reader, String field) throws IOException { return getTerms(reader, field, new DocTermsCreator(field)); } public DocTerms getTerms(IndexReader reader, String field, boolean fasterButMoreRAM) throws IOException { return getTerms(reader, field, new DocTermsCreator(field, fasterButMoreRAM ? DocTermsCreator.FASTER_BUT_MORE_RAM : 0)); } @SuppressWarnings("unchecked") public DocTerms getTerms(IndexReader reader, String field, EntryCreator<DocTerms> creator) throws IOException { return (DocTerms)caches.get(DocTerms.class).get(reader, new Entry(field, creator)); } private volatile PrintStream infoStream; public void setInfoStream(PrintStream stream) { infoStream = stream; } public PrintStream getInfoStream() { return infoStream; } }