From efc985cbf4930b97deab8a9dd16bafbec74690ee Mon Sep 17 00:00:00 2001 From: Michael McCandless Date: Mon, 8 Dec 2008 21:07:45 +0000 Subject: [PATCH] LUCENE-1478: allow SortField to use a custom numeric FieldCache parser git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@724484 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 4 + .../apache/lucene/search/trie/TrieUtils.java | 62 +++++++++++ .../search/trie/TestTrieRangeQuery.java | 38 ++++++- .../lucene/search/ExtendedFieldCache.java | 4 +- .../org/apache/lucene/search/FieldCache.java | 16 ++- .../apache/lucene/search/FieldCacheImpl.java | 12 +- .../lucene/search/FieldSortedHitQueue.java | 86 ++++++++++----- .../org/apache/lucene/search/SortField.java | 103 ++++++++++++++---- .../org/apache/lucene/search/TestSort.java | 82 +++++++++++--- 9 files changed, 332 insertions(+), 75 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index aa4092db9c6..0da829a1425 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -124,6 +124,10 @@ New features handles a subset of this filter, has been deprecated. (Andi Vajda, Steven Rowe via Mark Miller) +10. LUCENE-1478: Added new SortField constructor allowing you to + specify a custom FieldCache parser to generate numeric values from + terms for a field. (Uwe Schindler via Mike McCandless) + Optimizations 1. LUCENE-1427: Fixed QueryWrapperFilter to not waste time computing diff --git a/contrib/queries/src/java/org/apache/lucene/search/trie/TrieUtils.java b/contrib/queries/src/java/org/apache/lucene/search/trie/TrieUtils.java index eec6defb5d2..e36022dba4c 100644 --- a/contrib/queries/src/java/org/apache/lucene/search/trie/TrieUtils.java +++ b/contrib/queries/src/java/org/apache/lucene/search/trie/TrieUtils.java @@ -21,6 +21,8 @@ import java.util.Date; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.ExtendedFieldCache; /** *This is a helper class to construct the trie-based index entries for numerical values. @@ -68,6 +70,26 @@ public final class TrieUtils { /** Character used as lower end */ public static final char TRIE_CODED_SYMBOL_MIN=(char)0x100; + /** + * A parser instance for filling a {@link ExtendedFieldCache}, that parses trie encoded fields as longs, + * auto detecting the trie encoding variant using the String length. + */ + public static final ExtendedFieldCache.LongParser FIELD_CACHE_LONG_PARSER_AUTO=new ExtendedFieldCache.LongParser(){ + public final long parseLong(String val) { + return trieCodedToLongAuto(val); + } + }; + + /** + * A parser instance for filling a {@link ExtendedFieldCache}, that parses trie encoded fields as doubles, + * auto detecting the trie encoding variant using the String length. + */ + public static final ExtendedFieldCache.DoubleParser FIELD_CACHE_DOUBLE_PARSER_AUTO=new ExtendedFieldCache.DoubleParser(){ + public final double parseDouble(String val) { + return trieCodedToDoubleAuto(val); + } + }; + private static TrieUtils defaultTrieVariant=TrieUtils.VARIANT_8BIT; /** @@ -130,6 +152,22 @@ public final class TrieUtils { return autoDetectVariant(s).trieCodedToDate(s); } + /** + * A factory method, that generates a {@link SortField} instance for sorting trie encoded values, + * automatically detecting the trie encoding variant using the String length. + */ + public static final SortField getSortFieldAuto(final String field) { + return new SortField(field, FIELD_CACHE_LONG_PARSER_AUTO); + } + + /** + * A factory method, that generates a {@link SortField} instance for sorting trie encoded values, + * automatically detecting the trie encoding variant using the String length. + */ + public static final SortField getSortFieldAuto(final String field, boolean reverse) { + return new SortField(field, FIELD_CACHE_LONG_PARSER_AUTO, reverse); + } + // TrieUtils instance's part private TrieUtils(int bits) { @@ -338,6 +376,30 @@ public final class TrieUtils { addConvertedTrieCodedDocumentField(ldoc, fieldname, longToTrieCoded(val), index, store); } + /** A factory method, that generates a {@link SortField} instance for sorting trie encoded values. */ + public SortField getSortField(final String field) { + return new SortField(field, FIELD_CACHE_LONG_PARSER); + } + + /** A factory method, that generates a {@link SortField} instance for sorting trie encoded values. */ + public SortField getSortField(final String field, boolean reverse) { + return new SortField(field, FIELD_CACHE_LONG_PARSER, reverse); + } + + /** A parser instance for filling a {@link ExtendedFieldCache}, that parses trie encoded fields as longs. */ + public final ExtendedFieldCache.LongParser FIELD_CACHE_LONG_PARSER=new ExtendedFieldCache.LongParser(){ + public final long parseLong(String val) { + return trieCodedToLong(val); + } + }; + + /** A parser instance for filling a {@link ExtendedFieldCache}, that parses trie encoded fields as doubles. */ + public final ExtendedFieldCache.DoubleParser FIELD_CACHE_DOUBLE_PARSER=new ExtendedFieldCache.DoubleParser(){ + public final double parseDouble(String val) { + return trieCodedToDouble(val); + } + }; + private final long mask; /** Number of bits used in this trie variant (2, 4, or 8) */ diff --git a/contrib/queries/src/test/org/apache/lucene/search/trie/TestTrieRangeQuery.java b/contrib/queries/src/test/org/apache/lucene/search/trie/TestTrieRangeQuery.java index 782e3163665..316c04f0fc5 100644 --- a/contrib/queries/src/test/org/apache/lucene/search/trie/TestTrieRangeQuery.java +++ b/contrib/queries/src/test/org/apache/lucene/search/trie/TestTrieRangeQuery.java @@ -163,7 +163,7 @@ public class TestTrieRangeQuery extends LuceneTestCase private void testRangeSplit(final TrieUtils variant) throws Exception { String field="ascfield"+variant.TRIE_BITS; - // 50 random tests, the tests may also return 0 results, if min>max, but this is ok + // 50 random tests for (int i=0; i<50; i++) { long lower=(long)(rnd.nextDouble()*10000L); long upper=(long)(rnd.nextDouble()*10000L); @@ -188,4 +188,40 @@ public class TestTrieRangeQuery extends LuceneTestCase testRangeSplit(TrieUtils.VARIANT_2BIT); } + private void testSorting(final TrieUtils variant) throws Exception { + String field="field"+variant.TRIE_BITS; + // 10 random tests, the index order is ascending, + // so using a reverse sort field should retun descending documents + for (int i=0; i<10; i++) { + long lower=(long)(rnd.nextDouble()*10000L*distance); + long upper=(long)(rnd.nextDouble()*10000L*distance); + if (lower>upper) { + long a=lower; lower=upper; upper=a; + } + TrieRangeQuery tq=new TrieRangeQuery(field, new Long(lower), new Long(upper), variant); + TopDocs topDocs = searcher.search(tq, null, 10000, new Sort(variant.getSortField(field, true))); + if (topDocs.totalHits==0) continue; + ScoreDoc[] sd = topDocs.scoreDocs; + assertNotNull(sd); + long last=variant.trieCodedToLong(searcher.doc(sd[0].doc).get(field)); + for (int j=1; jact ); + last=act; + } + } + } + + public void testSorting_8bit() throws Exception { + testSorting(TrieUtils.VARIANT_8BIT); + } + + public void testSorting_4bit() throws Exception { + testSorting(TrieUtils.VARIANT_4BIT); + } + + public void testSorting_2bit() throws Exception { + testSorting(TrieUtils.VARIANT_2BIT); + } + } diff --git a/src/java/org/apache/lucene/search/ExtendedFieldCache.java b/src/java/org/apache/lucene/search/ExtendedFieldCache.java index 57a739b6952..813e26c1879 100644 --- a/src/java/org/apache/lucene/search/ExtendedFieldCache.java +++ b/src/java/org/apache/lucene/search/ExtendedFieldCache.java @@ -27,14 +27,14 @@ import java.io.IOException; * **/ public interface ExtendedFieldCache extends FieldCache { - public interface LongParser { + public interface LongParser extends Parser { /** * Return an long representation of this field's value. */ public long parseLong(String string); } - public interface DoubleParser { + public interface DoubleParser extends Parser { /** * Return an long representation of this field's value. */ diff --git a/src/java/org/apache/lucene/search/FieldCache.java b/src/java/org/apache/lucene/search/FieldCache.java index 22ff3794d75..d6a04ddd114 100644 --- a/src/java/org/apache/lucene/search/FieldCache.java +++ b/src/java/org/apache/lucene/search/FieldCache.java @@ -74,10 +74,18 @@ public interface FieldCache { } } + /** + * Marker interface as super-interface to all parsers. It + * is used to specify a custom parser to {@link + * SortField#SortField(String, FieldCache.Parser)}. + */ + public interface Parser { + } + /** Interface to parse bytes from document fields. * @see FieldCache#getBytes(IndexReader, String, FieldCache.ByteParser) */ - public interface ByteParser { + public interface ByteParser extends Parser { /** Return a single Byte representation of this field's value. */ public byte parseByte(String string); } @@ -85,7 +93,7 @@ public interface FieldCache { /** Interface to parse shorts from document fields. * @see FieldCache#getShorts(IndexReader, String, FieldCache.ShortParser) */ - public interface ShortParser { + public interface ShortParser extends Parser { /** Return a short representation of this field's value. */ public short parseShort(String string); } @@ -93,7 +101,7 @@ public interface FieldCache { /** Interface to parse ints from document fields. * @see FieldCache#getInts(IndexReader, String, FieldCache.IntParser) */ - public interface IntParser { + public interface IntParser extends Parser { /** Return an integer representation of this field's value. */ public int parseInt(String string); } @@ -101,7 +109,7 @@ public interface FieldCache { /** Interface to parse floats from document fields. * @see FieldCache#getFloats(IndexReader, String, FieldCache.FloatParser) */ - public interface FloatParser { + public interface FloatParser extends Parser { /** Return an float representation of this field's value. */ public float parseFloat(String string); } diff --git a/src/java/org/apache/lucene/search/FieldCacheImpl.java b/src/java/org/apache/lucene/search/FieldCacheImpl.java index a5bebbdd091..139dfdc6ae5 100644 --- a/src/java/org/apache/lucene/search/FieldCacheImpl.java +++ b/src/java/org/apache/lucene/search/FieldCacheImpl.java @@ -88,7 +88,7 @@ implements FieldCache { static class Entry { final String field; // which Fieldable final int type; // which SortField type - final Object custom; // which custom comparator + final Object custom; // which custom comparator or parser final Locale locale; // the locale we're sorting (if string) /** Creates one of these objects. */ @@ -99,7 +99,7 @@ implements FieldCache { this.locale = locale; } - /** Creates one of these objects for a custom comparator. */ + /** Creates one of these objects for a custom comparator/parser. */ Entry (String field, Object custom) { this.field = field.intern(); this.type = SortField.CUSTOM; @@ -107,6 +107,14 @@ implements FieldCache { this.locale = null; } + /** Creates one of these objects for a custom type with parser, needed by FieldSortedHitQueue. */ + Entry (String field, int type, Parser parser) { + this.field = field.intern(); + this.type = type; + this.custom = parser; + this.locale = null; + } + /** Two of these are equal iff they reference the same field and type. */ public boolean equals (Object o) { if (o instanceof Entry) { diff --git a/src/java/org/apache/lucene/search/FieldSortedHitQueue.java b/src/java/org/apache/lucene/search/FieldSortedHitQueue.java index 46af0056d34..dd9c0303817 100644 --- a/src/java/org/apache/lucene/search/FieldSortedHitQueue.java +++ b/src/java/org/apache/lucene/search/FieldSortedHitQueue.java @@ -52,12 +52,17 @@ extends PriorityQueue { this.fields = new SortField[n]; for (int i=0; inull. */ public SortField (String field) { - this.field = field.intern(); + initFieldType(field, AUTO); } /** Creates a sort, possibly in reverse, by terms in the given field where @@ -114,7 +115,7 @@ implements Serializable { * @param reverse True if natural order should be reversed. */ public SortField (String field, boolean reverse) { - this.field = field.intern(); + initFieldType(field, AUTO); this.reverse = reverse; } @@ -125,8 +126,7 @@ implements Serializable { * @param type Type of values in the terms. */ public SortField (String field, int type) { - this.field = (field != null) ? field.intern() : field; - this.type = type; + initFieldType(field, type); } /** Creates a sort, possibly in reverse, by terms in the given field with the @@ -137,19 +137,73 @@ implements Serializable { * @param reverse True if natural order should be reversed. */ public SortField (String field, int type, boolean reverse) { - this.field = (field != null) ? field.intern() : field; - this.type = type; + initFieldType(field, type); this.reverse = reverse; } + /** 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. + * @param parser Instance of a {@link FieldCache.Parser}, + * which must subclass one of the existing numeric + * parsers from {@link FieldCache} or {@link + * ExtendedFieldCache}. Sort type is inferred by testing + * which numeric parser the parser subclasses. + * @throws IllegalArgumentException if the parser fails to + * subclass an existing numeric parser, or field is null + */ + public SortField (String field, FieldCache.Parser parser) { + this(field, parser, false); + } + + /** 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 parser Instance of a {@link FieldCache.Parser}, + * which must subclass one of the existing numeric + * parsers from {@link FieldCache} or {@link + * ExtendedFieldCache}. Sort type is inferred by testing + * which numeric parser the parser subclasses. + * @param reverse True if natural order should be reversed. + * @throws IllegalArgumentException if the parser fails to + * subclass an existing numeric parser, or field is null + */ + public SortField (String field, FieldCache.Parser parser, boolean reverse) { + + if (parser instanceof FieldCache.IntParser) this.type=INT; + else if (parser instanceof FieldCache.FloatParser) this.type=FLOAT; + else if (parser instanceof FieldCache.ShortParser) this.type=SHORT; + else if (parser instanceof FieldCache.ByteParser) this.type=BYTE; + else if (parser instanceof ExtendedFieldCache.LongParser) this.type=LONG; + else if (parser instanceof ExtendedFieldCache.DoubleParser) this.type=DOUBLE; + else + throw new IllegalArgumentException("Parser instance does not subclass existing numeric parser from FieldCache or ExtendedFieldCache (got" + parser + ")"); + + initFieldType(field, type); + + this.reverse = reverse; + this.parser = parser; + } + + // 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 = field.intern(); + } + } + /** 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 null. * @param locale Locale of values in the field. */ public SortField (String field, Locale locale) { - this.field = field.intern(); - this.type = STRING; + initFieldType(field, STRING); this.locale = locale; } @@ -159,8 +213,7 @@ implements Serializable { * @param locale Locale of values in the field. */ public SortField (String field, Locale locale, boolean reverse) { - this.field = field.intern(); - this.type = STRING; + initFieldType(field, STRING); this.locale = locale; this.reverse = reverse; } @@ -170,8 +223,7 @@ implements Serializable { * @param comparator Returns a comparator for sorting hits. */ public SortField (String field, SortComparatorSource comparator) { - this.field = (field != null) ? field.intern() : field; - this.type = CUSTOM; + initFieldType(field, CUSTOM); this.factory = comparator; } @@ -181,8 +233,7 @@ implements Serializable { * @param reverse True if natural order should be reversed. */ public SortField (String field, SortComparatorSource comparator, boolean reverse) { - this.field = (field != null) ? field.intern() : field; - this.type = CUSTOM; + initFieldType(field, CUSTOM); this.reverse = reverse; this.factory = comparator; } @@ -210,6 +261,14 @@ implements Serializable { return locale; } + /** Returns the instance of a {@link FieldCache} parser that fits to the given sort type. + * May return null if no parser was specified. Sorting is using the default parser then. + * @return An instance of a {@link FieldCache} parser, or null. + */ + public FieldCache.Parser getParser() { + return parser; + } + /** Returns whether the sort should be reversed. * @return True if natural order should be reversed. */ @@ -240,14 +299,16 @@ implements Serializable { } 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 o is equal to this. If a - * {@link #SortComparatorSource} was provided, it must - * properly implement equals. */ + * {@link SortComparatorSource} or {@link + * FieldCache.Parser} was provided, it must properly + * implement equals (unless a singleton is always used). */ public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SortField)) return false; @@ -258,17 +319,21 @@ implements Serializable { && other.reverse == this.reverse && (other.locale == null ? this.locale == null : other.locale.equals(this.locale)) && (other.factory == null ? this.factory == null : other.factory.equals(this.factory)) + && (other.parser == null ? this.parser == null : other.parser.equals(this.parser)) ); } - /** Returns a hash code value for this object. If a - * {@link #SortComparatorSource} was provided, it must - * properly implement hashCode. */ + /** Returns true if o is equal to this. If a + * {@link SortComparatorSource} or {@link + * FieldCache.Parser} was provided, it must properly + * implement hashCode (unless a singleton is always + * used). */ 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 (factory != null) hash += factory.hashCode()^0x34987555; + if (parser != null) hash += parser.hashCode()^0x3aaf56ff; return hash; } } diff --git a/src/test/org/apache/lucene/search/TestSort.java b/src/test/org/apache/lucene/search/TestSort.java index b847afde200..48ade4dc6c7 100644 --- a/src/test/org/apache/lucene/search/TestSort.java +++ b/src/test/org/apache/lucene/search/TestSort.java @@ -98,21 +98,21 @@ implements Serializable { // the string field to sort by string // the i18n field includes accented characters for testing locale-specific sorting private String[][] data = new String[][] { - // tracer contents int float string custom i18n long double, 'short', byte - { "A", "x a", "5", "4f", "c", "A-3", "p\u00EAche", "10", "-4.0", "3", "126"},//A, x - { "B", "y a", "5", "3.4028235E38", "i", "B-10", "HAT", "1000000000", "40.0", "24", "1"},//B, y - { "C", "x a b c", "2147483647", "1.0", "j", "A-2", "p\u00E9ch\u00E9", "99999999", "40.00002343", "125", "15"},//C, x - { "D", "y a b c", "-1", "0.0f", "a", "C-0", "HUT", String.valueOf(Long.MAX_VALUE), String.valueOf(Double.MIN_VALUE), String.valueOf(Short.MIN_VALUE), String.valueOf(Byte.MIN_VALUE)},//D, y - { "E", "x a b c d", "5", "2f", "h", "B-8", "peach", String.valueOf(Long.MIN_VALUE), String.valueOf(Double.MAX_VALUE), String.valueOf(Short.MAX_VALUE), String.valueOf(Byte.MAX_VALUE)},//E,x - { "F", "y a b c d", "2", "3.14159f", "g", "B-1", "H\u00C5T", "-44", "343.034435444", "-3", "0"},//F,y - { "G", "x a b c d", "3", "-1.0", "f", "C-100", "sin", "323254543543", "4.043544", "5", "100"},//G,x - { "H", "y a b c d", "0", "1.4E-45", "e", "C-88", "H\u00D8T", "1023423423005","4.043545", "10", "-50"},//H,y - { "I", "x a b c d e f", "-2147483648", "1.0e+0", "d", "A-10", "s\u00EDn", "332422459999", "4.043546", "-340", "51"},//I,x - { "J", "y a b c d e f", "4", ".5", "b", "C-7", "HOT", "34334543543", "4.0000220343", "300", "2"},//J,y - { "W", "g", "1", null, null, null, null, null, null, null, null}, - { "X", "g", "1", "0.1", null, null, null, null, null, null, null}, - { "Y", "g", "1", "0.2", null, null, null, null, null, null, null}, - { "Z", "f g", null, null, null, null, null, null, null, null, null} + // tracer contents int float string custom i18n long double, 'short', byte, 'custom parser encoding' + { "A", "x a", "5", "4f", "c", "A-3", "p\u00EAche", "10", "-4.0", "3", "126", "J"},//A, x + { "B", "y a", "5", "3.4028235E38", "i", "B-10", "HAT", "1000000000", "40.0", "24", "1", "I"},//B, y + { "C", "x a b c", "2147483647", "1.0", "j", "A-2", "p\u00E9ch\u00E9", "99999999", "40.00002343", "125", "15", "H"},//C, x + { "D", "y a b c", "-1", "0.0f", "a", "C-0", "HUT", String.valueOf(Long.MAX_VALUE), String.valueOf(Double.MIN_VALUE), String.valueOf(Short.MIN_VALUE), String.valueOf(Byte.MIN_VALUE), "G"},//D, y + { "E", "x a b c d", "5", "2f", "h", "B-8", "peach", String.valueOf(Long.MIN_VALUE), String.valueOf(Double.MAX_VALUE), String.valueOf(Short.MAX_VALUE), String.valueOf(Byte.MAX_VALUE), "F"},//E,x + { "F", "y a b c d", "2", "3.14159f", "g", "B-1", "H\u00C5T", "-44", "343.034435444", "-3", "0", "E"},//F,y + { "G", "x a b c d", "3", "-1.0", "f", "C-100", "sin", "323254543543", "4.043544", "5", "100", "D"},//G,x + { "H", "y a b c d", "0", "1.4E-45", "e", "C-88", "H\u00D8T", "1023423423005","4.043545", "10", "-50", "C"},//H,y + { "I", "x a b c d e f", "-2147483648", "1.0e+0", "d", "A-10", "s\u00EDn", "332422459999", "4.043546", "-340", "51", "B"},//I,x + { "J", "y a b c d e f", "4", ".5", "b", "C-7", "HOT", "34334543543", "4.0000220343", "300", "2", "A"},//J,y + { "W", "g", "1", null, null, null, null, null, null, null, null, null}, + { "X", "g", "1", "0.1", null, null, null, null, null, null, null, null}, + { "Y", "g", "1", "0.2", null, null, null, null, null, null, null, null}, + { "Z", "f g", null, null, null, null, null, null, null, null, null, null} }; // create an index of all the documents, or just the x, or just the y documents @@ -132,8 +132,9 @@ implements Serializable { if (data[i][6] != null) doc.add (new Field ("i18n", data[i][6], Field.Store.NO, Field.Index.NOT_ANALYZED)); if (data[i][7] != null) doc.add (new Field ("long", data[i][7], Field.Store.NO, Field.Index.NOT_ANALYZED)); if (data[i][8] != null) doc.add (new Field ("double", data[i][8], Field.Store.NO, Field.Index.NOT_ANALYZED)); - if (data[i][8] != null) doc.add (new Field ("short", data[i][9], Field.Store.NO, Field.Index.NOT_ANALYZED)); - if (data[i][8] != null) doc.add (new Field ("byte", data[i][10], Field.Store.NO, Field.Index.NOT_ANALYZED)); + if (data[i][9] != null) doc.add (new Field ("short", data[i][9], Field.Store.NO, Field.Index.NOT_ANALYZED)); + if (data[i][10] != null) doc.add (new Field ("byte", data[i][10], Field.Store.NO, Field.Index.NOT_ANALYZED)); + if (data[i][11] != null) doc.add (new Field ("parser", data[i][11], Field.Store.NO, Field.Index.NOT_ANALYZED)); doc.setBoost(2); // produce some scores above 1.0 writer.addDocument (doc); } @@ -218,6 +219,53 @@ implements Serializable { assertMatches (full, queryY, sort, "DJHFB"); } + // test sorts where the type of field is specified and a custom field parser is used, that + // uses a simple char encoding. The sorted string contains a character beginning from 'A' that + // is mapped to a numeric value using some "funny" algorithm to be different for each data type. + public void testCustomFieldParserSort() throws Exception { + sort.setSort (new SortField[] { new SortField ("parser", new FieldCache.IntParser(){ + public final int parseInt(final String val) { + return (int) (val.charAt(0)-'A') * 123456; + } + }), SortField.FIELD_DOC }); + assertMatches (full, queryA, sort, "JIHGFEDCBA"); + + sort.setSort (new SortField[] { new SortField ("parser", new FieldCache.FloatParser(){ + public final float parseFloat(final String val) { + return (float) Math.sqrt( (double) val.charAt(0) ); + } + }), SortField.FIELD_DOC }); + assertMatches (full, queryA, sort, "JIHGFEDCBA"); + + sort.setSort (new SortField[] { new SortField ("parser", new ExtendedFieldCache.LongParser(){ + public final long parseLong(final String val) { + return (long) (val.charAt(0)-'A') * 1234567890L; + } + }), SortField.FIELD_DOC }); + assertMatches (full, queryA, sort, "JIHGFEDCBA"); + + sort.setSort (new SortField[] { new SortField ("parser", new ExtendedFieldCache.DoubleParser(){ + public final double parseDouble(final String val) { + return Math.pow( (double) val.charAt(0), (double) (val.charAt(0)-'A') ); + } + }), SortField.FIELD_DOC }); + assertMatches (full, queryA, sort, "JIHGFEDCBA"); + + sort.setSort (new SortField[] { new SortField ("parser", new FieldCache.ByteParser(){ + public final byte parseByte(final String val) { + return (byte) (val.charAt(0)-'A'); + } + }), SortField.FIELD_DOC }); + assertMatches (full, queryA, sort, "JIHGFEDCBA"); + + sort.setSort (new SortField[] { new SortField ("parser", new FieldCache.ShortParser(){ + public final short parseShort(final String val) { + return (short) (val.charAt(0)-'A'); + } + }), SortField.FIELD_DOC }); + assertMatches (full, queryA, sort, "JIHGFEDCBA"); + } + // test sorts when there's nothing in the index public void testEmptyIndex() throws Exception { Searcher empty = getEmptyIndex();