You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by us...@apache.org on 2011/08/18 22:44:42 UTC

svn commit: r1159411 [2/2] - in /lucene/dev/branches/branch_3x: ./ lucene/ lucene/backwards/ lucene/backwards/src/test-framework/ lucene/backwards/src/test/ lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/core/messages/ lucene/contrib...

Added: lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericRangeQueryNodeProcessor.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericRangeQueryNodeProcessor.java?rev=1159411&view=auto
==============================================================================
--- lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericRangeQueryNodeProcessor.java (added)
+++ lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/NumericRangeQueryNodeProcessor.java Thu Aug 18 20:44:40 2011
@@ -0,0 +1,160 @@
+package org.apache.lucene.queryParser.standard.processors;
+
+/**
+ * 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.text.NumberFormat;
+import java.text.ParseException;
+import java.util.List;
+
+import org.apache.lucene.messages.MessageImpl;
+import org.apache.lucene.queryParser.core.QueryNodeException;
+import org.apache.lucene.queryParser.core.QueryNodeParseException;
+import org.apache.lucene.queryParser.core.config.FieldConfig;
+import org.apache.lucene.queryParser.core.config.QueryConfigHandler;
+import org.apache.lucene.queryParser.core.messages.QueryParserMessages;
+import org.apache.lucene.queryParser.core.nodes.ParametricQueryNode;
+import org.apache.lucene.queryParser.core.nodes.ParametricRangeQueryNode;
+import org.apache.lucene.queryParser.core.nodes.QueryNode;
+import org.apache.lucene.queryParser.core.nodes.ParametricQueryNode.CompareOperator;
+import org.apache.lucene.queryParser.core.processors.QueryNodeProcessorImpl;
+import org.apache.lucene.queryParser.core.util.StringUtils;
+import org.apache.lucene.queryParser.standard.config.NumericConfig;
+import org.apache.lucene.queryParser.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
+import org.apache.lucene.queryParser.standard.nodes.NumericQueryNode;
+import org.apache.lucene.queryParser.standard.nodes.NumericRangeQueryNode;
+
+/**
+ * This processor is used to convert {@link ParametricRangeQueryNode}s to
+ * {@link NumericRangeQueryNode}s. It looks for
+ * {@link ConfigurationKeys#NUMERIC_CONFIG} set in the {@link FieldConfig} of
+ * every {@link ParametricRangeQueryNode} found. If
+ * {@link ConfigurationKeys#NUMERIC_CONFIG} is found, it considers that
+ * {@link ParametricRangeQueryNode} to be a numeric range query and convert it to
+ * {@link NumericRangeQueryNode}.
+ * 
+ * @see ConfigurationKeys#NUMERIC_CONFIG
+ * @see ParametricRangeQueryNode
+ * @see NumericConfig
+ * @see NumericRangeQueryNode
+ */
+public class NumericRangeQueryNodeProcessor extends QueryNodeProcessorImpl {
+  
+  /**
+   * Constructs an empty {@link NumericRangeQueryNode} object.
+   */
+  public NumericRangeQueryNodeProcessor() {
+  // empty constructor
+  }
+  
+  @Override
+  protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException {
+    
+    if (node instanceof ParametricRangeQueryNode) {
+      QueryConfigHandler config = getQueryConfigHandler();
+      
+      if (config != null) {
+        ParametricRangeQueryNode parametricRangeNode = (ParametricRangeQueryNode) node;
+        FieldConfig fieldConfig = config.getFieldConfig(StringUtils
+            .toString(parametricRangeNode.getField()));
+        
+        if (fieldConfig != null) {
+          
+          NumericConfig numericConfig = fieldConfig
+              .get(ConfigurationKeys.NUMERIC_CONFIG);
+          
+          if (numericConfig != null) {
+            
+            ParametricQueryNode lower = parametricRangeNode.getLowerBound();
+            ParametricQueryNode upper = parametricRangeNode.getUpperBound();
+            
+            NumberFormat numberFormat = numericConfig.getNumberFormat();
+            Number lowerNumber, upperNumber;
+            
+            try {
+              lowerNumber = numberFormat.parse(lower.getTextAsString());
+              
+            } catch (ParseException e) {
+              throw new QueryNodeParseException(new MessageImpl(
+                  QueryParserMessages.COULD_NOT_PARSE_NUMBER, lower
+                      .getTextAsString(), numberFormat.getClass()
+                      .getCanonicalName()), e);
+            }
+            
+            try {
+              upperNumber = numberFormat.parse(upper.getTextAsString());
+              
+            } catch (ParseException e) {
+              throw new QueryNodeParseException(new MessageImpl(
+                  QueryParserMessages.COULD_NOT_PARSE_NUMBER, upper
+                      .getTextAsString(), numberFormat.getClass()
+                      .getCanonicalName()), e);
+            }
+            
+            switch (numericConfig.getType()) {
+              case LONG:
+                upperNumber = upperNumber.longValue();
+                lowerNumber = lowerNumber.longValue();
+                break;
+              case INT:
+                upperNumber = upperNumber.intValue();
+                lowerNumber = lowerNumber.intValue();
+                break;
+              case DOUBLE:
+                upperNumber = upperNumber.doubleValue();
+                lowerNumber = lowerNumber.doubleValue();
+                break;
+              case FLOAT:
+                upperNumber = upperNumber.floatValue();
+                lowerNumber = lowerNumber.floatValue();
+            }
+            
+            NumericQueryNode lowerNode = new NumericQueryNode(
+                parametricRangeNode.getField(), lowerNumber, numberFormat);
+            NumericQueryNode upperNode = new NumericQueryNode(
+                parametricRangeNode.getField(), upperNumber, numberFormat);
+            
+            boolean upperInclusive = upper.getOperator() == CompareOperator.LE;
+            boolean lowerInclusive = lower.getOperator() == CompareOperator.GE;
+            
+            return new NumericRangeQueryNode(lowerNode, upperNode,
+                lowerInclusive, upperInclusive, numericConfig);
+            
+          }
+          
+        }
+        
+      }
+      
+    }
+    
+    return node;
+    
+  }
+  
+  @Override
+  protected QueryNode preProcessNode(QueryNode node) throws QueryNodeException {
+    return node;
+  }
+  
+  @Override
+  protected List<QueryNode> setChildrenOrder(List<QueryNode> children)
+      throws QueryNodeException {
+    return children;
+  }
+  
+}

Modified: lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/ParametricRangeQueryNodeProcessor.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/ParametricRangeQueryNodeProcessor.java?rev=1159411&r1=1159410&r2=1159411&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/ParametricRangeQueryNodeProcessor.java (original)
+++ lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/ParametricRangeQueryNodeProcessor.java Thu Aug 18 20:44:40 2011
@@ -56,18 +56,18 @@ import org.apache.lucene.queryParser.sta
  * 
  * @see ConfigurationKeys#DATE_RESOLUTION
  * @see ConfigurationKeys#LOCALE
- * @see RangeQueryNode
+ * @see TermRangeQueryNode
  * @see ParametricRangeQueryNode
  */
 public class ParametricRangeQueryNodeProcessor extends QueryNodeProcessorImpl {
-
+  
   public ParametricRangeQueryNodeProcessor() {
-    // empty constructor
+  // empty constructor
   }
-
+  
   @Override
   protected QueryNode postProcessNode(QueryNode node) throws QueryNodeException {
-
+    
     if (node instanceof ParametricRangeQueryNode) {
       ParametricRangeQueryNode parametricRangeNode = (ParametricRangeQueryNode) node;
       ParametricQueryNode upper = parametricRangeNode.getUpperBound();

Modified: lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/StandardQueryNodeProcessorPipeline.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/StandardQueryNodeProcessorPipeline.java?rev=1159411&r1=1159410&r2=1159411&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/StandardQueryNodeProcessorPipeline.java (original)
+++ lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/StandardQueryNodeProcessorPipeline.java Thu Aug 18 20:44:40 2011
@@ -52,6 +52,8 @@ public class StandardQueryNodeProcessorP
     add(new MultiFieldQueryNodeProcessor());
     add(new FuzzyQueryNodeProcessor());
     add(new MatchAllDocsQueryNodeProcessor());
+    add(new NumericQueryNodeProcessor());
+    add(new NumericRangeQueryNodeProcessor());
     add(new LowercaseExpandedTermsQueryNodeProcessor());
     add(new ParametricRangeQueryNodeProcessor());
     add(new AllowLeadingWildcardProcessor());    

Modified: lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/package.html
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/package.html?rev=1159411&r1=1159410&r2=1159411&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/package.html (original)
+++ lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/processors/package.html Thu Aug 18 20:44:40 2011
@@ -27,7 +27,7 @@ The package org.apache.lucene.queryParse
 that modifies the query node tree according to the actual Lucene queries.
 </p>
 <p>
-This processors are already assembled correctly in the StandardQueryNodeProcessorPipeline. 
+These processors are already assembled correctly in the StandardQueryNodeProcessorPipeline. 
 </p>
 </body>
 </html>

Modified: lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/resources/org/apache/lucene/queryParser/core/messages/QueryParserMessages.properties
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/resources/org/apache/lucene/queryParser/core/messages/QueryParserMessages.properties?rev=1159411&r1=1159410&r2=1159411&view=diff
==============================================================================
--- lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/resources/org/apache/lucene/queryParser/core/messages/QueryParserMessages.properties (original)
+++ lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/resources/org/apache/lucene/queryParser/core/messages/QueryParserMessages.properties Thu Aug 18 20:44:40 2011
@@ -43,3 +43,12 @@ TOO_MANY_BOOLEAN_CLAUSES = Too many bool
 
 #<CREATEDBY>Apache Lucene Community</CREATEDBY>
 LEADING_WILDCARD_NOT_ALLOWED = Leading wildcard is not allowed: {0}
+
+#<CREATEDBY>Apache Lucene Community</CREATEDBY>
+COULD_NOT_PARSE_NUMBER = Could not parse text "{0}" using {1}
+
+#<CREATEDBY>Apache Lucene Community</CREATEDBY>
+NUMBER_CLASS_NOT_SUPPORTED_BY_NUMERIC_RANGE_QUERY = Number class not supported by NumericRangeQueryNode: {0}
+
+#<CREATEDBY>Apache Lucene Community</CREATEDBY>
+UNSUPPORTED_NUMERIC_DATA_TYPE = Unsupported NumericField.DataType: {0}

Added: lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/standard/TestNumericQueryParser.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/standard/TestNumericQueryParser.java?rev=1159411&view=auto
==============================================================================
--- lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/standard/TestNumericQueryParser.java (added)
+++ lucene/dev/branches/branch_3x/lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/standard/TestNumericQueryParser.java Thu Aug 18 20:44:40 2011
@@ -0,0 +1,503 @@
+package org.apache.lucene.queryParser.standard;
+
+/**
+ * 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.text.DateFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+import java.util.TimeZone;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.queryParser.core.QueryNodeException;
+import org.apache.lucene.queryParser.core.parser.EscapeQuerySyntax;
+import org.apache.lucene.queryParser.standard.config.NumberDateFormat;
+import org.apache.lucene.queryParser.standard.config.NumericConfig;
+import org.apache.lucene.queryParser.standard.parser.EscapeQuerySyntaxImpl;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util._TestUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestNumericQueryParser extends LuceneTestCase {
+  
+  private static enum NumberType {
+    NEGATIVE, ZERO, POSITIVE;
+  }
+  
+  final private static int[] DATE_STYLES = {DateFormat.FULL, DateFormat.LONG,
+      DateFormat.MEDIUM, DateFormat.SHORT};
+  
+  final private static int PRECISION_STEP = 8;
+  final private static String FIELD_NAME = "field";
+  private static Locale LOCALE;
+  private static TimeZone TIMEZONE;
+  private static Map<String,Number> RANDOM_NUMBER_MAP;
+  final private static EscapeQuerySyntax ESCAPER = new EscapeQuerySyntaxImpl();
+  final private static String DATE_FIELD_NAME = "date";
+  private static int DATE_STYLE;
+  private static int TIME_STYLE;
+  
+  private static Analyzer ANALYZER;
+  
+  private static NumberFormat NUMBER_FORMAT;
+  
+  private static StandardQueryParser qp;
+  
+  private static NumberDateFormat DATE_FORMAT;
+  
+  private static Directory directory = null;
+  private static IndexReader reader = null;
+  private static IndexSearcher searcher = null;
+  
+  private static boolean checkDateFormatSanity(DateFormat dateFormat, long date)
+      throws ParseException {
+    return date == dateFormat.parse(dateFormat.format(new Date(date)))
+        .getTime();
+  }
+  
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    ANALYZER = new MockAnalyzer(random);
+    
+    qp = new StandardQueryParser(ANALYZER);
+    
+    final HashMap<String,Number> randomNumberMap = new HashMap<String,Number>();
+    
+    SimpleDateFormat dateFormat;
+    long randomDate;
+    boolean dateFormatSanityCheckPass;
+    int count = 0;
+    do {
+      if (count > 100) {
+        fail("This test has problems to find a sane random DateFormat/NumberFormat. Stopped trying after 100 iterations.");
+      }
+      
+      dateFormatSanityCheckPass = true;
+      LOCALE = randomLocale(random);
+      TIMEZONE = randomTimeZone(random);
+      DATE_STYLE = randomDateStyle(random);
+      TIME_STYLE = randomDateStyle(random);
+      
+      // assumes localized date pattern will have at least year, month, day,
+      // hour, minute
+      dateFormat = (SimpleDateFormat) DateFormat.getDateTimeInstance(
+          DATE_STYLE, TIME_STYLE, LOCALE);
+      
+      // not all date patterns includes era, full year, timezone and second,
+      // so we add them here
+      dateFormat.applyPattern(dateFormat.toPattern() + " G s Z yyyy");
+      dateFormat.setTimeZone(TIMEZONE);
+      
+      DATE_FORMAT = new NumberDateFormat(dateFormat);
+      
+      do {
+        randomDate = random.nextLong();
+        
+        // prune date value so it doesn't pass in insane values to some
+        // calendars.
+        randomDate = randomDate % 3400000000000l;
+        
+        // truncate to second
+        randomDate = (randomDate / 1000L) * 1000L;
+        
+        // only positive values
+        randomDate = Math.abs(randomDate);
+      } while (randomDate == 0L);
+      
+      dateFormatSanityCheckPass &= checkDateFormatSanity(dateFormat, randomDate);
+      
+      dateFormatSanityCheckPass &= checkDateFormatSanity(dateFormat, 0);
+      
+      dateFormatSanityCheckPass &= checkDateFormatSanity(dateFormat,
+          -randomDate);
+      
+      count++;
+    } while (!dateFormatSanityCheckPass);
+    
+    NUMBER_FORMAT = NumberFormat.getNumberInstance(LOCALE);
+    NUMBER_FORMAT.setMaximumFractionDigits((random.nextInt() & 20) + 1);
+    NUMBER_FORMAT.setMinimumFractionDigits((random.nextInt() & 20) + 1);
+    NUMBER_FORMAT.setMaximumIntegerDigits((random.nextInt() & 20) + 1);
+    NUMBER_FORMAT.setMinimumIntegerDigits((random.nextInt() & 20) + 1);
+    
+    double randomDouble;
+    long randomLong;
+    int randomInt;
+    float randomFloat;
+    
+    while ((randomLong = normalizeNumber(Math.abs(random.nextLong()))
+        .longValue()) == 0L)
+      ;
+    while ((randomDouble = normalizeNumber(Math.abs(random.nextDouble()))
+        .doubleValue()) == 0.0)
+      ;
+    while ((randomFloat = normalizeNumber(Math.abs(random.nextFloat()))
+        .floatValue()) == 0.0f)
+      ;
+    while ((randomInt = normalizeNumber(Math.abs(random.nextInt())).intValue()) == 0)
+      ;
+    
+    randomNumberMap.put(NumericField.DataType.LONG.name(), randomLong);
+    randomNumberMap.put(NumericField.DataType.INT.name(), randomInt);
+    randomNumberMap.put(NumericField.DataType.FLOAT.name(), randomFloat);
+    randomNumberMap.put(NumericField.DataType.DOUBLE.name(), randomDouble);
+    randomNumberMap.put(DATE_FIELD_NAME, randomDate);
+    
+    RANDOM_NUMBER_MAP = Collections.unmodifiableMap(randomNumberMap);
+    
+    directory = newDirectory();
+    RandomIndexWriter writer = new RandomIndexWriter(random, directory,
+        newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random))
+            .setMaxBufferedDocs(_TestUtil.nextInt(random, 50, 1000))
+            .setMergePolicy(newLogMergePolicy()));
+    
+    Document doc = new Document();
+    HashMap<String,NumericConfig> numericConfigMap = new HashMap<String,NumericConfig>();
+    HashMap<String,NumericField> numericFieldMap = new HashMap<String,NumericField>();
+    qp.setNumericConfigMap(numericConfigMap);
+    
+    for (NumericField.DataType type : NumericField.DataType.values()) {
+      numericConfigMap.put(type.name(), new NumericConfig(PRECISION_STEP,
+          NUMBER_FORMAT, type));
+      
+      NumericField field = new NumericField(type.name(), PRECISION_STEP,
+          Field.Store.YES, true);
+      
+      numericFieldMap.put(type.name(), field);
+      doc.add(field);
+      
+    }
+    
+    numericConfigMap.put(DATE_FIELD_NAME, new NumericConfig(PRECISION_STEP,
+        DATE_FORMAT, NumericField.DataType.LONG));
+    NumericField dateField = new NumericField(DATE_FIELD_NAME, PRECISION_STEP,
+        Field.Store.YES, true);
+    numericFieldMap.put(DATE_FIELD_NAME, dateField);
+    doc.add(dateField);
+    
+    for (NumberType numberType : NumberType.values()) {
+      setFieldValues(numberType, numericFieldMap);
+      if (VERBOSE) System.out.println("Indexing document: " + doc);
+      writer.addDocument(doc);
+    }
+    
+    reader = writer.getReader();
+    searcher = newSearcher(reader);
+    writer.close();
+    
+  }
+  
+  private static Number getNumberType(NumberType numberType, String fieldName) {
+    
+    if (numberType == null) {
+      return null;
+    }
+    
+    switch (numberType) {
+      
+      case POSITIVE:
+        return RANDOM_NUMBER_MAP.get(fieldName);
+        
+      case NEGATIVE:
+        Number number = RANDOM_NUMBER_MAP.get(fieldName);
+        
+        if (NumericField.DataType.LONG.name().equals(fieldName)
+            || DATE_FIELD_NAME.equals(fieldName)) {
+          number = -number.longValue();
+          
+        } else if (NumericField.DataType.DOUBLE.name().equals(fieldName)) {
+          number = -number.doubleValue();
+          
+        } else if (NumericField.DataType.FLOAT.name().equals(fieldName)) {
+          number = -number.floatValue();
+          
+        } else if (NumericField.DataType.INT.name().equals(fieldName)) {
+          number = -number.intValue();
+          
+        } else {
+          throw new IllegalArgumentException("field name not found: "
+              + fieldName);
+        }
+        
+        return number;
+        
+      default:
+        return 0;
+        
+    }
+    
+  }
+  
+  private static void setFieldValues(NumberType numberType,
+      HashMap<String,NumericField> numericFieldMap) {
+    
+    Number number = getNumberType(numberType, NumericField.DataType.DOUBLE
+        .name());
+    numericFieldMap.get(NumericField.DataType.DOUBLE.name()).setDoubleValue(
+        number.doubleValue());
+    
+    number = getNumberType(numberType, NumericField.DataType.INT.name());
+    numericFieldMap.get(NumericField.DataType.INT.name()).setIntValue(
+        number.intValue());
+    
+    number = getNumberType(numberType, NumericField.DataType.LONG.name());
+    numericFieldMap.get(NumericField.DataType.LONG.name()).setLongValue(
+        number.longValue());
+    
+    number = getNumberType(numberType, NumericField.DataType.FLOAT.name());
+    numericFieldMap.get(NumericField.DataType.FLOAT.name()).setFloatValue(
+        number.floatValue());
+    
+    number = getNumberType(numberType, DATE_FIELD_NAME);
+    numericFieldMap.get(DATE_FIELD_NAME).setLongValue(number.longValue());
+    
+  }
+  
+  private static int randomDateStyle(Random random) {
+    return DATE_STYLES[random.nextInt(DATE_STYLES.length)];
+  }
+  
+  @Test
+  public void testInclusiveNumericRange() throws Exception {
+    assertRangeQuery(NumberType.ZERO, NumberType.ZERO, true, true, 1);
+    assertRangeQuery(NumberType.ZERO, NumberType.POSITIVE, true, true, 2);
+    assertRangeQuery(NumberType.NEGATIVE, NumberType.ZERO, true, true, 2);
+    assertRangeQuery(NumberType.NEGATIVE, NumberType.POSITIVE, true, true, 3);
+    assertRangeQuery(NumberType.NEGATIVE, NumberType.NEGATIVE, true, true, 1);
+  }
+  
+//   @Test
+//  // test disabled since standard syntax parser does not work with inclusive and
+//  // exclusive at the same time
+//  public void testInclusiveLowerNumericRange() throws Exception {
+//    assertRangeQuery(NumberType.NEGATIVE, NumberType.ZERO, false, true, 1);
+//    assertRangeQuery(NumberType.ZERO, NumberType.POSITIVE, false, true, 1);
+//    assertRangeQuery(NumberType.NEGATIVE, NumberType.POSITIVE, false, true, 2);
+//    assertRangeQuery(NumberType.NEGATIVE, NumberType.NEGATIVE, false, true, 0);
+//   }
+//  
+//  @Test
+//  // test disabled since standard syntax parser does not work with inclusive and
+//  // exclusive at the same time
+//  public void testInclusiveUpperNumericRange() throws Exception {
+//    assertRangeQuery(NumberType.NEGATIVE, NumberType.ZERO, true, false, 1);
+//    assertRangeQuery(NumberType.ZERO, NumberType.POSITIVE, true, false, 1);
+//    assertRangeQuery(NumberType.NEGATIVE, NumberType.POSITIVE, true, false, 2);
+//    assertRangeQuery(NumberType.NEGATIVE, NumberType.NEGATIVE, true, false, 0);
+//  }
+  
+  @Test
+  public void testExclusiveNumericRange() throws Exception {
+    assertRangeQuery(NumberType.ZERO, NumberType.ZERO, false, false, 0);
+    assertRangeQuery(NumberType.ZERO, NumberType.POSITIVE, false, false, 0);
+    assertRangeQuery(NumberType.NEGATIVE, NumberType.ZERO, false, false, 0);
+    assertRangeQuery(NumberType.NEGATIVE, NumberType.POSITIVE, false, false, 1);
+    assertRangeQuery(NumberType.NEGATIVE, NumberType.NEGATIVE, false, false, 0);
+  }
+  
+//  @Test
+//// test disabled since standard syntax parser does not work with open range
+//  public void testOpenRangeNumericQuery() throws Exception {
+//    assertOpenRangeQuery(NumberType.ZERO, "<", 1);
+//    assertOpenRangeQuery(NumberType.POSITIVE, "<", 2);
+//    assertOpenRangeQuery(NumberType.NEGATIVE, "<", 0);
+//    
+//    assertOpenRangeQuery(NumberType.ZERO, "<=", 2);
+//    assertOpenRangeQuery(NumberType.POSITIVE, "<=", 3);
+//    assertOpenRangeQuery(NumberType.NEGATIVE, "<=", 1);
+//    
+//    assertOpenRangeQuery(NumberType.ZERO, ">", 1);
+//    assertOpenRangeQuery(NumberType.POSITIVE, ">", 0);
+//    assertOpenRangeQuery(NumberType.NEGATIVE, ">", 2);
+//    
+//    assertOpenRangeQuery(NumberType.ZERO, ">=", 2);
+//    assertOpenRangeQuery(NumberType.POSITIVE, ">=", 1);
+//    assertOpenRangeQuery(NumberType.NEGATIVE, ">=", 3);
+//    
+//    assertOpenRangeQuery(NumberType.NEGATIVE, "=", 1);
+//    assertOpenRangeQuery(NumberType.ZERO, "=", 1);
+//    assertOpenRangeQuery(NumberType.POSITIVE, "=", 1);
+//    
+//    assertRangeQuery(NumberType.NEGATIVE, null, true, true, 3);
+//    assertRangeQuery(NumberType.NEGATIVE, null, false, true, 2);
+//    assertRangeQuery(NumberType.POSITIVE, null, true, false, 1);
+//    assertRangeQuery(NumberType.ZERO, null, false, false, 1);
+//
+//    assertRangeQuery(null, NumberType.POSITIVE, true, true, 3);
+//    assertRangeQuery(null, NumberType.POSITIVE, true, false, 2);
+//    assertRangeQuery(null, NumberType.NEGATIVE, false, true, 1);
+//    assertRangeQuery(null, NumberType.ZERO, false, false, 1);
+//    
+//    assertRangeQuery(null, null, false, false, 3);
+//    assertRangeQuery(null, null, true, true, 3);
+//    
+//  }
+  
+  @Test
+  public void testSimpleNumericQuery() throws Exception {
+    assertSimpleQuery(NumberType.ZERO, 1);
+    assertSimpleQuery(NumberType.POSITIVE, 1);
+    assertSimpleQuery(NumberType.NEGATIVE, 1);
+  }
+  
+  public void assertRangeQuery(NumberType lowerType, NumberType upperType,
+      boolean lowerInclusive, boolean upperInclusive, int expectedDocCount)
+      throws QueryNodeException, IOException {
+    
+    StringBuilder sb = new StringBuilder();
+    
+    String lowerInclusiveStr = (lowerInclusive ? "[" : "{");
+    String upperInclusiveStr = (upperInclusive ? "]" : "}");
+    
+    for (NumericField.DataType type : NumericField.DataType.values()) {
+      String lowerStr = numberToString(getNumberType(lowerType, type.name()));
+      String upperStr = numberToString(getNumberType(upperType, type.name()));
+      
+      sb.append("+").append(type.name()).append(':').append(lowerInclusiveStr)
+          .append('"').append(lowerStr).append("\" TO \"").append(upperStr)
+          .append('"').append(upperInclusiveStr).append(' ');
+    }
+    
+    Number lowerDateNumber = getNumberType(lowerType, DATE_FIELD_NAME);
+    Number upperDateNumber = getNumberType(upperType, DATE_FIELD_NAME);
+    String lowerDateStr;
+    String upperDateStr;
+    
+    if (lowerDateNumber != null) {
+      lowerDateStr = ESCAPER.escape(
+          DATE_FORMAT.format(new Date(lowerDateNumber.longValue())), LOCALE,
+          EscapeQuerySyntax.Type.STRING).toString();
+      
+    } else {
+      lowerDateStr = "*";
+    }
+    
+    if (upperDateNumber != null) {
+    upperDateStr = ESCAPER.escape(
+          DATE_FORMAT.format(new Date(upperDateNumber.longValue())), LOCALE,
+          EscapeQuerySyntax.Type.STRING).toString();
+    
+    } else {
+      upperDateStr = "*";
+    }
+    
+    sb.append("+").append(DATE_FIELD_NAME).append(':')
+        .append(lowerInclusiveStr).append('"').append(lowerDateStr).append(
+            "\" TO \"").append(upperDateStr).append('"').append(
+            upperInclusiveStr);
+    
+    testQuery(sb.toString(), expectedDocCount);
+    
+  }
+  
+  public void assertOpenRangeQuery(NumberType boundType, String operator, int expectedDocCount)
+      throws QueryNodeException, IOException {
+
+    StringBuilder sb = new StringBuilder();
+    
+    for (NumericField.DataType type : NumericField.DataType.values()) {
+      String boundStr = numberToString(getNumberType(boundType, type.name()));
+      
+      sb.append("+").append(type.name()).append(operator).append('"').append(boundStr).append('"').append(' ');
+    }
+    
+    String boundDateStr = ESCAPER.escape(
+        DATE_FORMAT.format(new Date(getNumberType(boundType, DATE_FIELD_NAME)
+            .longValue())), LOCALE, EscapeQuerySyntax.Type.STRING).toString();
+    
+    sb.append("+").append(DATE_FIELD_NAME).append(operator).append('"').append(boundDateStr).append('"');
+    
+    testQuery(sb.toString(), expectedDocCount);
+  }
+  
+  public void assertSimpleQuery(NumberType numberType, int expectedDocCount)
+      throws QueryNodeException, IOException {
+    StringBuilder sb = new StringBuilder();
+    
+    for (NumericField.DataType type : NumericField.DataType.values()) {
+      String numberStr = numberToString(getNumberType(numberType, type.name()));
+      sb.append('+').append(type.name()).append(":\"").append(numberStr)
+          .append("\" ");
+    }
+    
+    String dateStr = ESCAPER.escape(
+        DATE_FORMAT.format(new Date(getNumberType(numberType, DATE_FIELD_NAME)
+            .longValue())), LOCALE, EscapeQuerySyntax.Type.STRING).toString();
+    
+    sb.append('+').append(DATE_FIELD_NAME).append(":\"").append(dateStr)
+        .append('"');
+    
+    testQuery(sb.toString(), expectedDocCount);
+    
+  }
+  
+  private void testQuery(String queryStr, int expectedDocCount)
+      throws QueryNodeException, IOException {
+    if (VERBOSE) System.out.println("Parsing: " + queryStr);
+    
+    Query query = qp.parse(queryStr, FIELD_NAME);
+    if (VERBOSE) System.out.println("Querying: " + query);
+    TopDocs topDocs = searcher.search(query, 1000);
+    
+    String msg = "Query <" + queryStr + "> retrieved " + topDocs.totalHits
+        + " document(s), " + expectedDocCount + " document(s) expected.";
+    
+    if (VERBOSE) System.out.println(msg);
+    
+    assertEquals(msg, expectedDocCount, topDocs.totalHits);
+  }
+  
+  private static String numberToString(Number number) {
+    return number == null ? "*" : ESCAPER.escape(NUMBER_FORMAT.format(number),
+        LOCALE, EscapeQuerySyntax.Type.STRING).toString();
+  }
+  
+  private static Number normalizeNumber(Number number) throws ParseException {
+    return NUMBER_FORMAT.parse(NUMBER_FORMAT.format(number));
+  }
+  
+  @AfterClass
+  public static void afterClass() throws Exception {
+    searcher.close();
+    searcher = null;
+    reader.close();
+    reader = null;
+    directory.close();
+    directory = null;
+  }
+  
+}