Index: src/test/org/apache/lucene/util/LuceneTestCase.java
===================================================================
--- src/test/org/apache/lucene/util/LuceneTestCase.java	(revision 887931)
+++ src/test/org/apache/lucene/util/LuceneTestCase.java	(working copy)
@@ -25,9 +25,6 @@
 import junit.framework.TestCase;
 
 import org.apache.lucene.index.ConcurrentMergeScheduler;
-import org.apache.lucene.search.FieldCache;
-import org.apache.lucene.search.FieldCache.CacheEntry;
-import org.apache.lucene.util.FieldCacheSanityChecker.Insanity;
 
 /** 
  * Base class for all Lucene unit tests.  
@@ -67,88 +64,22 @@
     ConcurrentMergeScheduler.setTestMode();
   }
 
-  /**
-   * Forcible purges all cache entries from the FieldCache.
-   * <p>
-   * This method will be called by tearDown to clean up FieldCache.DEFAULT.
-   * If a (poorly written) test has some expectation that the FieldCache
-   * will persist across test methods (ie: a static IndexReader) this 
-   * method can be overridden to do nothing.
-   * </p>
-   * @see FieldCache#purgeAllCaches()
-   */
-  protected void purgeFieldCache(final FieldCache fc) {
-    fc.purgeAllCaches();
-  }
-
   protected String getTestLabel() {
     return getClass().getName() + "." + getName();
   }
 
   @Override
   protected void tearDown() throws Exception {
-    try {
-      // this isn't as useful as calling directly from the scope where the 
-      // index readers are used, because they could be gc'ed just before
-      // tearDown is called.
-      // But it's better then nothing.
-      assertSaneFieldCaches(getTestLabel());
-      
       if (ConcurrentMergeScheduler.anyUnhandledExceptions()) {
         // Clear the failure so that we don't just keep
         // failing subsequent test cases
         ConcurrentMergeScheduler.clearUnhandledExceptions();
         fail("ConcurrentMergeScheduler hit unhandled exceptions");
       }
-    } finally {
-      purgeFieldCache(FieldCache.DEFAULT);
-    }
     
     super.tearDown();
   }
 
-  /** 
-   * Asserts that FieldCacheSanityChecker does not detect any 
-   * problems with FieldCache.DEFAULT.
-   * <p>
-   * If any problems are found, they are logged to System.err 
-   * (allong with the msg) when the Assertion is thrown.
-   * </p>
-   * <p>
-   * This method is called by tearDown after every test method, 
-   * however IndexReaders scoped inside test methods may be garbage 
-   * collected prior to this method being called, causing errors to 
-   * be overlooked. Tests are encouraged to keep their IndexReaders 
-   * scoped at the class level, or to explicitly call this method 
-   * directly in the same scope as the IndexReader.
-   * </p>
-   * @see FieldCacheSanityChecker
-   */
-  protected void assertSaneFieldCaches(final String msg) {
-    final CacheEntry[] entries = FieldCache.DEFAULT.getCacheEntries();
-    Insanity[] insanity = null;
-    try {
-      try {
-        insanity = FieldCacheSanityChecker.checkSanity(entries);
-      } catch (RuntimeException e) {
-        dumpArray(msg+ ": FieldCache", entries, System.err);
-        throw e;
-      }
-
-      assertEquals(msg + ": Insane FieldCache usage(s) found", 
-                   0, insanity.length);
-      insanity = null;
-    } finally {
-
-      // report this in the event of any exception/failure
-      // if no failure, then insanity will be null anyway
-      if (null != insanity) {
-        dumpArray(msg + ": Insane FieldCache usage(s)", insanity, System.err);
-      }
-
-    }
-  }
-
   /**
    * Convinience method for logging an iterator.
    * @param label String logged before/after the items in the iterator
Index: src/test/org/apache/lucene/util/LuceneTestCaseJ4.java
===================================================================
--- src/test/org/apache/lucene/util/LuceneTestCaseJ4.java	(revision 887931)
+++ src/test/org/apache/lucene/util/LuceneTestCaseJ4.java	(working copy)
@@ -18,9 +18,6 @@
  */
 
 import org.apache.lucene.index.ConcurrentMergeScheduler;
-import org.apache.lucene.search.FieldCache;
-import org.apache.lucene.search.FieldCache.CacheEntry;
-import org.apache.lucene.util.FieldCacheSanityChecker.Insanity;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -92,87 +89,18 @@
     seed = null;
   }
 
-
-  /**
-   * Forcible purges all cache entries from the FieldCache.
-   * <p>
-   * This method will be called by tearDown to clean up FieldCache.DEFAULT.
-   * If a (poorly written) test has some expectation that the FieldCache
-   * will persist across test methods (ie: a static IndexReader) this
-   * method can be overridden to do nothing.
-   * </p>
-   *
-   * @see FieldCache#purgeAllCaches()
-   */
-  protected void purgeFieldCache(final FieldCache fc) {
-    fc.purgeAllCaches();
-  }
-
   protected String getTestLabel() {
     return getClass().getName() + "." + getName();
   }
 
   @After
   public void tearDown() throws Exception {
-    try {
-      // this isn't as useful as calling directly from the scope where the
-      // index readers are used, because they could be gc'ed just before
-      // tearDown is called.
-      // But it's better then nothing.
-      assertSaneFieldCaches(getTestLabel());
-
       if (ConcurrentMergeScheduler.anyUnhandledExceptions()) {
         // Clear the failure so that we don't just keep
         // failing subsequent test cases
         ConcurrentMergeScheduler.clearUnhandledExceptions();
         fail("ConcurrentMergeScheduler hit unhandled exceptions");
       }
-    } finally {
-      purgeFieldCache(FieldCache.DEFAULT);
-    }
-  }
-
-  /**
-   * Asserts that FieldCacheSanityChecker does not detect any
-   * problems with FieldCache.DEFAULT.
-   * <p>
-   * If any problems are found, they are logged to System.err
-   * (allong with the msg) when the Assertion is thrown.
-   * </p>
-   * <p>
-   * This method is called by tearDown after every test method,
-   * however IndexReaders scoped inside test methods may be garbage
-   * collected prior to this method being called, causing errors to
-   * be overlooked. Tests are encouraged to keep their IndexReaders
-   * scoped at the class level, or to explicitly call this method
-   * directly in the same scope as the IndexReader.
-   * </p>
-   *
-   * @see FieldCacheSanityChecker
-   */
-  protected void assertSaneFieldCaches(final String msg) {
-    final CacheEntry[] entries = FieldCache.DEFAULT.getCacheEntries();
-    Insanity[] insanity = null;
-    try {
-      try {
-        insanity = FieldCacheSanityChecker.checkSanity(entries);
-      } catch (RuntimeException e) {
-        dumpArray(msg + ": FieldCache", entries, System.err);
-        throw e;
-      }
-
-      assertEquals(msg + ": Insane FieldCache usage(s) found",
-              0, insanity.length);
-      insanity = null;
-    } finally {
-
-      // report this in the event of any exception/failure
-      // if no failure, then insanity will be null anyway
-      if (null != insanity) {
-        dumpArray(msg + ": Insane FieldCache usage(s)", insanity, System.err);
-      }
-
-    }
   }
 
   /**
Index: src/test/org/apache/lucene/util/TestFieldCacheSanityChecker.java
===================================================================
--- src/test/org/apache/lucene/util/TestFieldCacheSanityChecker.java	(revision 887931)
+++ src/test/org/apache/lucene/util/TestFieldCacheSanityChecker.java	(working copy)
@@ -19,14 +19,16 @@
 import org.apache.lucene.analysis.WhitespaceAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.DoubleParser;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.IntParser;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.MultiReader;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.LuceneTestCase;
-import org.apache.lucene.util.FieldCacheSanityChecker.Insanity;
-import org.apache.lucene.util.FieldCacheSanityChecker.InsanityType;
+import org.apache.lucene.util.IndexFieldCacheSanityChecker.Insanity;
+import org.apache.lucene.util.IndexFieldCacheSanityChecker.InsanityType;
 
 import java.io.IOException;
 
@@ -86,52 +88,56 @@
   }
 
   public void testSanity() throws IOException {
-    FieldCache cache = FieldCache.DEFAULT;
-    cache.purgeAllCaches();
 
     double [] doubles;
     int [] ints;
 
-    doubles = cache.getDoubles(readerA, "theDouble");
-    doubles = cache.getDoubles(readerA, "theDouble", 
-                               FieldCache.DEFAULT_DOUBLE_PARSER);
-    doubles = cache.getDoubles(readerB, "theDouble", 
-                               FieldCache.DEFAULT_DOUBLE_PARSER);
+    IndexFieldCache cacheA = readerA.getIndexCache().getFieldCache();
+    IndexFieldCache cacheB = readerB.getIndexCache().getFieldCache();
+    
+    doubles = cacheA.getDoubles("theDouble");
+    doubles = cacheA.getDoubles("theDouble", 
+                               DoubleParser.DEFAULT);
+    doubles = cacheB.getDoubles("theDouble", 
+                               DoubleParser.DEFAULT);
+    
+    IndexFieldCache cacheX = readerX.getIndexCache().getFieldCache();
 
-    ints = cache.getInts(readerX, "theInt");
-    ints = cache.getInts(readerX, "theInt", 
-                         FieldCache.DEFAULT_INT_PARSER);
+    ints = cacheX.getInts("theInt");
+    ints = cacheX.getInts("theInt", 
+                         IntParser.DEFAULT);
 
     // // // 
 
+    for(IndexFieldCache cache : new IndexFieldCache[]{cacheA,cacheB,cacheX}) {
     Insanity[] insanity = 
-      FieldCacheSanityChecker.checkSanity(cache.getCacheEntries());
+      IndexFieldCacheSanityChecker.checkSanity(cache.getCacheEntries());
     
     if (0 < insanity.length)
       dumpArray(getTestLabel() + " INSANITY", insanity, System.err);
 
     assertEquals("shouldn't be any cache insanity", 0, insanity.length);
-    cache.purgeAllCaches();
+    cache.clear();
+    }
   }
 
   public void testInsanity1() throws IOException {
-    FieldCache cache = FieldCache.DEFAULT;
-    cache.purgeAllCaches();
-
     int [] ints;
     String [] strings;
     byte [] bytes;
 
-    ints = cache.getInts(readerX, "theInt", FieldCache.DEFAULT_INT_PARSER);
-    strings = cache.getStrings(readerX, "theInt");
+    final IndexFieldCache cache = readerX.getIndexCache().getFieldCache();
+    
+    ints = cache.getInts("theInt", IntParser.DEFAULT);
+    strings = cache.getStrings("theInt");
 
     // this one is ok
-    bytes = cache.getBytes(readerX, "theByte");
+    bytes = cache.getBytes("theByte");
 
     // // // 
 
     Insanity[] insanity = 
-      FieldCacheSanityChecker.checkSanity(cache.getCacheEntries());
+      IndexFieldCacheSanityChecker.checkSanity(cache.getCacheEntries());
 
     assertEquals("wrong number of cache errors", 1, insanity.length);
     assertEquals("wrong type of cache error", 
@@ -141,38 +147,38 @@
                  insanity[0].getCacheEntries().length);
 
     // we expect bad things, don't let tearDown complain about them
-    cache.purgeAllCaches();
+    cache.clear();
   }
 
   public void testInsanity2() throws IOException {
-    FieldCache cache = FieldCache.DEFAULT;
-    cache.purgeAllCaches();
 
     String [] strings;
     byte [] bytes;
 
-    strings = cache.getStrings(readerA, "theString");
-    strings = cache.getStrings(readerB, "theString");
-    strings = cache.getStrings(readerX, "theString");
+    IndexFieldCache cacheA = readerA.getIndexCache().getFieldCache();
+    IndexFieldCache cacheB = readerB.getIndexCache().getFieldCache();
+    IndexFieldCache cacheX = readerX.getIndexCache().getFieldCache();
+    
+    strings = cacheA.getStrings("theString");
+    strings = cacheB.getStrings("theString");
+    strings = cacheX.getStrings( "theString");
 
     // this one is ok
-    bytes = cache.getBytes(readerX, "theByte");
+    bytes = cacheX.getBytes("theByte");
 
 
     // // // 
+    for(IndexFieldCache cache : new IndexFieldCache[]{cacheA,cacheB,cacheX}) {
 
     Insanity[] insanity = 
-      FieldCacheSanityChecker.checkSanity(cache.getCacheEntries());
+      IndexFieldCacheSanityChecker.checkSanity(cache.getCacheEntries());
     
-    assertEquals("wrong number of cache errors", 1, insanity.length);
-    assertEquals("wrong type of cache error", 
-                 InsanityType.SUBREADER,
-                 insanity[0].getType());
-    assertEquals("wrong number of entries in cache error", 3,
-                 insanity[0].getCacheEntries().length);
+    // with the introduction of IndexCache no insanity errors should occur
+    assertEquals("wrong number of cache errors", 0, insanity.length);
 
     // we expect bad things, don't let tearDown complain about them
-    cache.purgeAllCaches();
+    cache.clear();
+    }
   }
   
   public void testInsanity3() throws IOException {
Index: src/test/org/apache/lucene/index/TestIndexReader.java
===================================================================
--- src/test/org/apache/lucene/index/TestIndexReader.java	(revision 887931)
+++ src/test/org/apache/lucene/index/TestIndexReader.java	(working copy)
@@ -42,7 +42,6 @@
 import org.apache.lucene.document.Fieldable;
 import org.apache.lucene.document.SetBasedFieldSelector;
 import org.apache.lucene.index.IndexReader.FieldOption;
-import org.apache.lucene.search.FieldCache;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.TermQuery;
@@ -1614,16 +1613,19 @@
 
     // Open reader
     IndexReader r = SegmentReader.getOnlySegmentReader(dir);
-    final int[] ints = FieldCache.DEFAULT.getInts(r, "number");
+    final int[] ints = r.getIndexCache().getFieldCache().getInts("number");
     assertEquals(1, ints.length);
     assertEquals(17, ints[0]);
 
     // Clone reader
     IndexReader r2 = (IndexReader) r.clone();
-    r.close();
     assertTrue(r2 != r);
-    final int[] ints2 = FieldCache.DEFAULT.getInts(r2, "number");
+    final int[] ints2 = r2.getIndexCache().getFieldCache().getInts("number");
     r2.close();
+    
+    // r2 is a clone of r and may access the same IndexCache
+    // for cache reuse, however, r must be closed after r2. 
+    r.close(); 
 
     assertEquals(1, ints2.length);
     assertEquals(17, ints2[0]);
@@ -1646,7 +1648,7 @@
     // Open reader1
     IndexReader r = IndexReader.open(dir, false);
     IndexReader r1 = SegmentReader.getOnlySegmentReader(r);
-    final int[] ints = FieldCache.DEFAULT.getInts(r1, "number");
+    final int[] ints = r1.getIndexCache().getFieldCache().getInts("number");
     assertEquals(1, ints.length);
     assertEquals(17, ints[0]);
 
@@ -1655,14 +1657,47 @@
     writer.commit();
 
     // Reopen reader1 --> reader2
-    IndexReader r2 = r.reopen();
-    r.close();
-    IndexReader sub0 = r2.getSequentialSubReaders()[0];
-    final int[] ints2 = FieldCache.DEFAULT.getInts(sub0, "number");
-    r2.close();
-    assertTrue(ints == ints2);
+    {
+        IndexReader r2 = r.reopen();
+        r.close();
+        IndexReader sub0 = r2.getSequentialSubReaders()[0];
+        final int[] ints2 = sub0.getIndexCache().getFieldCache().getInts("number");
+        r2.close();
+        // If r is closed before r2, the cache cannot be reused
+        assertFalse(ints == ints2);
+    }
+  }
+  public void testFieldCacheReuseAfterReopen2() throws Exception {
+      Directory dir = new MockRAMDirectory();
+      IndexWriter writer = new IndexWriter(dir, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.UNLIMITED);
+      Document doc = new Document();
+      doc.add(new Field("number", "17", Field.Store.NO, Field.Index.NOT_ANALYZED));
+      writer.addDocument(doc);
+      writer.commit();
 
-    dir.close();
+      // Open reader1
+      IndexReader r = IndexReader.open(dir, false);
+      IndexReader r1 = SegmentReader.getOnlySegmentReader(r);
+      final int[] ints = r1.getIndexCache().getFieldCache().getInts("number");
+      assertEquals(1, ints.length);
+      assertEquals(17, ints[0]);
+
+      // Add new segment
+      writer.addDocument(doc);
+      writer.commit();
+
+      // Reopen reader1 --> reader2
+      {
+        IndexReader r2 = r.reopen();
+        IndexReader sub0 = r2.getSequentialSubReaders()[0];
+        final int[] ints2 = sub0.getIndexCache().getFieldCache().getInts("number");
+        // For cache reuse, r must be closed after r2
+        r2.close();
+        r.close();
+        assertTrue(ints == ints2);
+      }
+    
+      dir.close();
   }
 
   // LUCENE-1579: Make sure all SegmentReaders are new when
@@ -1679,7 +1714,7 @@
     IndexReader r = IndexReader.open(dir, false);
     assertTrue(r instanceof DirectoryReader);
     IndexReader r1 = SegmentReader.getOnlySegmentReader(r);
-    final int[] ints = FieldCache.DEFAULT.getInts(r1, "number");
+    final int[] ints = r1.getIndexCache().getFieldCache().getInts("number");
     assertEquals(1, ints.length);
     assertEquals(17, ints[0]);
 
@@ -1694,11 +1729,11 @@
 
     // Reopen reader1 --> reader2
     IndexReader r2 = r.reopen(true);
-    r.close();
     assertTrue(r2 instanceof ReadOnlyDirectoryReader);
     IndexReader[] subs = r2.getSequentialSubReaders();
-    final int[] ints2 = FieldCache.DEFAULT.getInts(subs[0], "number");
+    final int[] ints2 = subs[0].getIndexCache().getFieldCache().getInts("number");
     r2.close();
+    r.close();
 
     assertTrue(subs[0] instanceof ReadOnlySegmentReader);
     assertTrue(subs[1] instanceof ReadOnlySegmentReader);
Index: src/test/org/apache/lucene/search/TestCachingWrapperFilter.java
===================================================================
--- src/test/org/apache/lucene/search/TestCachingWrapperFilter.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestCachingWrapperFilter.java	(working copy)
@@ -23,6 +23,7 @@
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.search.fields.IndexFieldCacheRangeFilter;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.LuceneTestCase;
@@ -81,7 +82,7 @@
     // returns default empty docidset, always cacheable:
     assertDocIdSetCacheable(reader, NumericRangeFilter.newIntRange("test", Integer.valueOf(10000), Integer.valueOf(-10000), true, true), true);
     // is cacheable:
-    assertDocIdSetCacheable(reader, FieldCacheRangeFilter.newIntRange("test", Integer.valueOf(10), Integer.valueOf(20), true, true), true);
+    assertDocIdSetCacheable(reader, IndexFieldCacheRangeFilter.newIntRange("test", Integer.valueOf(10), Integer.valueOf(20), true, true), true);
     // a openbitset filter is always cacheable
     assertDocIdSetCacheable(reader, new Filter() {
       @Override
Index: src/test/org/apache/lucene/search/TestCustomSearcherSort.java
===================================================================
--- src/test/org/apache/lucene/search/TestCustomSearcherSort.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestCustomSearcherSort.java	(working copy)
@@ -38,6 +38,7 @@
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.search.fields.SortField;
 
 /**
  * Unit test for sorting code.
@@ -158,7 +159,7 @@
         for(int hitid=0;hitid<hitsByRank.length; ++hitid) {
             resultMap.put(
                     Integer.valueOf(hitsByRank[hitid].doc),  // Key:   Lucene Document ID
-                    Integer.valueOf(hitid));				// Value: Hits-Objekt Index
+                    Integer.valueOf(hitid));        // Value: Hits-Objekt Index
         }
         
         // now make a query using the sort criteria
Index: src/test/org/apache/lucene/search/TestDateSort.java
===================================================================
--- src/test/org/apache/lucene/search/TestDateSort.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestDateSort.java	(working copy)
@@ -30,10 +30,10 @@
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.Version;
+import org.apache.lucene.search.fields.SortField;
 
 /**
  * Test date sorting, i.e. auto-sorting of fields with type "long".
Index: src/test/org/apache/lucene/search/TestNumericRangeQuery32.java
===================================================================
--- src/test/org/apache/lucene/search/TestNumericRangeQuery32.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestNumericRangeQuery32.java	(working copy)
@@ -29,6 +29,7 @@
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.search.fields.SortField;
 
 public class TestNumericRangeQuery32 extends LuceneTestCase {
   // distance of entries
Index: src/test/org/apache/lucene/search/TestFieldCache.java
===================================================================
--- src/test/org/apache/lucene/search/TestFieldCache.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestFieldCache.java	(working copy)
@@ -21,6 +21,13 @@
 import org.apache.lucene.document.Field;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.search.fields.ByteParser;
+import org.apache.lucene.search.fields.DoubleParser;
+import org.apache.lucene.search.fields.FloatParser;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.IntParser;
+import org.apache.lucene.search.fields.LongParser;
+import org.apache.lucene.search.fields.ShortParser;
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.LuceneTestCase;
 
@@ -62,68 +69,69 @@
   }
 
   public void testInfoStream() throws Exception {
+    final IndexFieldCache cache = reader.getIndexCache().getFieldCache();
     try {
-      FieldCache cache = FieldCache.DEFAULT;
       ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
       cache.setInfoStream(new PrintStream(bos));
-      double [] doubles = cache.getDoubles(reader, "theDouble");
-      float [] floats = cache.getFloats(reader, "theDouble");
-      assertTrue(bos.toString().indexOf("WARNING") != -1);
+      double [] doubles = cache.getDoubles("theDouble");
+      float [] floats = cache.getFloats("theDouble");
+      // with the introduction of IndexCache, no warning should be output
+      assertTrue(bos.toString().indexOf("WARNING") == -1);
     } finally {
-      FieldCache.DEFAULT.purgeAllCaches();
+      cache.clear();
     }
   }
 
   public void test() throws IOException {
-    FieldCache cache = FieldCache.DEFAULT;
-    double [] doubles = cache.getDoubles(reader, "theDouble");
-    assertSame("Second request to cache return same array", doubles, cache.getDoubles(reader, "theDouble"));
-    assertSame("Second request with explicit parser return same array", doubles, cache.getDoubles(reader, "theDouble", FieldCache.DEFAULT_DOUBLE_PARSER));
+    final IndexFieldCache cache = reader.getIndexCache().getFieldCache();
+    double [] doubles = cache.getDoubles("theDouble");
+    assertSame("Second request to cache return same array", doubles, cache.getDoubles("theDouble"));
+    assertSame("Second request with explicit parser return same array", doubles, cache.getDoubles("theDouble", DoubleParser.DEFAULT));
     assertTrue("doubles Size: " + doubles.length + " is not: " + NUM_DOCS, doubles.length == NUM_DOCS);
     for (int i = 0; i < doubles.length; i++) {
       assertTrue(doubles[i] + " does not equal: " + (Double.MAX_VALUE - i), doubles[i] == (Double.MAX_VALUE - i));
 
     }
     
-    long [] longs = cache.getLongs(reader, "theLong");
-    assertSame("Second request to cache return same array", longs, cache.getLongs(reader, "theLong"));
-    assertSame("Second request with explicit parser return same array", longs, cache.getLongs(reader, "theLong", FieldCache.DEFAULT_LONG_PARSER));
+    long [] longs = cache.getLongs("theLong");
+    assertSame("Second request to cache return same array", longs, cache.getLongs("theLong"));
+    assertSame("Second request with explicit parser return same array", longs, cache.getLongs("theLong", LongParser.DEFAULT));
     assertTrue("longs Size: " + longs.length + " is not: " + NUM_DOCS, longs.length == NUM_DOCS);
     for (int i = 0; i < longs.length; i++) {
       assertTrue(longs[i] + " does not equal: " + (Long.MAX_VALUE - i), longs[i] == (Long.MAX_VALUE - i));
 
     }
     
-    byte [] bytes = cache.getBytes(reader, "theByte");
-    assertSame("Second request to cache return same array", bytes, cache.getBytes(reader, "theByte"));
-    assertSame("Second request with explicit parser return same array", bytes, cache.getBytes(reader, "theByte", FieldCache.DEFAULT_BYTE_PARSER));
+    byte [] bytes = cache.getBytes("theByte");
+    assertSame("Second request to cache return same array", bytes, cache.getBytes("theByte"));
+    assertSame("Second request with explicit parser return same array", bytes, cache.getBytes("theByte", ByteParser.DEFAULT));
     assertTrue("bytes Size: " + bytes.length + " is not: " + NUM_DOCS, bytes.length == NUM_DOCS);
     for (int i = 0; i < bytes.length; i++) {
       assertTrue(bytes[i] + " does not equal: " + (Byte.MAX_VALUE - i), bytes[i] == (byte) (Byte.MAX_VALUE - i));
 
     }
     
-    short [] shorts = cache.getShorts(reader, "theShort");
-    assertSame("Second request to cache return same array", shorts, cache.getShorts(reader, "theShort"));
-    assertSame("Second request with explicit parser return same array", shorts, cache.getShorts(reader, "theShort", FieldCache.DEFAULT_SHORT_PARSER));
+    short [] shorts = cache.getShorts("theShort");
+    assertSame("Second request to cache return same array", shorts, cache.getShorts("theShort"));
+    assertSame("Second request with explicit parser return same array", shorts, cache.getShorts("theShort", ShortParser.DEFAULT));
     assertTrue("shorts Size: " + shorts.length + " is not: " + NUM_DOCS, shorts.length == NUM_DOCS);
     for (int i = 0; i < shorts.length; i++) {
       assertTrue(shorts[i] + " does not equal: " + (Short.MAX_VALUE - i), shorts[i] == (short) (Short.MAX_VALUE - i));
 
     }
     
-    int [] ints = cache.getInts(reader, "theInt");
-    assertSame("Second request to cache return same array", ints, cache.getInts(reader, "theInt"));
-    assertSame("Second request with explicit parser return same array", ints, cache.getInts(reader, "theInt", FieldCache.DEFAULT_INT_PARSER));
+    int [] ints = cache.getInts("theInt");
+    assertSame("Second request to cache return same array", ints, cache.getInts("theInt"));
+    assertSame("Second request with explicit parser return same array", ints, cache.getInts("theInt", IntParser.DEFAULT));
     assertTrue("ints Size: " + ints.length + " is not: " + NUM_DOCS, ints.length == NUM_DOCS);
     for (int i = 0; i < ints.length; i++) {
       assertTrue(ints[i] + " does not equal: " + (Integer.MAX_VALUE - i), ints[i] == (Integer.MAX_VALUE - i));
 
     }
     
-    float [] floats = cache.getFloats(reader, "theFloat");
-    assertSame("Second request to cache return same array", floats, cache.getFloats(reader, "theFloat"));
-    assertSame("Second request with explicit parser return same array", floats, cache.getFloats(reader, "theFloat", FieldCache.DEFAULT_FLOAT_PARSER));
+    float [] floats = cache.getFloats("theFloat");
+    assertSame("Second request to cache return same array", floats, cache.getFloats("theFloat"));
+    assertSame("Second request with explicit parser return same array", floats, cache.getFloats("theFloat", FloatParser.DEFAULT));
     assertTrue("floats Size: " + floats.length + " is not: " + NUM_DOCS, floats.length == NUM_DOCS);
     for (int i = 0; i < floats.length; i++) {
       assertTrue(floats[i] + " does not equal: " + (Float.MAX_VALUE - i), floats[i] == (Float.MAX_VALUE - i));
Index: src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java
===================================================================
--- src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java	(working copy)
@@ -26,6 +26,7 @@
 import org.apache.lucene.analysis.SimpleAnalyzer;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
+import org.apache.lucene.search.fields.IndexFieldCacheRangeFilter;
 import org.apache.lucene.store.RAMDirectory;
 
 /**
@@ -65,69 +66,69 @@
     Query q = new TermQuery(new Term("body","body"));
 
     // test id, bounded on both ends
-    FieldCacheRangeFilter<String> fcrf;
-    result = search.search(q,fcrf = FieldCacheRangeFilter.newStringRange("id",minIP,maxIP,T,T), numDocs).scoreDocs;
+    IndexFieldCacheRangeFilter<String> fcrf;
+    result = search.search(q,fcrf = IndexFieldCacheRangeFilter.newStringRange("id",minIP,maxIP,T,T), numDocs).scoreDocs;
     assertTrue(fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,maxIP,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,maxIP,T,F), numDocs).scoreDocs;
     assertEquals("all but last", numDocs-1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,maxIP,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,maxIP,F,T), numDocs).scoreDocs;
     assertEquals("all but first", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,maxIP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,maxIP,F,F), numDocs).scoreDocs;
     assertEquals("all but ends", numDocs-2, result.length);
     
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",medIP,maxIP,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",medIP,maxIP,T,T), numDocs).scoreDocs;
     assertEquals("med and up", 1+ maxId-medId, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,medIP,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,medIP,T,T), numDocs).scoreDocs;
     assertEquals("up to med", 1+ medId-minId, result.length);
 
     // unbounded id
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",null,null,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",null,null,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,null,T,F), numDocs).scoreDocs;
     assertEquals("min and up", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",null,maxIP,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",null,maxIP,F,T), numDocs).scoreDocs;
     assertEquals("max and down", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,null,F,F), numDocs).scoreDocs;
     assertEquals("not min, but up", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",null,maxIP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",null,maxIP,F,F), numDocs).scoreDocs;
     assertEquals("not max, but down", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",medIP,maxIP,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",medIP,maxIP,T,F), numDocs).scoreDocs;
     assertEquals("med and up, not max", maxId-medId, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,medIP,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,medIP,F,T), numDocs).scoreDocs;
     assertEquals("not min, up to med", medId-minId, result.length);
 
     // very small sets
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,minIP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,minIP,F,F), numDocs).scoreDocs;
     assertEquals("min,min,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",medIP,medIP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",medIP,medIP,F,F), numDocs).scoreDocs;
     assertEquals("med,med,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",maxIP,maxIP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",maxIP,maxIP,F,F), numDocs).scoreDocs;
     assertEquals("max,max,F,F", 0, result.length);
                      
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",minIP,minIP,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",minIP,minIP,T,T), numDocs).scoreDocs;
     assertEquals("min,min,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",null,minIP,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",null,minIP,F,T), numDocs).scoreDocs;
     assertEquals("nul,min,F,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",maxIP,maxIP,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",maxIP,maxIP,T,T), numDocs).scoreDocs;
     assertEquals("max,max,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",maxIP,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",maxIP,null,T,F), numDocs).scoreDocs;
     assertEquals("max,nul,T,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("id",medIP,medIP,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("id",medIP,medIP,T,T), numDocs).scoreDocs;
     assertEquals("med,med,T,T", 1, result.length);
         
   }
@@ -149,47 +150,47 @@
 
     // test extremes, bounded on both ends
         
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,T,F), numDocs).scoreDocs;
     assertEquals("all but biggest", numDocs-1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,F,T), numDocs).scoreDocs;
     assertEquals("all but smallest", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",minRP,maxRP,F,F), numDocs).scoreDocs;
     assertEquals("all but extremes", numDocs-2, result.length);
     
     // unbounded
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",minRP,null,T,F), numDocs).scoreDocs;
     assertEquals("smallest and up", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",null,maxRP,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",null,maxRP,F,T), numDocs).scoreDocs;
     assertEquals("biggest and down", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",minRP,null,F,F), numDocs).scoreDocs;
     assertEquals("not smallest, but up", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",null,maxRP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",null,maxRP,F,F), numDocs).scoreDocs;
     assertEquals("not biggest, but down", numDocs-1, result.length);
         
     // very small sets
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,minRP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",minRP,minRP,F,F), numDocs).scoreDocs;
     assertEquals("min,min,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",maxRP,maxRP,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",maxRP,maxRP,F,F), numDocs).scoreDocs;
     assertEquals("max,max,F,F", 0, result.length);
                      
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",minRP,minRP,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",minRP,minRP,T,T), numDocs).scoreDocs;
     assertEquals("min,min,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",null,minRP,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",null,minRP,F,T), numDocs).scoreDocs;
     assertEquals("nul,min,F,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",maxRP,maxRP,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",maxRP,maxRP,T,T), numDocs).scoreDocs;
     assertEquals("max,max,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newStringRange("rand",maxRP,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newStringRange("rand",maxRP,null,T,F), numDocs).scoreDocs;
     assertEquals("max,nul,T,T", 1, result.length);
   }
   
@@ -212,77 +213,77 @@
     Query q = new TermQuery(new Term("body","body"));
 
     // test id, bounded on both ends
-    FieldCacheRangeFilter<Short> fcrf;
-    result = search.search(q,fcrf=FieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs;
+    IndexFieldCacheRangeFilter<Short> fcrf;
+    result = search.search(q,fcrf=IndexFieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertTrue(fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs;
     assertEquals("all but last", numDocs-1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs;
     assertEquals("all but first", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("all but ends", numDocs-2, result.length);
     
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertEquals("med and up", 1+ maxId-medId, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
     assertEquals("up to med", 1+ medId-minId, result.length);
     
     // unbounded id
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,null,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",null,null,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,null,T,F), numDocs).scoreDocs;
     assertEquals("min and up", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,maxIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",null,maxIdO,F,T), numDocs).scoreDocs;
     assertEquals("max and down", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,null,F,F), numDocs).scoreDocs;
     assertEquals("not min, but up", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",null,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("not max, but down", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs;
     assertEquals("med and up, not max", maxId-medId, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs;
     assertEquals("not min, up to med", medId-minId, result.length);
 
     // very small sets
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs;
     assertEquals("min,min,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs;
     assertEquals("med,med,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("max,max,F,F", 0, result.length);
                      
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs;
     assertEquals("min,min,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,minIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",null,minIdO,F,T), numDocs).scoreDocs;
     assertEquals("nul,min,F,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertEquals("max,max,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",maxIdO,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",maxIdO,null,T,F), numDocs).scoreDocs;
     assertEquals("max,nul,T,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs;
     assertEquals("med,med,T,T", 1, result.length);
     
     // special cases
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",Short.valueOf(Short.MAX_VALUE),null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",Short.valueOf(Short.MAX_VALUE),null,F,F), numDocs).scoreDocs;
     assertEquals("overflow special case", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",null,Short.valueOf(Short.MIN_VALUE),F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",null,Short.valueOf(Short.MIN_VALUE),F,F), numDocs).scoreDocs;
     assertEquals("overflow special case", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newShortRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newShortRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs;
     assertEquals("inverse range", 0, result.length);
   }
   
@@ -304,77 +305,77 @@
 
     // test id, bounded on both ends
         
-    FieldCacheRangeFilter<Integer> fcrf;
-    result = search.search(q,fcrf=FieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs;
+    IndexFieldCacheRangeFilter<Integer> fcrf;
+    result = search.search(q,fcrf=IndexFieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertTrue(fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs;
     assertEquals("all but last", numDocs-1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs;
     assertEquals("all but first", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("all but ends", numDocs-2, result.length);
     
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertEquals("med and up", 1+ maxId-medId, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
     assertEquals("up to med", 1+ medId-minId, result.length);
     
     // unbounded id
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,null,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",null,null,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,null,T,F), numDocs).scoreDocs;
     assertEquals("min and up", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,maxIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",null,maxIdO,F,T), numDocs).scoreDocs;
     assertEquals("max and down", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,null,F,F), numDocs).scoreDocs;
     assertEquals("not min, but up", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",null,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("not max, but down", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs;
     assertEquals("med and up, not max", maxId-medId, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs;
     assertEquals("not min, up to med", medId-minId, result.length);
 
     // very small sets
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs;
     assertEquals("min,min,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs;
     assertEquals("med,med,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("max,max,F,F", 0, result.length);
                      
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs;
     assertEquals("min,min,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,minIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",null,minIdO,F,T), numDocs).scoreDocs;
     assertEquals("nul,min,F,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertEquals("max,max,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",maxIdO,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",maxIdO,null,T,F), numDocs).scoreDocs;
     assertEquals("max,nul,T,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs;
     assertEquals("med,med,T,T", 1, result.length);
     
     // special cases
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",Integer.valueOf(Integer.MAX_VALUE),null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",Integer.valueOf(Integer.MAX_VALUE),null,F,F), numDocs).scoreDocs;
     assertEquals("overflow special case", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",null,Integer.valueOf(Integer.MIN_VALUE),F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",null,Integer.valueOf(Integer.MIN_VALUE),F,F), numDocs).scoreDocs;
     assertEquals("overflow special case", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newIntRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newIntRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs;
     assertEquals("inverse range", 0, result.length);
   }
   
@@ -396,77 +397,77 @@
 
     // test id, bounded on both ends
         
-    FieldCacheRangeFilter<Long> fcrf;
-    result = search.search(q,fcrf=FieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs;
+    IndexFieldCacheRangeFilter<Long> fcrf;
+    result = search.search(q,fcrf=IndexFieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertTrue(fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,T,F), numDocs).scoreDocs;
     assertEquals("all but last", numDocs-1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,F,T), numDocs).scoreDocs;
     assertEquals("all but first", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("all but ends", numDocs-2, result.length);
     
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",medIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertEquals("med and up", 1+ maxId-medId, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
     assertEquals("up to med", 1+ medId-minId, result.length);
     
     // unbounded id
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,null,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",null,null,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,null,T,F), numDocs).scoreDocs;
     assertEquals("min and up", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,maxIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",null,maxIdO,F,T), numDocs).scoreDocs;
     assertEquals("max and down", numDocs, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,null,F,F), numDocs).scoreDocs;
     assertEquals("not min, but up", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",null,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("not max, but down", numDocs-1, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",medIdO,maxIdO,T,F), numDocs).scoreDocs;
     assertEquals("med and up, not max", maxId-medId, result.length);
         
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,medIdO,F,T), numDocs).scoreDocs;
     assertEquals("not min, up to med", medId-minId, result.length);
 
     // very small sets
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,minIdO,F,F), numDocs).scoreDocs;
     assertEquals("min,min,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",medIdO,medIdO,F,F), numDocs).scoreDocs;
     assertEquals("med,med,F,F", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",maxIdO,maxIdO,F,F), numDocs).scoreDocs;
     assertEquals("max,max,F,F", 0, result.length);
                      
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",minIdO,minIdO,T,T), numDocs).scoreDocs;
     assertEquals("min,min,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,minIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",null,minIdO,F,T), numDocs).scoreDocs;
     assertEquals("nul,min,F,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",maxIdO,maxIdO,T,T), numDocs).scoreDocs;
     assertEquals("max,max,T,T", 1, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",maxIdO,null,T,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",maxIdO,null,T,F), numDocs).scoreDocs;
     assertEquals("max,nul,T,T", 1, result.length);
 
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",medIdO,medIdO,T,T), numDocs).scoreDocs;
     assertEquals("med,med,T,T", 1, result.length);
     
     // special cases
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",Long.valueOf(Long.MAX_VALUE),null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",Long.valueOf(Long.MAX_VALUE),null,F,F), numDocs).scoreDocs;
     assertEquals("overflow special case", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",null,Long.valueOf(Long.MIN_VALUE),F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",null,Long.valueOf(Long.MIN_VALUE),F,F), numDocs).scoreDocs;
     assertEquals("overflow special case", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newLongRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newLongRange("id",maxIdO,minIdO,T,T), numDocs).scoreDocs;
     assertEquals("inverse range", 0, result.length);
   }
   
@@ -484,19 +485,19 @@
     ScoreDoc[] result;
     Query q = new TermQuery(new Term("body","body"));
 
-    result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newFloatRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs/2, result.length);
     int count = 0;
-    result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",null,medIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newFloatRange("id",null,medIdO,F,T), numDocs).scoreDocs;
     count += result.length;
-    result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",medIdO,null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newFloatRange("id",medIdO,null,F,F), numDocs).scoreDocs;
     count += result.length;
     assertEquals("sum of two concenatted ranges", numDocs, count);
-    result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",null,null,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newFloatRange("id",null,null,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",Float.valueOf(Float.POSITIVE_INFINITY),null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newFloatRange("id",Float.valueOf(Float.POSITIVE_INFINITY),null,F,F), numDocs).scoreDocs;
     assertEquals("infinity special case", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newFloatRange("id",null,Float.valueOf(Float.NEGATIVE_INFINITY),F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newFloatRange("id",null,Float.valueOf(Float.NEGATIVE_INFINITY),F,F), numDocs).scoreDocs;
     assertEquals("infinity special case", 0, result.length);
   }
   
@@ -512,19 +513,19 @@
     ScoreDoc[] result;
     Query q = new TermQuery(new Term("body","body"));
 
-    result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newDoubleRange("id",minIdO,medIdO,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs/2, result.length);
     int count = 0;
-    result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",null,medIdO,F,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newDoubleRange("id",null,medIdO,F,T), numDocs).scoreDocs;
     count += result.length;
-    result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",medIdO,null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newDoubleRange("id",medIdO,null,F,F), numDocs).scoreDocs;
     count += result.length;
     assertEquals("sum of two concenatted ranges", numDocs, count);
-    result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",null,null,T,T), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newDoubleRange("id",null,null,T,T), numDocs).scoreDocs;
     assertEquals("find all", numDocs, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",Double.valueOf(Double.POSITIVE_INFINITY),null,F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newDoubleRange("id",Double.valueOf(Double.POSITIVE_INFINITY),null,F,F), numDocs).scoreDocs;
     assertEquals("infinity special case", 0, result.length);
-    result = search.search(q,FieldCacheRangeFilter.newDoubleRange("id",null, Double.valueOf(Double.NEGATIVE_INFINITY),F,F), numDocs).scoreDocs;
+    result = search.search(q,IndexFieldCacheRangeFilter.newDoubleRange("id",null, Double.valueOf(Double.NEGATIVE_INFINITY),F,F), numDocs).scoreDocs;
     assertEquals("infinity special case", 0, result.length);
   }
   
@@ -549,26 +550,26 @@
     assertTrue(reader.hasDeletions());
 
     ScoreDoc[] result;
-    FieldCacheRangeFilter<Byte> fcrf;
+    IndexFieldCacheRangeFilter<Byte> fcrf;
     Query q = new TermQuery(new Term("body","body"));
 
-    result = search.search(q,fcrf=FieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) -20),Byte.valueOf((byte) 20),T,T), 100).scoreDocs;
+    result = search.search(q,fcrf=IndexFieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) -20),Byte.valueOf((byte) 20),T,T), 100).scoreDocs;
     assertFalse("DocIdSet must be not cacheable", fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", 40, result.length);
 
-    result = search.search(q,fcrf=FieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) 0),Byte.valueOf((byte) 20),T,T), 100).scoreDocs;
+    result = search.search(q,fcrf=IndexFieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) 0),Byte.valueOf((byte) 20),T,T), 100).scoreDocs;
     assertFalse("DocIdSet must be not cacheable", fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", 20, result.length);
 
-    result = search.search(q,fcrf=FieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) -20),Byte.valueOf((byte) 0),T,T), 100).scoreDocs;
+    result = search.search(q,fcrf=IndexFieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) -20),Byte.valueOf((byte) 0),T,T), 100).scoreDocs;
     assertFalse("DocIdSet must be not cacheable", fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", 20, result.length);
 
-    result = search.search(q,fcrf=FieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) 10),Byte.valueOf((byte) 20),T,T), 100).scoreDocs;
+    result = search.search(q,fcrf=IndexFieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) 10),Byte.valueOf((byte) 20),T,T), 100).scoreDocs;
     assertTrue("DocIdSet must be cacheable", fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", 11, result.length);
 
-    result = search.search(q,fcrf=FieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) -20),Byte.valueOf((byte) -10),T,T), 100).scoreDocs;
+    result = search.search(q,fcrf=IndexFieldCacheRangeFilter.newByteRange("id",Byte.valueOf((byte) -20),Byte.valueOf((byte) -10),T,T), 100).scoreDocs;
     assertTrue("DocIdSet must be cacheable", fcrf.getDocIdSet(reader.getSequentialSubReaders()[0]).isCacheable());
     assertEquals("find all", 11, result.length);
   }
Index: src/test/org/apache/lucene/search/TestSort.java
===================================================================
--- src/test/org/apache/lucene/search/TestSort.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestSort.java	(working copy)
@@ -37,10 +37,18 @@
 import org.apache.lucene.index.Term;
 import org.apache.lucene.queryParser.ParseException;
 import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.fields.ByteParser;
+import org.apache.lucene.search.fields.DoubleParser;
+import org.apache.lucene.search.fields.FloatParser;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.IntParser;
+import org.apache.lucene.search.fields.LongParser;
+import org.apache.lucene.search.fields.ShortParser;
 import org.apache.lucene.store.LockObtainFailedException;
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.DocIdBitSet;
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.search.fields.SortField;
 
 /**
  * Unit tests for sorting code.
@@ -329,62 +337,48 @@
   public void testCustomFieldParserSort() throws Exception {
     // since tests explicilty uses different parsers on the same fieldname
     // we explicitly check/purge the FieldCache between each assertMatch
-    FieldCache fc = FieldCache.DEFAULT;
-
 
-    sort.setSort (new SortField ("parser", new FieldCache.IntParser(){
+    sort.setSort (new SortField ("parser",  new IntParser(){
       public final int parseInt(final String val) {
         return (val.charAt(0)-'A') * 123456;
       }
     }), SortField.FIELD_DOC );
     assertMatches (full, queryA, sort, "JIHGFEDCBA");
-    assertSaneFieldCaches(getName() + " IntParser");
-    fc.purgeAllCaches();
 
-    sort.setSort (new SortField ("parser", new FieldCache.FloatParser(){
+    sort.setSort (new SortField ("parser", new FloatParser(){
       public final float parseFloat(final String val) {
         return (float) Math.sqrt( val.charAt(0) );
       }
     }), SortField.FIELD_DOC );
     assertMatches (full, queryA, sort, "JIHGFEDCBA");
-    assertSaneFieldCaches(getName() + " FloatParser");
-    fc.purgeAllCaches();
 
-    sort.setSort (new SortField ("parser", new FieldCache.LongParser(){
+    sort.setSort (new SortField ("parser", new LongParser(){
       public final long parseLong(final String val) {
         return (val.charAt(0)-'A') * 1234567890L;
       }
     }), SortField.FIELD_DOC );
     assertMatches (full, queryA, sort, "JIHGFEDCBA");
-    assertSaneFieldCaches(getName() + " LongParser");
-    fc.purgeAllCaches();
 
-    sort.setSort (new SortField ("parser", new FieldCache.DoubleParser(){
+    sort.setSort (new SortField ("parser", new DoubleParser(){
       public final double parseDouble(final String val) {
         return Math.pow( val.charAt(0), (val.charAt(0)-'A') );
       }
     }), SortField.FIELD_DOC );
     assertMatches (full, queryA, sort, "JIHGFEDCBA");
-    assertSaneFieldCaches(getName() + " DoubleParser");
-    fc.purgeAllCaches();
 
-    sort.setSort (new SortField ("parser", new FieldCache.ByteParser(){
+    sort.setSort (new SortField ("parser", new ByteParser(){
       public final byte parseByte(final String val) {
         return (byte) (val.charAt(0)-'A');
       }
     }), SortField.FIELD_DOC );
     assertMatches (full, queryA, sort, "JIHGFEDCBA");
-    assertSaneFieldCaches(getName() + " ByteParser");
-    fc.purgeAllCaches();
 
-    sort.setSort (new SortField ("parser", new FieldCache.ShortParser(){
+    sort.setSort (new SortField ("parser", new ShortParser(){
       public final short parseShort(final String val) {
         return (short) (val.charAt(0)-'A');
       }
     }), SortField.FIELD_DOC );
     assertMatches (full, queryA, sort, "JIHGFEDCBA");
-    assertSaneFieldCaches(getName() + " ShortParser");
-    fc.purgeAllCaches();
   }
 
   // test sorts when there's nothing in the index
@@ -438,7 +432,7 @@
 
     @Override
     public void setNextReader(IndexReader reader, int docBase) throws IOException {
-      docValues = FieldCache.DEFAULT.getInts(reader, "parser", new FieldCache.IntParser() {
+      docValues = reader.getIndexCache().getFieldCache().getInts("parser", new IntParser() {
           public final int parseInt(final String val) {
             return (val.charAt(0)-'A') * 123456;
           }
@@ -938,12 +932,6 @@
     sort.setSort(new SortField ("string", SortField.STRING, true));
     assertMatches(multi, queryF, sort, "IJZ");
 
-    // up to this point, all of the searches should have "sane" 
-    // FieldCache behavior, and should have reused hte cache in several cases
-    assertSaneFieldCaches(getName() + " various");
-    // next we'll check Locale based (String[]) for 'string', so purge first
-    FieldCache.DEFAULT.purgeAllCaches();
-
     sort.setSort(new SortField ("string", Locale.US) );
     assertMatches(multi, queryA, sort, "DJAIHGFEBC");
 
@@ -952,10 +940,6 @@
 
     sort.setSort(new SortField ("string", Locale.UK) );
     assertMatches(multi, queryA, sort, "DJAIHGFEBC");
-
-    assertSaneFieldCaches(getName() + " Locale.US + Locale.UK");
-    FieldCache.DEFAULT.purgeAllCaches();
-
   }
 
   // make sure the documents returned by the search match the expected list
Index: src/test/org/apache/lucene/search/TestNumericRangeQuery64.java
===================================================================
--- src/test/org/apache/lucene/search/TestNumericRangeQuery64.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestNumericRangeQuery64.java	(working copy)
@@ -28,6 +28,7 @@
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.search.fields.SortField;
 
 public class TestNumericRangeQuery64 extends LuceneTestCase {
   // distance of entries
Index: src/test/org/apache/lucene/search/JustCompileSearch.java
===================================================================
--- src/test/org/apache/lucene/search/JustCompileSearch.java	(revision 887931)
+++ src/test/org/apache/lucene/search/JustCompileSearch.java	(working copy)
@@ -25,6 +25,9 @@
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermPositions;
+import org.apache.lucene.search.fields.DoubleParser;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.LongParser;
 import org.apache.lucene.util.PriorityQueue;
 
 /**
@@ -200,7 +203,7 @@
     }
   }
   
-  static final class JustCompileExtendedFieldCacheLongParser implements FieldCache.LongParser {
+  static final class JustCompileExtendedFieldCacheLongParser extends LongParser {
 
     public long parseLong(String string) {
       throw new UnsupportedOperationException(UNSUPPORTED_MSG);
@@ -208,7 +211,7 @@
     
   }
   
-  static final class JustCompileExtendedFieldCacheDoubleParser implements FieldCache.DoubleParser {
+  static final class JustCompileExtendedFieldCacheDoubleParser extends DoubleParser {
     
     public double parseDouble(String string) {
       throw new UnsupportedOperationException(UNSUPPORTED_MSG);
Index: src/test/org/apache/lucene/search/TestElevationComparator.java
===================================================================
--- src/test/org/apache/lucene/search/TestElevationComparator.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestElevationComparator.java	(working copy)
@@ -23,6 +23,9 @@
 import org.apache.lucene.index.*;
 import org.apache.lucene.store.*;
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.SortField;
+import org.apache.lucene.search.fields.StringIndex;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -136,7 +139,7 @@
   public FieldComparator newComparator(final String fieldname, final int numHits, int sortPos, boolean reversed) throws IOException {
    return new FieldComparator() {
 
-     FieldCache.StringIndex idIndex;
+     StringIndex idIndex;
      private final int[] values = new int[numHits];
      int bottomVal;
 
@@ -168,7 +171,7 @@
 
      @Override
      public void setNextReader(IndexReader reader, int docBase) throws IOException {
-       idIndex = FieldCache.DEFAULT.getStringIndex(reader, fieldname);
+       idIndex = reader.getIndexCache().getFieldCache().getStringIndex(fieldname);
      }
 
      @Override
Index: src/test/org/apache/lucene/search/function/JustCompileSearchSpans.java
===================================================================
--- src/test/org/apache/lucene/search/function/JustCompileSearchSpans.java	(revision 887931)
+++ src/test/org/apache/lucene/search/function/JustCompileSearchSpans.java	(working copy)
@@ -18,7 +18,7 @@
  */
 
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.IndexFieldCache;
 
 import java.io.IOException;
 
@@ -63,8 +63,7 @@
     }
 
     @Override
-    public DocValues getCachedFieldValues(FieldCache cache, String field,
-                                          IndexReader reader) throws IOException {
+    public DocValues getCachedFieldValues(IndexFieldCache cache, String field) throws IOException {
       throw new UnsupportedOperationException(UNSUPPORTED_MSG);
     }
 
Index: src/test/org/apache/lucene/search/TestFilteredQuery.java
===================================================================
--- src/test/org/apache/lucene/search/TestFilteredQuery.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestFilteredQuery.java	(working copy)
@@ -29,6 +29,7 @@
 import org.apache.lucene.util.DocIdBitSet;
 
 import java.util.BitSet;
+import org.apache.lucene.search.fields.SortField;
 
 /**
  * FilteredQuery JUnit tests.
Index: src/test/org/apache/lucene/search/TestExplanations.java
===================================================================
--- src/test/org/apache/lucene/search/TestExplanations.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestExplanations.java	(working copy)
@@ -24,6 +24,7 @@
 import org.apache.lucene.document.Field;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.search.fields.IndexFieldCacheTermsFilter;
 import org.apache.lucene.search.spans.SpanFirstQuery;
 import org.apache.lucene.search.spans.SpanNearQuery;
 import org.apache.lucene.search.spans.SpanNotQuery;
@@ -120,7 +121,7 @@
   /** 
    * Convenience subclass of FieldCacheTermsFilter
    */
-  public static class ItemizedFilter extends FieldCacheTermsFilter {
+  public static class ItemizedFilter extends IndexFieldCacheTermsFilter {
     private static String[] int2str(int [] terms) {
       String [] out = new String[terms.length];
       for (int i = 0; i < terms.length; i++) {
Index: src/test/org/apache/lucene/search/TestFieldCacheTermsFilter.java
===================================================================
--- src/test/org/apache/lucene/search/TestFieldCacheTermsFilter.java	(revision 887931)
+++ src/test/org/apache/lucene/search/TestFieldCacheTermsFilter.java	(working copy)
@@ -25,6 +25,7 @@
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriter.MaxFieldLength;
+import org.apache.lucene.search.fields.IndexFieldCacheTermsFilter;
 import org.apache.lucene.store.MockRAMDirectory;
 
 import java.util.ArrayList;
@@ -33,7 +34,7 @@
 /**
  * A basic unit test for FieldCacheTermsFilter
  *
- * @see org.apache.lucene.search.FieldCacheTermsFilter
+ * @see org.apache.lucene.search.fields.IndexFieldCacheTermsFilter
  */
 public class TestFieldCacheTermsFilter extends LuceneTestCase {
   public void testMissingTerms() throws Exception {
@@ -53,21 +54,21 @@
     int numDocs = reader.numDocs();
     ScoreDoc[] results;
     MatchAllDocsQuery q = new MatchAllDocsQuery();
-
+    
     List<String> terms = new ArrayList<String>();
     terms.add("5");
-    results = searcher.search(q, new FieldCacheTermsFilter(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
+    results = searcher.search(q, new IndexFieldCacheTermsFilter(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
     assertEquals("Must match nothing", 0, results.length);
 
     terms = new ArrayList<String>();
     terms.add("10");
-    results = searcher.search(q, new FieldCacheTermsFilter(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
+    results = searcher.search(q, new IndexFieldCacheTermsFilter(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
     assertEquals("Must match 1", 1, results.length);
 
     terms = new ArrayList<String>();
     terms.add("10");
     terms.add("20");
-    results = searcher.search(q, new FieldCacheTermsFilter(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
+    results = searcher.search(q, new IndexFieldCacheTermsFilter(fieldName,  terms.toArray(new String[0])), numDocs).scoreDocs;
     assertEquals("Must match 2", 2, results.length);
 
     reader.close();
Index: src/java/org/apache/lucene/analysis/NumericTokenStream.java
===================================================================
--- src/java/org/apache/lucene/analysis/NumericTokenStream.java	(revision 887928)
+++ src/java/org/apache/lucene/analysis/NumericTokenStream.java	(working copy)
@@ -22,8 +22,6 @@
 import org.apache.lucene.document.NumericField; // for javadocs
 import org.apache.lucene.search.NumericRangeQuery; // for javadocs
 import org.apache.lucene.search.NumericRangeFilter; // for javadocs
-import org.apache.lucene.search.SortField; // for javadocs
-import org.apache.lucene.search.FieldCache; // javadocs
 import org.apache.lucene.analysis.tokenattributes.TermAttribute;
 import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
Index: src/java/org/apache/lucene/analysis/Analyzer.java
===================================================================
--- src/java/org/apache/lucene/analysis/Analyzer.java	(revision 887928)
+++ src/java/org/apache/lucene/analysis/Analyzer.java	(working copy)
@@ -137,7 +137,7 @@
   }
 
   /** Frees persistent resources used by this Analyzer */
-  public void close() {
+  public void close() throws IOException {
     tokenStreams.close();
     tokenStreams = null;
   }
Index: src/java/org/apache/lucene/document/NumericField.java
===================================================================
--- src/java/org/apache/lucene/document/NumericField.java	(revision 887928)
+++ src/java/org/apache/lucene/document/NumericField.java	(working copy)
@@ -25,7 +25,7 @@
 import org.apache.lucene.search.NumericRangeQuery; // javadocs
 import org.apache.lucene.search.NumericRangeFilter; // javadocs
 import org.apache.lucene.search.SortField; // javadocs
-import org.apache.lucene.search.FieldCache; // javadocs
+import org.apache.lucene.search.fields.IndexFieldCache;
 
 /**
  * <p>This class provides a {@link Field} that enables indexing
@@ -70,7 +70,7 @@
  * NumericRangeFilter}.  To sort according to a
  * <code>NumericField</code>, use the normal numeric sort types, eg
  * {@link SortField#INT}. <code>NumericField</code> values
- * can also be loaded directly from {@link FieldCache}.</p>
+ * can also be loaded directly from {@link IndexFieldCache}.</p>
  *
  * <p>By default, a <code>NumericField</code>'s value is not stored but
  * is indexed for range filtering and sorting.  You can use
Index: src/java/org/apache/lucene/search/Sort.java
===================================================================
--- src/java/org/apache/lucene/search/Sort.java	(revision 887928)
+++ src/java/org/apache/lucene/search/Sort.java	(working copy)
@@ -20,7 +20,6 @@
 import java.io.Serializable;
 import java.util.Arrays;
 
-
 /**
  * Encapsulates sort criteria for returned hits.
  *
@@ -99,6 +98,8 @@
  */
 public class Sort
 implements Serializable {
+  // NOTE: Must probably be changed once the old SortField class has been removed
+  private static final long serialVersionUID = 1L;
 
   /**
    * Represents sorting by computed relevance. Using this sort criteria returns
@@ -109,7 +110,7 @@
   public static final Sort RELEVANCE = new Sort();
 
   /** Represents sorting by index order. */
-  public static final Sort INDEXORDER = new Sort(SortField.FIELD_DOC);
+  public static final Sort INDEXORDER = new Sort(org.apache.lucene.search.fields.SortField.FIELD_DOC);
 
   // internal representation of the sort criteria
   SortField[] fields;
@@ -120,29 +121,56 @@
    * only with slightly more overhead.
    */
   public Sort() {
-    this(SortField.FIELD_SCORE);
+    this(org.apache.lucene.search.fields.SortField.FIELD_SCORE);
   }
 
-  /** Sorts by the criteria in the given SortField. */
+  /** Sorts by the criteria in the given SortField.
+   * @deprecated Use {@link Sort#Sort(org.apache.lucene.search.fields.SortField)} instead.
+   */
   public Sort(SortField field) {
     setSort(field);
   }
 
-  /** Sorts in succession by the criteria in each SortField. */
+  /** Sorts in succession by the criteria in each SortField.
+   * @deprecated Use {@link Sort#Sort(org.apache.lucene.search.fields.SortField...)} instead. 
+   */
   public Sort(SortField... fields) {
     setSort(fields);
   }
 
-  /** Sets the sort to the given criteria. */
+  /** Sets the sort to the given criteria.
+   * @deprecated Use {@link #setSort(org.apache.lucene.search.fields.SortField)} instead.
+   */
   public void setSort(SortField field) {
     this.fields = new SortField[] { field };
   }
 
-  /** Sets the sort to the given criteria in succession. */
+  /** Sets the sort to the given criteria in succession.
+   * @deprecated Use {@link #setSort(org.apache.lucene.search.fields.SortField...)} instead.
+   */
   public void setSort(SortField... fields) {
     this.fields = fields;
   }
   
+  /** Sorts by the criteria in the given SortField. */
+  public Sort(org.apache.lucene.search.fields.SortField field) {
+    setSort(field);
+  }
+
+  /** Sorts in succession by the criteria in each SortField. */
+  public Sort(org.apache.lucene.search.fields.SortField... fields) {
+    setSort(fields);
+  }
+
+  /** Sets the sort to the given criteria. */
+  public void setSort(org.apache.lucene.search.fields.SortField field) {
+    this.fields = new SortField[] { field };
+  }
+
+  /** Sets the sort to the given criteria in succession. */
+  public void setSort(org.apache.lucene.search.fields.SortField... fields) {
+    this.fields = fields;
+  }  
   /**
    * Representation of the sort criteria.
    * @return Array of SortField objects used in this sort criteria
Index: src/java/org/apache/lucene/search/function/OrdFieldSource.java
===================================================================
--- src/java/org/apache/lucene/search/function/OrdFieldSource.java	(revision 887928)
+++ src/java/org/apache/lucene/search/function/OrdFieldSource.java	(working copy)
@@ -18,13 +18,13 @@
 package org.apache.lucene.search.function;
 
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.IndexFieldCache;
 
 import java.io.IOException;
 
 /**
  * Expert: obtains the ordinal of the field value from the default Lucene 
- * {@link org.apache.lucene.search.FieldCache Fieldcache} using getStringIndex().
+ * {@link IndexFieldCache} using getStringIndex().
  * <p>
  * The native lucene index order is used to assign an ordinal value for each field value.
  * <p
@@ -47,7 +47,7 @@
  * <p><b>NOTE</b>: with the switch in 2.9 to segment-based
  * searching, if {@link #getValues} is invoked with a
  * composite (multi-segment) reader, this can easily cause
- * double RAM usage for the values in the FieldCache.  It's
+ * double RAM usage for the values in the {@link IndexFieldCache}.  It's
  * best to switch your application to pass only atomic
  * (single segment) readers to this API.</p>
  */
@@ -72,7 +72,7 @@
   /*(non-Javadoc) @see org.apache.lucene.search.function.ValueSource#getValues(org.apache.lucene.index.IndexReader) */
   @Override
   public DocValues getValues(IndexReader reader) throws IOException {
-    final int[] arr = FieldCache.DEFAULT.getStringIndex(reader, field).order;
+    final int[] arr = reader.getIndexCache().getFieldCache().getStringIndex(field).order;
     return new DocValues() {
       /*(non-Javadoc) @see org.apache.lucene.search.function.DocValues#floatVal(int) */
       @Override
Index: src/java/org/apache/lucene/search/function/ShortFieldSource.java
===================================================================
--- src/java/org/apache/lucene/search/function/ShortFieldSource.java	(revision 887928)
+++ src/java/org/apache/lucene/search/function/ShortFieldSource.java	(working copy)
@@ -18,7 +18,8 @@
  */
 
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.ShortParser;
 import org.apache.lucene.search.function.DocValues;
 
 import java.io.IOException;
@@ -48,7 +49,7 @@
  * but will not consume double the FieldCache RAM.</p>
  */
 public class ShortFieldSource extends FieldCacheSource {
-  private FieldCache.ShortParser parser;
+  private ShortParser parser;
 
   /**
    * Create a cached short field source with default string-to-short parser. 
@@ -60,7 +61,7 @@
   /**
    * Create a cached short field source with a specific string-to-short parser. 
    */
-  public ShortFieldSource(String field, FieldCache.ShortParser parser) {
+  public ShortFieldSource(String field, ShortParser parser) {
     super(field);
     this.parser = parser;
   }
@@ -71,10 +72,10 @@
     return "short(" + super.description() + ')';
   }
 
-  /*(non-Javadoc) @see org.apache.lucene.search.function.FieldCacheSource#getCachedValues(org.apache.lucene.search.FieldCache, java.lang.String, org.apache.lucene.index.IndexReader) */
+  /*(non-Javadoc) @see org.apache.lucene.search.function.FieldCacheSource#getCachedValues(org.apache.lucene.search.IndexFieldCache, java.lang.String) */
   @Override
-  public DocValues getCachedFieldValues (FieldCache cache, String field, IndexReader reader) throws IOException {
-    final short[] arr = cache.getShorts(reader, field, parser);
+  public DocValues getCachedFieldValues (IndexFieldCache cache, String field) throws IOException {
+    final short[] arr = cache.getShorts(field, parser);
     return new DocValues() {
       /*(non-Javadoc) @see org.apache.lucene.search.function.DocValues#floatVal(int) */
       @Override
Index: src/java/org/apache/lucene/search/function/ReverseOrdFieldSource.java
===================================================================
--- src/java/org/apache/lucene/search/function/ReverseOrdFieldSource.java	(revision 887928)
+++ src/java/org/apache/lucene/search/function/ReverseOrdFieldSource.java	(working copy)
@@ -18,13 +18,14 @@
 package org.apache.lucene.search.function;
 
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.StringIndex;
 
 import java.io.IOException;
 
 /**
  * Expert: obtains the ordinal of the field value from the default Lucene 
- * {@link org.apache.lucene.search.FieldCache FieldCache} using getStringIndex()
+ * {@link IndexFieldCache} using getStringIndex()
  * and reverses the order.
  * <p>
  * The native lucene index order is used to assign an ordinal value for each field value.
@@ -48,7 +49,7 @@
  * <p><b>NOTE</b>: with the switch in 2.9 to segment-based
  * searching, if {@link #getValues} is invoked with a
  * composite (multi-segment) reader, this can easily cause
- * double RAM usage for the values in the FieldCache.  It's
+ * double RAM usage for the values in the {@link IndexFieldCache}.  It's
  * best to switch your application to pass only atomic
  * (single segment) readers to this API.</p>
  */
@@ -73,7 +74,7 @@
   /*(non-Javadoc) @see org.apache.lucene.search.function.ValueSource#getValues(org.apache.lucene.index.IndexReader) */
   @Override
   public DocValues getValues(IndexReader reader) throws IOException {
-    final FieldCache.StringIndex sindex = FieldCache.DEFAULT.getStringIndex(reader, field);
+    final StringIndex sindex = reader.getIndexCache().getFieldCache().getStringIndex(field);
 
     final int arr[] = sindex.order;
     final int end = sindex.lookup.length;
Index: src/java/org/apache/lucene/search/function/ByteFieldSource.java
===================================================================
--- src/java/org/apache/lucene/search/function/ByteFieldSource.java	(revision 887928)
+++ src/java/org/apache/lucene/search/function/ByteFieldSource.java	(working copy)
@@ -18,7 +18,8 @@
  */
 
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.ByteParser;
+import org.apache.lucene.search.fields.IndexFieldCache;
 import org.apache.lucene.search.function.DocValues;
 
 import java.io.IOException;
@@ -48,7 +49,7 @@
  * but will not consume double the FieldCache RAM.</p>
  */
 public class ByteFieldSource extends FieldCacheSource {
-  private FieldCache.ByteParser parser;
+  private ByteParser parser;
 
   /**
    * Create a cached byte field source with default string-to-byte parser. 
@@ -60,7 +61,7 @@
   /**
    * Create a cached byte field source with a specific string-to-byte parser. 
    */
-  public ByteFieldSource(String field, FieldCache.ByteParser parser) {
+  public ByteFieldSource(String field, ByteParser parser) {
     super(field);
     this.parser = parser;
   }
@@ -73,8 +74,8 @@
 
   /*(non-Javadoc) @see org.apache.lucene.search.function.FieldCacheSource#getCachedValues(org.apache.lucene.search.FieldCache, java.lang.String, org.apache.lucene.index.IndexReader) */
   @Override
-  public DocValues getCachedFieldValues (FieldCache cache, String field, IndexReader reader) throws IOException {
-    final byte[] arr = cache.getBytes(reader, field, parser);
+  public DocValues getCachedFieldValues (IndexFieldCache cache, String field) throws IOException {
+    final byte[] arr = cache.getBytes(field, parser);
     return new DocValues() {
       /*(non-Javadoc) @see org.apache.lucene.search.function.DocValues#floatVal(int) */
       @Override
Index: src/java/org/apache/lucene/search/function/FieldCacheSource.java
===================================================================
--- src/java/org/apache/lucene/search/function/FieldCacheSource.java	(revision 887928)
+++ src/java/org/apache/lucene/search/function/FieldCacheSource.java	(working copy)
@@ -21,6 +21,7 @@
 
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.IndexFieldCache;
 
 /**
  * Expert: A base class for ValueSource implementations that retrieve values for
@@ -59,7 +60,7 @@
   /* (non-Javadoc) @see org.apache.lucene.search.function.ValueSource#getValues(org.apache.lucene.index.IndexReader) */
   @Override
   public final DocValues getValues(IndexReader reader) throws IOException {
-    return getCachedFieldValues(FieldCache.DEFAULT, field, reader);
+    return getCachedFieldValues(reader.getIndexCache().getFieldCache(), field);
   }
 
   /* (non-Javadoc) @see org.apache.lucene.search.function.ValueSource#description() */
@@ -70,11 +71,20 @@
 
   /**
    * Return cached DocValues for input field and reader.
-   * @param cache FieldCache so that values of a field are loaded once per reader (RAM allowing)
+   * @param cache The IndexFieldCache of the reader for which values are required (values of a field are loaded once per reader)
    * @param field Field for which values are required.
    * @see ValueSource
    */
-  public abstract DocValues getCachedFieldValues(FieldCache cache, String field, IndexReader reader) throws IOException;
+  public DocValues getCachedFieldValues(IndexFieldCache cache, String field) throws IOException {
+      throw new UnsupportedOperationException("You must override FieldCacheSource#getCachedFieldValues");
+  }
+  public DocValues getCachedFieldValues(FieldCache cache, String field, IndexReader reader) throws IOException {
+      if(cache == FieldCache.DEFAULT) {
+          return getCachedFieldValues(reader.getIndexCache().getFieldCache(), field);
+      } else {
+          throw new UnsupportedOperationException("You must override FieldCacheSource#getCachedFieldValues");
+      }
+  }
 
   /*(non-Javadoc) @see java.lang.Object#equals(java.lang.Object) */
   @Override
Index: src/java/org/apache/lucene/search/function/FloatFieldSource.java
===================================================================
--- src/java/org/apache/lucene/search/function/FloatFieldSource.java	(revision 887928)
+++ src/java/org/apache/lucene/search/function/FloatFieldSource.java	(working copy)
@@ -18,14 +18,15 @@
  */
 
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.FloatParser;
+import org.apache.lucene.search.fields.IndexFieldCache;
 import org.apache.lucene.search.function.DocValues;
 
 import java.io.IOException;
 
 /**
  * Expert: obtains float field values from the 
- * {@link org.apache.lucene.search.FieldCache FieldCache}
+ * {@link IndexFieldCache}
  * using <code>getFloats()</code> and makes those values 
  * available as other numeric types, casting as needed.
  * 
@@ -40,15 +41,15 @@
  * <p><b>NOTE</b>: with the switch in 2.9 to segment-based
  * searching, if {@link #getValues} is invoked with a
  * composite (multi-segment) reader, this can easily cause
- * double RAM usage for the values in the FieldCache.  It's
+ * double RAM usage for the values in the {@link IndexFieldCache}.  It's
  * best to switch your application to pass only atomic
  * (single segment) readers to this API.  Alternatively, for
  * a short-term fix, you could wrap your ValueSource using
  * {@link MultiValueSource}, which costs more CPU per lookup
- * but will not consume double the FieldCache RAM.</p>
+ * but will not consume double the {@link IndexFieldCache} RAM.</p>
  */
 public class FloatFieldSource extends FieldCacheSource {
-  private FieldCache.FloatParser parser;
+  private FloatParser parser;
 
   /**
    * Create a cached float field source with default string-to-float parser. 
@@ -60,7 +61,7 @@
   /**
    * Create a cached float field source with a specific string-to-float parser. 
    */
-  public FloatFieldSource(String field, FieldCache.FloatParser parser) {
+  public FloatFieldSource(String field, FloatParser parser) {
     super(field);
     this.parser = parser;
   }
@@ -73,8 +74,8 @@
 
   /*(non-Javadoc) @see org.apache.lucene.search.function.FieldCacheSource#getCachedValues(org.apache.lucene.search.FieldCache, java.lang.String, org.apache.lucene.index.IndexReader) */
   @Override
-  public DocValues getCachedFieldValues (FieldCache cache, String field, IndexReader reader) throws IOException {
-    final float[] arr = cache.getFloats(reader, field, parser);
+  public DocValues getCachedFieldValues (IndexFieldCache cache, String field) throws IOException {
+    final float[] arr = cache.getFloats(field, parser);
     return new DocValues() {
       /*(non-Javadoc) @see org.apache.lucene.search.function.DocValues#floatVal(int) */
       @Override
Index: src/java/org/apache/lucene/search/function/IntFieldSource.java
===================================================================
--- src/java/org/apache/lucene/search/function/IntFieldSource.java	(revision 887928)
+++ src/java/org/apache/lucene/search/function/IntFieldSource.java	(working copy)
@@ -18,7 +18,8 @@
  */
 
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.IntParser;
 import org.apache.lucene.search.function.DocValues;
 
 import java.io.IOException;
@@ -48,7 +49,7 @@
  * but will not consume double the FieldCache RAM.</p>
  */
 public class IntFieldSource extends FieldCacheSource {
-  private FieldCache.IntParser parser;
+  private IntParser parser;
 
   /**
    * Create a cached int field source with default string-to-int parser. 
@@ -60,7 +61,7 @@
   /**
    * Create a cached int field source with a specific string-to-int parser. 
    */
-  public IntFieldSource(String field, FieldCache.IntParser parser) {
+  public IntFieldSource(String field, IntParser parser) {
     super(field);
     this.parser = parser;
   }
@@ -73,8 +74,8 @@
 
   /*(non-Javadoc) @see org.apache.lucene.search.function.FieldCacheSource#getCachedValues(org.apache.lucene.search.FieldCache, java.lang.String, org.apache.lucene.index.IndexReader) */
   @Override
-  public DocValues getCachedFieldValues (FieldCache cache, String field, IndexReader reader) throws IOException {
-    final int[] arr = cache.getInts(reader, field, parser);
+  public DocValues getCachedFieldValues (IndexFieldCache cache, String field) throws IOException {
+    final int[] arr = cache.getInts(field, parser);
     return new DocValues() {
       /*(non-Javadoc) @see org.apache.lucene.search.function.DocValues#floatVal(int) */
       @Override
Index: src/java/org/apache/lucene/search/TermRangeFilter.java
===================================================================
--- src/java/org/apache/lucene/search/TermRangeFilter.java	(revision 887928)
+++ src/java/org/apache/lucene/search/TermRangeFilter.java	(working copy)
@@ -19,6 +19,8 @@
 
 import java.text.Collator;
 
+import org.apache.lucene.search.fields.IndexFieldCacheRangeFilter;
+
 /**
  * A Filter that restricts search results to a range of term
  * values in a given field.
@@ -29,7 +31,7 @@
  * for numerical ranges; use {@link NumericRangeFilter} instead.
  *
  * <p>If you construct a large number of range filters with different ranges but on the 
- * same field, {@link FieldCacheRangeFilter} may have significantly better performance. 
+ * same field, {@link IndexFieldCacheRangeFilter} may have significantly better performance. 
  * @since 2.9
  */
 public class TermRangeFilter extends MultiTermQueryWrapperFilter<TermRangeQuery> {
Index: src/java/org/apache/lucene/search/FieldCacheImpl.java
===================================================================
--- src/java/org/apache/lucene/search/FieldCacheImpl.java	(revision 887928)
+++ src/java/org/apache/lucene/search/FieldCacheImpl.java	(working copy)
@@ -29,6 +29,7 @@
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermDocs;
 import org.apache.lucene.index.TermEnum;
+import org.apache.lucene.search.fields.IndexFieldCacheImpl;
 import org.apache.lucene.util.StringHelper;
 import org.apache.lucene.util.FieldCacheSanityChecker;
 
@@ -39,6 +40,8 @@
  * <p>Created: May 19, 2004 4:40:36 PM
  *
  * @since   lucene 1.4
+ * @deprecated Not used anymore. Just there for backwards-compatibility. Use {@link IndexFieldCacheImpl} instead.
+ * @see IndexFieldCacheImpl
  */
 class FieldCacheImpl implements FieldCache {
 	
Index: src/java/org/apache/lucene/search/FieldCacheRangeFilter.java
===================================================================
--- src/java/org/apache/lucene/search/FieldCacheRangeFilter.java	(revision 887928)
+++ src/java/org/apache/lucene/search/FieldCacheRangeFilter.java	(working copy)
@@ -20,6 +20,7 @@
 
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.search.fields.IndexFieldCacheImpl;
 import org.apache.lucene.util.NumericUtils;
 import org.apache.lucene.document.NumericField; // for javadocs
 
@@ -50,8 +51,9 @@
  *
  * <p>This class does not have an constructor, use one of the static factory methods available,
  * that create a correct instance for different data types supported by {@link FieldCache}.
+ * @deprecated Not used anymore. Just there for backwards-compatibility. Use {@link IndexFieldCacheImpl} instead.
+ * @see IndexFieldCacheImpl
  */
-
 public abstract class FieldCacheRangeFilter<T> extends Filter {
   final String field;
   final FieldCache.Parser parser;
Index: src/java/org/apache/lucene/search/FieldCacheTermsFilter.java
===================================================================
--- src/java/org/apache/lucene/search/FieldCacheTermsFilter.java	(revision 887928)
+++ src/java/org/apache/lucene/search/FieldCacheTermsFilter.java	(working copy)
@@ -20,6 +20,7 @@
 import java.io.IOException;
 
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.fields.IndexFieldCacheImpl;
 import org.apache.lucene.util.OpenBitSet;
 import org.apache.lucene.index.TermDocs;  // for javadocs
 
@@ -91,8 +92,9 @@
  * <p/>
  *
  * Which filter is best is very application dependent.
+ * @deprecated Not used anymore. Just there for backwards-compatibility. Use {@link IndexFieldCacheImpl} instead.
+ * @see IndexFieldCacheImpl
  */
-
 public class FieldCacheTermsFilter extends Filter {
   private String field;
   private String[] terms;
Index: src/java/org/apache/lucene/search/FieldComparatorSource.java
===================================================================
--- src/java/org/apache/lucene/search/FieldComparatorSource.java	(revision 887928)
+++ src/java/org/apache/lucene/search/FieldComparatorSource.java	(working copy)
@@ -20,6 +20,8 @@
 import java.io.IOException;
 import java.io.Serializable;
 
+import org.apache.lucene.search.fields.FieldParser;
+
 /**
  * Provides a {@link FieldComparator} for custom field sorting.
  *
@@ -28,16 +30,53 @@
  *
  */
 public abstract class FieldComparatorSource implements Serializable {
+    private static final long serialVersionUID = 1L;
+    
+    public abstract static class Fieldless extends FieldComparatorSource {
+        private static final long serialVersionUID = 1L;
+        
+    }
 
+/**
+   * Creates a comparator for the field in the given index.
+   * 
+   * @param field
+   *          Name of the field to create comparator for.
+   * @return FieldComparator.
+   * @throws IOException
+   *           If an error occurs reading the index.
+   * @deprecated Use {@link #newComparator(String, int, int, boolean, FieldParser)} instead.
+   */
+  public FieldComparator newComparator(String field, int numHits, int sortPos, boolean reverse)
+      throws IOException {
+      throw new UnsupportedOperationException("You must override FieldComparator#newComparator");
+  }
+  
   /**
    * Creates a comparator for the field in the given index.
    * 
-   * @param fieldname
+   * @param field
    *          Name of the field to create comparator for.
    * @return FieldComparator.
    * @throws IOException
    *           If an error occurs reading the index.
    */
-  public abstract FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed)
-      throws IOException;
+  public FieldComparator newComparator(String field, int numHits, int sortPos, boolean reverse, final FieldParser parser)
+      throws IOException {
+      return newComparator(field, numHits, sortPos, reverse);
+  }
+  
+  /**
+   * Returns a numeric identifier for this FieldComparatorSource's type.
+   * 
+   * @return
+   * @deprecated Only for backwards-compatibility with {@link SortField#getType()}
+   */
+  public int getTypeId() {
+      return 9; // SortField.CUSTOM in Lucene <= 3.0
+  }
+  
+  public String getString(final String field) {
+      return "<custom:\""+field+"\": "+toString()+">";
+  }
 }
Index: src/java/org/apache/lucene/search/FieldCache.java
===================================================================
--- src/java/org/apache/lucene/search/FieldCache.java	(revision 887928)
+++ src/java/org/apache/lucene/search/FieldCache.java	(working copy)
@@ -18,6 +18,8 @@
  */
 
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.fields.FieldParser;
+import org.apache.lucene.search.fields.IndexFieldCache;
 import org.apache.lucene.util.NumericUtils;
 import org.apache.lucene.util.RamUsageEstimator;
 import org.apache.lucene.document.NumericField; // for javadocs
@@ -36,6 +38,8 @@
  *
  * @since   lucene 1.4
  * @see org.apache.lucene.util.FieldCacheSanityChecker
+ * @deprecated Not necessary anymore. Just for backwards-compatibility. Use {@link IndexFieldCache} instead.
+ * @see IndexFieldCache
  */
 public interface FieldCache {
 
@@ -49,7 +53,9 @@
   public static final int STRING_INDEX = -1;
 
 
-  /** Expert: Stores term text values and document ordering data. */
+  /** Expert: Stores term text values and document ordering data.
+   * @deprecated Use {@link org.apache.lucene.search.fields.StringIndex} instead.
+   **/
   public static class StringIndex {
 	  
     public int binarySearchLookup(String key) {
@@ -91,12 +97,14 @@
    * Marker interface as super-interface to all parsers. It
    * is used to specify a custom parser to {@link
    * SortField#SortField(String, FieldCache.Parser)}.
+   * @deprecated Use {@link FieldParser} instead
    */
   public interface Parser extends Serializable {
   }
 
   /** Interface to parse bytes from document fields.
    * @see FieldCache#getBytes(IndexReader, String, FieldCache.ByteParser)
+   * @deprecated Use {@link org.apache.lucene.search.fields.ByteParser} instead.
    */
   public interface ByteParser extends Parser {
     /** Return a single Byte representation of this field's value. */
@@ -105,6 +113,7 @@
 
   /** Interface to parse shorts from document fields.
    * @see FieldCache#getShorts(IndexReader, String, FieldCache.ShortParser)
+   * @deprecated Use {@link org.apache.lucene.search.fields.ShortParser} instead.
    */
   public interface ShortParser extends Parser {
     /** Return a short representation of this field's value. */
@@ -113,6 +122,7 @@
 
   /** Interface to parse ints from document fields.
    * @see FieldCache#getInts(IndexReader, String, FieldCache.IntParser)
+   * @deprecated Use {@link org.apache.lucene.search.fields.IntParser} instead.
    */
   public interface IntParser extends Parser {
     /** Return an integer representation of this field's value. */
@@ -121,6 +131,7 @@
 
   /** Interface to parse floats from document fields.
    * @see FieldCache#getFloats(IndexReader, String, FieldCache.FloatParser)
+   * @deprecated Use {@link org.apache.lucene.search.fields.FloatParser} instead.
    */
   public interface FloatParser extends Parser {
     /** Return an float representation of this field's value. */
@@ -129,6 +140,7 @@
 
   /** Interface to parse long from document fields.
    * @see FieldCache#getLongs(IndexReader, String, FieldCache.LongParser)
+   * @deprecated Use {@link org.apache.lucene.search.fields.LongParser} instead.
    */
   public interface LongParser extends Parser {
     /** Return an long representation of this field's value. */
@@ -137,13 +149,16 @@
 
   /** Interface to parse doubles from document fields.
    * @see FieldCache#getDoubles(IndexReader, String, FieldCache.DoubleParser)
+   * @deprecated Use {@link org.apache.lucene.search.fields.DoubleParser} instead.
    */
   public interface DoubleParser extends Parser {
     /** Return an long representation of this field's value. */
     public double parseDouble(String string);
   }
 
-  /** Expert: The cache used internally by sorting and range query classes. */
+  /** Expert: The cache used internally by sorting and range query classes.
+   * @deprecated With the introduction of {@link IndexFieldCache} there is no "default" cache anymore.
+   */
   public static FieldCache DEFAULT = new FieldCacheImpl();
   
   /** The default parser for byte values, which are encoded by {@link Byte#toString(byte)} */
@@ -502,6 +517,7 @@
    * releases 
    * of Lucene.
    * </p>
+   * @deprecated Use {@link org.apache.lucene.search.fields.CacheEntry} instead.
    */
   public static abstract class CacheEntry {
     public abstract Object getReaderKey();
Index: src/java/org/apache/lucene/search/FieldValueHitQueue.java
===================================================================
--- src/java/org/apache/lucene/search/FieldValueHitQueue.java	(revision 887928)
+++ src/java/org/apache/lucene/search/FieldValueHitQueue.java	(working copy)
@@ -68,7 +68,7 @@
 
       SortField field = fields[0];
       comparator = field.getComparator(size, 0);
-      oneReverseMul = field.reverse ? -1 : 1;
+      oneReverseMul = field.getReverse() ? -1 : 1;
 
       comparators[0] = comparator;
       reverseMul[0] = oneReverseMul;
@@ -113,7 +113,7 @@
       for (int i = 0; i < numComparators; ++i) {
         SortField field = fields[i];
 
-        reverseMul[i] = field.reverse ? -1 : 1;
+        reverseMul[i] = field.getReverse() ? -1 : 1;
         comparators[i] = field.getComparator(size, i);
       }
 
Index: src/java/org/apache/lucene/search/FieldComparator.java
===================================================================
--- src/java/org/apache/lucene/search/FieldComparator.java	(revision 887928)
+++ src/java/org/apache/lucene/search/FieldComparator.java	(working copy)
@@ -29,6 +29,7 @@
 import org.apache.lucene.search.FieldCache.IntParser;
 import org.apache.lucene.search.FieldCache.ShortParser;
 import org.apache.lucene.search.FieldCache.StringIndex;
+import org.apache.lucene.search.fields.IndexFieldCache;
 
 /**
  * Expert: a FieldComparator compares hits so as to determine their
@@ -161,10 +162,12 @@
    * @param slot the value
    * @return value in this slot upgraded to Comparable
    */
-  public abstract Comparable value(int slot);
+  public abstract Comparable<?> value(int slot);
 
   /** Parses field's values as byte (using {@link
-   *  FieldCache#getBytes} and sorts by ascending value */
+   *  FieldCache#getBytes} and sorts by ascending value 
+   *  @deprecated Use {@link org.apache.lucene.search.fields.ByteComparator} instead
+   */
   public static final class ByteComparator extends FieldComparator {
     private final byte[] values;
     private byte[] currentReaderValues;
@@ -204,12 +207,15 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return Byte.valueOf(values[slot]);
     }
   }
 
-  /** Sorts by ascending docID */
+  /** Sorts by ascending docID
+   *
+   * @deprecated Use {@link org.apache.lucene.search.fields.DocComparator} instead
+   */
   public static final class DocComparator extends FieldComparator {
     private final int[] docIDs;
     private int docBase;
@@ -250,13 +256,15 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return Integer.valueOf(docIDs[slot]);
     }
   }
 
   /** Parses field's values as double (using {@link
-   *  FieldCache#getDoubles} and sorts by ascending value */
+   *  FieldCache#getDoubles} and sorts by ascending value 
+   *  @deprecated Use {@link org.apache.lucene.search.fields.DoubleComparator} instead.
+   */
   public static final class DoubleComparator extends FieldComparator {
     private final double[] values;
     private double[] currentReaderValues;
@@ -311,13 +319,15 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return Double.valueOf(values[slot]);
     }
   }
 
   /** Parses field's values as float (using {@link
-   *  FieldCache#getFloats} and sorts by ascending value */
+   *  FieldCache#getFloats} and sorts by ascending value
+   *  @deprecated Use {@link org.apache.lucene.search.fields.FloatComparator} instead.
+   */
   public static final class FloatComparator extends FieldComparator {
     private final float[] values;
     private float[] currentReaderValues;
@@ -376,13 +386,15 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return Float.valueOf(values[slot]);
     }
   }
 
   /** Parses field's values as int (using {@link
-   *  FieldCache#getInts} and sorts by ascending value */
+   *  FieldCache#getInts} and sorts by ascending value
+   *  @deprecated Use {@link org.apache.lucene.search.fields.IntComparator} instead.
+   */
   public static final class IntComparator extends FieldComparator {
     private final int[] values;
     private int[] currentReaderValues;
@@ -445,13 +457,15 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return Integer.valueOf(values[slot]);
     }
   }
 
   /** Parses field's values as long (using {@link
-   *  FieldCache#getLongs} and sorts by ascending value */
+   *  FieldCache#getLongs} and sorts by ascending value
+   *  @deprecated Use {@link org.apache.lucene.search.fields.LongComparator} instead.
+   */
   public static final class LongComparator extends FieldComparator {
     private final long[] values;
     private long[] currentReaderValues;
@@ -510,7 +524,7 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return Long.valueOf(values[slot]);
     }
   }
@@ -520,7 +534,10 @@
    *  secondarily by ascending docID, performance is faster
    *  using {@link TopScoreDocCollector} directly (which {@link
    *  IndexSearcher#search} uses when no {@link Sort} is
-   *  specified). */
+   *  specified).
+   *
+   *  @deprecated Use {@link org.apache.lucene.search.fields.RelevanceComparator} instead.
+   */
   public static final class RelevanceComparator extends FieldComparator {
     private final float[] scores;
     private float bottom;
@@ -565,13 +582,15 @@
     }
     
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return Float.valueOf(scores[slot]);
     }
   }
 
   /** Parses field's values as short (using {@link
-   *  FieldCache#getShorts} and sorts by ascending value */
+   *  FieldCache#getShorts} and sorts by ascending value
+   *  @deprecated Use {@link org.apache.lucene.search.fields.ShortComparator} instead.
+   */
   public static final class ShortComparator extends FieldComparator {
     private final short[] values;
     private short[] currentReaderValues;
@@ -611,13 +630,15 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return Short.valueOf(values[slot]);
     }
   }
 
   /** Sorts by a field's value using the Collator for a
-   *  given Locale.*/
+   *  given Locale.
+   *  @deprecated Use {@link org.apache.lucene.search.fields.StringComparatorLocale} instead.
+   */
   public static final class StringComparatorLocale extends FieldComparator {
 
     private final String[] values;
@@ -677,7 +698,7 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return values[slot];
     }
   }
@@ -690,7 +711,9 @@
    *  does most comparisons using the ordinals.  For medium
    *  to large results, this comparator will be much faster
    *  than {@link StringValComparator}.  For very small
-   *  result sets it may be slower. */
+   *  result sets it may be slower.
+   *  @deprecated Use {@link org.apache.lucene.search.fields.StringOrdValComparator} instead.
+   */
   public static final class StringOrdValComparator extends FieldComparator {
 
     private final int[] ords;
@@ -826,7 +849,7 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return values[slot];
     }
 
@@ -846,7 +869,9 @@
   /** Sorts by field's natural String sort order.  All
    *  comparisons are done using String.compareTo, which is
    *  slow for medium to large result sets but possibly
-   *  very fast for very small results sets. */
+   *  very fast for very small results sets.
+   *  @deprecated Use {@link org.apache.lucene.search.fields.StringValComparator} instead.
+   */
   public static final class StringValComparator extends FieldComparator {
 
     private String[] values;
@@ -905,7 +930,7 @@
     }
 
     @Override
-    public Comparable value(int slot) {
+    public Comparable<?> value(int slot) {
       return values[slot];
     }
   }
Index: src/java/org/apache/lucene/search/fields/ByteCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/ByteCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/ByteCache.java	(revision 0)
@@ -0,0 +1,48 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+/**
+ * Expert: A {@link Cache} that converts terms to bytes.
+ */
+final class ByteCache extends Cache {
+  private final IndexReader reader;
+
+  ByteCache(final IndexReader reader) {
+    this.reader = reader;
+  }  
+  @Override
+  protected Object createValue(Entry entryKey)
+      throws IOException {
+    Entry entry = entryKey;
+    String field = entry.field;
+    ByteParser parser = (ByteParser) entry.custom;
+    if (parser == null) {
+      parser = ByteParser.DEFAULT;
+    }
+    final byte[] retArray = new byte[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;
+        byte termval = parser.parseByte(term.text());
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } catch (StopFillCacheException stop) {
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    return retArray;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/ByteComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/ByteComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/ByteComparator.java	(revision 0)
@@ -0,0 +1,51 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Parses field's values as byte and sorts by ascending value */
+  public final class ByteComparator extends FieldComparator {
+    private final byte[] values;
+    private byte[] currentReaderValues;
+    private final String field;
+    private ByteParser parser;
+    private byte bottom;
+
+    ByteComparator(int numHits, String field, FieldParser parser) {
+      values = new byte[numHits];
+      this.field = field;
+      this.parser = (ByteParser) parser;
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      return values[slot1] - values[slot2];
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      return bottom - currentReaderValues[doc];
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      values[slot] = currentReaderValues[doc];
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      currentReaderValues = reader.getIndexCache().getFieldCache().getBytes(field, parser);
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      this.bottom = values[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return Byte.valueOf(values[slot]);
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/ByteParser.java
===================================================================
--- src/java/org/apache/lucene/search/fields/ByteParser.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/ByteParser.java	(revision 0)
@@ -0,0 +1,34 @@
+package org.apache.lucene.search.fields;
+
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.FieldComparatorSource;
+
+
+/** Parse bytes from document fields.
+ */
+public abstract class ByteParser extends FieldParser.WithFieldComparatorSource implements FieldCache.ByteParser {
+    private static final long serialVersionUID = 1L;
+
+/** Return a single Byte representation of this field's value. */
+  public abstract byte parseByte(String string);
+
+  public final FieldComparatorSource getFieldComparatorSource() {
+      return SortField.BYTE;
+  }
+
+  /** The default parser for byte values, which are encoded by {@link ByteParser#toString(byte)} */
+  public static final ByteParser DEFAULT = new ByteParser() {
+    private static final long serialVersionUID = 1L;
+    public byte parseByte(String value) {
+      return java.lang.Byte.parseByte(value);
+    }
+    protected Object readResolve() {
+      return DEFAULT;
+    }
+    @Override
+    public String toString() { 
+      return ByteParser.class.getName()+".DEFAULT"; 
+    }
+  };
+
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/Cache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/Cache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/Cache.java	(revision 0)
@@ -0,0 +1,37 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.search.fields.IndexFieldCache.CreationPlaceholder;
+
+/** Expert: Internal cache implementation for {@link IndexFieldCache}. */
+abstract class Cache {
+  Cache() {
+  }
+
+  final Map<Entry,Object> innerCache = new HashMap<Entry,Object>();
+  
+  protected abstract Object createValue(Entry key)
+      throws IOException;
+
+  public Object get(Entry key) throws IOException {
+    Object 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(key);
+          innerCache.put(key, progress.value);
+        }
+        return progress.value;
+      }
+    }
+    return value;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/CacheEntry.java
===================================================================
--- src/java/org/apache/lucene/search/fields/CacheEntry.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/CacheEntry.java	(revision 0)
@@ -0,0 +1,67 @@
+package org.apache.lucene.search.fields;
+
+import java.text.DecimalFormat;
+
+import org.apache.lucene.util.RamUsageEstimator;
+
+/**
+ * EXPERT: A unique Identifier/Description for each item in the IndexFieldCache. 
+ * Can be useful for logging/debugging.
+ * <p>
+ * <b>EXPERIMENTAL API:</b> This API is considered extremely advanced 
+ * and experimental.  It may be removed or altered w/o warning in future 
+ * releases 
+ * of Lucene.
+ * </p>
+ */
+public abstract class CacheEntry {
+  public abstract String getFieldName();
+  public abstract Class<?> getCacheType();
+  public abstract Object getCustom();
+  public abstract Object getValue();
+  private String size = null;
+  protected final void setEstimatedSize(String size) {
+    this.size = size;
+  }
+  /** 
+   * @see #estimateSize(RamUsageEstimator)
+   */
+  public void estimateSize() {
+    estimateSize(new RamUsageEstimator(false)); // doesn't check for interned
+  }
+  /** 
+   * Computes (and stores) the estimated size of the cache Value 
+   * @see #getEstimatedSize
+   */
+  public void estimateSize(RamUsageEstimator ramCalc) {
+    long size = ramCalc.estimateRamUsage(getValue());
+    setEstimatedSize(RamUsageEstimator.humanReadableUnits
+                     (size, new DecimalFormat("0.#")));
+                      
+  }
+  /**
+   * The most recently estimated size of the value, null unless 
+   * estimateSize has been called.
+   */
+  public final String getEstimatedSize() {
+    return size;
+  }
+  
+  
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append("'").append(getFieldName()).append("',");
+    b.append(getCacheType()).append(",").append(getCustom());
+    b.append("=>").append(getValue().getClass().getName()).append("#");
+    b.append(System.identityHashCode(getValue()));
+    
+    String s = getEstimatedSize();
+    if(null != s) {
+      b.append(" (size =~ ").append(s).append(')');
+    }
+
+    return b.toString();
+  }
+
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/CacheEntryImpl.java
===================================================================
--- src/java/org/apache/lucene/search/fields/CacheEntryImpl.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/CacheEntryImpl.java	(revision 0)
@@ -0,0 +1,25 @@
+package org.apache.lucene.search.fields;
+
+final class CacheEntryImpl extends CacheEntry {
+  private final String fieldName;
+  private final Class<?> cacheType;
+  private final Object custom;
+  private final Object value;
+  CacheEntryImpl(String fieldName,
+                 Class<?> cacheType,
+                 Object custom,
+                 Object value) {
+      this.fieldName = fieldName;
+      this.cacheType = cacheType;
+      this.custom = custom;
+      this.value = value;
+  }
+  @Override
+  public String getFieldName() { return fieldName; }
+  @Override
+  public Class<?> getCacheType() { return cacheType; }
+  @Override
+  public Object getCustom() { return custom; }
+  @Override
+  public Object getValue() { return value; }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/DocComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/DocComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/DocComparator.java	(revision 0)
@@ -0,0 +1,50 @@
+package org.apache.lucene.search.fields;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Sorts by ascending docID */
+  public final class DocComparator extends FieldComparator {
+    private final int[] docIDs;
+    private int docBase;
+    private int bottom;
+
+    DocComparator(int numHits) {
+      docIDs = new int[numHits];
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      // No overflow risk because docIDs are non-negative
+      return docIDs[slot1] - docIDs[slot2];
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      // No overflow risk because docIDs are non-negative
+      return bottom - (docBase + doc);
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      docIDs[slot] = docBase + doc;
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) {
+      // TODO: can we "map" our docIDs to the current
+      // reader? saves having to then subtract on every
+      // compare call
+      this.docBase = docBase;
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      this.bottom = docIDs[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return Integer.valueOf(docIDs[slot]);
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/DoubleCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/DoubleCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/DoubleCache.java	(revision 0)
@@ -0,0 +1,68 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+/**
+ * Expert: A {@link Cache} that converts terms to doubles.
+ */
+final class DoubleCache extends Cache {
+  private final IndexReader reader;
+
+  DoubleCache(final IndexReader reader) {
+    this.reader = reader;
+  }
+  
+  @Override
+  protected Object createValue(Entry entryKey)
+      throws IOException {
+    Entry entry = entryKey;
+    String field = entry.field;
+    DoubleParser parser = (DoubleParser) entry.custom;
+    double[] retArray = null;
+    TermDocs termDocs = reader.termDocs();
+    TermEnum termEnum = reader.terms (new Term (field));
+    try {
+      do {
+        Term term = termEnum.term();
+        if (term==null || term.field() != field) break;
+
+        double termval;        
+        final String text = term.text();
+        if (parser == null) {
+          parser = DoubleParser.DEFAULT;
+        }
+        try {
+          termval = parser.parseDouble(text);
+        } catch(NumberFormatException ne) {
+          if(parser == DoubleParser.DEFAULT) {
+            // continue with NUMERIC_UTILS parser
+            parser = DoubleParser.NUMERIC_UTILS;
+            termval = parser.parseDouble(text);
+          } else {
+            // unexpected
+            throw ne;
+          }
+        }
+        
+        if (retArray == null) // late init
+          retArray = new double[reader.maxDoc()];
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } catch (StopFillCacheException stop) {
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    if (retArray == null) // no values
+      retArray = new double[reader.maxDoc()];
+    return retArray;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/DoubleComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/DoubleComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/DoubleComparator.java	(revision 0)
@@ -0,0 +1,66 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Parses field's values as double and sorts by ascending value */
+  public final class DoubleComparator extends FieldComparator {
+    private final double[] values;
+    private double[] currentReaderValues;
+    private final String field;
+    private DoubleParser parser;
+    private double bottom;
+
+    DoubleComparator(int numHits, String field, FieldParser parser) {
+      values = new double[numHits];
+      this.field = field;
+      this.parser = (DoubleParser) parser;
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      final double v1 = values[slot1];
+      final double v2 = values[slot2];
+      if (v1 > v2) {
+        return 1;
+      } else if (v1 < v2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      final double v2 = currentReaderValues[doc];
+      if (bottom > v2) {
+        return 1;
+      } else if (bottom < v2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      values[slot] = currentReaderValues[doc];
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      currentReaderValues = reader.getIndexCache().getFieldCache().getDoubles(field, parser);
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      this.bottom = values[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return Double.valueOf(values[slot]);
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/DoubleParser.java
===================================================================
--- src/java/org/apache/lucene/search/fields/DoubleParser.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/DoubleParser.java	(revision 0)
@@ -0,0 +1,58 @@
+package org.apache.lucene.search.fields;
+
+import org.apache.lucene.analysis.NumericTokenStream;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.FieldComparatorSource;
+import org.apache.lucene.util.NumericUtils;
+
+/** Parse doubles from document fields.
+ */
+public abstract class DoubleParser extends FieldParser.WithFieldComparatorSource implements FieldCache.DoubleParser {
+    private static final long serialVersionUID = 1L;
+
+    /** Return an long representation of this field's value. */
+  public abstract double parseDouble(String string);
+
+  public final FieldComparatorSource getFieldComparatorSource() {
+      return SortField.DOUBLE;
+  }
+
+  /** The default parser for double values, which are encoded by {@link DoubleParser#toString(double)} */
+  public static final DoubleParser DEFAULT = new DoubleParser() {
+    private static final long serialVersionUID = 1L;
+    public double parseDouble(String value) {
+      return java.lang.Double.parseDouble(value);
+    }
+    protected Object readResolve() {
+      return DEFAULT;
+    }
+    @Override
+    public String toString() { 
+      return DoubleParser.class.getName()+".DEFAULT"; 
+    }
+  };
+
+
+  /**
+   * A parser instance for double values encoded with {@link NumericUtils}, e.g. when indexed
+   * via {@link NumericField}/{@link NumericTokenStream}.
+   */
+  public static final DoubleParser NUMERIC_UTILS = new DoubleParser(){
+    private static final long serialVersionUID = 1L;
+    public double parseDouble(String val) {
+      final int shift = val.charAt(0)-NumericUtils.SHIFT_START_LONG;
+      if (shift>0 && shift<=63)
+        throw new StopFillCacheException();
+      return NumericUtils.sortableLongToDouble(NumericUtils.prefixCodedToLong(val));
+    }
+    protected Object readResolve() {
+      return NUMERIC_UTILS;
+    }
+    @Override
+    public String toString() { 
+      return DoubleParser.class.getName()+".NUMERIC_UTILS"; 
+    }
+  };
+            
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/Entry.java
===================================================================
--- src/java/org/apache/lucene/search/fields/Entry.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/Entry.java	(revision 0)
@@ -0,0 +1,37 @@
+package org.apache.lucene.search.fields;
+
+import org.apache.lucene.util.StringHelper;
+
+/** Expert: Every composite-key in the internal cache is of this type. */
+class Entry {
+  final String field;        // which Fieldable
+  final Object custom;       // which custom comparator or parser
+
+  /** Creates one of these objects for a custom comparator/parser. */
+  Entry (String field, Object custom) {
+    this.field = StringHelper.intern(field);
+    this.custom = 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.custom == null) {
+          if (custom == null) return true;
+        } else if (other.custom.equals (custom)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /** Composes a hashcode based on the field and type. */
+  @Override
+  public int hashCode() {
+    return field.hashCode() ^ (custom==null ? 0 : custom.hashCode());
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/FieldParser.java
===================================================================
--- src/java/org/apache/lucene/search/fields/FieldParser.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/FieldParser.java	(revision 0)
@@ -0,0 +1,31 @@
+package org.apache.lucene.search.fields;
+
+import java.io.Serializable;
+
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.FieldComparatorSource;
+import org.apache.lucene.search.SortField;
+
+/**
+ * Provides a base for parsers used by {@link SortField}.
+ * 
+ * NOTE: This class currently implements the deprecated
+ * {@link FieldCache.Parser}. This interface will be removed later, please do
+ * not rely on it.
+ */
+@SuppressWarnings("deprecation")
+public abstract class FieldParser implements Serializable, FieldCache.Parser {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * This kind of {@link FieldParser} provides its own {@link FieldComparatorSource}.
+   * 
+   * @author Christian Kohlschuetter
+   */
+  public abstract static class WithFieldComparatorSource extends FieldParser {
+    private static final long serialVersionUID = 1L;
+
+    public abstract FieldComparatorSource getFieldComparatorSource();
+  }
+
+}
Index: src/java/org/apache/lucene/search/fields/FloatCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/FloatCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/FloatCache.java	(revision 0)
@@ -0,0 +1,68 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+/**
+ * Expert: A {@link Cache} that converts terms to floats.
+ */
+final class FloatCache extends Cache {
+  private final IndexReader reader;
+
+  FloatCache(final IndexReader reader) {
+    this.reader = reader;
+  }
+  
+  @Override
+  protected Object createValue(Entry entryKey)
+      throws IOException {
+    Entry entry = entryKey;
+    String field = entry.field;
+    FloatParser parser = (FloatParser) entry.custom;
+    float[] retArray = null;
+    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;
+        final String text = term.text();
+        if (parser == null) {
+          parser = FloatParser.DEFAULT;
+        }
+        try {
+          termval = parser.parseFloat(text);
+        } catch(NumberFormatException ne) {
+          if(parser == FloatParser.DEFAULT) {
+            // continue with NUMERIC_UTILS parser
+            parser = FloatParser.NUMERIC_UTILS;
+            termval = parser.parseFloat(text);
+          } else {
+            // unexpected
+            throw ne;
+          }
+        }
+        
+        if (retArray == null) // late init
+          retArray = new float[reader.maxDoc()];
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } catch (StopFillCacheException stop) {
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    if (retArray == null) // no values
+      retArray = new float[reader.maxDoc()];
+    return retArray;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/FloatComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/FloatComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/FloatComparator.java	(revision 0)
@@ -0,0 +1,70 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Parses field's values as float and sorts by ascending value */
+  public final class FloatComparator extends FieldComparator {
+    private final float[] values;
+    private float[] currentReaderValues;
+    private final String field;
+    private FloatParser parser;
+    private float bottom;
+
+    FloatComparator(int numHits, String field, FieldParser parser) {
+      values = new float[numHits];
+      this.field = field;
+      this.parser = (FloatParser) parser;
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      // TODO: are there sneaky non-branch ways to compute
+      // sign of float?
+      final float v1 = values[slot1];
+      final float v2 = values[slot2];
+      if (v1 > v2) {
+        return 1;
+      } else if (v1 < v2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      // TODO: are there sneaky non-branch ways to compute
+      // sign of float?
+      final float v2 = currentReaderValues[doc];
+      if (bottom > v2) {
+        return 1;
+      } else if (bottom < v2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      values[slot] = currentReaderValues[doc];
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      currentReaderValues = reader.getIndexCache().getFieldCache().getFloats(field, parser);
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      this.bottom = values[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return Float.valueOf(values[slot]);
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/FloatParser.java
===================================================================
--- src/java/org/apache/lucene/search/fields/FloatParser.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/FloatParser.java	(revision 0)
@@ -0,0 +1,57 @@
+package org.apache.lucene.search.fields;
+
+import org.apache.lucene.analysis.NumericTokenStream;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.FieldComparatorSource;
+import org.apache.lucene.util.NumericUtils;
+
+/** Parse floats from document fields.
+ */
+public abstract class FloatParser extends FieldParser.WithFieldComparatorSource implements FieldCache.FloatParser {
+    private static final long serialVersionUID = 1L;
+
+    /** Return an float representation of this field's value. */
+  public abstract float parseFloat(String string);
+
+  public final FieldComparatorSource getFieldComparatorSource() {
+      return SortField.FLOAT;
+  }
+  
+  /** The default parser for float values, which are encoded by {@link FloatParser#toString(float)} */
+  public static final FloatParser DEFAULT = new FloatParser() {
+    private static final long serialVersionUID = 1L;
+    public float parseFloat(String value) {
+      return java.lang.Float.parseFloat(value);
+    }
+    protected Object readResolve() {
+      return DEFAULT;
+    }
+    @Override
+    public String toString() { 
+      return FloatParser.class.getName()+".DEFAULT"; 
+    }
+  };
+
+  /**
+   * A parser instance for float values encoded with {@link NumericUtils}, e.g. when indexed
+   * via {@link NumericField}/{@link NumericTokenStream}.
+   */
+  public static final FloatParser NUMERIC_UTILS = new FloatParser(){
+    private static final long serialVersionUID = 1L;
+    public float parseFloat(String val) {
+      final int shift = val.charAt(0)-NumericUtils.SHIFT_START_INT;
+      if (shift>0 && shift<=31)
+        throw new StopFillCacheException();
+      return NumericUtils.sortableIntToFloat(NumericUtils.prefixCodedToInt(val));
+    }
+    protected Object readResolve() {
+      return NUMERIC_UTILS;
+    }
+    @Override
+    public String toString() { 
+      return FloatParser.class.getName()+".NUMERIC_UTILS"; 
+    }
+  };
+
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/IndexFieldCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/IndexFieldCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/IndexFieldCache.java	(revision 0)
@@ -0,0 +1,239 @@
+package org.apache.lucene.search.fields;
+
+/**
+ * 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.Closeable;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import org.apache.lucene.index.IndexReader;
+
+/**
+ * Expert: Maintains caches of term values.
+ */
+public interface IndexFieldCache extends Closeable {
+
+  public static final class CreationPlaceholder {
+    Object value;
+  }
+  
+  /** Checks the internal cache for an appropriate entry, and if none is
+   * found, reads the terms in <code>field</code> as a single byte and returns an array
+   * of size <code>reader.maxDoc()</code> of the value each document
+   * has in the given field.
+   * @param reader  Used to get field values.
+   * @param field   Which field contains the single byte values.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public byte[] getBytes (String field)
+  throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if none is found,
+   * reads the terms in <code>field</code> as bytes and returns an array of
+   * size <code>reader.maxDoc()</code> of the value each document has in the
+   * given field.
+   * @param field   Which field contains the bytes.
+   * @param parser  Computes byte for string values.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public byte[] getBytes (String field, ByteParser parser)
+  throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if none is
+   * found, reads the terms in <code>field</code> as shorts and returns an array
+   * of size <code>reader.maxDoc()</code> of the value each document
+   * has in the given field.
+   * @param field   Which field contains the shorts.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public short[] getShorts (String field)
+  throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if none is found,
+   * reads the terms in <code>field</code> as shorts and returns an array of
+   * size <code>reader.maxDoc()</code> of the value each document has in the
+   * given field.
+   * @param field   Which field contains the shorts.
+   * @param parser  Computes short for string values.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public short[] getShorts (String field, ShortParser parser)
+  throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if none is
+   * found, reads the terms in <code>field</code> as integers and returns an array
+   * of size <code>reader.maxDoc()</code> of the value each document
+   * has in the given field.
+   * @param reader  Used to get field values.
+   * @param field   Which field contains the integers.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public int[] getInts (String field)
+  throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if none is found,
+   * reads the terms in <code>field</code> as integers and returns an array of
+   * size <code>reader.maxDoc()</code> of the value each document has in the
+   * given field.
+   * @param field   Which field contains the integers.
+   * @param parser  Computes integer for string values.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public int[] getInts (String field, IntParser parser)
+  throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if
+   * none is found, reads the terms in <code>field</code> as floats and returns an array
+   * of size <code>reader.maxDoc()</code> of the value each document
+   * has in the given field.
+   * @param field   Which field contains the floats.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public float[] getFloats (String field)
+  throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if
+   * none is found, reads the terms in <code>field</code> as floats and returns an array
+   * of size <code>reader.maxDoc()</code> of the value each document
+   * has in the given field.
+   * @param field   Which field contains the floats.
+   * @param parser  Computes float for string values.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public float[] getFloats (String field,
+                            FloatParser parser) throws IOException;
+  
+  /**
+   * Checks the internal cache for an appropriate entry, and if none is
+   * found, reads the terms in <code>field</code> as longs and returns an array
+   * of size <code>reader.maxDoc()</code> of the value each document
+   * has in the given field.
+   *
+   * @param reader Used to get field values.
+   * @param field  Which field contains the longs.
+   * @return The values in the given field for each document.
+   * @throws java.io.IOException If any error occurs.
+   */
+  public long[] getLongs(String field)
+          throws IOException;
+
+  /**
+   * Checks the internal cache for an appropriate entry, and if none is found,
+   * reads the terms in <code>field</code> as longs and returns an array of
+   * size <code>reader.maxDoc()</code> of the value each document has in the
+   * given field.
+   *
+   * @param field  Which field contains the longs.
+   * @param parser Computes integer for string values.
+   * @return The values in the given field for each document.
+   * @throws IOException If any error occurs.
+   */
+  public long[] getLongs(String field, LongParser parser)
+          throws IOException;
+
+
+  /**
+   * Checks the internal cache for an appropriate entry, and if none is
+   * found, reads the terms in <code>field</code> as integers and returns an array
+   * of size <code>reader.maxDoc()</code> of the value each document
+   * has in the given field.
+   *
+   * @param field  Which field contains the doubles.
+   * @return The values in the given field for each document.
+   * @throws IOException If any error occurs.
+   */
+  public double[] getDoubles(String field)
+          throws IOException;
+
+  /**
+   * Checks the internal cache for an appropriate entry, and if none is found,
+   * reads the terms in <code>field</code> as doubles and returns an array of
+   * size <code>reader.maxDoc()</code> of the value each document has in the
+   * given field.
+   *
+   * @param field  Which field contains the doubles.
+   * @param parser Computes integer for string values.
+   * @return The values in the given field for each document.
+   * @throws IOException If any error occurs.
+   */
+  public double[] getDoubles(String field, DoubleParser parser)
+          throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if none
+   * is found, reads the term values in <code>field</code> and returns an array
+   * of size <code>reader.maxDoc()</code> containing the value each document
+   * has in the given field.
+   * @param reader  Used to get field values.
+   * @param field   Which field contains the strings.
+   * @return The values in the given field for each document.
+   * @throws IOException  If any error occurs.
+   */
+  public String[] getStrings (String field)
+  throws IOException;
+
+  /** Checks the internal cache for an appropriate entry, and if none
+   * is found reads the term values in <code>field</code> and returns
+   * an array of them in natural order, along with an array telling
+   * which element in the term array each document uses.
+   * @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.
+   */
+  public StringIndex getStringIndex (String field)
+  throws IOException;
+
+  /**
+   * EXPERT: Generates an array of CacheEntry objects representing all items 
+   * currently in the IndexFieldCache.
+   * <p>
+   * <b>EXPERIMENTAL API:</b> This API is considered extremely advanced 
+   * and experimental.  It may be removed or altered w/o warning in future 
+   * releases 
+   * of Lucene.
+   * </p>
+   */
+  public abstract CacheEntry[] getCacheEntries();
+  
+  /**
+   * Instructs the {@link IndexFieldCache} to forcibly expunge all entries from
+   * the underlying caches.
+   */
+  public abstract void clear() throws IOException;
+
+  /**
+   * May be used for debugging output in {@link IndexFieldCacheImpl}.
+   */
+  public void setInfoStream(PrintStream stream);
+
+  /** counterpart of {@link #setInfoStream(PrintStream)} */
+  public PrintStream getInfoStream();
+  
+  /**
+   * Returns the {@link IndexReader} for this {@link IndexFieldCache}.
+   * @return The {@link IndexReader}.
+   */
+  public IndexReader getIndexReader();
+}
Index: src/java/org/apache/lucene/search/fields/IndexFieldCacheImpl.java
===================================================================
--- src/java/org/apache/lucene/search/fields/IndexFieldCacheImpl.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/IndexFieldCacheImpl.java	(revision 0)
@@ -0,0 +1,195 @@
+package org.apache.lucene.search.fields;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+
+/**
+ * Expert: The default {@link IndexFieldCache} implementation, storing all values in memory.
+ * A {@link HashMap} is used for storage.
+ */
+public class IndexFieldCacheImpl implements IndexFieldCache {
+	
+  private Map<Class<?>,Cache> caches;
+  final IndexReader reader;
+  public IndexFieldCacheImpl(final IndexReader reader) {
+    this.reader = reader;
+    init();
+  }
+  private synchronized void init() {
+    caches = new HashMap<Class<?>,Cache>(7);
+    caches.put(Byte.TYPE, new ByteCache(reader));
+    caches.put(Short.TYPE, new ShortCache(reader));
+    caches.put(Integer.TYPE, new IntCache(reader));
+    caches.put(Float.TYPE, new FloatCache(reader));
+    caches.put(Long.TYPE, new LongCache(reader));
+    caches.put(Double.TYPE, new DoubleCache(reader));
+    caches.put(String.class, new StringCache(reader));
+    caches.put(StringIndex.class, new StringIndexCache(reader));
+  }
+
+  /**
+   * @deprecated Use {@link #clear()} instead.
+   */
+  public void purgeAllCaches() {
+    init();
+  }
+
+  public void clear() throws IOException {
+    init();
+  }
+  
+  public void close() throws IOException {
+      clear();
+  }
+  
+  public CacheEntry[] getCacheEntries() {
+    List<CacheEntry> result = new ArrayList<CacheEntry>(17);
+    for(final Class<?> cacheType: caches.keySet()) {
+      Cache cache = caches.get(cacheType);
+
+      for (final Map.Entry<Entry, Object> mapEntry : cache.innerCache.entrySet()) {
+        Entry entry = mapEntry.getKey();
+        result.add(new CacheEntryImpl(entry.field,
+                                      cacheType, entry.custom,
+                                      mapEntry.getValue()));
+      }
+    }
+    return result.toArray(new CacheEntry[result.size()]);
+  }
+  
+  // inherit javadocs
+  public byte[] getBytes (String field) throws IOException {
+    return getBytes(field, null);
+  }
+
+  // inherit javadocs
+  public byte[] getBytes(String field, ByteParser parser)
+      throws IOException {
+    if(parser == null) {
+      parser = ByteParser.DEFAULT;
+    }
+    return (byte[]) caches.get(Byte.TYPE).get(new Entry(field, parser));
+  }
+
+  // inherit javadocs
+  public short[] getShorts (String field) throws IOException {
+    return getShorts(field, null);
+  }
+
+  // inherit javadocs
+  public short[] getShorts(String field, ShortParser parser)
+      throws IOException {
+    if(parser == null) {
+      parser = ShortParser.DEFAULT;
+    }
+    return (short[]) caches.get(Short.TYPE).get(new Entry(field, parser));
+  }
+
+  // inherit javadocs
+  public int[] getInts (String field) throws IOException {
+    return getInts(field, null);
+  }
+
+  // inherit javadocs
+  public int[] getInts(String field, IntParser parser)
+      throws IOException {
+    if(parser == null) {
+      parser = IntParser.DEFAULT;
+    }
+    return (int[]) caches.get(Integer.TYPE).get(new Entry(field, parser));
+  }
+
+  // inherit javadocs
+  public float[] getFloats (String field)
+    throws IOException {
+    return getFloats(field, null);
+  }
+
+  // inherit javadocs
+  public float[] getFloats(String field, FloatParser parser)
+    throws IOException {
+    if(parser == null) {
+      parser = FloatParser.DEFAULT;
+    }
+    return (float[]) caches.get(Float.TYPE).get(new Entry(field, parser));
+  }
+
+  // inherit javadocs
+  public long[] getLongs(String field) throws IOException {
+    return getLongs(field, null);
+  }
+
+  // inherit javadocs
+  public long[] getLongs(String field, LongParser parser)
+      throws IOException {
+    if(parser == null) {
+      parser = LongParser.DEFAULT;
+    }
+    return (long[]) caches.get(Long.TYPE).get(new Entry(field, parser));
+  }
+
+  // inherit javadocs
+  public double[] getDoubles(String field)
+    throws IOException {
+    return getDoubles(field, null);
+  }
+
+  // inherit javadocs
+  public double[] getDoubles(String field, DoubleParser parser)
+      throws IOException {
+    if(parser == null) {
+      parser = DoubleParser.DEFAULT;
+    }
+    return (double[]) caches.get(Double.TYPE).get(new Entry(field, parser));
+  }
+
+  // inherit javadocs
+  public String[] getStrings(String field)
+      throws IOException {
+    return (String[]) caches.get(String.class).get(new Entry(field, (FieldParser)null));
+  }
+
+  // inherit javadocs
+  public StringIndex getStringIndex(String field)
+      throws IOException {
+    return (StringIndex) caches.get(StringIndex.class).get(new Entry(field, (FieldParser)null));
+  }
+
+  private volatile PrintStream infoStream;
+
+  public void setInfoStream(PrintStream stream) {
+    infoStream = stream;
+  }
+
+  public PrintStream getInfoStream() {
+    return infoStream;
+  }
+  
+  public IndexReader getIndexReader() {
+      return reader;
+  }
+}
+
Index: src/java/org/apache/lucene/search/fields/IndexFieldCacheRangeFilter.java
===================================================================
--- src/java/org/apache/lucene/search/fields/IndexFieldCacheRangeFilter.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/IndexFieldCacheRangeFilter.java	(revision 0)
@@ -0,0 +1,617 @@
+package org.apache.lucene.search.fields;
+/**
+ * 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 org.apache.lucene.document.NumericField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.NumericRangeFilter;
+import org.apache.lucene.search.TermRangeFilter;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * A range filter built on top of a cached single term field (in {@link IndexFieldCache}).
+ * 
+ * <p>{@code IndexFieldCacheRangeFilter} builds a single cache for the field the first time it is used.
+ * Each subsequent {@code IndexFieldCacheRangeFilter} on the same field then reuses this cache,
+ * even if the range itself changes. 
+ * 
+ * <p>This means that {@code IndexFieldCacheRangeFilter} is much faster (sometimes more than 100x as fast) 
+ * as building a {@link TermRangeFilter}, if using a {@link #newStringRange}.
+ * However, if the range never changes it is slower (around 2x as slow) than building
+ * a CachingWrapperFilter on top of a single {@link TermRangeFilter}.
+ *
+ * For numeric data types, this filter may be significantly faster than {@link NumericRangeFilter}.
+ * Furthermore, it does not need the numeric values encoded by {@link NumericField}. But
+ * it has the problem that it only works with exact one value/document (see below).
+ *
+ * <p>As with all {@link IndexFieldCache} based functionality, {@code IndexFieldCacheRangeFilter}
+ * is only valid for 
+ * fields which exact one term for each document (except for {@link #newStringRange}
+ * where 0 terms are also allowed). Due to a restriction of {@link IndexFieldCache}, for numeric ranges
+ * all terms that do not have a numeric value, 0 is assumed.
+ *
+ * <p>Thus it works on dates, prices and other single value fields but will not work on
+ * regular text fields. It is preferable to use a <code>NOT_ANALYZED</code> field to ensure that
+ * there is only a single term. 
+ *
+ * <p>This class does not have an constructor, use one of the static factory methods available,
+ * that create a correct instance for different data types supported by {@link IndexFieldCache}.
+ */
+public abstract class IndexFieldCacheRangeFilter<T> extends Filter {
+    private static final long serialVersionUID = 1L;
+final String field;
+  final FieldParser parser;
+  final T lowerVal;
+  final T upperVal;
+  final boolean includeLower;
+  final boolean includeUpper;
+  
+  private IndexFieldCacheRangeFilter(String field, FieldParser parser, T lowerVal, T upperVal, boolean includeLower, boolean includeUpper) {
+    this.field = field;
+    this.parser = parser;
+    this.lowerVal = lowerVal;
+    this.upperVal = upperVal;
+    this.includeLower = includeLower;
+    this.includeUpper = includeUpper;
+  }
+  
+  /** This method is implemented for each data type */
+  @Override
+  public abstract DocIdSet getDocIdSet(IndexReader reader) throws IOException;
+
+  /**
+   * Creates a string range filter using {@link IndexFieldCache#getStringIndex}. This works with all
+   * fields containing zero or one term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<String> newStringRange(String field, String lowerVal, String upperVal, boolean includeLower, boolean includeUpper) {
+    return new IndexFieldCacheRangeFilter<String>(field, null, lowerVal, upperVal, includeLower, includeUpper) {
+        private static final long serialVersionUID = 1L;
+
+    @Override
+      public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+        final StringIndex fcsi = reader.getIndexCache().getFieldCache().getStringIndex(field);
+        final int lowerPoint = fcsi.binarySearchLookup(lowerVal);
+        final int upperPoint = fcsi.binarySearchLookup(upperVal);
+        
+        final int inclusiveLowerPoint, inclusiveUpperPoint;
+
+        // Hints:
+        // * binarySearchLookup returns 0, if value was null.
+        // * the value is <0 if no exact hit was found, the returned value
+        //   is (-(insertion point) - 1)
+        if (lowerPoint == 0) {
+          assert lowerVal == null;
+          inclusiveLowerPoint = 1;
+        } else if (includeLower && lowerPoint > 0) {
+          inclusiveLowerPoint = lowerPoint;
+        } else if (lowerPoint > 0) {
+          inclusiveLowerPoint = lowerPoint + 1;
+        } else {
+          inclusiveLowerPoint = Math.max(1, -lowerPoint - 1);
+        }
+        
+        if (upperPoint == 0) {
+          assert upperVal == null;
+          inclusiveUpperPoint = Integer.MAX_VALUE;  
+        } else if (includeUpper && upperPoint > 0) {
+          inclusiveUpperPoint = upperPoint;
+        } else if (upperPoint > 0) {
+          inclusiveUpperPoint = upperPoint - 1;
+        } else {
+          inclusiveUpperPoint = -upperPoint - 2;
+        }      
+
+        if (inclusiveUpperPoint <= 0 || inclusiveLowerPoint > inclusiveUpperPoint)
+          return DocIdSet.EMPTY_DOCIDSET;
+        
+        assert inclusiveLowerPoint > 0 && inclusiveUpperPoint > 0;
+        
+        // for this DocIdSet, we never need to use TermDocs,
+        // because deleted docs have an order of 0 (null entry in StringIndex)
+        return new FieldCacheDocIdSet(reader, false) {
+          @Override
+          final boolean matchDoc(int doc) {
+            return fcsi.order[doc] >= inclusiveLowerPoint && fcsi.order[doc] <= inclusiveUpperPoint;
+          }
+        };
+      }
+    };
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getBytes(IndexReader,String)}. This works with all
+   * byte fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Byte> newByteRange(String field, Byte lowerVal, Byte upperVal, boolean includeLower, boolean includeUpper) {
+    return newByteRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link IndexFieldCache#getBytes(String,ByteParser)}. This works with all
+   * byte fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Byte> newByteRange(String field, ByteParser parser, Byte lowerVal, Byte upperVal, boolean includeLower, boolean includeUpper) {
+    return new IndexFieldCacheRangeFilter<Byte>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
+        private static final long serialVersionUID = 1L;
+      @Override
+      public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+        final byte inclusiveLowerPoint, inclusiveUpperPoint;
+        if (lowerVal != null) {
+          final byte i = lowerVal.byteValue();
+          if (!includeLower && i == Byte.MAX_VALUE)
+            return DocIdSet.EMPTY_DOCIDSET;
+          inclusiveLowerPoint = (byte) (includeLower ?  i : (i + 1));
+        } else {
+          inclusiveLowerPoint = Byte.MIN_VALUE;
+        }
+        if (upperVal != null) {
+          final byte i = upperVal.byteValue();
+          if (!includeUpper && i == Byte.MIN_VALUE)
+            return DocIdSet.EMPTY_DOCIDSET;
+          inclusiveUpperPoint = (byte) (includeUpper ? i : (i - 1));
+        } else {
+          inclusiveUpperPoint = Byte.MAX_VALUE;
+        }
+        
+        if (inclusiveLowerPoint > inclusiveUpperPoint)
+          return DocIdSet.EMPTY_DOCIDSET;
+        
+        final byte[] values = reader.getIndexCache().getFieldCache().getBytes(field, (ByteParser) parser);
+        // we only request the usage of termDocs, if the range contains 0
+        return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0 && inclusiveUpperPoint >= 0)) {
+          @Override
+          boolean matchDoc(int doc) {
+            return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
+          }
+        };
+      }
+    };
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getShorts(IndexReader,String)}. This works with all
+   * short fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Short> newShortRange(String field, Short lowerVal, Short upperVal, boolean includeLower, boolean includeUpper) {
+    return newShortRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getShorts(IndexReader,String,FieldCache.ShortParser)}. This works with all
+   * short fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Short> newShortRange(String field, FieldParser parser, Short lowerVal, Short upperVal, boolean includeLower, boolean includeUpper) {
+    return new IndexFieldCacheRangeFilter<Short>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
+        private static final long serialVersionUID = 1L;
+      @Override
+      public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+        final short inclusiveLowerPoint, inclusiveUpperPoint;
+        if (lowerVal != null) {
+          short i = lowerVal.shortValue();
+          if (!includeLower && i == Short.MAX_VALUE)
+            return DocIdSet.EMPTY_DOCIDSET;
+          inclusiveLowerPoint = (short) (includeLower ? i : (i + 1));
+        } else {
+          inclusiveLowerPoint = Short.MIN_VALUE;
+        }
+        if (upperVal != null) {
+          short i = upperVal.shortValue();
+          if (!includeUpper && i == Short.MIN_VALUE)
+            return DocIdSet.EMPTY_DOCIDSET;
+          inclusiveUpperPoint = (short) (includeUpper ? i : (i - 1));
+        } else {
+          inclusiveUpperPoint = Short.MAX_VALUE;
+        }
+        
+        if (inclusiveLowerPoint > inclusiveUpperPoint)
+          return DocIdSet.EMPTY_DOCIDSET;
+        
+        final short[] values = reader.getIndexCache().getFieldCache().getShorts(field, (ShortParser) parser);
+        // we only request the usage of termDocs, if the range contains 0
+        return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0 && inclusiveUpperPoint >= 0)) {
+          @Override
+          boolean matchDoc(int doc) {
+            return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
+          }
+        };
+      }
+    };
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getInts(IndexReader,String)}. This works with all
+   * int fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Integer> newIntRange(String field, Integer lowerVal, Integer upperVal, boolean includeLower, boolean includeUpper) {
+    return newIntRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getInts(IndexReader,String,FieldCache.IntParser)}. This works with all
+   * int fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Integer> newIntRange(String field, IntParser parser, Integer lowerVal, Integer upperVal, boolean includeLower, boolean includeUpper) {
+    return new IndexFieldCacheRangeFilter<Integer>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
+        private static final long serialVersionUID = 1L;
+      @Override
+      public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+        final int inclusiveLowerPoint, inclusiveUpperPoint;
+        if (lowerVal != null) {
+          int i = lowerVal.intValue();
+          if (!includeLower && i == Integer.MAX_VALUE)
+            return DocIdSet.EMPTY_DOCIDSET;
+          inclusiveLowerPoint = includeLower ? i : (i + 1);
+        } else {
+          inclusiveLowerPoint = Integer.MIN_VALUE;
+        }
+        if (upperVal != null) {
+          int i = upperVal.intValue();
+          if (!includeUpper && i == Integer.MIN_VALUE)
+            return DocIdSet.EMPTY_DOCIDSET;
+          inclusiveUpperPoint = includeUpper ? i : (i - 1);
+        } else {
+          inclusiveUpperPoint = Integer.MAX_VALUE;
+        }
+        
+        if (inclusiveLowerPoint > inclusiveUpperPoint)
+          return DocIdSet.EMPTY_DOCIDSET;
+        
+        final int[] values = reader.getIndexCache().getFieldCache().getInts(field, (IntParser) parser);
+        // we only request the usage of termDocs, if the range contains 0
+        return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0 && inclusiveUpperPoint >= 0)) {
+          @Override
+          boolean matchDoc(int doc) {
+            return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
+          }
+        };
+      }
+    };
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getLongs(IndexReader,String)}. This works with all
+   * long fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Long> newLongRange(String field, Long lowerVal, Long upperVal, boolean includeLower, boolean includeUpper) {
+    return newLongRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getLongs(IndexReader,String,FieldCache.LongParser)}. This works with all
+   * long fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Long> newLongRange(String field, LongParser parser, Long lowerVal, Long upperVal, boolean includeLower, boolean includeUpper) {
+    return new IndexFieldCacheRangeFilter<Long>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
+        private static final long serialVersionUID = 1L;
+      @Override
+      public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+        final long inclusiveLowerPoint, inclusiveUpperPoint;
+        if (lowerVal != null) {
+          long i = lowerVal.longValue();
+          if (!includeLower && i == Long.MAX_VALUE)
+            return DocIdSet.EMPTY_DOCIDSET;
+          inclusiveLowerPoint = includeLower ? i : (i + 1L);
+        } else {
+          inclusiveLowerPoint = Long.MIN_VALUE;
+        }
+        if (upperVal != null) {
+          long i = upperVal.longValue();
+          if (!includeUpper && i == Long.MIN_VALUE)
+            return DocIdSet.EMPTY_DOCIDSET;
+          inclusiveUpperPoint = includeUpper ? i : (i - 1L);
+        } else {
+          inclusiveUpperPoint = Long.MAX_VALUE;
+        }
+        
+        if (inclusiveLowerPoint > inclusiveUpperPoint)
+          return DocIdSet.EMPTY_DOCIDSET;
+        
+        final long[] values = reader.getIndexCache().getFieldCache().getLongs(field, (LongParser)parser);
+        // we only request the usage of termDocs, if the range contains 0
+        return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0L && inclusiveUpperPoint >= 0L)) {
+          @Override
+          boolean matchDoc(int doc) {
+            return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
+          }
+        };
+      }
+    };
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getFloats(IndexReader,String)}. This works with all
+   * float fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Float> newFloatRange(String field, Float lowerVal, Float upperVal, boolean includeLower, boolean includeUpper) {
+    return newFloatRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getFloats(IndexReader,String,FieldCache.FloatParser)}. This works with all
+   * float fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Float> newFloatRange(String field, FloatParser parser, Float lowerVal, Float upperVal, boolean includeLower, boolean includeUpper) {
+    return new IndexFieldCacheRangeFilter<Float>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
+        private static final long serialVersionUID = 1L;
+      @Override
+      public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+        // we transform the floating point numbers to sortable integers
+        // using NumericUtils to easier find the next bigger/lower value
+        final float inclusiveLowerPoint, inclusiveUpperPoint;
+        if (lowerVal != null) {
+          float f = lowerVal.floatValue();
+          if (!includeUpper && f > 0.0f && Float.isInfinite(f))
+            return DocIdSet.EMPTY_DOCIDSET;
+          int i = NumericUtils.floatToSortableInt(f);
+          inclusiveLowerPoint = NumericUtils.sortableIntToFloat( includeLower ?  i : (i + 1) );
+        } else {
+          inclusiveLowerPoint = Float.NEGATIVE_INFINITY;
+        }
+        if (upperVal != null) {
+          float f = upperVal.floatValue();
+          if (!includeUpper && f < 0.0f && Float.isInfinite(f))
+            return DocIdSet.EMPTY_DOCIDSET;
+          int i = NumericUtils.floatToSortableInt(f);
+          inclusiveUpperPoint = NumericUtils.sortableIntToFloat( includeUpper ? i : (i - 1) );
+        } else {
+          inclusiveUpperPoint = Float.POSITIVE_INFINITY;
+        }
+        
+        if (inclusiveLowerPoint > inclusiveUpperPoint)
+          return DocIdSet.EMPTY_DOCIDSET;
+        
+        final float[] values = reader.getIndexCache().getFieldCache().getFloats(field, (FloatParser) parser);
+        // we only request the usage of termDocs, if the range contains 0
+        return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0.0f && inclusiveUpperPoint >= 0.0f)) {
+          @Override
+          boolean matchDoc(int doc) {
+            return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
+          }
+        };
+      }
+    };
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getDoubles(IndexReader,String)}. This works with all
+   * double fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Double> newDoubleRange(String field, Double lowerVal, Double upperVal, boolean includeLower, boolean includeUpper) {
+    return newDoubleRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
+  }
+  
+  /**
+   * Creates a numeric range filter using {@link FieldCache#getDoubles(IndexReader,String,FieldCache.DoubleParser)}. This works with all
+   * double fields containing exactly one numeric term in the field. The range can be half-open by setting one
+   * of the values to <code>null</code>.
+   */
+  public static IndexFieldCacheRangeFilter<Double> newDoubleRange(String field, DoubleParser parser, Double lowerVal, Double upperVal, boolean includeLower, boolean includeUpper) {
+    return new IndexFieldCacheRangeFilter<Double>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
+        private static final long serialVersionUID = 1L;
+      @Override
+      public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+        // we transform the floating point numbers to sortable integers
+        // using NumericUtils to easier find the next bigger/lower value
+        final double inclusiveLowerPoint, inclusiveUpperPoint;
+        if (lowerVal != null) {
+          double f = lowerVal.doubleValue();
+          if (!includeUpper && f > 0.0 && Double.isInfinite(f))
+            return DocIdSet.EMPTY_DOCIDSET;
+          long i = NumericUtils.doubleToSortableLong(f);
+          inclusiveLowerPoint = NumericUtils.sortableLongToDouble( includeLower ?  i : (i + 1L) );
+        } else {
+          inclusiveLowerPoint = Double.NEGATIVE_INFINITY;
+        }
+        if (upperVal != null) {
+          double f = upperVal.doubleValue();
+          if (!includeUpper && f < 0.0 && Double.isInfinite(f))
+            return DocIdSet.EMPTY_DOCIDSET;
+          long i = NumericUtils.doubleToSortableLong(f);
+          inclusiveUpperPoint = NumericUtils.sortableLongToDouble( includeUpper ? i : (i - 1L) );
+        } else {
+          inclusiveUpperPoint = Double.POSITIVE_INFINITY;
+        }
+        
+        if (inclusiveLowerPoint > inclusiveUpperPoint)
+          return DocIdSet.EMPTY_DOCIDSET;
+        
+        final double[] values = reader.getIndexCache().getFieldCache().getDoubles(field, (DoubleParser) parser);
+        // we only request the usage of termDocs, if the range contains 0
+        return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0.0 && inclusiveUpperPoint >= 0.0)) {
+          @Override
+          boolean matchDoc(int doc) {
+            return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
+          }
+        };
+      }
+    };
+  }
+  
+  @Override
+  public final String toString() {
+    final StringBuilder sb = new StringBuilder(field).append(":");
+    return sb.append(includeLower ? '[' : '{')
+      .append((lowerVal == null) ? "*" : lowerVal.toString())
+      .append(" TO ")
+      .append((upperVal == null) ? "*" : upperVal.toString())
+      .append(includeUpper ? ']' : '}')
+      .toString();
+  }
+
+  @Override
+  public final boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof IndexFieldCacheRangeFilter<?>)) return false;
+    IndexFieldCacheRangeFilter<?> other = (IndexFieldCacheRangeFilter<?>) o;
+
+    if (!this.field.equals(other.field)
+        || this.includeLower != other.includeLower
+        || this.includeUpper != other.includeUpper
+    ) { return false; }
+    if (this.lowerVal != null ? !this.lowerVal.equals(other.lowerVal) : other.lowerVal != null) return false;
+    if (this.upperVal != null ? !this.upperVal.equals(other.upperVal) : other.upperVal != null) return false;
+    if (this.parser != null ? !this.parser.equals(other.parser) : other.parser != null) return false;
+    return true;
+  }
+  
+  @Override
+  public final int hashCode() {
+    int h = field.hashCode();
+    h ^= (lowerVal != null) ? lowerVal.hashCode() : 550356204;
+    h = (h << 1) | (h >>> 31);  // rotate to distinguish lower from upper
+    h ^= (upperVal != null) ? upperVal.hashCode() : -1674416163;
+    h ^= (parser != null) ? parser.hashCode() : -1572457324;
+    h ^= (includeLower ? 1549299360 : -365038026) ^ (includeUpper ? 1721088258 : 1948649653);
+    return h;
+  }
+
+  /** Returns the field name for this filter */
+  public String getField() { return field; }
+
+  /** Returns <code>true</code> if the lower endpoint is inclusive */
+  public boolean includesLower() { return includeLower; }
+  
+  /** Returns <code>true</code> if the upper endpoint is inclusive */
+  public boolean includesUpper() { return includeUpper; }
+
+  /** Returns the lower value of this range filter */
+  public T getLowerVal() { return lowerVal; }
+
+  /** Returns the upper value of this range filter */
+  public T getUpperVal() { return upperVal; }
+  
+  /** Returns the current numeric parser ({@code null} for {@code T} is {@code String}} */
+  public FieldParser getParser() { return parser; }
+  
+  static abstract class FieldCacheDocIdSet extends DocIdSet {
+    private final IndexReader reader;
+    private boolean mayUseTermDocs;
+  
+    FieldCacheDocIdSet(IndexReader reader, boolean mayUseTermDocs) {
+      this.reader = reader;
+      this.mayUseTermDocs = mayUseTermDocs;
+    }
+  
+    /** this method checks, if a doc is a hit, should throw AIOBE, when position invalid */
+    abstract boolean matchDoc(int doc) throws ArrayIndexOutOfBoundsException;
+    
+    /** this DocIdSet is cacheable, if it works solely with FieldCache and no TermDocs */
+    @Override
+    public boolean isCacheable() {
+      return !(mayUseTermDocs && reader.hasDeletions());
+    }
+
+    @Override
+    public DocIdSetIterator iterator() throws IOException {
+      // Synchronization needed because deleted docs BitVector
+      // can change after call to hasDeletions until TermDocs creation.
+      // We only use an iterator with termDocs, when this was requested (e.g. range contains 0)
+      // and the index has deletions
+      final TermDocs termDocs;
+      synchronized(reader) {
+        termDocs = isCacheable() ? null : reader.termDocs(null);
+      }
+      if (termDocs != null) {
+        // a DocIdSetIterator using TermDocs to iterate valid docIds
+        return new DocIdSetIterator() {
+          private int doc = -1;
+          
+          @Override
+          public int docID() {
+            return doc;
+          }
+          
+          @Override
+          public int nextDoc() throws IOException {
+            do {
+              if (!termDocs.next())
+                return doc = NO_MORE_DOCS;
+            } while (!matchDoc(doc = termDocs.doc()));
+            return doc;
+          }
+          
+          @Override
+          public int advance(int target) throws IOException {
+            if (!termDocs.skipTo(target))
+              return doc = NO_MORE_DOCS;
+            while (!matchDoc(doc = termDocs.doc())) { 
+              if (!termDocs.next())
+                return doc = NO_MORE_DOCS;
+            }
+            return doc;
+          }
+        };
+      } else {
+        // a DocIdSetIterator generating docIds by incrementing a variable -
+        // this one can be used if there are no deletions are on the index
+        return new DocIdSetIterator() {
+          private int doc = -1;
+          
+          @Override
+          public int docID() {
+            return doc;
+          }
+          
+          @Override
+          public int nextDoc() {
+            try {
+              do {
+                doc++;
+              } while (!matchDoc(doc));
+              return doc;
+            } catch (ArrayIndexOutOfBoundsException e) {
+              return doc = NO_MORE_DOCS;
+            }
+          }
+          
+          @Override
+          public int advance(int target) {
+            try {
+              doc = target;
+              while (!matchDoc(doc)) { 
+                doc++;
+              }
+              return doc;
+            } catch (ArrayIndexOutOfBoundsException e) {
+              return doc = NO_MORE_DOCS;
+            }
+          }
+        };
+      }
+    }
+  }
+
+}
Index: src/java/org/apache/lucene/search/fields/IndexFieldCacheTermsFilter.java
===================================================================
--- src/java/org/apache/lucene/search/fields/IndexFieldCacheTermsFilter.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/IndexFieldCacheTermsFilter.java	(revision 0)
@@ -0,0 +1,175 @@
+package org.apache.lucene.search.fields;
+
+/**
+ * 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 org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.FieldCache.StringIndex;
+import org.apache.lucene.util.OpenBitSet;
+
+/**
+ * A {@link Filter} that only accepts documents whose single
+ * term value in the specified field is contained in the
+ * provided set of allowed terms.
+ * 
+ * <p/>
+ * 
+ * This is the same functionality as TermsFilter (from
+ * contrib/queries), except this filter requires that the
+ * field contains only a single term for all documents.
+ * Because of drastically different implementations, they
+ * also have different performance characteristics, as
+ * described below.
+ * 
+ * <p/>
+ * 
+ * The first invocation of this filter on a given field will
+ * be slower, since a {@link FieldCache.StringIndex} must be
+ * created.  Subsequent invocations using the same field
+ * will re-use this cache.  However, as with all
+ * functionality based on {@link FieldCache}, persistent RAM
+ * is consumed to hold the cache, and is not freed until the
+ * {@link IndexReader} is closed.  In contrast, TermsFilter
+ * has no persistent RAM consumption.
+ * 
+ * 
+ * <p/>
+ * 
+ * With each search, this filter translates the specified
+ * set of Terms into a private {@link OpenBitSet} keyed by
+ * term number per unique {@link IndexReader} (normally one
+ * reader per segment).  Then, during matching, the term
+ * number for each docID is retrieved from the cache and
+ * then checked for inclusion using the {@link OpenBitSet}.
+ * Since all testing is done using RAM resident data
+ * structures, performance should be very fast, most likely
+ * fast enough to not require further caching of the
+ * DocIdSet for each possible combination of terms.
+ * However, because docIDs are simply scanned linearly, an
+ * index with a great many small documents may find this
+ * linear scan too costly.
+ * 
+ * <p/>
+ * 
+ * In contrast, TermsFilter builds up an {@link OpenBitSet},
+ * keyed by docID, every time it's created, by enumerating
+ * through all matching docs using {@link TermDocs} to seek
+ * and scan through each term's docID list.  While there is
+ * no linear scan of all docIDs, besides the allocation of
+ * the underlying array in the {@link OpenBitSet}, this
+ * approach requires a number of "disk seeks" in proportion
+ * to the number of terms, which can be exceptionally costly
+ * when there are cache misses in the OS's IO cache.
+ * 
+ * <p/>
+ * 
+ * Generally, this filter will be slower on the first
+ * invocation for a given field, but subsequent invocations,
+ * even if you change the allowed set of Terms, should be
+ * faster than TermsFilter, especially as the number of
+ * Terms being matched increases.  If you are matching only
+ * a very small number of terms, and those terms in turn
+ * match a very small number of documents, TermsFilter may
+ * perform faster.
+ *
+ * <p/>
+ *
+ * Which filter is best is very application dependent.
+ */
+
+public class IndexFieldCacheTermsFilter extends Filter {
+  private static final long serialVersionUID = 1L;
+  private String field;
+  private String[] terms;
+
+  public IndexFieldCacheTermsFilter(String field, String... terms) {
+    this.field = field;
+    this.terms = terms;
+  }
+
+  @Override
+  public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
+    return new FieldCacheTermsFilterDocIdSet(reader.getIndexCache().getFieldCache().getStringIndex(field));
+  }
+
+  protected class FieldCacheTermsFilterDocIdSet extends DocIdSet {
+    private StringIndex fcsi;
+
+    private OpenBitSet openBitSet;
+
+    public FieldCacheTermsFilterDocIdSet(StringIndex fcsi) {
+      this.fcsi = fcsi;
+      openBitSet = new OpenBitSet(this.fcsi.lookup.length);
+      for (int i=0;i<terms.length;i++) {
+        int termNumber = this.fcsi.binarySearchLookup(terms[i]);
+        if (termNumber > 0) {
+          openBitSet.fastSet(termNumber);
+        }
+      }
+    }
+
+    @Override
+    public DocIdSetIterator iterator() {
+      return new FieldCacheTermsFilterDocIdSetIterator();
+    }
+
+    /** This DocIdSet implementation is cacheable. */
+    @Override
+    public boolean isCacheable() {
+      return true;
+    }
+
+    protected class FieldCacheTermsFilterDocIdSetIterator extends DocIdSetIterator {
+      private int doc = -1;
+
+      @Override
+      public int docID() {
+        return doc;
+      }
+
+      @Override
+      public int nextDoc() {
+        try {
+          while (!openBitSet.fastGet(fcsi.order[++doc])) {}
+        } catch (ArrayIndexOutOfBoundsException e) {
+          doc = NO_MORE_DOCS;
+        }
+        return doc;
+      }
+
+      @Override
+      public int advance(int target) {
+        try {
+          doc = target;
+          while (!openBitSet.fastGet(fcsi.order[doc])) {
+            doc++;
+          }
+        } catch (ArrayIndexOutOfBoundsException e) {
+          doc = NO_MORE_DOCS;
+        }
+        return doc;
+      }
+    }
+  }
+}
Index: src/java/org/apache/lucene/search/fields/IntCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/IntCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/IntCache.java	(revision 0)
@@ -0,0 +1,68 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+/**
+ * Expert: A {@link Cache} that converts terms to ints.
+ */
+final class IntCache extends Cache {
+  private final IndexReader reader;
+
+  IntCache(final IndexReader reader) {
+    this.reader = reader;
+  }
+  
+  @Override
+  protected Object createValue(Entry entryKey)
+      throws IOException {
+    Entry entry = entryKey;
+    String field = entry.field;
+    IntParser parser = (IntParser) entry.custom;
+    int[] retArray = null;
+    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;
+        final String text = term.text();
+        if (parser == null) {
+          parser = IntParser.DEFAULT;
+        }
+        try {
+          termval = parser.parseInt(text);
+        } catch(NumberFormatException ne) {
+          if(parser == IntParser.DEFAULT) {
+            // continue with NUMERIC_UTILS parser
+            parser = IntParser.NUMERIC_UTILS;
+            termval = parser.parseInt(text);
+          } else {
+            // unexpected
+            throw ne;
+          }
+        }
+        
+        if (retArray == null) // late init
+          retArray = new int[reader.maxDoc()];
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } catch (StopFillCacheException stop) {
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    if (retArray == null) // no values
+      retArray = new int[reader.maxDoc()];
+    return retArray;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/IntComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/IntComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/IntComparator.java	(revision 0)
@@ -0,0 +1,74 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Parses field's values as int and sorts by ascending value */
+  public final class IntComparator extends FieldComparator {
+    private final int[] values;
+    private int[] currentReaderValues;
+    private final String field;
+    private IntParser parser;
+    private int bottom;                           // Value of bottom of queue
+
+    IntComparator(int numHits, String field, FieldParser parser) {
+      values = new int[numHits];
+      this.field = field;
+      this.parser = (IntParser) parser;
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      // TODO: there are sneaky non-branch ways to compute
+      // -1/+1/0 sign
+      // Cannot return values[slot1] - values[slot2] because that
+      // may overflow
+      final int v1 = values[slot1];
+      final int v2 = values[slot2];
+      if (v1 > v2) {
+        return 1;
+      } else if (v1 < v2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      // TODO: there are sneaky non-branch ways to compute
+      // -1/+1/0 sign
+      // Cannot return bottom - values[slot2] because that
+      // may overflow
+      final int v2 = currentReaderValues[doc];
+      if (bottom > v2) {
+        return 1;
+      } else if (bottom < v2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      values[slot] = currentReaderValues[doc];
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      currentReaderValues = reader.getIndexCache().getFieldCache().getInts(field, parser);
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      this.bottom = values[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return Integer.valueOf(values[slot]);
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/IntParser.java
===================================================================
--- src/java/org/apache/lucene/search/fields/IntParser.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/IntParser.java	(revision 0)
@@ -0,0 +1,58 @@
+package org.apache.lucene.search.fields;
+
+import org.apache.lucene.analysis.NumericTokenStream;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.FieldComparatorSource;
+import org.apache.lucene.util.NumericUtils;
+
+/** Parse ints from document fields.
+ */
+public abstract class IntParser extends FieldParser.WithFieldComparatorSource implements FieldCache.IntParser {
+    private static final long serialVersionUID = 1L;
+
+    /** Return an integer representation of this field's value. */
+  public abstract int parseInt(String string);
+  
+  public final FieldComparatorSource getFieldComparatorSource() {
+        return SortField.INT;
+  }
+
+/** The default parser for int values, which are encoded by {@link Integer#toString(int)} */
+  public static final IntParser DEFAULT = new IntParser() {
+    private static final long serialVersionUID = 1L;
+    public int parseInt(String value) {
+      return java.lang.Integer.parseInt(value);
+    }
+    protected Object readResolve() {
+      return DEFAULT;
+    }
+    @Override
+    public String toString() { 
+      return IntParser.class.getName()+".DEFAULT"; 
+    }
+    
+  };
+
+  /**
+   * A parser instance for int values encoded by {@link NumericUtils#intToPrefixCoded(int)}, e.g. when indexed
+   * via {@link NumericField}/{@link NumericTokenStream}.
+   */
+  public static final IntParser NUMERIC_UTILS = new IntParser(){
+    private static final long serialVersionUID = 1L;
+    public int parseInt(String val) {
+      final int shift = val.charAt(0)-NumericUtils.SHIFT_START_INT;
+      if (shift>0 && shift<=31)
+        throw new StopFillCacheException();
+      return NumericUtils.prefixCodedToInt(val);
+    }
+    protected Object readResolve() {
+      return NUMERIC_UTILS;
+    }
+    @Override
+    public String toString() { 
+      return IntParser.class.getName()+".NUMERIC_UTILS"; 
+    }
+  };
+
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/LongCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/LongCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/LongCache.java	(revision 0)
@@ -0,0 +1,67 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+/**
+ * Expert: A {@link Cache} that converts terms to longs.
+ */
+final class LongCache extends Cache {
+  private final IndexReader reader;
+
+  LongCache(final IndexReader reader) {
+    this.reader = reader;
+  }
+  
+  @Override
+  protected Object createValue(Entry entry)
+      throws IOException {
+    String field = entry.field;
+    LongParser parser = (LongParser) entry.custom;
+    long[] retArray = null;
+    TermDocs termDocs = reader.termDocs();
+    TermEnum termEnum = reader.terms (new Term(field));
+    try {
+      do {
+        Term term = termEnum.term();
+        if (term==null || term.field() != field) break;
+
+        long termval;
+        final String text = term.text();
+        if (parser == null) {
+          parser = LongParser.DEFAULT;
+        }
+        try {
+          termval = parser.parseLong(text);
+        } catch(NumberFormatException ne) {
+          if(parser == LongParser.DEFAULT) {
+            // continue with NUMERIC_UTILS parser
+            parser = LongParser.NUMERIC_UTILS;
+            termval = parser.parseLong(text);
+          } else {
+            // unexpected
+            throw ne;
+          }
+        }        
+        
+        if (retArray == null) // late init
+          retArray = new long[reader.maxDoc()];
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } catch (StopFillCacheException stop) {
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    if (retArray == null) // no values
+      retArray = new long[reader.maxDoc()];
+    return retArray;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/LongComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/LongComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/LongComparator.java	(revision 0)
@@ -0,0 +1,70 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Parses field's values as long and sorts by ascending value */
+  public final class LongComparator extends FieldComparator {
+    private final long[] values;
+    private long[] currentReaderValues;
+    private final String field;
+    private LongParser parser;
+    private long bottom;
+
+    LongComparator(int numHits, String field, FieldParser parser) {
+      values = new long[numHits];
+      this.field = field;
+      this.parser = (LongParser) parser;
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      // TODO: there are sneaky non-branch ways to compute
+      // -1/+1/0 sign
+      final long v1 = values[slot1];
+      final long v2 = values[slot2];
+      if (v1 > v2) {
+        return 1;
+      } else if (v1 < v2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      // TODO: there are sneaky non-branch ways to compute
+      // -1/+1/0 sign
+      final long v2 = currentReaderValues[doc];
+      if (bottom > v2) {
+        return 1;
+      } else if (bottom < v2) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      values[slot] = currentReaderValues[doc];
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      currentReaderValues = reader.getIndexCache().getFieldCache().getLongs(field, parser);
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      this.bottom = values[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return Long.valueOf(values[slot]);
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/LongParser.java
===================================================================
--- src/java/org/apache/lucene/search/fields/LongParser.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/LongParser.java	(revision 0)
@@ -0,0 +1,57 @@
+package org.apache.lucene.search.fields;
+
+import org.apache.lucene.analysis.NumericTokenStream;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.FieldComparatorSource;
+import org.apache.lucene.util.NumericUtils;
+
+/** Parse longs from document fields.
+ */
+public abstract class LongParser extends FieldParser.WithFieldComparatorSource  implements FieldCache.LongParser {
+    private static final long serialVersionUID = 1L;
+
+    /** Return an long representation of this field's value. */
+  public abstract long parseLong(String string);
+
+  public final FieldComparatorSource getFieldComparatorSource() {
+      return SortField.LONG;
+  }
+
+  /** The default parser for long values, which are encoded by {@link LongParser#toString(long)} */
+  public static final LongParser DEFAULT = new LongParser() {
+    private static final long serialVersionUID = 1L;
+    public long parseLong(String value) {
+      return java.lang.Long.parseLong(value);
+    }
+    protected Object readResolve() {
+      return DEFAULT;
+    }
+    @Override
+    public String toString() { 
+      return LongParser.class.getName()+".DEFAULT"; 
+    }
+  };
+
+  /**
+   * A parser instance for long values encoded by {@link NumericUtils#longToPrefixCoded(long)}, e.g. when indexed
+   * via {@link NumericField}/{@link NumericTokenStream}.
+   */
+  public static final LongParser NUMERIC_UTILS = new LongParser(){
+    private static final long serialVersionUID = 1L;
+    public long parseLong(String val) {
+      final int shift = val.charAt(0)-NumericUtils.SHIFT_START_LONG;
+      if (shift>0 && shift<=63)
+        throw new StopFillCacheException();
+      return NumericUtils.prefixCodedToLong(val);
+    }
+    protected Object readResolve() {
+      return NUMERIC_UTILS;
+    }
+    @Override
+    public String toString() { 
+      return LongParser.class.getName()+".NUMERIC_UTILS"; 
+    }
+  };
+
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/RelevanceComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/RelevanceComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/RelevanceComparator.java	(revision 0)
@@ -0,0 +1,66 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.ScoreCachingWrappingScorer;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TopScoreDocCollector;
+
+/** Sorts by descending relevance.  NOTE: if you are
+   *  sorting only by descending relevance and then
+   *  secondarily by ascending docID, performance is faster
+   *  using {@link TopScoreDocCollector} directly (which {@link
+   *  IndexSearcher#search} uses when no {@link Sort} is
+   *  specified). */
+public final class RelevanceComparator extends FieldComparator {
+  private final float[] scores;
+  private float bottom;
+  private Scorer scorer;
+  
+  RelevanceComparator(int numHits) {
+    scores = new float[numHits];
+  }
+
+  @Override
+  public int compare(int slot1, int slot2) {
+    final float score1 = scores[slot1];
+    final float score2 = scores[slot2];
+    return score1 > score2 ? -1 : (score1 < score2 ? 1 : 0);
+  }
+
+  @Override
+  public int compareBottom(int doc) throws IOException {
+    float score = scorer.score();
+    return bottom > score ? -1 : (bottom < score ? 1 : 0);
+  }
+
+  @Override
+  public void copy(int slot, int doc) throws IOException {
+    scores[slot] = scorer.score();
+  }
+
+  @Override
+  public void setNextReader(IndexReader reader, int docBase) {
+  }
+  
+  @Override
+  public void setBottom(final int bottom) {
+    this.bottom = scores[bottom];
+  }
+
+  @Override
+  public void setScorer(Scorer scorer) {
+    // wrap with a ScoreCachingWrappingScorer so that successive calls to
+    // score() will not incur score computation over and over again.
+    this.scorer = new ScoreCachingWrappingScorer(scorer);
+  }
+  
+  @Override
+  public Comparable<?> value(int slot) {
+    return Float.valueOf(scores[slot]);
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/ShortCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/ShortCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/ShortCache.java	(revision 0)
@@ -0,0 +1,49 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+
+/**
+ * Expert: A {@link Cache} that converts terms to shorts.
+ */
+final class ShortCache extends Cache {
+  private final IndexReader reader;
+
+  ShortCache(final IndexReader reader) {
+    this.reader = reader;
+  }
+  
+  @Override
+  protected Object createValue(Entry entryKey)
+      throws IOException {
+    Entry entry =  entryKey;
+    String field = entry.field;
+    ShortParser parser = (ShortParser) entry.custom;
+    if (parser == null) {
+      parser = ShortParser.DEFAULT;
+    }
+    final short[] retArray = new short[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;
+        short termval = parser.parseShort(term.text());
+        termDocs.seek (termEnum);
+        while (termDocs.next()) {
+          retArray[termDocs.doc()] = termval;
+        }
+      } while (termEnum.next());
+    } catch (StopFillCacheException stop) {
+    } finally {
+      termDocs.close();
+      termEnum.close();
+    }
+    return retArray;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/ShortComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/ShortComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/ShortComparator.java	(revision 0)
@@ -0,0 +1,51 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Parses field's values as short and sorts by ascending value */
+  public final class ShortComparator extends FieldComparator {
+    private final short[] values;
+    private short[] currentReaderValues;
+    private final String field;
+    private ShortParser parser;
+    private short bottom;
+
+    ShortComparator(int numHits, String field, FieldParser parser) {
+      values = new short[numHits];
+      this.field = field;
+      this.parser = (ShortParser) parser;
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      return values[slot1] - values[slot2];
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      return bottom - currentReaderValues[doc];
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      values[slot] = currentReaderValues[doc];
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      currentReaderValues = reader.getIndexCache().getFieldCache().getShorts(field, parser);
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      this.bottom = values[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return Short.valueOf(values[slot]);
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/ShortParser.java
===================================================================
--- src/java/org/apache/lucene/search/fields/ShortParser.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/ShortParser.java	(revision 0)
@@ -0,0 +1,33 @@
+package org.apache.lucene.search.fields;
+
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.FieldComparatorSource;
+
+
+/** Parse shorts from document fields.
+ */
+public abstract class ShortParser extends FieldParser.WithFieldComparatorSource implements FieldCache.ShortParser {
+    private static final long serialVersionUID = 1L;
+
+    /** Return a short representation of this field's value. */
+  public abstract short parseShort(String string);
+
+  public final FieldComparatorSource getFieldComparatorSource() {
+      return SortField.SHORT;
+  }
+
+  /** The default parser for short values, which are encoded by {@link ShortParser#toString(short)} */
+  public static final ShortParser DEFAULT = new ShortParser() {
+    private static final long serialVersionUID = 1L;
+    public short parseShort(String value) {
+      return java.lang.Short.parseShort(value);
+    }
+    protected Object readResolve() {
+      return DEFAULT;
+    }
+    @Override
+    public String toString() { 
+      return ShortParser.class.getName()+".DEFAULT"; 
+    }
+  };
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/SortField.java
===================================================================
--- src/java/org/apache/lucene/search/fields/SortField.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/SortField.java	(revision 0)
@@ -0,0 +1,507 @@
+package org.apache.lucene.search.fields;
+
+/**
+ * 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.Serializable;
+import java.util.Locale;
+
+import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.FieldComparatorSource;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.util.StringHelper;
+
+/**
+ * Stores information about how to sort documents by terms in an individual
+ * field.  Fields must be indexed in order to sort by them.
+ * 
+ * This class extends the deprecated class {@link org.apache.lucene.search.SortField}.
+ * Do not rely on it since that class will eventually be removed.
+ *
+ * @see Sort
+ */
+public class SortField extends org.apache.lucene.search.SortField
+implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+    /**
+     * Sort by document score (relevance). Sort values are Float and higher
+     * values are at the front.
+     */
+    public static final FieldComparatorSource SCORE = new FieldComparatorSource.Fieldless() {
+        private static final long serialVersionUID = 1L;
+
+        public FieldComparator newComparator(String field, int numHits,
+                int sortPos, boolean reverse, final FieldParser parser) throws IOException {
+            return new RelevanceComparator(numHits);
+        }
+
+        @Override
+        public int getTypeId() {
+            return 0; // SortField.SCORE in Lucene <= 3.0
+        }
+
+        @Override
+        public String getString(final String field) {
+            return "<score>";
+        }
+    };
+
+  /** Sort by document number (index order).  Sort values are Integer and lower
+   * values are at the front. */
+  public static final FieldComparatorSource DOC = new FieldComparatorSource.Fieldless() {
+      private static final long serialVersionUID = 1L;
+
+    public FieldComparator newComparator(String field, int numHits,
+            int sortPos, boolean reverse, final FieldParser parser) throws IOException {
+        return new DocComparator(numHits);
+    };
+    
+    @Override
+    public int getTypeId() {
+        return 1; // SortField.DOC in Lucene <= 3.0
+    }
+
+    @Override
+    public String getString(final String field) {
+        return "<doc>";
+    }
+};
+
+  /** Sort using term values as Strings.  Sort values are String and lower
+   * values are at the front. */
+  public static final FieldComparatorSource STRING = new FieldComparatorSource() {
+      private static final long serialVersionUID = 1L;
+    
+    public FieldComparator newComparator(String field, int numHits,
+            int sortPos, boolean reverse, final FieldParser parser) throws IOException {
+        return new StringOrdValComparator(numHits, field, sortPos, reverse);
+    }
+
+    @Override
+    public int getTypeId() {
+        return 3; // SortField.STRING in Lucene <= 3.0
+    }
+
+    @Override
+    public String getString(final String field) {
+        return "<string: \""+field+"\">";
+    }
+};
+
+  /** Sort using term values as encoded Integers.  Sort values are Integer and
+   * lower values are at the front. */
+  public static final FieldComparatorSource INT = new FieldComparatorSource() {
+    private static final long serialVersionUID = 1L;
+
+    public FieldComparator newComparator(String field, int numHits,
+            int sortPos, boolean reverse, final FieldParser parser) throws IOException {
+        return new IntComparator(numHits, field, parser);
+    }
+
+    @Override
+    public int getTypeId() {
+        return 4; // SortField.INT in Lucene <= 3.0
+    }
+
+    @Override
+    public String getString(final String field) {
+        return "<int: \""+field+"\">";
+    }
+};
+
+  /** Sort using term values as encoded Floats.  Sort values are Float and
+   * lower values are at the front. */
+  public static final FieldComparatorSource FLOAT = new FieldComparatorSource() {
+    private static final long serialVersionUID = 1L;
+
+    public FieldComparator newComparator(String field, int numHits,
+            int sortPos, boolean reverse, FieldParser parser)
+            throws IOException {
+        return new FloatComparator(numHits, field, parser);
+    }
+
+    @Override
+    public int getTypeId() {
+        return 5; // SortField.FLOAT in Lucene <= 3.0
+    }
+
+    @Override
+    public String getString(final String field) {
+        return "<float: \""+field+"\">";
+    }
+  };
+
+  /** Sort using term values as encoded Longs.  Sort values are Long and
+   * lower values are at the front. */
+  public static final FieldComparatorSource LONG = new FieldComparatorSource() {
+      private static final long serialVersionUID = 1L;
+
+      public FieldComparator newComparator(String field, int numHits,
+              int sortPos, boolean reverse, FieldParser parser)
+              throws IOException {
+          return new LongComparator(numHits, field, parser);
+      }
+
+      @Override
+      public int getTypeId() {
+          return 6; // SortField.LONG in Lucene <= 3.0
+      }
+
+      @Override
+      public String getString(final String field) {
+          return "<long: \""+field+"\">";
+      }
+    };
+    
+  /** Sort using term values as encoded Doubles.  Sort values are Double and
+   * lower values are at the front. */
+    public static final FieldComparatorSource DOUBLE = new FieldComparatorSource() {
+        private static final long serialVersionUID = 1L;
+
+        public FieldComparator newComparator(String field, int numHits,
+                int sortPos, boolean reverse, FieldParser parser)
+                throws IOException {
+            return new DoubleComparator(numHits, field, parser);
+        }
+
+        @Override
+        public int getTypeId() {
+            return 7; // SortField.DOUBLE in Lucene <= 3.0
+        }
+
+        @Override
+        public String getString(final String field) {
+            return "<double: \""+field+"\">";
+        }
+      };
+      
+  /** Sort using term values as encoded Shorts.  Sort values are Short and
+   * lower values are at the front. */
+  public static final FieldComparatorSource SHORT = new FieldComparatorSource() {
+      private static final long serialVersionUID = 1L;
+
+      public FieldComparator newComparator(String field, int numHits,
+              int sortPos, boolean reverse, FieldParser parser)
+              throws IOException {
+          return new ShortComparator(numHits, field, (FieldParser)parser);
+      }
+
+      @Override
+      public int getTypeId() {
+          return 8; // SortField.SHORT in Lucene <= 3.0
+      }
+
+      @Override
+      public String getString(final String field) {
+          return "<short: \""+field+"\">";
+      }
+    };
+   
+  /** Sort using term values as encoded Bytes.  Sort values are Byte and
+   * lower values are at the front. */
+    public static final FieldComparatorSource BYTE = new FieldComparatorSource() {
+        private static final long serialVersionUID = 1L;
+
+        public FieldComparator newComparator(String field, int numHits,
+                int sortPos, boolean reverse, FieldParser parser)
+                throws IOException {
+            return new ByteComparator(numHits, field, parser);
+        }
+
+        @Override
+        public int getTypeId() {
+            return 10; // SortField.BYTE in Lucene <= 3.0
+        }
+
+        @Override
+        public String getString(final String field) {
+            return "<byte: \"" + field + "\">";
+        }
+      };  
+  /** Sort using term values as Strings, but comparing by
+   * value (using String.compareTo) for all comparisons.
+   * This is typically slower than {@link #STRING}, which
+   * uses ordinals to do the sorting. */
+  public static final FieldComparatorSource STRING_VAL = new FieldComparatorSource() {
+      private static final long serialVersionUID = 1L;
+
+      public FieldComparator newComparator(String field, int numHits,
+              int sortPos, boolean reverse, FieldParser parser)
+              throws IOException {
+          return new StringValComparator(numHits, field);
+      }
+
+      @Override
+      public int getTypeId() {
+          return 11; // SortField.STRING_VAL in Lucene <= 3.0
+      }
+
+      @Override
+      public String getString(final String field) {
+          return "<string_val: \""+field+"\">";
+      }
+    };    
+
+  /** Represents sorting by document score (relevance). */
+  public static final SortField FIELD_SCORE = new SortField (null, SCORE);
+
+  /** Represents sorting by document number (index order). */
+  public static final SortField FIELD_DOC = new SortField (null, DOC);
+  
+  static {
+    // We have to initialize the deprecated SortField's static fields that
+    // depend on us
+    org.apache.lucene.search.SortField.FIELD_SCORE = FIELD_SCORE;
+    org.apache.lucene.search.SortField.FIELD_DOC = FIELD_DOC;
+  }
+
+  private String field;
+  private FieldComparatorSource type; 
+  private Locale locale;    // defaults to "natural order" (no Locale)
+  private final boolean reverse;  // defaults to natural order
+  private FieldParser parser;
+
+  /** Creates a sort by terms in the given field with the type of term
+   * values explicitly given.
+   * @param field  Name of field to sort by.  Can be <code>null</code> if
+   *               <code>type</code> is SCORE or DOC.
+   * @param type   Type of values in the terms.
+   */
+  public SortField (String field, FieldComparatorSource type) {
+    this(field,type,false);
+  }
+
+  /** Creates a sort, possibly in reverse, by terms in the given field with the
+   * type of term values explicitly given.
+   * @param field  Name of field to sort by.  Can be <code>null</code> if
+   *               <code>type</code> is SCORE or DOC.
+   * @param type   Type of values in the terms.
+   * @param reverse True if natural order should be reversed.
+   */
+  public SortField (String field, FieldComparatorSource type, boolean reverse) {
+    super(reverse);
+    initFieldType(field, type);
+    this.reverse = reverse;
+  }
+
+  /** Creates a sort by terms in the given field, parsed
+   * to numeric values using a custom {@link FieldParser}.
+   * @param field  Name of field to sort by.  Must not be null unless type is {@link FieldComparatorSource.Fieldless}.
+   * @param type   Type of values in the terms.
+   * @param parser Instance of a {@link FieldParser}.
+   * @throws IllegalArgumentException if field is null
+   */
+  public SortField (String field, FieldComparatorSource type, FieldParser parser) {
+    this(field, type, parser, false);
+  }
+
+  /** Creates a sort by terms in the given field, parsed
+   * to numeric values using a custom {@link FieldParser}.
+   * @param field  Name of field to sort by.  Must not be null unless type is {@link FieldComparatorSource.Fieldless}.
+   * @param parser Instance of a {@link FieldParser.WithFieldComparatorSource} that also provides the {@link FieldComparatorSource}.
+   * @throws IllegalArgumentException if field is null
+   */
+  public SortField (String field, FieldParser.WithFieldComparatorSource parser) {
+    this(field, parser, false);
+  }  
+
+  /** Creates a sort by terms in the given field, parsed
+   * to numeric values using a custom {@link FieldParser}.
+   * @param field  Name of field to sort by.  Must not be null unless type is {@link FieldComparatorSource.Fieldless}.
+   * @param parser Instance of a {@link FieldParser.WithFieldComparatorSource} that also provides the {@link FieldComparatorSource}.
+   * @param reverse True if natural order should be reversed.
+   * @throws IllegalArgumentException if field is null
+   */
+  public SortField (String field, FieldParser.WithFieldComparatorSource parser, boolean reverse) {
+    this(field, parser.getFieldComparatorSource(), parser, reverse);
+  }  
+  
+  /** Creates a sort, possibly in reverse, by terms in the given field, parsed
+   * to numeric values using a custom {@link FieldCache.Parser}.
+   * @param field  Name of field to sort by.  Must not be null.
+   * @param type   Type of values in the terms.
+   * @param parser Instance of a {@link FieldCache.Parser}.
+   * @param reverse True if natural order should be reversed.
+   * @throws IllegalArgumentException if field is null
+   */
+  public SortField (String field, FieldComparatorSource type, FieldParser parser, boolean reverse) {
+    super(reverse);
+    initFieldType(field, type);
+    this.reverse = reverse;
+    this.parser = parser;
+  }
+
+  /** Creates a sort by terms in the given field sorted
+   * according to the given locale.
+   * @param field  Name of field to sort by, cannot be <code>null</code>.
+   * @param locale Locale of values in the field.
+   */
+  public SortField (String field, Locale locale) {
+    this(field,locale,false);
+  }
+
+  /** Creates a sort, possibly in reverse, by terms in the given field sorted
+   * according to the given locale.
+   * @param field  Name of field to sort by, cannot be <code>null</code>.
+   * @param locale Locale of values in the field.
+   */
+  public SortField (String field, Locale locale, boolean reverse) {
+    super(reverse);
+    initFieldType(field, STRING);
+    this.locale = locale;
+    this.reverse = reverse;
+  }
+  
+  // Sets field & type, and ensures field is not NULL unless
+  // type is SCORE or DOC
+  private void initFieldType(String field, FieldComparatorSource type) {
+    this.type = type;
+    if (field == null) {
+      if (!(type instanceof FieldComparatorSource.Fieldless)) {
+        throw new IllegalArgumentException("field can only be null when type is FieldComparatorSource.Fieldless (like SortField.SCORE or SortField.DOC)");
+      }
+    } else {
+      this.field = StringHelper.intern(field);
+    }
+  }
+
+  /** Returns the name of the field.  Could return <code>null</code>
+   * if the sort is by SCORE or DOC.
+   * @return Name of field, possibly <code>null</code>.
+   */
+  public String getField() {
+    return field;
+  }
+
+  /** Returns the type of contents in the field.
+   * @return One of the constants SCORE, DOC, STRING, INT or FLOAT.
+   * @deprecated
+   */
+  public int getType() {
+    return type.getTypeId();
+  }
+
+  /**
+   * Returns the {@link FieldComparatorSource} used for this SortField.
+   * @return
+   */
+  public FieldComparatorSource getFieldComparatorSource() {
+      return type;
+  }
+
+  /** Returns the Locale by which term values are interpreted.
+   * May return <code>null</code> if no Locale was specified.
+   * @return Locale, or <code>null</code>.
+   */
+  public Locale getLocale() {
+    return locale;
+  }
+
+  /** Returns the instance of a {@link FieldParser} that fits to the given sort type.
+   * May return <code>null</code> if no parser was specified. Sorting is using the default parser then.
+   * @return An instance of a {@link FieldParser}, or <code>null</code>.
+   */
+  public FieldParser getParser() {
+    return parser;
+  }
+
+  /** Returns whether the sort should be reversed.
+   * @return  True if natural order should be reversed.
+   */
+  public boolean getReverse() {
+    return reverse;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder buffer = new StringBuilder();
+    buffer.append(type.getString(field));
+
+    if (locale != null) buffer.append('(').append(locale).append(')');
+    if (parser != null) buffer.append('(').append(parser).append(')');
+    if (reverse) buffer.append('!');
+
+    return buffer.toString();
+  }
+
+  /** Returns true if <code>o</code> is equal to this.  If a
+   *  {@link FieldComparatorSource} or {@link
+   *  FieldCache.Parser} was provided, it must properly
+   *  implement equals (unless a singleton is always used). */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof SortField)) return false;
+    final SortField other = (SortField)o;
+    return (
+      other.field == this.field // field is always interned
+      && other.type == this.type
+      && other.reverse == this.reverse
+      && (other.locale == null ? this.locale == null : other.locale.equals(this.locale))
+      && (other.parser == null ? this.parser == null : other.parser.equals(this.parser))
+    );
+  }
+
+  /** Returns true if <code>o</code> is equal to this.  If a
+   *  {@link FieldComparatorSource} or {@link
+   *  FieldCache.Parser} was provided, it must properly
+   *  implement hashCode (unless a singleton is always
+   *  used). */
+  @Override
+  public int hashCode() {
+    int hash=(type.getTypeId())^0x346565dd + Boolean.valueOf(reverse).hashCode()^0xaf5998bb;
+    if (field != null) hash += field.hashCode()^0xff5685dd;
+    if (locale != null) hash += locale.hashCode()^0x08150815;
+    if (type.getTypeId() == 9 /* CUSTOM */) hash += type.hashCode();
+    if (parser != null) hash += parser.hashCode()^0x3aaf56ff;
+    return hash;
+  }
+
+  // field must be interned after reading from stream
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
+    in.defaultReadObject();
+    if (field != null)
+      field = StringHelper.intern(field);
+  }
+
+  /** Returns the {@link FieldComparator} to use for
+   * sorting.
+   *
+   * <b>NOTE:</b> This API is experimental and might change in
+   * incompatible ways in the next release.
+   *
+   * @param numHits number of top hits the queue will store
+   * @param sortPos position of this SortField within {@link
+   *   Sort}.  The comparator is primary if sortPos==0,
+   *   secondary if sortPos==1, etc.  Some comparators can
+   *   optimize themselves when they are the primary sort.
+   * @return {@link FieldComparator} to use when sorting
+   */
+  public FieldComparator getComparator(final int numHits, final int sortPos) throws IOException {
+    if (locale != null) {
+      // TODO: it'd be nice to allow FieldCache.getStringIndex
+      // to optionally accept a Locale so sorting could then use
+      // the faster StringComparator impls
+      return new StringComparatorLocale(numHits, field, locale);
+    }
+
+    return type.newComparator(field, numHits, sortPos, reverse, parser);
+  }
+}
Index: src/java/org/apache/lucene/search/fields/StopFillCacheException.java
===================================================================
--- src/java/org/apache/lucene/search/fields/StopFillCacheException.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/StopFillCacheException.java	(revision 0)
@@ -0,0 +1,10 @@
+package org.apache.lucene.search.fields;
+
+/**
+ * Hack: When thrown from a Parser (NUMERIC_UTILS_* ones), this stops
+ * processing terms and returns the current FieldCache
+ * array.
+ */
+public final class StopFillCacheException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/StringCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/StringCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/StringCache.java	(revision 0)
@@ -0,0 +1,44 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+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.util.StringHelper;
+
+/**
+ * Expert: A {@link Cache} that converts terms to strings.
+ */
+final class StringCache extends Cache {
+  private final IndexReader reader;
+
+  StringCache(final IndexReader reader) {
+    this.reader = reader;
+  }
+  
+  @Override
+  protected Object createValue(Entry entryKey)
+      throws IOException {
+    String field = StringHelper.intern(entryKey.field);
+    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;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/StringComparatorLocale.java
===================================================================
--- src/java/org/apache/lucene/search/fields/StringComparatorLocale.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/StringComparatorLocale.java	(revision 0)
@@ -0,0 +1,74 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+import java.text.Collator;
+import java.util.Locale;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Sorts by a field's value using the Collator for a
+   *  given Locale.*/
+  public final class StringComparatorLocale extends FieldComparator {
+
+    private final String[] values;
+    private String[] currentReaderValues;
+    private final String field;
+    final Collator collator;
+    private String bottom;
+
+    StringComparatorLocale(int numHits, String field, Locale locale) {
+      values = new String[numHits];
+      this.field = field;
+      collator = Collator.getInstance(locale);
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      final String val1 = values[slot1];
+      final String val2 = values[slot2];
+      if (val1 == null) {
+        if (val2 == null) {
+          return 0;
+        }
+        return -1;
+      } else if (val2 == null) {
+        return 1;
+      }
+      return collator.compare(val1, val2);
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      final String val2 = currentReaderValues[doc];
+      if (bottom == null) {
+        if (val2 == null) {
+          return 0;
+        }
+        return -1;
+      } else if (val2 == null) {
+        return 1;
+      }
+      return collator.compare(bottom, val2);
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      values[slot] = currentReaderValues[doc];
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      currentReaderValues = reader.getIndexCache().getFieldCache().getStrings(field);
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      this.bottom = values[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return values[slot];
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/StringIndex.java
===================================================================
--- src/java/org/apache/lucene/search/fields/StringIndex.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/StringIndex.java	(revision 0)
@@ -0,0 +1,47 @@
+package org.apache.lucene.search.fields;
+
+/**
+ * Expert: Stores term text values and document ordering data.
+ * 
+ * <p>
+ * <b>NOTE:</b> This class currently subclasses the deprecated
+ * {@link org.apache.lucene.search.FieldCache.StringIndex}. Do <em>not</em> rely
+ * on this since that class will eventually be removed.
+ */
+public final class StringIndex extends org.apache.lucene.search.FieldCache.StringIndex {
+  
+  public int binarySearchLookup(String key) {
+    // this special case is the reason that Arrays.binarySearch() isn't useful.
+    if (key == null)
+      return 0;
+  
+    int low = 1;
+    int high = lookup.length-1;
+
+    while (low <= high) {
+      int mid = (low + high) >>> 1;
+      int cmp = lookup[mid].compareTo(key);
+
+      if (cmp < 0)
+        low = mid + 1;
+      else if (cmp > 0)
+        high = mid - 1;
+      else
+        return mid; // key found
+    }
+    return -(low + 1);  // key not found.
+  }
+
+  /** 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) {
+    super(values, lookup);
+    this.order = values;
+    this.lookup = lookup;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/StringIndexCache.java
===================================================================
--- src/java/org/apache/lucene/search/fields/StringIndexCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/StringIndexCache.java	(revision 0)
@@ -0,0 +1,75 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+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.util.StringHelper;
+
+/**
+ * Expert: A {@link Cache} that converts terms to {@link StringIndex}.
+ */
+final class StringIndexCache extends Cache {
+  private final IndexReader reader;
+
+  StringIndexCache(final IndexReader reader) {
+    this.reader = reader;
+  }
+  @Override
+  protected Object createValue(Entry entryKey)
+      throws IOException {
+    String field = StringHelper.intern(entryKey.field);
+    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;
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/StringOrdValComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/StringOrdValComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/StringOrdValComparator.java	(revision 0)
@@ -0,0 +1,167 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Sorts by field's natural String sort order, using
+   *  ordinals.  This is functionally equivalent to {@link
+   *  StringValComparator}, but it first resolves the string
+   *  to their relative ordinal positions (using the index
+   *  returned by {@link IndexFieldCache#getStringIndex}), and
+   *  does most comparisons using the ordinals.  For medium
+   *  to large results, this comparator will be much faster
+   *  than {@link StringValComparator}.  For very small
+   *  result sets it may be slower. */
+  public final class StringOrdValComparator extends FieldComparator {
+
+    private final int[] ords;
+    private final String[] values;
+    private final int[] readerGen;
+
+    private int currentReaderGen = -1;
+    private String[] lookup;
+    private int[] order;
+    private final String field;
+
+    private int bottomSlot = -1;
+    private int bottomOrd;
+    private String bottomValue;
+    private final boolean reversed;
+    private final int sortPos;
+
+    public StringOrdValComparator(int numHits, String field, int sortPos, boolean reversed) {
+      ords = new int[numHits];
+      values = new String[numHits];
+      readerGen = new int[numHits];
+      this.sortPos = sortPos;
+      this.reversed = reversed;
+      this.field = field;
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+      if (readerGen[slot1] == readerGen[slot2]) {
+        int cmp = ords[slot1] - ords[slot2];
+        if (cmp != 0) {
+          return cmp;
+        }
+      }
+
+      final String val1 = values[slot1];
+      final String val2 = values[slot2];
+      if (val1 == null) {
+        if (val2 == null) {
+          return 0;
+        }
+        return -1;
+      } else if (val2 == null) {
+        return 1;
+      }
+      return val1.compareTo(val2);
+    }
+
+    @Override
+    public int compareBottom(int doc) {
+      assert bottomSlot != -1;
+      int order = this.order[doc];
+      final int cmp = bottomOrd - order;
+      if (cmp != 0) {
+        return cmp;
+      }
+
+      final String val2 = lookup[order];
+      if (bottomValue == null) {
+        if (val2 == null) {
+          return 0;
+        }
+        // bottom wins
+        return -1;
+      } else if (val2 == null) {
+        // doc wins
+        return 1;
+      }
+      return bottomValue.compareTo(val2);
+    }
+
+    private void convert(int slot) {
+      readerGen[slot] = currentReaderGen;
+      int index = 0;
+      String value = values[slot];
+      if (value == null) {
+        ords[slot] = 0;
+        return;
+      }
+
+      if (sortPos == 0 && bottomSlot != -1 && bottomSlot != slot) {
+        // Since we are the primary sort, the entries in the
+        // queue are bounded by bottomOrd:
+        assert bottomOrd < lookup.length;
+        if (reversed) {
+          index = binarySearch(lookup, value, bottomOrd, lookup.length-1);
+        } else {
+          index = binarySearch(lookup, value, 0, bottomOrd);
+        }
+      } else {
+        // Full binary search
+        index = binarySearch(lookup, value);
+      }
+
+      if (index < 0) {
+        index = -index - 2;
+      }
+      ords[slot] = index;
+    }
+
+    @Override
+    public void copy(int slot, int doc) {
+      final int ord = order[doc];
+      ords[slot] = ord;
+      assert ord >= 0;
+      values[slot] = lookup[ord];
+      readerGen[slot] = currentReaderGen;
+    }
+
+    @Override
+    public void setNextReader(IndexReader reader, int docBase) throws IOException {
+      StringIndex currentReaderValues = reader.getIndexCache().getFieldCache().getStringIndex(field);
+      currentReaderGen++;
+      order = currentReaderValues.order;
+      lookup = currentReaderValues.lookup;
+      assert lookup.length > 0;
+      if (bottomSlot != -1) {
+        convert(bottomSlot);
+        bottomOrd = ords[bottomSlot];
+      }
+    }
+    
+    @Override
+    public void setBottom(final int bottom) {
+      bottomSlot = bottom;
+      if (readerGen[bottom] != currentReaderGen) {
+        convert(bottomSlot);
+      }
+      bottomOrd = ords[bottom];
+      assert bottomOrd >= 0;
+      assert bottomOrd < lookup.length;
+      bottomValue = values[bottom];
+    }
+
+    @Override
+    public Comparable<?> value(int slot) {
+      return values[slot];
+    }
+
+    public String[] getValues() {
+      return values;
+    }
+
+    public int getBottomSlot() {
+      return bottomSlot;
+    }
+
+    public String getField() {
+      return field;
+    }
+  }
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/StringValComparator.java
===================================================================
--- src/java/org/apache/lucene/search/fields/StringValComparator.java	(revision 0)
+++ src/java/org/apache/lucene/search/fields/StringValComparator.java	(revision 0)
@@ -0,0 +1,73 @@
+package org.apache.lucene.search.fields;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.FieldComparator;
+
+/** Sorts by field's natural String sort order.  All
+   *  comparisons are done using String.compareTo, which is
+   *  slow for medium to large result sets but possibly
+   *  very fast for very small results sets. */
+public final class StringValComparator extends FieldComparator {
+
+  private String[] values;
+  private String[] currentReaderValues;
+  private final String field;
+  private String bottom;
+
+  StringValComparator(int numHits, String field) {
+    values = new String[numHits];
+    this.field = field;
+  }
+
+  @Override
+  public int compare(int slot1, int slot2) {
+    final String val1 = values[slot1];
+    final String val2 = values[slot2];
+    if (val1 == null) {
+      if (val2 == null) {
+        return 0;
+      }
+      return -1;
+    } else if (val2 == null) {
+      return 1;
+    }
+
+    return val1.compareTo(val2);
+  }
+
+  @Override
+  public int compareBottom(int doc) {
+    final String val2 = currentReaderValues[doc];
+    if (bottom == null) {
+      if (val2 == null) {
+        return 0;
+      }
+      return -1;
+    } else if (val2 == null) {
+      return 1;
+    }
+    return bottom.compareTo(val2);
+  }
+
+  @Override
+  public void copy(int slot, int doc) {
+    values[slot] = currentReaderValues[doc];
+  }
+
+  @Override
+  public void setNextReader(IndexReader reader, int docBase) throws IOException {
+    currentReaderValues = reader.getIndexCache().getFieldCache().getStrings(field);
+  }
+  
+  @Override
+  public void setBottom(final int bottom) {
+    this.bottom = values[bottom];
+  }
+
+  @Override
+  public Comparable<?> value(int slot) {
+    return values[slot];
+  }
+}
\ No newline at end of file
Index: src/java/org/apache/lucene/search/fields/package.html
===================================================================
--- src/java/org/apache/lucene/search/fields/package.html	(revision 0)
+++ src/java/org/apache/lucene/search/fields/package.html	(revision 0)
@@ -0,0 +1,25 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<!--
+ 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.
+-->
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+</head>
+<body>
+Provides search functionality specific to document fields (e.g., field caching and parsing)
+</body>
+</html>
Index: src/java/org/apache/lucene/search/SortField.java
===================================================================
--- src/java/org/apache/lucene/search/SortField.java	(revision 887928)
+++ src/java/org/apache/lucene/search/SortField.java	(working copy)
@@ -21,11 +21,13 @@
 import java.io.Serializable;
 import java.util.Locale;
 
-import org.apache.lucene.document.NumericField; // javadocs
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.index.TermEnum;
-import org.apache.lucene.util.StringHelper;
+import org.apache.lucene.search.fields.ByteParser;
+import org.apache.lucene.search.fields.DoubleParser;
+import org.apache.lucene.search.fields.FieldParser;
+import org.apache.lucene.search.fields.FloatParser;
+import org.apache.lucene.search.fields.IntParser;
+import org.apache.lucene.search.fields.LongParser;
+import org.apache.lucene.search.fields.ShortParser;
 
 /**
  * Stores information about how to sort documents by terms in an individual
@@ -35,9 +37,11 @@
  *
  * @since   lucene 1.4
  * @see Sort
+ * @deprecated Use {@link org.apache.lucene.search.fields.SortField} instead.
  */
 public class SortField
 implements Serializable {
+  private static final long serialVersionUID = 1L;
 
   /** Sort by document score (relevancy).  Sort values are Float and higher
    * values are at the front. */
@@ -92,19 +96,21 @@
   // as FieldCache.STRING_INDEX.
 
   /** Represents sorting by document score (relevancy). */
-  public static final SortField FIELD_SCORE = new SortField (null, SCORE);
+  public static SortField FIELD_SCORE = null; /* set by SortFieldImpl */
 
   /** Represents sorting by document number (index order). */
-  public static final SortField FIELD_DOC = new SortField (null, DOC);
-
-  private String field;
-  private int type;  // defaults to determining type dynamically
-  private Locale locale;    // defaults to "natural order" (no Locale)
-  boolean reverse = false;  // defaults to natural order
-  private FieldCache.Parser parser;
+  public static SortField FIELD_DOC = null; /* set by SortFieldImpl */
+  
+  private org.apache.lucene.search.fields.SortField impl;
+  
+  final boolean reverse;
 
-  // Used for CUSTOM sort
-  private FieldComparatorSource comparatorSource;
+  protected SortField(final boolean reverse) {
+    // the only permitted subclass is org.apache.lucene.search.field.SortField, which overrides all
+    // methods of SortField.
+    this.impl = null;
+    this.reverse = reverse;
+  }
 
   /** Creates a sort by terms in the given field with the type of term
    * values explicitly given.
@@ -113,7 +119,7 @@
    * @param type   Type of values in the terms.
    */
   public SortField (String field, int type) {
-    initFieldType(field, type);
+    this(field,type,false);
   }
 
   /** Creates a sort, possibly in reverse, by terms in the given field with the
@@ -124,10 +130,36 @@
    * @param reverse True if natural order should be reversed.
    */
   public SortField (String field, int type, boolean reverse) {
-    initFieldType(field, type);
+    impl = new org.apache.lucene.search.fields.SortField(field, typeToFieldComparatorSource(type), reverse);
     this.reverse = reverse;
   }
 
+  private static FieldComparatorSource typeToFieldComparatorSource(int type) {
+    switch(type) {
+    case SCORE:
+      return org.apache.lucene.search.fields.SortField.SCORE;
+    case DOC:
+      return org.apache.lucene.search.fields.SortField.DOC;
+    case STRING:
+      return org.apache.lucene.search.fields.SortField.STRING;
+    case  INT:
+      return org.apache.lucene.search.fields.SortField.INT;
+    case FLOAT:
+      return org.apache.lucene.search.fields.SortField.FLOAT;
+    case LONG:
+      return org.apache.lucene.search.fields.SortField.LONG;
+    case DOUBLE:
+      return org.apache.lucene.search.fields.SortField.DOUBLE;
+    case SHORT:
+      return org.apache.lucene.search.fields.SortField.SHORT;
+    case BYTE:
+      return org.apache.lucene.search.fields.SortField.BYTE;
+    case STRING_VAL:
+      return org.apache.lucene.search.fields.SortField.STRING_VAL;
+    }
+    throw new IllegalArgumentException("Unknown type: "+type);
+  }
+
   /** Creates a sort by terms in the given field, parsed
    * to numeric values using a custom {@link FieldCache.Parser}.
    * @param field  Name of field to sort by.  Must not be null.
@@ -139,7 +171,7 @@
    *  subclass an existing numeric parser, or field is null
    */
   public SortField (String field, FieldCache.Parser parser) {
-    this(field, parser, false);
+    this(field,parser,false);
   }
 
   /** Creates a sort, possibly in reverse, by terms in the given field, parsed
@@ -154,17 +186,25 @@
    *  subclass an existing numeric parser, or field is null
    */
   public SortField (String field, FieldCache.Parser parser, boolean reverse) {
-    if (parser instanceof FieldCache.IntParser) initFieldType(field, INT);
-    else if (parser instanceof FieldCache.FloatParser) initFieldType(field, FLOAT);
-    else if (parser instanceof FieldCache.ShortParser) initFieldType(field, SHORT);
-    else if (parser instanceof FieldCache.ByteParser) initFieldType(field, BYTE);
-    else if (parser instanceof FieldCache.LongParser) initFieldType(field, LONG);
-    else if (parser instanceof FieldCache.DoubleParser) initFieldType(field, DOUBLE);
-    else
-      throw new IllegalArgumentException("Parser instance does not subclass existing numeric parser from FieldCache (got " + parser + ")");
-
+    if(!(parser instanceof FieldParser.WithFieldComparatorSource)) {
+      if(parser instanceof FieldCache.ByteParser) {
+        parser = new WrappingByteParser((FieldCache.ByteParser)parser);
+      } else if(parser instanceof FieldCache.DoubleParser) {
+        parser = new WrappingDoubleParser((FieldCache.DoubleParser)parser);
+      } else if(parser instanceof FieldCache.FloatParser) {
+        parser = new WrappingFloatParser((FieldCache.FloatParser)parser);
+      } else if(parser instanceof FieldCache.IntParser) {
+        parser = new WrappingIntParser((FieldCache.IntParser)parser);
+      } else if(parser instanceof FieldCache.LongParser) {
+        parser = new WrappingLongParser((FieldCache.LongParser)parser);
+      } else if(parser instanceof FieldCache.ShortParser) {
+        parser = new WrappingShortParser((FieldCache.ShortParser)parser);
+      } else {
+      throw new IllegalArgumentException("Unsupported parser: "+parser);
+      }
+    }
+    impl = new org.apache.lucene.search.fields.SortField(field, (FieldParser.WithFieldComparatorSource)parser, reverse); 
     this.reverse = reverse;
-    this.parser = parser;
   }
 
   /** Creates a sort by terms in the given field sorted
@@ -173,8 +213,7 @@
    * @param locale Locale of values in the field.
    */
   public SortField (String field, Locale locale) {
-    initFieldType(field, STRING);
-    this.locale = locale;
+    this(field,locale,false);
   }
 
   /** Creates a sort, possibly in reverse, by terms in the given field sorted
@@ -183,8 +222,7 @@
    * @param locale Locale of values in the field.
    */
   public SortField (String field, Locale locale, boolean reverse) {
-    initFieldType(field, STRING);
-    this.locale = locale;
+    impl = new org.apache.lucene.search.fields.SortField(field, locale, reverse);
     this.reverse = reverse;
   }
 
@@ -193,8 +231,7 @@
    * @param comparator Returns a comparator for sorting hits.
    */
   public SortField (String field, FieldComparatorSource comparator) {
-    initFieldType(field, CUSTOM);
-    this.comparatorSource = comparator;
+    this(field,comparator,false);
   }
 
   /** Creates a sort, possibly in reverse, with a custom comparison function.
@@ -203,21 +240,8 @@
    * @param reverse True if natural order should be reversed.
    */
   public SortField (String field, FieldComparatorSource comparator, boolean reverse) {
-    initFieldType(field, CUSTOM);
+    impl = new org.apache.lucene.search.fields.SortField(field, comparator, reverse);
     this.reverse = reverse;
-    this.comparatorSource = comparator;
-  }
-
-  // Sets field & type, and ensures field is not NULL unless
-  // type is SCORE or DOC
-  private void initFieldType(String field, int type) {
-    this.type = type;
-    if (field == null) {
-      if (type != SCORE && type != DOC)
-        throw new IllegalArgumentException("field can only be null when type is SCORE or DOC");
-    } else {
-      this.field = StringHelper.intern(field);
-    }
   }
 
   /** Returns the name of the field.  Could return <code>null</code>
@@ -225,14 +249,14 @@
    * @return Name of field, possibly <code>null</code>.
    */
   public String getField() {
-    return field;
+    return impl.getField();
   }
 
   /** Returns the type of contents in the field.
    * @return One of the constants SCORE, DOC, STRING, INT or FLOAT.
    */
   public int getType() {
-    return type;
+    return impl.getType();
   }
 
   /** Returns the Locale by which term values are interpreted.
@@ -240,7 +264,7 @@
    * @return Locale, or <code>null</code>.
    */
   public Locale getLocale() {
-    return locale;
+    return impl.getLocale();
   }
 
   /** Returns the instance of a {@link FieldCache} parser that fits to the given sort type.
@@ -248,74 +272,19 @@
    * @return An instance of a {@link FieldCache} parser, or <code>null</code>.
    */
   public FieldCache.Parser getParser() {
-    return parser;
+    return impl.getParser();
   }
 
   /** Returns whether the sort should be reversed.
    * @return  True if natural order should be reversed.
    */
   public boolean getReverse() {
-    return reverse;
+    return impl.getReverse();
   }
 
   @Override
   public String toString() {
-    StringBuilder buffer = new StringBuilder();
-    switch (type) {
-      case SCORE:
-        buffer.append("<score>");
-        break;
-
-      case DOC:
-        buffer.append("<doc>");
-        break;
-
-      case STRING:
-        buffer.append("<string: \"").append(field).append("\">");
-        break;
-
-      case STRING_VAL:
-        buffer.append("<string_val: \"").append(field).append("\">");
-        break;
-
-      case BYTE:
-        buffer.append("<byte: \"").append(field).append("\">");
-        break;
-
-      case SHORT:
-        buffer.append("<short: \"").append(field).append("\">");
-        break;
-
-      case INT:
-        buffer.append("<int: \"").append(field).append("\">");
-        break;
-
-      case LONG:
-        buffer.append("<long: \"").append(field).append("\">");
-        break;
-
-      case FLOAT:
-        buffer.append("<float: \"").append(field).append("\">");
-        break;
-
-      case DOUBLE:
-        buffer.append("<double: \"").append(field).append("\">");
-        break;
-
-      case CUSTOM:
-        buffer.append("<custom:\"").append(field).append("\": ").append(comparatorSource).append('>');
-        break;
-
-      default:
-        buffer.append("<???: \"").append(field).append("\">");
-        break;
-    }
-
-    if (locale != null) buffer.append('(').append(locale).append(')');
-    if (parser != null) buffer.append('(').append(parser).append(')');
-    if (reverse) buffer.append('!');
-
-    return buffer.toString();
+    return impl.toString();
   }
 
   /** Returns true if <code>o</code> is equal to this.  If a
@@ -327,14 +296,7 @@
     if (this == o) return true;
     if (!(o instanceof SortField)) return false;
     final SortField other = (SortField)o;
-    return (
-      other.field == this.field // field is always interned
-      && other.type == this.type
-      && other.reverse == this.reverse
-      && (other.locale == null ? this.locale == null : other.locale.equals(this.locale))
-      && (other.comparatorSource == null ? this.comparatorSource == null : other.comparatorSource.equals(this.comparatorSource))
-      && (other.parser == null ? this.parser == null : other.parser.equals(this.parser))
-    );
+    return other.impl.equals(impl);
   }
 
   /** Returns true if <code>o</code> is equal to this.  If a
@@ -344,19 +306,7 @@
    *  used). */
   @Override
   public int hashCode() {
-    int hash=type^0x346565dd + Boolean.valueOf(reverse).hashCode()^0xaf5998bb;
-    if (field != null) hash += field.hashCode()^0xff5685dd;
-    if (locale != null) hash += locale.hashCode()^0x08150815;
-    if (comparatorSource != null) hash += comparatorSource.hashCode();
-    if (parser != null) hash += parser.hashCode()^0x3aaf56ff;
-    return hash;
-  }
-
-  // field must be interned after reading from stream
-  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
-    in.defaultReadObject();
-    if (field != null)
-      field = StringHelper.intern(field);
+    return impl.hashCode();
   }
 
   /** Returns the {@link FieldComparator} to use for
@@ -373,51 +323,74 @@
    * @return {@link FieldComparator} to use when sorting
    */
   public FieldComparator getComparator(final int numHits, final int sortPos) throws IOException {
+    return impl.getComparator(numHits, sortPos);
+  }
+  
+  private static final class WrappingByteParser extends ByteParser implements FieldCache.ByteParser {
+    private final FieldCache.ByteParser parser;
 
-    if (locale != null) {
-      // TODO: it'd be nice to allow FieldCache.getStringIndex
-      // to optionally accept a Locale so sorting could then use
-      // the faster StringComparator impls
-      return new FieldComparator.StringComparatorLocale(numHits, field, locale);
+    private WrappingByteParser(final FieldCache.ByteParser parser) {
+      this.parser = parser;
     }
-
-    switch (type) {
-    case SortField.SCORE:
-      return new FieldComparator.RelevanceComparator(numHits);
-
-    case SortField.DOC:
-      return new FieldComparator.DocComparator(numHits);
-
-    case SortField.INT:
-      return new FieldComparator.IntComparator(numHits, field, parser);
-
-    case SortField.FLOAT:
-      return new FieldComparator.FloatComparator(numHits, field, parser);
-
-    case SortField.LONG:
-      return new FieldComparator.LongComparator(numHits, field, parser);
-
-    case SortField.DOUBLE:
-      return new FieldComparator.DoubleComparator(numHits, field, parser);
-
-    case SortField.BYTE:
-      return new FieldComparator.ByteComparator(numHits, field, parser);
+    
+    public byte parseByte(String string) {
+      return parser.parseByte(string);
+    }
+  }
+  private static final class WrappingDoubleParser extends DoubleParser implements FieldCache.DoubleParser {
+    private final FieldCache.DoubleParser parser;
 
-    case SortField.SHORT:
-      return new FieldComparator.ShortComparator(numHits, field, parser);
+    private WrappingDoubleParser(final FieldCache.DoubleParser parser) {
+      this.parser = parser;
+    }
+    
+    public double parseDouble(String string) {
+      return parser.parseDouble(string);
+    }
+  }
+  
+  private static final class WrappingFloatParser extends FloatParser implements FieldCache.FloatParser {
+    private final FieldCache.FloatParser parser;
 
-    case SortField.CUSTOM:
-      assert comparatorSource != null;
-      return comparatorSource.newComparator(field, numHits, sortPos, reverse);
+    private WrappingFloatParser(final FieldCache.FloatParser parser) {
+      this.parser = parser;
+    }
+    
+    public float parseFloat(String string) {
+      return parser.parseFloat(string);
+    }
+  }
+  private static final class WrappingIntParser extends IntParser implements FieldCache.IntParser {
+    private final FieldCache.IntParser parser;
 
-    case SortField.STRING:
-      return new FieldComparator.StringOrdValComparator(numHits, field, sortPos, reverse);
+    private WrappingIntParser(final FieldCache.IntParser parser) {
+      this.parser = parser;
+    }
+    
+    public int parseInt(String string) {
+      return parser.parseInt(string);
+    }
+  }
+  private static final class WrappingLongParser extends LongParser implements FieldCache.LongParser {
+    private final FieldCache.LongParser parser;
 
-    case SortField.STRING_VAL:
-      return new FieldComparator.StringValComparator(numHits, field);
-        
-    default:
-      throw new IllegalStateException("Illegal sort type: " + type);
+    private WrappingLongParser(final FieldCache.LongParser parser) {
+      this.parser = parser;
+    }
+    
+    public long parseLong(String string) {
+      return parser.parseLong(string);
     }
   }
+  private static final class WrappingShortParser extends ShortParser implements FieldCache.ShortParser {
+    private final FieldCache.ShortParser parser;
+
+    private WrappingShortParser(final FieldCache.ShortParser parser) {
+      this.parser = parser;
+    }
+    
+    public short parseShort(String string) {
+      return parser.parseShort(string);
+    }
+  }  
 }
Index: src/java/org/apache/lucene/search/IndexCache.java
===================================================================
--- src/java/org/apache/lucene/search/IndexCache.java	(revision 0)
+++ src/java/org/apache/lucene/search/IndexCache.java	(revision 0)
@@ -0,0 +1,56 @@
+package org.apache.lucene.search;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.IndexFieldCacheImpl;
+
+/**
+ * Provides a common base for caches that are common to a particular {@link IndexReader}
+ * or a set of {@link IndexReader}s with identical cache contents (useful for decorating
+ * {@link IndexReader IndexReaders}).
+ * 
+ */
+public class IndexCache implements Closeable {
+    protected final IndexReader reader;
+    private IndexFieldCache fieldCache;
+    
+    /**
+     * Creates a new IndexCache for the given {@link IndexReader}.
+     *
+     * @param reader The {@link IndexReader}.
+     */
+    public IndexCache(final IndexReader reader) {
+        this.reader = reader;
+    }
+    
+    /**
+     * Returns the {@link IndexReader} this {@link IndexCache} was created for.
+     * 
+     * @return The (original) {@link IndexReader}.
+     */
+    public IndexReader getIndexReader() {
+        return reader;
+    }
+
+    /**
+     * Closes this {@link IndexCache}.
+     */
+    public synchronized void close() throws IOException {
+        if(fieldCache != null) {
+            fieldCache.close();
+        }
+    }
+    
+    /**
+     * Returns the {@link IndexFieldCache} for this {@link IndexCache}.
+     */
+    public synchronized IndexFieldCache getFieldCache() {
+        if(fieldCache == null) {
+            fieldCache = new IndexFieldCacheImpl(reader);
+        }
+        return fieldCache;
+    }
+}
Index: src/java/org/apache/lucene/index/SegmentReaderIndexCache.java
===================================================================
--- src/java/org/apache/lucene/index/SegmentReaderIndexCache.java	(revision 0)
+++ src/java/org/apache/lucene/index/SegmentReaderIndexCache.java	(revision 0)
@@ -0,0 +1,69 @@
+package org.apache.lucene.index;
+
+import java.io.IOException;
+
+import org.apache.lucene.search.IndexCache;
+import org.apache.lucene.util.CloseableThreadLocal;
+
+/**
+ * An {@link IndexCache} for {@link SegmentReader SegmentReaders}.
+ */
+public class SegmentReaderIndexCache extends IndexCache {
+  private final SegmentReader segmentReader;
+
+  CloseableThreadLocal<FieldsReader> fieldsReaderLocal = new CloseableThreadLocal<FieldsReader>() {
+    @Override
+    protected FieldsReader initialValue() {
+      return (FieldsReader) segmentReader.core.getFieldsReaderOrig().clone();
+    }
+
+    protected void onClose(FieldsReader obj) throws IOException {
+      obj.close();
+    }
+    
+    
+  };
+  CloseableThreadLocal<TermVectorsReader> termVectorsLocal = new CloseableThreadLocal<TermVectorsReader>() {
+    protected TermVectorsReader initialValue() {
+        TermVectorsReader orig = segmentReader.core.getTermVectorsReaderOrig();
+        if (orig == null) {
+          return null;
+        } else {
+          try {
+            return (TermVectorsReader) orig.clone();
+          } catch (CloneNotSupportedException cnse) {
+            return null;
+          }
+        }
+    }
+  };
+
+  public SegmentReaderIndexCache(SegmentReader reader) {
+    super(reader);
+    segmentReader = reader;
+  }
+  
+  
+  
+
+  public synchronized void close() throws IOException {
+    super.close();
+    fieldsReaderLocal.close();
+    termVectorsLocal.close();
+  }
+
+
+  /**
+   * Create a clone from the initial TermVectorsReader and store it in the ThreadLocal.
+   * @return TermVectorsReader
+   */
+  TermVectorsReader getTermVectorsReader() throws IOException {
+    return termVectorsLocal.get();
+  }
+  
+  
+  FieldsReader getFieldsReader() throws IOException {
+    return fieldsReaderLocal.get();
+  }
+  
+}
Index: src/java/org/apache/lucene/index/IndexReader.java
===================================================================
--- src/java/org/apache/lucene/index/IndexReader.java	(revision 887928)
+++ src/java/org/apache/lucene/index/IndexReader.java	(working copy)
@@ -17,19 +17,24 @@
  * limitations under the License.
  */
 
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.FieldSelector;
-import org.apache.lucene.search.Similarity;
-import org.apache.lucene.store.*;
-
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.Closeable;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
 
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.search.IndexCache;
+import org.apache.lucene.search.Similarity;
+import org.apache.lucene.store.AlreadyClosedException;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.LockObtainFailedException;
+
 /** IndexReader is an abstract class, providing an interface for accessing an
  index.  Search of an index is done entirely through this abstract interface,
  so that any subclass which implements it is searchable.
@@ -161,6 +166,10 @@
     if (refCount == 1) {
       commit();
       doClose();
+      if(indexCache != null) {
+          indexCache.close();
+          indexCache = null;
+      }
     }
     refCount--;
   }
@@ -1145,7 +1154,10 @@
     return null;
   }
 
-  /** Expert */
+  /** Expert 
+   * @deprecated Use {@link IndexCache} instead.
+   **/
+
   public Object getFieldCacheKey() {
     return this;
   }
@@ -1165,4 +1177,59 @@
   public long getUniqueTermCount() throws IOException {
     throw new UnsupportedOperationException("this reader does not implement getUniqueTermCount()");
   }
+  
+  private IndexCache indexCache; 
+  
+  /**
+   * Returns the {@link IndexCache} associated with this {@link IndexReader}.
+   * Depending on the implementation, an {@link IndexCache} may be shared
+   * among IndexReaders (e.g., decorating ones like {@link FilterIndexReader}). 
+   * 
+   * @return The {@link IndexCache}.
+   * @throws IOException
+   */
+  public IndexCache getIndexCache() throws IOException {
+      return getOrCreateIndexCache();
+  }
+  
+  /** Checks whether an IndexCache instance has already been assigned and creates a new
+   * one if necessary. Subclasses should override {@link #createIndexCache()} in case
+   * more caching capability is required than the default implementation provides.
+   * 
+   * @return The IndexCache
+   * @throws IOException
+   */
+  protected final synchronized IndexCache getOrCreateIndexCache() throws IOException {
+      if(indexCache == null) {
+          indexCache = createIndexCache();
+      }
+      return indexCache;
+  }
+  
+  /**
+   * Instantiates a new {@link IndexCache} for this {@link IndexReader}.
+   * Override this method if:
+   * <ul>
+   * <li>you want to create an {@link IndexCache} for a decorated upstream</li>
+   * <li>you need to create a subclass of {@link IndexCache}</li> 
+   * </ul>
+   * @return The IndexCache
+   * @throws IOException
+   */
+  protected IndexCache createIndexCache() throws IOException {
+      return new IndexCache(this);
+  }
+  
+  /**
+   * Called by IndexReader implementations to notify the {@link IndexCache}
+   * to flush (invalidate) its contents.
+   * 
+   * The default implementation simply closes the {@link IndexCache}.
+   */
+  protected synchronized void notifyFlushIndexCache() throws IOException {
+      if(indexCache != null) {
+          indexCache.close();
+          indexCache = null;
+      }
+  }
 }
Index: src/java/org/apache/lucene/index/SegmentMerger.java
===================================================================
--- src/java/org/apache/lucene/index/SegmentMerger.java	(revision 887928)
+++ src/java/org/apache/lucene/index/SegmentMerger.java	(working copy)
@@ -324,7 +324,7 @@
           final SegmentReader matchingSegmentReader = matchingSegmentReaders[idx++];
           FieldsReader matchingFieldsReader = null;
           if (matchingSegmentReader != null) {
-            final FieldsReader fieldsReader = matchingSegmentReader.getFieldsReader();
+            final FieldsReader fieldsReader = matchingSegmentReader.getIndexCache().getFieldsReader();
             if (fieldsReader != null && fieldsReader.canReadRawDocs()) {            
               matchingFieldsReader = fieldsReader;
             }
Index: src/java/org/apache/lucene/index/SegmentReader.java
===================================================================
--- src/java/org/apache/lucene/index/SegmentReader.java	(revision 887928)
+++ src/java/org/apache/lucene/index/SegmentReader.java	(working copy)
@@ -23,20 +23,19 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.search.IndexCache;
 import org.apache.lucene.search.Similarity;
 import org.apache.lucene.store.BufferedIndexInput;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.IndexInput;
 import org.apache.lucene.store.IndexOutput;
 import org.apache.lucene.util.BitVector;
-import org.apache.lucene.util.CloseableThreadLocal;
 
 /** @version $Id */
 /**
@@ -49,9 +48,6 @@
   private SegmentInfo si;
   private int readBufferSize;
 
-  CloseableThreadLocal<FieldsReader> fieldsReaderLocal = new FieldsReaderLocal();
-  CloseableThreadLocal<TermVectorsReader> termVectorsLocal = new CloseableThreadLocal<TermVectorsReader>();
-
   BitVector deletedDocs = null;
   Ref deletedDocsRef = null;
   private boolean deletedDocsDirty = false;
@@ -98,8 +94,11 @@
     CompoundFileReader cfsReader;
     CompoundFileReader storeCFSReader;
 
-    CoreReaders(Directory dir, SegmentInfo si, int readBufferSize, int termsIndexDivisor) throws IOException {
-      segment = si.name;
+    private final SegmentReader originalSegmentReader;
+
+    CoreReaders(SegmentReader segmentReader, Directory dir, SegmentInfo si, int readBufferSize, int termsIndexDivisor) throws IOException {
+      this.originalSegmentReader = segmentReader;
+    segment = si.name;
       this.readBufferSize = readBufferSize;
       this.dir = dir;
 
@@ -289,16 +288,6 @@
     }
   }
 
-  /**
-   * Sets the initial value 
-   */
-  private class FieldsReaderLocal extends CloseableThreadLocal<FieldsReader> {
-    @Override
-    protected FieldsReader initialValue() {
-      return (FieldsReader) core.getFieldsReaderOrig().clone();
-    }
-  }
-  
   static class Ref {
     private int refCount = 1;
     
@@ -573,7 +562,7 @@
     boolean success = false;
 
     try {
-      instance.core = new CoreReaders(dir, si, readBufferSize, termInfosIndexDivisor);
+      instance.core = new CoreReaders(instance, dir, si, readBufferSize, termInfosIndexDivisor);
       if (doOpenStores) {
         instance.core.openDocStores(si);
       }
@@ -774,15 +763,10 @@
     }
   }
 
-  FieldsReader getFieldsReader() {
-    return fieldsReaderLocal.get();
-  }
+  private boolean closed = false;
 
   @Override
   protected void doClose() throws IOException {
-    termVectorsLocal.close();
-    fieldsReaderLocal.close();
-    
     if (deletedDocs != null) {
       deletedDocsRef.decRef();
       // null so if an app hangs on to us we still free most ram
@@ -795,6 +779,7 @@
     if (core != null) {
       core.decRef();
     }
+    closed = true;
   }
 
   static boolean hasDeletions(SegmentInfo si) throws IOException {
@@ -876,7 +861,7 @@
   @Override
   public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
     ensureOpen();
-    return getFieldsReader().doc(n, fieldSelector);
+    return getIndexCache().getFieldsReader().doc(n, fieldSelector);
   }
 
   @Override
@@ -1106,28 +1091,6 @@
     return norms.get(field).refCount == 0;
   }
 
-  /**
-   * Create a clone from the initial TermVectorsReader and store it in the ThreadLocal.
-   * @return TermVectorsReader
-   */
-  TermVectorsReader getTermVectorsReader() {
-    TermVectorsReader tvReader = termVectorsLocal.get();
-    if (tvReader == null) {
-      TermVectorsReader orig = core.getTermVectorsReaderOrig();
-      if (orig == null) {
-        return null;
-      } else {
-        try {
-          tvReader = (TermVectorsReader) orig.clone();
-        } catch (CloneNotSupportedException cnse) {
-          return null;
-        }
-      }
-      termVectorsLocal.set(tvReader);
-    }
-    return tvReader;
-  }
-
   TermVectorsReader getTermVectorsReaderOrig() {
     return core.getTermVectorsReaderOrig();
   }
@@ -1146,7 +1109,7 @@
     if (fi == null || !fi.storeTermVector) 
       return null;
     
-    TermVectorsReader termVectorsReader = getTermVectorsReader();
+    TermVectorsReader termVectorsReader = getIndexCache().getTermVectorsReader();
     if (termVectorsReader == null)
       return null;
     
@@ -1161,7 +1124,7 @@
     if (fi == null || !fi.storeTermVector)
       return;
 
-    TermVectorsReader termVectorsReader = getTermVectorsReader();
+    TermVectorsReader termVectorsReader = getIndexCache().getTermVectorsReader();
     if (termVectorsReader == null) {
       return;
     }
@@ -1175,7 +1138,7 @@
   public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
     ensureOpen();
 
-    TermVectorsReader termVectorsReader = getTermVectorsReader();
+    TermVectorsReader termVectorsReader = getIndexCache().getTermVectorsReader();
     if (termVectorsReader == null)
       return;
 
@@ -1193,7 +1156,7 @@
   public TermFreqVector[] getTermFreqVectors(int docNumber) throws IOException {
     ensureOpen();
     
-    TermVectorsReader termVectorsReader = getTermVectorsReader();
+    TermVectorsReader termVectorsReader = getIndexCache().getTermVectorsReader();
     if (termVectorsReader == null)
       return null;
     
@@ -1252,10 +1215,17 @@
   // same entry in the FieldCache.  See LUCENE-1579.
   @Override
   public final Object getFieldCacheKey() {
-    return core.freqStream;
+    return core.originalSegmentReader;
+  }
+  
+  public SegmentReaderIndexCache getIndexCache() throws IOException {
+      if(core.originalSegmentReader != this && !core.originalSegmentReader.closed) {
+          return core.originalSegmentReader.getIndexCache();
+      }
+      return (SegmentReaderIndexCache)super.getIndexCache();
   }
 
-  @Override
+@Override
   public long getUniqueTermCount() {
     return core.getTermsReader().size();
   }
@@ -1289,4 +1259,24 @@
   public int getTermInfosIndexDivisor() {
     return core.termsIndexDivisor;
   }
+  
+  /**
+   * @deprecated Use {@link SegmentReaderIndexCache#getTermVectorsReader()}
+   */
+  TermVectorsReader getTermVectorsReader() throws IOException {
+    return getIndexCache().getTermVectorsReader();
+  }
+  
+  /**
+   * @deprecated Use {@link SegmentReaderIndexCache#getFieldsReader()}
+   */
+  FieldsReader getFieldsReader() throws IOException {
+    return getIndexCache().getFieldsReader();
+  }
+
+  /** Called by {@link IndexReader#getOrCreateIndexCache()} */
+  protected IndexCache createIndexCache() throws IOException {
+    return new SegmentReaderIndexCache(this);
+  }
+  
 }
Index: src/java/org/apache/lucene/util/CloseableThreadLocal.java
===================================================================
--- src/java/org/apache/lucene/util/CloseableThreadLocal.java	(revision 887928)
+++ src/java/org/apache/lucene/util/CloseableThreadLocal.java	(working copy)
@@ -18,6 +18,7 @@
  */
 
 import java.io.Closeable;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -52,7 +53,7 @@
 
 public class CloseableThreadLocal<T> implements Closeable {
 
-  private ThreadLocal<WeakReference<T>> t = new ThreadLocal<WeakReference<T>>();
+  private ThreadLocal<WeakReference<T>> threadLocal = new ThreadLocal<WeakReference<T>>();
 
   private Map<Thread,T> hardRefs = new HashMap<Thread,T>();
   
@@ -61,7 +62,7 @@
   }
   
   public T get() {
-    WeakReference<T> weakRef = t.get();
+    WeakReference<T> weakRef = threadLocal.get();
     if (weakRef == null) {
       T iv = initialValue();
       if (iv != null) {
@@ -76,30 +77,55 @@
 
   public void set(T object) {
 
-    t.set(new WeakReference<T>(object));
+    threadLocal.set(new WeakReference<T>(object));
 
     synchronized(hardRefs) {
       hardRefs.put(Thread.currentThread(), object);
 
       // Purge dead threads
-      for (Iterator<Thread> it = hardRefs.keySet().iterator(); it.hasNext();) {
-        final Thread t = it.next();
-        if (!t.isAlive())
+      for (Iterator<Map.Entry<Thread, T>> it = hardRefs.entrySet().iterator(); it.hasNext();) {
+        final Map.Entry<Thread, T> en  = it.next();
+        if (!en.getKey().isAlive()) {
           it.remove();
+          try {
+            onClose(en.getValue());
+          } catch (IOException e) {
+            e.printStackTrace();
+          }
+        }
       }
     }
   }
+  
+  /**
+   * Called by {@link CloseableThreadLocal#close()} and by {@link #set(Object)} (for dead threads only),
+   * each with the thread's current local object.
+   * 
+   * Throwing an {@link IOException} from this method will interrupt {@link CloseableThreadLocal#close()}
+   * and cause a stack trace dump to <code>System.err</code> when called by {@link #set(Object)}. 
+   * 
+   * @param obj
+   * @throws IOException
+   */
+  protected void onClose(final T obj) throws IOException {
+  }
 
-  public void close() {
+  public void close() throws IOException {
     // Clear the hard refs; then, the only remaining refs to
     // all values we were storing are weak (unless somewhere
     // else is still using them) and so GC may reclaim them:
+    if(hardRefs != null) {
+      for(T t : hardRefs.values()) {
+        onClose(t);
+      }
+    }
+    hardRefs.clear();
     hardRefs = null;
     // Take care of the current thread right now; others will be
     // taken care of via the WeakReferences.
-    if (t != null) {
-      t.remove();
+    if (threadLocal != null) {
+      threadLocal.remove();
     }
-    t = null;
+    threadLocal = null;
   }
 }
Index: src/java/org/apache/lucene/util/IndexFieldCacheSanityChecker.java
===================================================================
--- src/java/org/apache/lucene/util/IndexFieldCacheSanityChecker.java	(revision 0)
+++ src/java/org/apache/lucene/util/IndexFieldCacheSanityChecker.java	(revision 0)
@@ -0,0 +1,426 @@
+package org.apache.lucene.util;
+/**
+ * Copyright 2009 The Apache Software Foundation
+ *
+ * Licensed 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.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.fields.CacheEntry;
+import org.apache.lucene.search.fields.IndexFieldCache;
+
+/** 
+ * Provides methods for sanity checking that entries in the FieldCache 
+ * are not wasteful or inconsistent.
+ * </p>
+ * <p>
+ * Lucene 2.9 Introduced numerous enhancements into how the FieldCache 
+ * is used by the low levels of Lucene searching (for Sorting and 
+ * ValueSourceQueries) to improve both the speed for Sorting, as well 
+ * as reopening of IndexReaders.  But these changes have shifted the 
+ * usage of FieldCache from "top level" IndexReaders (frequently a 
+ * MultiReader or DirectoryReader) down to the leaf level SegmentReaders.  
+ * As a result, existing applications that directly access the FieldCache 
+ * may find RAM usage increase significantly when upgrading to 2.9 or 
+ * Later.  This class provides an API for these applications (or their 
+ * Unit tests) to check at run time if the FieldCache contains "insane" 
+ * usages of the FieldCache.
+ * </p>
+ * <p>
+ * <b>EXPERIMENTAL API:</b> This API is considered extremely advanced and 
+ * experimental.  It may be removed or altered w/o warning in future releases 
+ * of Lucene.
+ * </p>
+ * @see FieldCache
+ * @see IndexFieldCacheSanityChecker.Insanity
+ * @see IndexFieldCacheSanityChecker.InsanityType
+ * @deprecated Not needed, just there to pass some junit test which later will be deprecated as well.
+ */
+public final class IndexFieldCacheSanityChecker {
+
+  private RamUsageEstimator ramCalc = null;
+  public IndexFieldCacheSanityChecker() {
+    /* NOOP */
+  }
+  /**
+   * If set, will be used to estimate size for all CacheEntry objects 
+   * dealt with.
+   */
+  public void setRamUsageEstimator(RamUsageEstimator r) {
+    ramCalc = r;
+  }
+
+
+  /** 
+   * Quick and dirty convenience method
+   * @see #check
+   */
+  public static Insanity[] checkSanity(IndexFieldCache cache) {
+    return checkSanity(cache.getCacheEntries());
+  }
+
+  /** 
+   * Quick and dirty convenience method that instantiates an instance with 
+   * "good defaults" and uses it to test the CacheEntrys
+   * @see #check
+   */
+  public static Insanity[] checkSanity(CacheEntry... cacheEntries) {
+    IndexFieldCacheSanityChecker sanityChecker = new IndexFieldCacheSanityChecker();
+    // doesn't check for interned
+    sanityChecker.setRamUsageEstimator(new RamUsageEstimator(false));
+    return sanityChecker.check(cacheEntries);
+  }
+
+
+  /**
+   * Tests a CacheEntry[] for indication of "insane" cache usage.
+   * <p>
+   * <B>NOTE:</b>FieldCache CreationPlaceholder objects are ignored.
+   * (:TODO: is this a bad idea? are we masking a real problem?)
+   * </p>
+   */
+  public Insanity[] check(CacheEntry... cacheEntries) {
+    if (null == cacheEntries || 0 == cacheEntries.length) 
+      return new Insanity[0];
+
+    if (null != ramCalc) {
+      for (int i = 0; i < cacheEntries.length; i++) {
+        cacheEntries[i].estimateSize(ramCalc);
+      }
+    }
+
+    // the indirect mapping lets MapOfSet dedup identical valIds for us
+    //
+    // maps the (valId) identityhashCode of cache values to 
+    // sets of CacheEntry instances
+    final MapOfSets<Integer, CacheEntry> valIdToItems = new MapOfSets<Integer, CacheEntry>(new HashMap<Integer, Set<CacheEntry>>(17));
+    // maps ReaderField keys to Sets of ValueIds
+    final MapOfSets<ReaderField, Integer> readerFieldToValIds = new MapOfSets<ReaderField, Integer>(new HashMap<ReaderField, Set<Integer>>(17));
+    //
+
+    // any keys that we know result in more then one valId
+    final Set<ReaderField> valMismatchKeys = new HashSet<ReaderField>();
+
+    // iterate over all the cacheEntries to get the mappings we'll need
+    for (int i = 0; i < cacheEntries.length; i++) {
+      final CacheEntry item = cacheEntries[i];
+      final Object val = item.getValue();
+
+      if (val instanceof IndexFieldCache.CreationPlaceholder)
+        continue;
+
+      final ReaderField rf = new ReaderField( 
+                                            item.getFieldName());
+
+      final Integer valId = Integer.valueOf(System.identityHashCode(val));
+
+      // indirect mapping, so the MapOfSet will dedup identical valIds for us
+      valIdToItems.put(valId, item);
+      if (1 < readerFieldToValIds.put(rf, valId)) {
+        valMismatchKeys.add(rf);
+      }
+    }
+
+    final List<Insanity> insanity = new ArrayList<Insanity>(valMismatchKeys.size() * 3);
+
+    insanity.addAll(checkValueMismatch(valIdToItems, 
+                                       readerFieldToValIds, 
+                                       valMismatchKeys));
+    insanity.addAll(checkSubreaders(valIdToItems, 
+                                    readerFieldToValIds));
+                    
+    return insanity.toArray(new Insanity[insanity.size()]);
+  }
+
+  /** 
+   * Internal helper method used by check that iterates over 
+   * valMismatchKeys and generates a Collection of Insanity 
+   * instances accordingly.  The MapOfSets are used to populate 
+   * the Insanity objects. 
+   * @see InsanityType#VALUEMISMATCH
+   */
+  private Collection<Insanity> checkValueMismatch(MapOfSets<Integer, CacheEntry> valIdToItems,
+                                        MapOfSets<ReaderField, Integer> readerFieldToValIds,
+                                        Set<ReaderField> valMismatchKeys) {
+
+    final List<Insanity> insanity = new ArrayList<Insanity>(valMismatchKeys.size() * 3);
+
+    if (! valMismatchKeys.isEmpty() ) { 
+      // we have multiple values for some ReaderFields
+
+      final Map<ReaderField, Set<Integer>> rfMap = readerFieldToValIds.getMap();
+      final Map<Integer, Set<CacheEntry>> valMap = valIdToItems.getMap();
+      for (final ReaderField rf : valMismatchKeys) {
+        final List<CacheEntry> badEntries = new ArrayList<CacheEntry>(valMismatchKeys.size() * 2);
+        for(final Integer value: rfMap.get(rf)) {
+          for (final CacheEntry cacheEntry : valMap.get(value)) {
+            badEntries.add(cacheEntry);
+          }
+        }
+
+        CacheEntry[] badness = new CacheEntry[badEntries.size()];
+        badness = badEntries.toArray(badness);
+
+        insanity.add(new Insanity(InsanityType.VALUEMISMATCH,
+                                  "Multiple distinct value objects for " + 
+                                  rf.toString(), badness));
+      }
+    }
+    return insanity;
+  }
+
+  /** 
+   * Internal helper method used by check that iterates over 
+   * the keys of readerFieldToValIds and generates a Collection 
+   * of Insanity instances whenever two (or more) ReaderField instances are 
+   * found that have an ancestry relationships.  
+   *
+   * @see InsanityType#SUBREADER
+   */
+  private Collection<Insanity> checkSubreaders( MapOfSets<Integer, CacheEntry>  valIdToItems,
+                                      MapOfSets<ReaderField, Integer> readerFieldToValIds) {
+
+    final List<Insanity> insanity = new ArrayList<Insanity>(23);
+
+    Map<ReaderField, Set<ReaderField>> badChildren = new HashMap<ReaderField, Set<ReaderField>>(17);
+    MapOfSets<ReaderField, ReaderField> badKids = new MapOfSets<ReaderField, ReaderField>(badChildren); // wrapper
+
+    Map<Integer, Set<CacheEntry>> viToItemSets = valIdToItems.getMap();
+    Map<ReaderField, Set<Integer>> rfToValIdSets = readerFieldToValIds.getMap();
+
+    Set<ReaderField> seen = new HashSet<ReaderField>(17);
+
+    Set<ReaderField> readerFields = rfToValIdSets.keySet();
+    for (final ReaderField rf : readerFields) {
+      
+      if (seen.contains(rf)) continue;
+
+//      List<Object> kids = getAllDecendentReaderKeys(rf.readerKey);
+//      for (Object kidKey : kids) {
+//        ReaderField kid = new ReaderField(kidKey, rf.fieldName);
+//        
+//        if (badChildren.containsKey(kid)) {
+//          // we've already process this kid as RF and found other problems
+//          // track those problems as our own
+//          badKids.put(rf, kid);
+//          badKids.putAll(rf, badChildren.get(kid));
+//          badChildren.remove(kid);
+//          
+//        } else if (rfToValIdSets.containsKey(kid)) {
+//          // we have cache entries for the kid
+//          badKids.put(rf, kid);
+//        }
+//        seen.add(kid);
+//      }
+      seen.add(rf);
+    }
+
+    // every mapping in badKids represents an Insanity
+    for (final ReaderField parent : badChildren.keySet()) {
+      Set<ReaderField> kids = badChildren.get(parent);
+
+      List<CacheEntry> badEntries = new ArrayList<CacheEntry>(kids.size() * 2);
+
+      // put parent entr(ies) in first
+      {
+        for (final Integer value  : rfToValIdSets.get(parent)) {
+          badEntries.addAll(viToItemSets.get(value));
+        }
+      }
+
+      // now the entries for the descendants
+      for (final ReaderField kid : kids) {
+        for (final Integer value : rfToValIdSets.get(kid)) {
+          badEntries.addAll(viToItemSets.get(value));
+        }
+      }
+
+      CacheEntry[] badness = new CacheEntry[badEntries.size()];
+      badness = badEntries.toArray(badness);
+
+      insanity.add(new Insanity(InsanityType.SUBREADER,
+                                "Found caches for decendents of " + 
+                                parent.toString(),
+                                badness));
+    }
+
+    return insanity;
+
+  }
+
+  /**
+   * Checks if the seed is an IndexReader, and if so will walk
+   * the hierarchy of subReaders building up a list of the objects 
+   * returned by obj.getFieldCacheKey()
+   */
+  private List<Object> getAllDecendentReaderKeys(Object seed) {
+    List<Object> all = new ArrayList<Object>(17); // will grow as we iter
+    all.add(seed);
+    for (int i = 0; i < all.size(); i++) {
+      Object obj = all.get(i);
+      if (obj instanceof IndexReader) {
+        IndexReader[] subs = ((IndexReader)obj).getSequentialSubReaders();
+        for (int j = 0; (null != subs) && (j < subs.length); j++) {
+          all.add(subs[j].getFieldCacheKey());
+        }
+      }
+      
+    }
+    // need to skip the first, because it was the seed
+    return all.subList(1, all.size());
+  }
+
+  /**
+   * Simple pair object for using "readerKey + fieldName" a Map key
+   */
+  private final static class ReaderField {
+    public final String fieldName;
+    public ReaderField(String fieldName) {
+      this.fieldName = fieldName;
+    }
+    @Override
+    public int hashCode() {
+      return fieldName.hashCode();
+    }
+    @Override
+    public boolean equals(Object that) {
+      if (! (that instanceof ReaderField)) return false;
+
+      ReaderField other = (ReaderField) that;
+      return (this.fieldName.equals(other.fieldName));
+    }
+    @Override
+    public String toString() {
+      return fieldName;
+    }
+  }
+
+  /**
+   * Simple container for a collection of related CacheEntry objects that 
+   * in conjunction with each other represent some "insane" usage of the 
+   * FieldCache.
+   */
+  public final static class Insanity {
+    private final InsanityType type;
+    private final String msg;
+    private final CacheEntry[] entries;
+    public Insanity(InsanityType type, String msg, CacheEntry... entries) {
+      if (null == type) {
+        throw new IllegalArgumentException
+          ("Insanity requires non-null InsanityType");
+      }
+      if (null == entries || 0 == entries.length) {
+        throw new IllegalArgumentException
+          ("Insanity requires non-null/non-empty CacheEntry[]");
+      }
+      this.type = type;
+      this.msg = msg;
+      this.entries = entries;
+      
+    }
+    /**
+     * Type of insane behavior this object represents
+     */
+    public InsanityType getType() { return type; }
+    /**
+     * Description of hte insane behavior
+     */
+    public String getMsg() { return msg; }
+    /**
+     * CacheEntry objects which suggest a problem
+     */
+    public CacheEntry[] getCacheEntries() { return entries; }
+    /**
+     * Multi-Line representation of this Insanity object, starting with 
+     * the Type and Msg, followed by each CacheEntry.toString() on it's 
+     * own line prefaced by a tab character
+     */
+    @Override
+    public String toString() {
+      StringBuilder buf = new StringBuilder();
+      buf.append(getType()).append(": ");
+
+      String m = getMsg();
+      if (null != m) buf.append(m);
+
+      buf.append('\n');
+
+      CacheEntry[] ce = getCacheEntries();
+      for (int i = 0; i < ce.length; i++) {
+        buf.append('\t').append(ce[i].toString()).append('\n');
+      }
+
+      return buf.toString();
+    }
+  }
+
+  /**
+   * An Enumeration of the different types of "insane" behavior that 
+   * may be detected in a FieldCache.
+   *
+   * @see InsanityType#SUBREADER
+   * @see InsanityType#VALUEMISMATCH
+   * @see InsanityType#EXPECTED
+   */
+  public final static class InsanityType {
+    private final String label;
+    private InsanityType(final String label) {
+      this.label = label;
+    }
+    @Override
+    public String toString() { return label; }
+
+    /** 
+     * Indicates an overlap in cache usage on a given field 
+     * in sub/super readers.
+     */
+    public final static InsanityType SUBREADER 
+      = new InsanityType("SUBREADER");
+
+    /** 
+     * <p>
+     * Indicates entries have the same reader+fieldname but 
+     * different cached values.  This can happen if different datatypes, 
+     * or parsers are used -- and while it's not necessarily a bug 
+     * it's typically an indication of a possible problem.
+     * </p>
+     * <p>
+     * <bPNOTE:</b> Only the reader, fieldname, and cached value are actually 
+     * tested -- if two cache entries have different parsers or datatypes but 
+     * the cached values are the same Object (== not just equal()) this method 
+     * does not consider that a red flag.  This allows for subtle variations 
+     * in the way a Parser is specified (null vs DEFAULT_LONG_PARSER, etc...)
+     * </p>
+     */
+    public final static InsanityType VALUEMISMATCH 
+      = new InsanityType("VALUEMISMATCH");
+
+    /** 
+     * Indicates an expected bit of "insanity".  This may be useful for 
+     * clients that wish to preserve/log information about insane usage 
+     * but indicate that it was expected. 
+     */
+    public final static InsanityType EXPECTED
+      = new InsanityType("EXPECTED");
+  }
+  
+  
+}
Index: src/java/org/apache/lucene/util/FieldCacheSanityChecker.java
===================================================================
--- src/java/org/apache/lucene/util/FieldCacheSanityChecker.java	(revision 887928)
+++ src/java/org/apache/lucene/util/FieldCacheSanityChecker.java	(working copy)
@@ -52,6 +52,9 @@
  * @see FieldCache
  * @see FieldCacheSanityChecker.Insanity
  * @see FieldCacheSanityChecker.InsanityType
+ * 
+ * @deprecated Not necessary anymore. Just there for backwards-compatibility
+ * @see IndexFieldCacheSanityChecker
  */
 public final class FieldCacheSanityChecker {
 
Index: contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java
===================================================================
--- contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java	(revision 887931)
+++ contrib/spatial/src/test/org/apache/lucene/spatial/tier/TestCartesian.java	(working copy)
@@ -32,7 +32,7 @@
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.fields.SortField;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.search.function.CustomScoreQuery;
Index: contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/SearchWithSortTask.java
===================================================================
--- contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/SearchWithSortTask.java	(revision 887931)
+++ contrib/benchmark/src/java/org/apache/lucene/benchmark/byTask/tasks/SearchWithSortTask.java	(working copy)
@@ -18,8 +18,9 @@
 
 import org.apache.lucene.benchmark.byTask.PerfRunData;
 import org.apache.lucene.benchmark.byTask.feeds.QueryMaker;
+import org.apache.lucene.search.FieldComparatorSource;
 import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.fields.SortField;
 
 /**
  * Does sort search on specified field.
@@ -75,8 +76,7 @@
         } else {
           throw new RuntimeException("You must specify the sort type ie page:int,subject:string");
         }
-        int type = getType(typeString);
-        sortField0 = new SortField(fieldName, type);
+        sortField0 = new SortField(fieldName, getType(typeString));
       }
       sortFields[upto++] = sortField0;
     }
@@ -89,8 +89,8 @@
     this.sort = new Sort(sortFields);
   }
 
-  private int getType(String typeString) {
-    int type;
+  private FieldComparatorSource getType(String typeString) {
+    FieldComparatorSource type;
     if (typeString.equals("float")) {
       type = SortField.FLOAT;
     } else if (typeString.equals("double")) {
Index: contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java	(revision 888526)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/geohash/GeoHashDistanceFilter.java	(working copy)
@@ -62,7 +62,7 @@
   @Override
   public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
 
-    final String[] geoHashValues = FieldCache.DEFAULT.getStrings(reader, geoHashField);
+    final String[] geoHashValues = reader.getIndexCache().getFieldCache().getStrings(geoHashField);
 
     final int docBase = nextDocBase;
     nextDocBase += reader.maxDoc();
Index: contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java
===================================================================
--- contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java	(revision 888526)
+++ contrib/spatial/src/java/org/apache/lucene/spatial/tier/LatLongDistanceFilter.java	(working copy)
@@ -23,6 +23,7 @@
 import org.apache.lucene.search.FieldCache;
 import org.apache.lucene.search.Filter;
 import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.fields.IndexFieldCache;
 
 
 /**
@@ -64,9 +65,9 @@
   
   @Override
   public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
-
-    final double[] latIndex = FieldCache.DEFAULT.getDoubles(reader, latField);
-    final double[] lngIndex = FieldCache.DEFAULT.getDoubles(reader, lngField);
+    final IndexFieldCache cache = reader.getIndexCache().getFieldCache();
+    final double[] latIndex = cache.getDoubles(latField);
+    final double[] lngIndex = cache.getDoubles(lngField);
 
     final int docBase = nextDocBase;
     nextDocBase += reader.maxDoc();
Index: contrib/collation/src/test/org/apache/lucene/collation/CollationTestBase.java
===================================================================
--- contrib/collation/src/test/org/apache/lucene/collation/CollationTestBase.java	(revision 887931)
+++ contrib/collation/src/test/org/apache/lucene/collation/CollationTestBase.java	(working copy)
@@ -34,7 +34,7 @@
 import org.apache.lucene.search.TermRangeQuery;
 import org.apache.lucene.search.Searcher;
 import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.fields.SortField;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.util.IndexableBinaryStringTools;
Index: contrib/remote/src/test/org/apache/lucene/search/TestRemoteSort.java
===================================================================
--- contrib/remote/src/test/org/apache/lucene/search/TestRemoteSort.java	(revision 887931)
+++ contrib/remote/src/test/org/apache/lucene/search/TestRemoteSort.java	(working copy)
@@ -36,9 +36,12 @@
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.Term;
+import org.apache.lucene.search.fields.IndexFieldCache;
+import org.apache.lucene.search.fields.IntParser;
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util._TestUtil;
+import org.apache.lucene.search.fields.SortField;
 
 /**
  * Unit tests for remote sorting code.
@@ -217,7 +220,7 @@
 
     @Override
     public void setNextReader(IndexReader reader, int docBase) throws IOException {
-      docValues = FieldCache.DEFAULT.getInts(reader, "parser", new FieldCache.IntParser() {
+      docValues = reader.getIndexCache().getFieldCache().getInts("parser", new IntParser() {
           public final int parseInt(final String val) {
             return (val.charAt(0)-'A') * 123456;
           }
@@ -353,20 +356,11 @@
     sort.setSort(new SortField ("string", SortField.STRING, true));
     assertMatches(multi, queryF, sort, "IJZ");
 
-    // up to this point, all of the searches should have "sane" 
-    // FieldCache behavior, and should have reused hte cache in several cases
-    assertSaneFieldCaches(getName() + " Basics");
-    // next we'll check an alternate Locale for string, so purge first
-    FieldCache.DEFAULT.purgeAllCaches();
-
     sort.setSort(new SortField ("string", Locale.US) );
     assertMatches(multi, queryA, sort, "DJAIHGFEBC");
 
     sort.setSort(new SortField ("string", Locale.US, true));
     assertMatches(multi, queryA, sort, "CBEFGHIAJD");
-
-    assertSaneFieldCaches(getName() + " Locale.US");
-    FieldCache.DEFAULT.purgeAllCaches();
   }
 
   // make sure the documents returned by the search match the expected list
Index: contrib/benchmark/src/test/org/apache/lucene/benchmark/byTask/TestPerfTasksLogic.java
===================================================================
--- contrib/benchmark/src/test/org/apache/lucene/benchmark/byTask/TestPerfTasksLogic.java	(revision 887931)
+++ contrib/benchmark/src/test/org/apache/lucene/benchmark/byTask/TestPerfTasksLogic.java	(working copy)
@@ -36,8 +36,7 @@
 import org.apache.lucene.index.LogDocMergePolicy;
 import org.apache.lucene.index.TermFreqVector;
 import org.apache.lucene.store.Directory;
-import org.apache.lucene.search.FieldCache.StringIndex;
-import org.apache.lucene.search.FieldCache;
+import org.apache.lucene.search.fields.StringIndex;
 import org.apache.lucene.util.LuceneTestCase;
 
 /**
@@ -319,7 +318,7 @@
     Benchmark benchmark = execBenchmark(algLines);
 
     IndexReader r = IndexReader.open(benchmark.getRunData().getDirectory(), true);
-    StringIndex idx = FieldCache.DEFAULT.getStringIndex(r, "country");
+    StringIndex idx = r.getIndexCache().getFieldCache().getStringIndex("country");
     final int maxDoc = r.maxDoc();
     assertEquals(1000, maxDoc);
     for(int i=0;i<1000;i++) {
