Index: src/java/org/apache/lucene/search/FieldCache.java
===================================================================
--- src/java/org/apache/lucene/search/FieldCache.java	(revision 544035)
+++ src/java/org/apache/lucene/search/FieldCache.java	(working copy)
@@ -18,6 +18,7 @@
  */
 
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.cache.*;
 import java.io.IOException;
 
 /**
@@ -28,6 +29,7 @@
  * @author  Tim Jones (Nacimiento Software)
  * @since   lucene 1.4
  * @version $Id$
+ * @deprecated use IndexReader.getCachedData
  */
 public interface FieldCache {
 
@@ -37,39 +39,30 @@
   public static final int STRING_INDEX = -1;
 
 
-  /** Expert: Stores term text values and document ordering data. */
-  public static class StringIndex {
-
-    /** All the term values, in natural order. */
-    public final String[] lookup;
-
-    /** For each document, an index into the lookup array. */
-    public final int[] order;
-
-    /** Creates one of these objects */
+  /** Expert: Stores term text values and document ordering data. 
+   * @deprecated use StringIndexCacheKey.StringIndex
+   */
+  public static class StringIndex extends StringIndexCacheKey.StringIndex { 
     public StringIndex (int[] values, String[] lookup) {
-      this.order = values;
-      this.lookup = lookup;
+      super(values,lookup);
     }
   }
 
   /** Interface to parse ints from document fields.
    * @see FieldCache#getInts(IndexReader, String, FieldCache.IntParser)
+   * @deprecated use IntArrayCacheKey.IntParser
    */
-  public interface IntParser {
-    /** Return an integer representation of this field's value. */
-    public int parseInt(String string);
-  }
+  public interface IntParser extends IntArrayCacheKey.IntParser { }
 
   /** Interface to parse floats from document fields.
    * @see FieldCache#getFloats(IndexReader, String, FieldCache.FloatParser)
+   * @deprecated use FloatArrayCacheKey.FloatParser
    */
-  public interface FloatParser {
-    /** Return an float representation of this field's value. */
-    public float parseFloat(String string);
-  }
+  public interface FloatParser extends FloatArrayCacheKey.FloatParser { }
 
-  /** Expert: The cache used internally by sorting and range query classes. */
+  /** Expert: The cache used internally by sorting and range query classes. 
+   * @deprecated use IndexReader.getCachedData
+   */
   public static FieldCache DEFAULT = new FieldCacheImpl();
 
   /** Checks the internal cache for an appropriate entry, and if none is
@@ -80,6 +73,7 @@
    * @param field   Which field contains the integers.
    * @return The values in the given field for each document.
    * @throws IOException  If any error occurs.
+   * @deprecated use IndexReader.getCachedData(new IntArrayCacheKey(field))
    */
   public int[] getInts (IndexReader reader, String field)
   throws IOException;
@@ -93,6 +87,7 @@
    * @param parser  Computes integer for string values.
    * @return The values in the given field for each document.
    * @throws IOException  If any error occurs.
+   * @deprecated use IndexReader.getCachedData(new IntArrayCacheKey(field,parser))
    */
   public int[] getInts (IndexReader reader, String field, IntParser parser)
   throws IOException;
@@ -105,6 +100,7 @@
    * @param field   Which field contains the floats.
    * @return The values in the given field for each document.
    * @throws IOException  If any error occurs.
+   * @deprecated use IndexReader.getCachedData(new FloatArrayCacheKey(field))
    */
   public float[] getFloats (IndexReader reader, String field)
   throws IOException;
@@ -118,6 +114,7 @@
    * @param parser  Computes float for string values.
    * @return The values in the given field for each document.
    * @throws IOException  If any error occurs.
+   * @deprecated use IndexReader.getCachedData(new FloatArrayCacheKey(field,parser))
    */
   public float[] getFloats (IndexReader reader, String field,
                             FloatParser parser) throws IOException;
@@ -130,6 +127,7 @@
    * @param field   Which field contains the strings.
    * @return The values in the given field for each document.
    * @throws IOException  If any error occurs.
+   * @deprecated use IndexReader.getCachedData(new StringArrayCacheKey(field))
    */
   public String[] getStrings (IndexReader reader, String field)
   throws IOException;
@@ -142,6 +140,7 @@
    * @param field   Which field contains the strings.
    * @return Array of terms and index into the array for each document.
    * @throws IOException  If any error occurs.
+   * @deprecated use IndexReader.getCachedData(new StringIndexCacheKey(field))
    */
   public StringIndex getStringIndex (IndexReader reader, String field)
   throws IOException;
@@ -170,6 +169,7 @@
    * @param comparator Used to convert terms into something to sort by.
    * @return Array of sort objects, one for each document.
    * @throws IOException  If any error occurs.
+   * @deprecated use IndexReader.getCacheData with a custom CacheKey
    */
   public Comparable[] getCustom (IndexReader reader, String field, SortComparator comparator)
   throws IOException;
Index: src/java/org/apache/lucene/search/SortComparatorCacheKey.java
===================================================================
--- src/java/org/apache/lucene/search/SortComparatorCacheKey.java	(revision 0)
+++ src/java/org/apache/lucene/search/SortComparatorCacheKey.java	(revision 0)
@@ -0,0 +1,80 @@
+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 org.apache.lucene.index.cache.*;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+import org.apache.lucene.search.SortComparator;
+
+import java.io.IOException;
+
+/* :TODO: javadocs 
+ *
+ * :TODO: this class must live in o.a.l.search because of access level of comparator.getComparable
+ */
+public class SortComparatorCacheKey extends CacheKey {
+
+  String field;
+  SortComparator comparator;
+    
+  public SortComparatorCacheKey(String f, SortComparator c) {
+    field = f.intern();
+    comparator = c;
+  }
+
+  public int hashCode() {
+    /* :TODO: improve */
+    return field.hashCode() ^ comparator.hashCode();
+  }
+      
+  public boolean equals(Object o) {
+    if (null == o) return false;
+    if (this == o) return true;
+    if (o instanceof SortComparatorCacheKey) {
+      SortComparatorCacheKey other = (SortComparatorCacheKey) o;
+      return field.equals(other.field) && comparator.equals(other.comparator);
+    }
+    return false;
+  }
+
+  public CacheData buildData(IndexReader reader) throws IOException {
+      
+    final Comparable[] retArray = new Comparable[reader.maxDoc()];
+    TermDocs termDocs = reader.termDocs();
+    TermEnum termEnum = reader.terms (new Term (field, ""));
+    try {
+      do {
+        Term term = termEnum.term();
+        if (term==null || term.field() != field) break;
+        Comparable termval = comparator.getComparable (term.text());
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } finally {
+      termDocs.close();
+      termEnum.close();
+      }
+    return new CacheData(retArray);
+  }
+}

Property changes on: src/java/org/apache/lucene/search/SortComparatorCacheKey.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/search/FieldCacheImpl.java
===================================================================
--- src/java/org/apache/lucene/search/FieldCacheImpl.java	(revision 544035)
+++ src/java/org/apache/lucene/search/FieldCacheImpl.java	(working copy)
@@ -21,6 +21,7 @@
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermDocs;
 import org.apache.lucene.index.TermEnum;
+import org.apache.lucene.index.cache.*;
 
 import java.io.IOException;
 import java.util.Locale;
@@ -37,11 +38,14 @@
  * @author  Tim Jones (Nacimiento Software)
  * @since   lucene 1.4
  * @version $Id$
+ * @deprecated Use IndexReader.getCachedData
  */
 class FieldCacheImpl
 implements FieldCache {
 	
-  /** Expert: Internal cache. */
+  /** Expert: Internal cache. 
+   * @deprecated
+   */
   abstract static class Cache {
     private final Map readerCache = new WeakHashMap();
     
@@ -81,11 +85,14 @@
     }
   }
 
+  /** @deprecated */
   static final class CreationPlaceholder {
     Object value;
   }
 
-  /** Expert: Every composite-key in the internal cache is of this type. */
+  /** Expert: Every composite-key in the internal cache is of this type. 
+   * @deprecated 
+   */
   static class Entry {
     final String field;        // which Fieldable
     final int type;            // which SortField type
@@ -131,197 +138,55 @@
     }
   }
 
-  private static final IntParser INT_PARSER = new IntParser() {
-      public int parseInt(String value) {
-        return Integer.parseInt(value);
-      }
-    };
-
-  private static final FloatParser FLOAT_PARSER = new FloatParser() {
-      public float parseFloat(String value) {
-        return Float.parseFloat(value);
-      }
-    };
-
   // inherit javadocs
   public int[] getInts (IndexReader reader, String field) throws IOException {
-    return getInts(reader, field, INT_PARSER);
+    return getInts(reader, field, null);
   }
 
   // inherit javadocs
   public int[] getInts(IndexReader reader, String field, IntParser parser)
-      throws IOException {
-    return (int[]) intsCache.get(reader, new Entry(field, parser));
+    throws IOException {
+    
+    return (int[]) reader.getCachedData
+      (new IntArrayCacheKey(field, parser)).getPayload();
   }
 
-  Cache intsCache = new Cache() {
-
-    protected Object createValue(IndexReader reader, Object entryKey)
-        throws IOException {
-      Entry entry = (Entry) entryKey;
-      String field = entry.field;
-      IntParser parser = (IntParser) entry.custom;
-      final int[] retArray = new int[reader.maxDoc()];
-      TermDocs termDocs = reader.termDocs();
-      TermEnum termEnum = reader.terms (new Term (field, ""));
-      try {
-        do {
-          Term term = termEnum.term();
-          if (term==null || term.field() != field) break;
-          int termval = parser.parseInt(term.text());
-          termDocs.seek (termEnum);
-          while (termDocs.next()) {
-            retArray[termDocs.doc()] = termval;
-          }
-        } while (termEnum.next());
-      } finally {
-        termDocs.close();
-        termEnum.close();
-      }
-      return retArray;
-    }
-  };
-
   // inherit javadocs
   public float[] getFloats (IndexReader reader, String field)
     throws IOException {
-    return getFloats(reader, field, FLOAT_PARSER);
+    return getFloats(reader, field, null);
   }
 
   // inherit javadocs
   public float[] getFloats(IndexReader reader, String field, FloatParser parser)
-      throws IOException {
-    return (float[]) floatsCache.get(reader, new Entry(field, parser));
+    throws IOException {
+
+    return (float[]) reader.getCachedData
+      (new FloatArrayCacheKey(field, parser)).getPayload();
   }
 
-  Cache floatsCache = new Cache() {
-
-    protected Object createValue(IndexReader reader, Object entryKey)
-        throws IOException {
-      Entry entry = (Entry) entryKey;
-      String field = entry.field;
-      FloatParser parser = (FloatParser) entry.custom;
-      final float[] retArray = new float[reader.maxDoc()];
-      TermDocs termDocs = reader.termDocs();
-      TermEnum termEnum = reader.terms (new Term (field, ""));
-      try {
-        do {
-          Term term = termEnum.term();
-          if (term==null || term.field() != field) break;
-          float termval = parser.parseFloat(term.text());
-          termDocs.seek (termEnum);
-          while (termDocs.next()) {
-            retArray[termDocs.doc()] = termval;
-          }
-        } while (termEnum.next());
-      } finally {
-        termDocs.close();
-        termEnum.close();
-      }
-      return retArray;
-    }
-  };
-
   // inherit javadocs
   public String[] getStrings(IndexReader reader, String field)
-      throws IOException {
-    return (String[]) stringsCache.get(reader, field);
+    throws IOException {
+
+    return (String[]) reader.getCachedData
+      (new StringArrayCacheKey(field)).getPayload();
   }
 
-  Cache stringsCache = new Cache() {
-
-    protected Object createValue(IndexReader reader, Object fieldKey)
-        throws IOException {
-      String field = ((String) fieldKey).intern();
-      final String[] retArray = new String[reader.maxDoc()];
-      TermDocs termDocs = reader.termDocs();
-      TermEnum termEnum = reader.terms (new Term (field, ""));
-      try {
-        do {
-          Term term = termEnum.term();
-          if (term==null || term.field() != field) break;
-          String termval = term.text();
-          termDocs.seek (termEnum);
-          while (termDocs.next()) {
-            retArray[termDocs.doc()] = termval;
-          }
-        } while (termEnum.next());
-      } finally {
-        termDocs.close();
-        termEnum.close();
-      }
-      return retArray;
-    }
-  };
-
   // inherit javadocs
   public StringIndex getStringIndex(IndexReader reader, String field)
-      throws IOException {
-    return (StringIndex) stringsIndexCache.get(reader, field);
+    throws IOException {
+
+    StringIndexCacheKey.StringIndex data = (StringIndexCacheKey.StringIndex) 
+      reader.getCachedData(new StringIndexCacheKey(field)).getPayload();
+    return new StringIndex(data.order, data.lookup);
   }
 
-  Cache stringsIndexCache = new Cache() {
 
-    protected Object createValue(IndexReader reader, Object fieldKey)
-        throws IOException {
-      String field = ((String) fieldKey).intern();
-      final int[] retArray = new int[reader.maxDoc()];
-      String[] mterms = new String[reader.maxDoc()+1];
-      TermDocs termDocs = reader.termDocs();
-      TermEnum termEnum = reader.terms (new Term (field, ""));
-      int t = 0;  // current term number
-
-      // an entry for documents that have no terms in this field
-      // should a document with no terms be at top or bottom?
-      // this puts them at the top - if it is changed, FieldDocSortedHitQueue
-      // needs to change as well.
-      mterms[t++] = null;
-
-      try {
-        do {
-          Term term = termEnum.term();
-          if (term==null || term.field() != field) break;
-
-          // store term text
-          // we expect that there is at most one term per document
-          if (t >= mterms.length) throw new RuntimeException ("there are more terms than " +
-                  "documents in field \"" + field + "\", but it's impossible to sort on " +
-                  "tokenized fields");
-          mterms[t] = term.text();
-
-          termDocs.seek (termEnum);
-          while (termDocs.next()) {
-            retArray[termDocs.doc()] = t;
-          }
-
-          t++;
-        } while (termEnum.next());
-      } finally {
-        termDocs.close();
-        termEnum.close();
-      }
-
-      if (t == 0) {
-        // if there are no terms, make the term array
-        // have a single null entry
-        mterms = new String[1];
-      } else if (t < mterms.length) {
-        // if there are less terms than documents,
-        // trim off the dead array space
-        String[] terms = new String[t];
-        System.arraycopy (mterms, 0, terms, 0, t);
-        mterms = terms;
-      }
-
-      StringIndex value = new StringIndex (retArray, mterms);
-      return value;
-    }
-  };
-
   /** The pattern used to detect integer values in a field */
   /** removed for java 1.3 compatibility
-   protected static final Pattern pIntegers = Pattern.compile ("[0-9\\-]+");
-   **/
+      protected static final Pattern pIntegers = Pattern.compile ("[0-9\\-]+");
+  **/
 
   /** The pattern used to detect float values in a field */
   /**
@@ -329,11 +194,14 @@
    * protected static final Object pFloats = Pattern.compile ("[0-9+\\-\\.eEfFdD]+");
    */
 
-	// inherit javadocs
+  // inherit javadocs
   public Object getAuto(IndexReader reader, String field) throws IOException {
+    /* :TODO: replace this method with something more direct */
+    /* :TODO: add deprecated info to javadoc */
     return autoCache.get(reader, field);
   }
 
+  /* :TODO: eliminate once getAuto has a better Impl */
   Cache autoCache = new Cache() {
 
     protected Object createValue(IndexReader reader, Object fieldKey)
@@ -383,37 +251,12 @@
 
   // inherit javadocs
   public Comparable[] getCustom(IndexReader reader, String field,
-      SortComparator comparator) throws IOException {
-    return (Comparable[]) customCache.get(reader, new Entry(field, comparator));
+                                SortComparator comparator) throws IOException {
+
+    return (Comparable[]) 
+      reader.getCachedData(new SortComparatorCacheKey
+                           (field, comparator)).getPayload();
   }
 
-  Cache customCache = new Cache() {
-
-    protected Object createValue(IndexReader reader, Object entryKey)
-        throws IOException {
-      Entry entry = (Entry) entryKey;
-      String field = entry.field;
-      SortComparator comparator = (SortComparator) entry.custom;
-      final Comparable[] retArray = new Comparable[reader.maxDoc()];
-      TermDocs termDocs = reader.termDocs();
-      TermEnum termEnum = reader.terms (new Term (field, ""));
-      try {
-        do {
-          Term term = termEnum.term();
-          if (term==null || term.field() != field) break;
-          Comparable termval = comparator.getComparable (term.text());
-          termDocs.seek (termEnum);
-          while (termDocs.next()) {
-            retArray[termDocs.doc()] = termval;
-          }
-        } while (termEnum.next());
-      } finally {
-        termDocs.close();
-        termEnum.close();
-      }
-      return retArray;
-    }
-  };
-  
 }
 
Index: src/java/org/apache/lucene/index/MultiReader.java
===================================================================
--- src/java/org/apache/lucene/index/MultiReader.java	(revision 544035)
+++ src/java/org/apache/lucene/index/MultiReader.java	(working copy)
@@ -17,6 +17,7 @@
  * limitations under the License.
  */
 
+import org.apache.lucene.index.cache.*;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.FieldSelector;
 import org.apache.lucene.store.Directory;
@@ -71,6 +72,18 @@
     starts[subReaders.length] = maxDoc;
   }
 
+  /** 
+   * Use this CacheFactory to generate a Cache for the current IndexReader, 
+   * as well as all of the subReaders.
+   *
+   * Closes the previous cache first.
+   */
+  public void useCacheFactory(CacheFactory factory) throws IOException {
+    super.useCacheFactory(factory);
+    for (int i = 0; i < subReaders.length; i++) {
+      subReaders[i].useCacheFactory(factory);
+    }
+  }
 
   public TermFreqVector[] getTermFreqVectors(int n) throws IOException {
     ensureOpen();
@@ -251,10 +264,32 @@
   }
 
   protected synchronized void doClose() throws IOException {
-    for (int i = 0; i < subReaders.length; i++)
+    for (int i = 0; i < subReaders.length; i++) 
       subReaders[i].close();
   }
 
+  /**
+   * Generates and caches the data identified by the CacheKey, 
+   * if it is not already in the cache.  
+   *
+   * Delegates to subReaders if the CacheKey is mergeable, and 
+   * then caches the merged result.
+   */
+  public CacheData getCachedData(CacheKey key) throws IOException {
+    if ( ! key.isMergable() )
+      return super.getCachedData(key);
+
+    CacheData[] data = new CacheData[subReaders.length];
+    for (int i = 0; i < subReaders.length; i++) {
+      data[i] = subReaders[i].getCachedData(key);
+    }
+    return key.mergeData(starts, data);
+  }
+
+
+  /**
+   * @see IndexReader#getFieldNames(IndexReader.FieldOption)
+   */
   public Collection getFieldNames (IndexReader.FieldOption fieldNames) {
     // maintain a unique set of field names
     ensureOpen();
Index: src/java/org/apache/lucene/index/cache/FloatArrayCacheKey.java
===================================================================
--- src/java/org/apache/lucene/index/cache/FloatArrayCacheKey.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/FloatArrayCacheKey.java	(revision 0)
@@ -0,0 +1,105 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+import java.io.IOException;
+
+/* :TODO: javadocs */
+public class FloatArrayCacheKey extends CacheKey {
+
+  /** 
+   * Interface to parse floats from document fields.
+   */
+  public interface FloatParser {
+    /** Return a float representation of this field's value. */
+    public float parseFloat(String string);
+  }
+
+  private static final FloatParser DEFAULT_PARSER = new FloatParser() {
+      public float parseFloat(String value) {
+        return Float.parseFloat(value);
+      }
+    };
+
+  String field;
+  FloatParser parser = DEFAULT_PARSER;
+    
+  public FloatArrayCacheKey(String f, FloatParser p) {
+    this(f);
+    if (null != p) parser = p;
+  }
+  public FloatArrayCacheKey(String f) {
+    field = f;
+  }
+
+  public int hashCode() {
+    /* :TODO: improve */
+    return field.hashCode() ^ parser.hashCode();
+  }
+      
+  public boolean equals(Object o) {
+    if (null == o) return false;
+    if (this == o) return true;
+    if (o instanceof FloatArrayCacheKey) {
+      FloatArrayCacheKey other = (FloatArrayCacheKey) o;
+      return parser.equals(other.parser) && field.equals(other.field);
+    }
+    return false;
+  }
+
+  public CacheData buildData(IndexReader reader) throws IOException {
+      
+    final float[] retArray = new float[reader.maxDoc()];
+    TermDocs termDocs = reader.termDocs();
+    TermEnum termEnum = reader.terms (new Term (field, ""));
+    try {
+      do {
+        Term term = termEnum.term();
+        if (term==null || term.field() != field) break;
+        float termval = parser.parseFloat(term.text());
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    return new CacheData(retArray);
+  }
+
+  public CacheData mergeData(int[] starts, CacheData[] data) 
+    throws UnsupportedOperationException {
+    
+    float[] results = new float[starts[starts.length-1]];
+    for (int i = 0; i < data.length; i++) {
+      float[] src = (float[]) data[i].getPayload();
+      System.arraycopy(src, 0, results, starts[i], src.length);
+    }
+    return new CacheData(results);
+  }
+
+  public boolean isMergable() { return true; }
+
+}

Property changes on: src/java/org/apache/lucene/index/cache/FloatArrayCacheKey.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/cache/StringArrayCacheKey.java
===================================================================
--- src/java/org/apache/lucene/index/cache/StringArrayCacheKey.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/StringArrayCacheKey.java	(revision 0)
@@ -0,0 +1,87 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+import java.io.IOException;
+
+/* :TODO: javadocs */
+public class StringArrayCacheKey extends CacheKey {
+
+  String field;
+    
+  public StringArrayCacheKey(String f) {
+    field = f.intern();
+  }
+
+  public int hashCode() {
+    /* :TODO: improve */
+    return field.hashCode() ^ 13;
+  }
+      
+  public boolean equals(Object o) {
+    if (null == o) return false;
+    if (this == o) return true;
+    if (o instanceof StringArrayCacheKey) {
+      StringArrayCacheKey other = (StringArrayCacheKey) o;
+      return field.equals(other.field);
+    }
+    return false;
+  }
+
+  public CacheData buildData(IndexReader reader) throws IOException {
+      
+    final String[] retArray = new String[reader.maxDoc()];
+    TermDocs termDocs = reader.termDocs();
+    TermEnum termEnum = reader.terms (new Term (field, ""));
+    try {
+      do {
+        Term term = termEnum.term();
+        if (term==null || term.field() != field) break;
+        String termval = term.text();
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+
+    return new CacheData(retArray);
+  }
+
+  public CacheData mergeData(int[] starts, CacheData[] data) 
+    throws UnsupportedOperationException {
+    
+    String[] results = new String[starts[starts.length-1]];
+    for (int i = 0; i < data.length; i++) {
+      String[] src = (String[]) data[i].getPayload();
+      System.arraycopy(src, 0, results, starts[i], src.length);
+    }
+    return new CacheData(results);
+  }
+
+  public boolean isMergable() { return true; }
+
+}

Property changes on: src/java/org/apache/lucene/index/cache/StringArrayCacheKey.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/cache/Cache.java
===================================================================
--- src/java/org/apache/lucene/index/cache/Cache.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/Cache.java	(revision 0)
@@ -0,0 +1,50 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+
+import java.util.Set;
+
+/**
+ * A simple Interface for modeling some form of Cache
+ */
+public interface Cache {
+
+  /**
+   * Get data from the Cache, returns null if the key is not found 
+   * in the cache.
+   */
+  public CacheData get(CacheKey key);
+
+  /**
+   * Puts data in the Cache.
+   */
+  public void put(CacheKey key, CacheData data);
+  /**
+   * returns true if the Cache contains data for the specified key.
+   */
+  public boolean containsKey(CacheKey key);
+
+  /**
+   * Called when this Cache will no longer be used anymore, so that 
+   * it can free any external resources it may have.
+   */
+  public void close();
+
+}

Property changes on: src/java/org/apache/lucene/index/cache/Cache.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/cache/IntArrayCacheKey.java
===================================================================
--- src/java/org/apache/lucene/index/cache/IntArrayCacheKey.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/IntArrayCacheKey.java	(revision 0)
@@ -0,0 +1,105 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+import java.io.IOException;
+
+/* :TODO: javadocs */
+public class IntArrayCacheKey extends CacheKey {
+
+  /** 
+   * Interface to parse ints from document fields.
+   */
+  public interface IntParser {
+    /** Return an integer representation of this field's value. */
+    public int parseInt(String string);
+  }
+
+  private static final IntParser DEFAULT_PARSER = new IntParser() {
+      public int parseInt(String value) {
+        return Integer.parseInt(value);
+      }
+    };
+  
+  String field;
+  IntParser parser = DEFAULT_PARSER;
+    
+  public IntArrayCacheKey(String f, IntParser p) {
+    this(f);
+    if (null != p) parser = p;
+  }
+  public IntArrayCacheKey(String f) {
+    field = f;
+  }
+
+  public int hashCode() {
+    /* :TODO: improve */
+    return field.hashCode() ^ parser.hashCode();
+  }
+      
+  public boolean equals(Object o) {
+    if (null == o) return false;
+    if (this == o) return true;
+    if (o instanceof IntArrayCacheKey) {
+      IntArrayCacheKey other = (IntArrayCacheKey) o;
+      return parser.equals(other.parser) && field.equals(other.field);
+    }
+    return false;
+  }
+
+  public CacheData buildData(IndexReader reader) throws IOException {
+      
+    final int[] retArray = new int[reader.maxDoc()];
+    TermDocs termDocs = reader.termDocs();
+    TermEnum termEnum = reader.terms (new Term (field, ""));
+    try {
+      do {
+        Term term = termEnum.term();
+        if (term==null || term.field() != field) break;
+        int termval = parser.parseInt(term.text());
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    return new CacheData(retArray);
+  }
+  
+  public CacheData mergeData(int[] starts, CacheData[] data) 
+    throws UnsupportedOperationException {
+    
+    int[] results = new int[starts[starts.length-1]];
+    for (int i = 0; i < data.length; i++) {
+      int[] src = (int[]) data[i].getPayload();
+      System.arraycopy(src, 0, results, starts[i], src.length);
+    }
+    return new CacheData(results);
+  }
+
+  public boolean isMergable() { return true; }
+
+}

Property changes on: src/java/org/apache/lucene/index/cache/IntArrayCacheKey.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/cache/SimpleMapCache.java
===================================================================
--- src/java/org/apache/lucene/index/cache/SimpleMapCache.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/SimpleMapCache.java	(revision 0)
@@ -0,0 +1,58 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+
+/* :TODO: javadocs */
+public class SimpleMapCache implements Cache {
+
+  private Map data = new HashMap();
+  
+  public SimpleMapCache() { /* NOOP */ }
+
+  public synchronized void close() { /* NOOP */ }
+
+  public synchronized void put(CacheKey k, CacheData v) {
+    data.put(k, v);
+  }
+
+  public synchronized boolean containsKey(CacheKey key) {
+    return data.containsKey(key);
+  }
+  public synchronized Set keySet() {
+    return Collections.unmodifiableSet(data.keySet());
+  }
+
+  public synchronized CacheData get(CacheKey key) {
+    return (CacheData) data.get(key);
+  }
+
+  public static CacheFactory FACTORY = new CacheFactory() {
+      public Cache getCache(IndexReader r) {
+        return new SimpleMapCache();
+      }
+    };
+  
+}

Property changes on: src/java/org/apache/lucene/index/cache/SimpleMapCache.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/cache/CacheFactory.java
===================================================================
--- src/java/org/apache/lucene/index/cache/CacheFactory.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/CacheFactory.java	(revision 0)
@@ -0,0 +1,29 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+
+import java.io.IOException;
+
+/** :TODO: javadocs */
+public interface CacheFactory {
+
+  public Cache getCache(IndexReader r) throws IOException;
+
+}

Property changes on: src/java/org/apache/lucene/index/cache/CacheFactory.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/cache/StringIndexCacheKey.java
===================================================================
--- src/java/org/apache/lucene/index/cache/StringIndexCacheKey.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/StringIndexCacheKey.java	(revision 0)
@@ -0,0 +1,120 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+import java.io.IOException;
+
+/* :TODO: javadocs */
+public class StringIndexCacheKey extends CacheKey {
+
+  public static class StringIndex {
+
+    /** All the term values, in natural order. */
+    public final String[] lookup;
+
+    /** For each document, an index into the lookup array. */
+    public final int[] order;
+
+    /** Creates one of these objects */
+    public StringIndex (int[] values, String[] lookup) {
+      this.order = values;
+      this.lookup = lookup;
+    }
+  }
+
+  String field;
+    
+  public StringIndexCacheKey(String f) {
+    field = f.intern();
+  }
+
+  public int hashCode() {
+    /* :TODO: improve */
+    return field.hashCode() ^ 37;
+  }
+      
+  public boolean equals(Object o) {
+    if (null == o) return false;
+    if (this == o) return true;
+    if (o instanceof StringIndexCacheKey) {
+      StringIndexCacheKey other = (StringIndexCacheKey) o;
+      return field.equals(other.field);
+    }
+    return false;
+  }
+
+  public CacheData buildData(IndexReader reader) throws IOException {
+      
+    final int[] retArray = new int[reader.maxDoc()];
+    String[] mterms = new String[reader.maxDoc()+1];
+    TermDocs termDocs = reader.termDocs();
+    TermEnum termEnum = reader.terms (new Term (field, ""));
+    int t = 0;  // current term number
+    
+    // an entry for documents that have no terms in this field
+    // should a document with no terms be at top or bottom?
+    // this puts them at the top - if it is changed, FieldDocSortedHitQueue
+    // needs to change as well.
+    mterms[t++] = null;
+    
+    try {
+      do {
+        Term term = termEnum.term();
+        if (term==null || term.field() != field) break;
+        
+        // store term text
+        // we expect that there is at most one term per document
+        if (t >= mterms.length) 
+          throw new RuntimeException 
+            ("there are more terms than documents in field \"" + field + 
+             "\", but it's impossible to sort on tokenized fields");
+        mterms[t] = term.text();
+        
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = t;
+        }
+        
+        t++;
+      } while (termEnum.next());
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    
+    if (t == 0) {
+      // if there are no terms, make the term array
+      // have a single null entry
+      mterms = new String[1];
+    } else if (t < mterms.length) {
+      // if there are less terms than documents,
+      // trim off the dead array space
+      String[] terms = new String[t];
+      System.arraycopy (mterms, 0, terms, 0, t);
+      mterms = terms;
+    }
+    
+    return new CacheData(new StringIndex (retArray, mterms));
+
+  }
+}

Property changes on: src/java/org/apache/lucene/index/cache/StringIndexCacheKey.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/cache/CacheKey.java
===================================================================
--- src/java/org/apache/lucene/index/cache/CacheKey.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/CacheKey.java	(revision 0)
@@ -0,0 +1,67 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.MultiReader;
+
+import java.io.IOException;
+
+/** 
+ * A Key for identifying cacheable data.
+ *
+ * CacheKey instances provide all of the functionality for generating 
+ * CacheData based on an IndexReader instance.
+ */
+public abstract class CacheKey {
+
+  public abstract boolean equals(Object o);
+  public abstract int hashCode();
+
+  /**
+   * Builds up the CacheData assocaited with this Key for the 
+   * specified IndexReader.
+   */
+  public abstract CacheData buildData(IndexReader r) throws IOException;
+
+  /**
+   * Merges the CacheData returned by buildData for various IndexReaders 
+   * such that the result is the same as if buildData had been called on a 
+   * MultiReader wrapping those IndexReaders.
+   *
+   * @param starts from a MultiReader, n+1 elements, where the first n are the starting offsets of each IndexReader and the last element is the total maxDoc.
+   * @param data n elements resulting from n calls to buildData
+   * @exception UnsupportedOperationException unless isMergable returns true.
+   */
+  public CacheData mergeData(int[] starts, CacheData[] data) 
+    throws UnsupportedOperationException {
+
+    throw new UnsupportedOperationException
+      ("data from this CacheKey cannot be merged");
+  }
+
+  /**
+   * Returns true if mergeData is a supported method for this CacheKey
+   *
+   * @see #mergeData
+   */
+  public boolean isMergable() {
+    return false;
+  }
+
+}

Property changes on: src/java/org/apache/lucene/index/cache/CacheKey.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/cache/CacheData.java
===================================================================
--- src/java/org/apache/lucene/index/cache/CacheData.java	(revision 0)
+++ src/java/org/apache/lucene/index/cache/CacheData.java	(revision 0)
@@ -0,0 +1,28 @@
+package org.apache.lucene.index.cache;
+
+/**
+ * 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.
+ */
+
+
+/** Encapsulates cached data */
+public class CacheData {
+  public CacheData(Object payload) {
+    this.payload = payload;
+  }
+  private Object payload;
+  public Object getPayload() { return payload; }
+}

Property changes on: src/java/org/apache/lucene/index/cache/CacheData.java
___________________________________________________________________
Name: svn:keywords
   + Date Author Id Revision HeadURL
Name: svn:eol-style
   + native

Index: src/java/org/apache/lucene/index/IndexReader.java
===================================================================
--- src/java/org/apache/lucene/index/IndexReader.java	(revision 544035)
+++ src/java/org/apache/lucene/index/IndexReader.java	(working copy)
@@ -17,6 +17,7 @@
  * limitations under the License.
  */
 
+import org.apache.lucene.index.cache.*;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.FieldSelector;
 import org.apache.lucene.search.Similarity;
@@ -112,6 +113,7 @@
     this.segmentInfos = segmentInfos;
     this.directoryOwner = directoryOwner;
     this.closeDirectory = closeDirectory;
+    this.cache = new SimpleMapCache();
   }
 
   private Directory directory;
@@ -133,6 +135,7 @@
   private Lock writeLock;
   private boolean stale;
   private boolean hasChanges;
+  private Cache cache;
 
   /** Used by commit() to record pre-commit state in case
    * rollback is necessary */
@@ -839,6 +842,7 @@
   public final synchronized void close() throws IOException {
     if (!closed) {
       commit();
+      if (null != cache) cache.close();
       doClose();
       if (directoryOwner)
         closed = true;
@@ -850,6 +854,55 @@
   /** Implements close. */
   protected abstract void doClose() throws IOException;
 
+  /** 
+   * Use this CacheFactory to generate a Cache for the current IndexReader.
+   * Closes the previous cache first.
+   */
+  public void useCacheFactory(CacheFactory factory) throws IOException {
+    if (null != cache) cache.close();
+    cache = factory.getCache(this);
+  }
+
+  /**
+   * EXPERT: raw access to the cache for introspection
+   */
+  public Cache getCache() {
+    return cache;
+  }
+
+  /**
+   * Generates and caches the data identified by the CacheKey, 
+   * if it is not already in the cache.
+   */
+  public CacheData getCachedData(CacheKey key) throws IOException {
+    CacheData value;
+    synchronized (cache) {
+      value = cache.get(key);
+      if (value == null) {
+        value = new CacheData(new CacheCreationPlaceholder());
+        cache.put(key, value);
+      }
+    }
+    Object payload = value.getPayload();
+    if (payload instanceof CacheCreationPlaceholder) {
+      synchronized (payload) {
+        CacheCreationPlaceholder progress = (CacheCreationPlaceholder) payload;
+        if (progress.value == null) {
+            progress.value = key.buildData(this);
+            synchronized (cache) {
+              cache.put(key, progress.value);
+            }
+        }
+        return progress.value;
+      }
+    }
+    return value;
+  }
+
+  private static final class CacheCreationPlaceholder {
+    CacheData value;
+  }
+
   /** Release the write lock, if needed. */
   protected void finalize() throws Throwable {
     try {
