You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by cp...@apache.org on 2017/03/13 15:40:39 UTC
[44/50] [abbrv] lucene-solr:jira/solr-9045: LUCENE-7740: Refactor
Range Fields to remove Field suffix (e.g., DoubleRange),
move InetAddressRange and InetAddressPoint from sandbox to misc module,
and refactor all other range fields from sandbox to core.
LUCENE-7740: Refactor Range Fields to remove Field suffix (e.g., DoubleRange),
move InetAddressRange and InetAddressPoint from sandbox to misc module, and
refactor all other range fields from sandbox to core.
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/d34d81f9
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/d34d81f9
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/d34d81f9
Branch: refs/heads/jira/solr-9045
Commit: d34d81f9af89657fdd4fe0b3174459142955215b
Parents: 182c20c
Author: Nicholas Knize <nk...@gmail.com>
Authored: Mon Mar 13 01:59:04 2017 -0500
Committer: Nicholas Knize <nk...@gmail.com>
Committed: Mon Mar 13 02:22:29 2017 -0500
----------------------------------------------------------------------
lucene/CHANGES.txt | 8 +-
.../org/apache/lucene/document/DoubleRange.java | 271 +++++++++++++++
.../org/apache/lucene/document/FloatRange.java | 271 +++++++++++++++
.../org/apache/lucene/document/IntRange.java | 276 +++++++++++++++
.../org/apache/lucene/document/LongRange.java | 269 ++++++++++++++
.../apache/lucene/document/RangeFieldQuery.java | 340 ++++++++++++++++++
.../org/apache/lucene/index/PointValues.java | 2 +-
.../search/TestDoubleRangeFieldQueries.java | 251 ++++++++++++++
.../search/TestFloatRangeFieldQueries.java | 251 ++++++++++++++
.../lucene/search/TestIntRangeFieldQueries.java | 251 ++++++++++++++
.../search/TestLongRangeFieldQueries.java | 251 ++++++++++++++
.../lucene/document/InetAddressPoint.java | 313 +++++++++++++++++
.../lucene/document/InetAddressRange.java | 168 +++++++++
.../lucene/document/TestInetAddressPoint.java | 176 ++++++++++
.../search/TestInetAddressRangeQueries.java | 215 ++++++++++++
.../lucene/document/DoubleRangeField.java | 282 ---------------
.../apache/lucene/document/FloatRangeField.java | 282 ---------------
.../lucene/document/InetAddressPoint.java | 313 -----------------
.../lucene/document/InetAddressRangeField.java | 168 ---------
.../apache/lucene/document/IntRangeField.java | 282 ---------------
.../apache/lucene/document/LongRangeField.java | 280 ---------------
.../apache/lucene/document/RangeFieldQuery.java | 340 ------------------
.../org/apache/lucene/document/package.html | 3 +-
.../lucene/document/TestDoubleRangeField.java | 10 +-
.../lucene/document/TestInetAddressPoint.java | 176 ----------
.../search/BaseRangeFieldQueryTestCase.java | 344 ------------------
.../search/TestDoubleRangeFieldQueries.java | 251 --------------
.../search/TestFloatRangeFieldQueries.java | 251 --------------
.../lucene/search/TestIntRangeFieldQueries.java | 251 --------------
.../lucene/search/TestIpRangeFieldQueries.java | 220 ------------
.../search/TestLongRangeFieldQueries.java | 251 --------------
.../search/BaseRangeFieldQueryTestCase.java | 346 +++++++++++++++++++
32 files changed, 3662 insertions(+), 3701 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index c2fe191..e14ab53 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -84,6 +84,10 @@ Other
API Changes
+* LUCENE-7740: Refactor Range Fields to remove Field suffix (e.g., DoubleRange),
+ move InetAddressRange and InetAddressPoint from sandbox to misc module, and
+ refactor all other range fields from sandbox to core. (Nick Knize)
+
* LUCENE-7624: TermsQuery has been renamed as TermInSetQuery and moved to core.
(Alan Woodward)
@@ -131,8 +135,8 @@ API Changes
New Features
-* LUCENE-7738: Add new InetAddressRangeField for indexing and querying
- InetAddress ranges. (Nick Knize)
+* LUCENE-7738: Add new InetAddressRange for indexing and querying InetAddress
+ ranges. (Nick Knize)
* LUCENE-7449: Add CROSSES relation support to RangeFieldQuery. (Nick Knize)
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/core/src/java/org/apache/lucene/document/DoubleRange.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/DoubleRange.java b/lucene/core/src/java/org/apache/lucene/document/DoubleRange.java
new file mode 100644
index 0000000..90a8eb9
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/DoubleRange.java
@@ -0,0 +1,271 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.document;
+
+import org.apache.lucene.document.RangeFieldQuery.QueryType;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * An indexed Double Range field.
+ * <p>
+ * This field indexes dimensional ranges defined as min/max pairs. It supports
+ * up to a maximum of 4 dimensions (indexed as 8 numeric values). With 1 dimension representing a single double range,
+ * 2 dimensions representing a bounding box, 3 dimensions a bounding cube, and 4 dimensions a tesseract.
+ * <p>
+ * Multiple values for the same field in one document is supported, and open ended ranges can be defined using
+ * {@code Double.NEGATIVE_INFINITY} and {@code Double.POSITIVE_INFINITY}.
+ *
+ * <p>
+ * This field defines the following static factory methods for common search operations over double ranges:
+ * <ul>
+ * <li>{@link #newIntersectsQuery newIntersectsQuery()} matches ranges that intersect the defined search range.
+ * <li>{@link #newWithinQuery newWithinQuery()} matches ranges that are within the defined search range.
+ * <li>{@link #newContainsQuery newContainsQuery()} matches ranges that contain the defined search range.
+ * </ul>
+ */
+public class DoubleRange extends Field {
+ /** stores double values so number of bytes is 8 */
+ public static final int BYTES = Double.BYTES;
+
+ /**
+ * Create a new DoubleRange type, from min/max parallel arrays
+ *
+ * @param name field name. must not be null.
+ * @param min range min values; each entry is the min value for the dimension
+ * @param max range max values; each entry is the max value for the dimension
+ */
+ public DoubleRange(String name, final double[] min, final double[] max) {
+ super(name, getType(min.length));
+ setRangeValues(min, max);
+ }
+
+ /** set the field type */
+ private static FieldType getType(int dimensions) {
+ if (dimensions > 4) {
+ throw new IllegalArgumentException("DoubleRange does not support greater than 4 dimensions");
+ }
+
+ FieldType ft = new FieldType();
+ // dimensions is set as 2*dimension size (min/max per dimension)
+ ft.setDimensions(dimensions*2, BYTES);
+ ft.freeze();
+ return ft;
+ }
+
+ /**
+ * Changes the values of the field.
+ * @param min array of min values. (accepts {@code Double.NEGATIVE_INFINITY})
+ * @param max array of max values. (accepts {@code Double.POSITIVE_INFINITY})
+ * @throws IllegalArgumentException if {@code min} or {@code max} is invalid
+ */
+ public void setRangeValues(double[] min, double[] max) {
+ checkArgs(min, max);
+ if (min.length*2 != type.pointDimensionCount() || max.length*2 != type.pointDimensionCount()) {
+ throw new IllegalArgumentException("field (name=" + name + ") uses " + type.pointDimensionCount()/2
+ + " dimensions; cannot change to (incoming) " + min.length + " dimensions");
+ }
+
+ final byte[] bytes;
+ if (fieldsData == null) {
+ bytes = new byte[BYTES*2*min.length];
+ fieldsData = new BytesRef(bytes);
+ } else {
+ bytes = ((BytesRef)fieldsData).bytes;
+ }
+ verifyAndEncode(min, max, bytes);
+ }
+
+ /** validate the arguments */
+ private static void checkArgs(final double[] min, final double[] max) {
+ if (min == null || max == null || min.length == 0 || max.length == 0) {
+ throw new IllegalArgumentException("min/max range values cannot be null or empty");
+ }
+ if (min.length != max.length) {
+ throw new IllegalArgumentException("min/max ranges must agree");
+ }
+ if (min.length > 4) {
+ throw new IllegalArgumentException("DoubleRange does not support greater than 4 dimensions");
+ }
+ }
+
+ /**
+ * Encodes the min, max ranges into a byte array
+ */
+ private static byte[] encode(double[] min, double[] max) {
+ checkArgs(min, max);
+ byte[] b = new byte[BYTES*2*min.length];
+ verifyAndEncode(min, max, b);
+ return b;
+ }
+
+ /**
+ * encode the ranges into a sortable byte array ({@code Double.NaN} not allowed)
+ * <p>
+ * example for 4 dimensions (8 bytes per dimension value):
+ * minD1 ... minD4 | maxD1 ... maxD4
+ */
+ static void verifyAndEncode(double[] min, double[] max, byte[] bytes) {
+ for (int d=0,i=0,j=min.length*BYTES; d<min.length; ++d, i+=BYTES, j+=BYTES) {
+ if (Double.isNaN(min[d])) {
+ throw new IllegalArgumentException("invalid min value (" + Double.NaN + ")" + " in DoubleRange");
+ }
+ if (Double.isNaN(max[d])) {
+ throw new IllegalArgumentException("invalid max value (" + Double.NaN + ")" + " in DoubleRange");
+ }
+ if (min[d] > max[d]) {
+ throw new IllegalArgumentException("min value (" + min[d] + ") is greater than max value (" + max[d] + ")");
+ }
+ encode(min[d], bytes, i);
+ encode(max[d], bytes, j);
+ }
+ }
+
+ /** encode the given value into the byte array at the defined offset */
+ private static void encode(double val, byte[] bytes, int offset) {
+ NumericUtils.longToSortableBytes(NumericUtils.doubleToSortableLong(val), bytes, offset);
+ }
+
+ /**
+ * Get the min value for the given dimension
+ * @param dimension the dimension, always positive
+ * @return the decoded min value
+ */
+ public double getMin(int dimension) {
+ if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+ throw new IllegalArgumentException("dimension request (" + dimension +
+ ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+ }
+ return decodeMin(((BytesRef)fieldsData).bytes, dimension);
+ }
+
+ /**
+ * Get the max value for the given dimension
+ * @param dimension the dimension, always positive
+ * @return the decoded max value
+ */
+ public double getMax(int dimension) {
+ if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+ throw new IllegalArgumentException("dimension request (" + dimension +
+ ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+ }
+ return decodeMax(((BytesRef)fieldsData).bytes, dimension);
+ }
+
+ /** decodes the min value (for the defined dimension) from the encoded input byte array */
+ static double decodeMin(byte[] b, int dimension) {
+ int offset = dimension*BYTES;
+ return NumericUtils.sortableLongToDouble(NumericUtils.sortableBytesToLong(b, offset));
+ }
+
+ /** decodes the max value (for the defined dimension) from the encoded input byte array */
+ static double decodeMax(byte[] b, int dimension) {
+ int offset = b.length/2 + dimension*BYTES;
+ return NumericUtils.sortableLongToDouble(NumericUtils.sortableBytesToLong(b, offset));
+ }
+
+ /**
+ * Create a query for matching indexed ranges that intersect the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Double.NEGATIVE_INFINITY})
+ * @param max array of max values. (accepts {@code Double.POSITIVE_INFINITY})
+ * @return query for matching intersecting ranges (overlap, within, or contains)
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newIntersectsQuery(String field, final double[] min, final double[] max) {
+ return newRelationQuery(field, min, max, QueryType.INTERSECTS);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that contain the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Double.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Double.MAX_VALUE})
+ * @return query for matching ranges that contain the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newContainsQuery(String field, final double[] min, final double[] max) {
+ return newRelationQuery(field, min, max, QueryType.CONTAINS);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that are within the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Double.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Double.MAX_VALUE})
+ * @return query for matching ranges within the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newWithinQuery(String field, final double[] min, final double[] max) {
+ return newRelationQuery(field, min, max, QueryType.WITHIN);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that cross the defined range.
+ * A CROSSES is defined as any set of ranges that are not disjoint and not wholly contained by
+ * the query. Effectively, its the complement of union(WITHIN, DISJOINT).
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Double.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Double.MAX_VALUE})
+ * @return query for matching ranges within the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newCrossesQuery(String field, final double[] min, final double[] max) {
+ return newRelationQuery(field, min, max, QueryType.CROSSES);
+ }
+
+ /** helper method for creating the desired relational query */
+ private static Query newRelationQuery(String field, final double[] min, final double[] max, QueryType relation) {
+ checkArgs(min, max);
+ return new RangeFieldQuery(field, encode(min, max), min.length, relation) {
+ @Override
+ protected String toString(byte[] ranges, int dimension) {
+ return DoubleRange.toString(ranges, dimension);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(" <");
+ sb.append(name);
+ sb.append(':');
+ byte[] b = ((BytesRef)fieldsData).bytes;
+ toString(b, 0);
+ for (int d=1; d<type.pointDimensionCount(); ++d) {
+ sb.append(' ');
+ toString(b, d);
+ }
+ sb.append('>');
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the String representation for the range at the given dimension
+ * @param ranges the encoded ranges, never null
+ * @param dimension the dimension of interest
+ * @return The string representation for the range at the provided dimension
+ */
+ private static String toString(byte[] ranges, int dimension) {
+ return "[" + Double.toString(decodeMin(ranges, dimension)) + " : "
+ + Double.toString(decodeMax(ranges, dimension)) + "]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/core/src/java/org/apache/lucene/document/FloatRange.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/FloatRange.java b/lucene/core/src/java/org/apache/lucene/document/FloatRange.java
new file mode 100644
index 0000000..8b40538
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/FloatRange.java
@@ -0,0 +1,271 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.document;
+
+import org.apache.lucene.document.RangeFieldQuery.QueryType;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * An indexed Float Range field.
+ * <p>
+ * This field indexes dimensional ranges defined as min/max pairs. It supports
+ * up to a maximum of 4 dimensions (indexed as 8 numeric values). With 1 dimension representing a single float range,
+ * 2 dimensions representing a bounding box, 3 dimensions a bounding cube, and 4 dimensions a tesseract.
+ * <p>
+ * Multiple values for the same field in one document is supported, and open ended ranges can be defined using
+ * {@code Float.NEGATIVE_INFINITY} and {@code Float.POSITIVE_INFINITY}.
+ *
+ * <p>
+ * This field defines the following static factory methods for common search operations over float ranges:
+ * <ul>
+ * <li>{@link #newIntersectsQuery newIntersectsQuery()} matches ranges that intersect the defined search range.
+ * <li>{@link #newWithinQuery newWithinQuery()} matches ranges that are within the defined search range.
+ * <li>{@link #newContainsQuery newContainsQuery()} matches ranges that contain the defined search range.
+ * </ul>
+ */
+public class FloatRange extends Field {
+ /** stores float values so number of bytes is 4 */
+ public static final int BYTES = Float.BYTES;
+
+ /**
+ * Create a new FloatRange type, from min/max parallel arrays
+ *
+ * @param name field name. must not be null.
+ * @param min range min values; each entry is the min value for the dimension
+ * @param max range max values; each entry is the max value for the dimension
+ */
+ public FloatRange(String name, final float[] min, final float[] max) {
+ super(name, getType(min.length));
+ setRangeValues(min, max);
+ }
+
+ /** set the field type */
+ private static FieldType getType(int dimensions) {
+ if (dimensions > 4) {
+ throw new IllegalArgumentException("FloatRange does not support greater than 4 dimensions");
+ }
+
+ FieldType ft = new FieldType();
+ // dimensions is set as 2*dimension size (min/max per dimension)
+ ft.setDimensions(dimensions*2, BYTES);
+ ft.freeze();
+ return ft;
+ }
+
+ /**
+ * Changes the values of the field.
+ * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+ * @param max array of max values. (accepts {@code Float.POSITIVE_INFINITY})
+ * @throws IllegalArgumentException if {@code min} or {@code max} is invalid
+ */
+ public void setRangeValues(float[] min, float[] max) {
+ checkArgs(min, max);
+ if (min.length*2 != type.pointDimensionCount() || max.length*2 != type.pointDimensionCount()) {
+ throw new IllegalArgumentException("field (name=" + name + ") uses " + type.pointDimensionCount()/2
+ + " dimensions; cannot change to (incoming) " + min.length + " dimensions");
+ }
+
+ final byte[] bytes;
+ if (fieldsData == null) {
+ bytes = new byte[BYTES*2*min.length];
+ fieldsData = new BytesRef(bytes);
+ } else {
+ bytes = ((BytesRef)fieldsData).bytes;
+ }
+ verifyAndEncode(min, max, bytes);
+ }
+
+ /** validate the arguments */
+ private static void checkArgs(final float[] min, final float[] max) {
+ if (min == null || max == null || min.length == 0 || max.length == 0) {
+ throw new IllegalArgumentException("min/max range values cannot be null or empty");
+ }
+ if (min.length != max.length) {
+ throw new IllegalArgumentException("min/max ranges must agree");
+ }
+ if (min.length > 4) {
+ throw new IllegalArgumentException("FloatRange does not support greater than 4 dimensions");
+ }
+ }
+
+ /**
+ * Encodes the min, max ranges into a byte array
+ */
+ private static byte[] encode(float[] min, float[] max) {
+ checkArgs(min, max);
+ byte[] b = new byte[BYTES*2*min.length];
+ verifyAndEncode(min, max, b);
+ return b;
+ }
+
+ /**
+ * encode the ranges into a sortable byte array ({@code Float.NaN} not allowed)
+ * <p>
+ * example for 4 dimensions (8 bytes per dimension value):
+ * minD1 ... minD4 | maxD1 ... maxD4
+ */
+ static void verifyAndEncode(float[] min, float[] max, byte[] bytes) {
+ for (int d=0,i=0,j=min.length*BYTES; d<min.length; ++d, i+=BYTES, j+=BYTES) {
+ if (Double.isNaN(min[d])) {
+ throw new IllegalArgumentException("invalid min value (" + Float.NaN + ")" + " in FloatRange");
+ }
+ if (Double.isNaN(max[d])) {
+ throw new IllegalArgumentException("invalid max value (" + Float.NaN + ")" + " in FloatRange");
+ }
+ if (min[d] > max[d]) {
+ throw new IllegalArgumentException("min value (" + min[d] + ") is greater than max value (" + max[d] + ")");
+ }
+ encode(min[d], bytes, i);
+ encode(max[d], bytes, j);
+ }
+ }
+
+ /** encode the given value into the byte array at the defined offset */
+ private static void encode(float val, byte[] bytes, int offset) {
+ NumericUtils.intToSortableBytes(NumericUtils.floatToSortableInt(val), bytes, offset);
+ }
+
+ /**
+ * Get the min value for the given dimension
+ * @param dimension the dimension, always positive
+ * @return the decoded min value
+ */
+ public float getMin(int dimension) {
+ if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+ throw new IllegalArgumentException("dimension request (" + dimension +
+ ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+ }
+ return decodeMin(((BytesRef)fieldsData).bytes, dimension);
+ }
+
+ /**
+ * Get the max value for the given dimension
+ * @param dimension the dimension, always positive
+ * @return the decoded max value
+ */
+ public float getMax(int dimension) {
+ if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+ throw new IllegalArgumentException("dimension request (" + dimension +
+ ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+ }
+ return decodeMax(((BytesRef)fieldsData).bytes, dimension);
+ }
+
+ /** decodes the min value (for the defined dimension) from the encoded input byte array */
+ static float decodeMin(byte[] b, int dimension) {
+ int offset = dimension*BYTES;
+ return NumericUtils.sortableIntToFloat(NumericUtils.sortableBytesToInt(b, offset));
+ }
+
+ /** decodes the max value (for the defined dimension) from the encoded input byte array */
+ static float decodeMax(byte[] b, int dimension) {
+ int offset = b.length/2 + dimension*BYTES;
+ return NumericUtils.sortableIntToFloat(NumericUtils.sortableBytesToInt(b, offset));
+ }
+
+ /**
+ * Create a query for matching indexed ranges that intersect the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+ * @param max array of max values. (accepts {@code Float.MAX_VALUE})
+ * @return query for matching intersecting ranges (overlap, within, or contains)
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newIntersectsQuery(String field, final float[] min, final float[] max) {
+ return newRelationQuery(field, min, max, QueryType.INTERSECTS);
+ }
+
+ /**
+ * Create a query for matching indexed float ranges that contain the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+ * @param max array of max values. (accepts {@code Float.POSITIVE_INFINITY})
+ * @return query for matching ranges that contain the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newContainsQuery(String field, final float[] min, final float[] max) {
+ return newRelationQuery(field, min, max, QueryType.CONTAINS);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that are within the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+ * @param max array of max values. (accepts {@code Float.POSITIVE_INFINITY})
+ * @return query for matching ranges within the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newWithinQuery(String field, final float[] min, final float[] max) {
+ return newRelationQuery(field, min, max, QueryType.WITHIN);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that cross the defined range.
+ * A CROSSES is defined as any set of ranges that are not disjoint and not wholly contained by
+ * the query. Effectively, its the complement of union(WITHIN, DISJOINT).
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Float.NEGATIVE_INFINITY})
+ * @param max array of max values. (accepts {@code Float.POSITIVE_INFINITY})
+ * @return query for matching ranges within the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newCrossesQuery(String field, final float[] min, final float[] max) {
+ return newRelationQuery(field, min, max, QueryType.CROSSES);
+ }
+
+ /** helper method for creating the desired relational query */
+ private static Query newRelationQuery(String field, final float[] min, final float[] max, QueryType relation) {
+ checkArgs(min, max);
+ return new RangeFieldQuery(field, encode(min, max), min.length, relation) {
+ @Override
+ protected String toString(byte[] ranges, int dimension) {
+ return FloatRange.toString(ranges, dimension);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(" <");
+ sb.append(name);
+ sb.append(':');
+ byte[] b = ((BytesRef)fieldsData).bytes;
+ toString(b, 0);
+ for (int d=1; d<type.pointDimensionCount(); ++d) {
+ sb.append(' ');
+ toString(b, d);
+ }
+ sb.append('>');
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the String representation for the range at the given dimension
+ * @param ranges the encoded ranges, never null
+ * @param dimension the dimension of interest
+ * @return The string representation for the range at the provided dimension
+ */
+ private static String toString(byte[] ranges, int dimension) {
+ return "[" + Float.toString(decodeMin(ranges, dimension)) + " : "
+ + Float.toString(decodeMax(ranges, dimension)) + "]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/core/src/java/org/apache/lucene/document/IntRange.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/IntRange.java b/lucene/core/src/java/org/apache/lucene/document/IntRange.java
new file mode 100644
index 0000000..2618f14
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/IntRange.java
@@ -0,0 +1,276 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.document;
+
+import org.apache.lucene.document.RangeFieldQuery.QueryType;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * An indexed Integer Range field.
+ * <p>
+ * This field indexes dimensional ranges defined as min/max pairs. It supports
+ * up to a maximum of 4 dimensions (indexed as 8 numeric values). With 1 dimension representing a single integer range,
+ * 2 dimensions representing a bounding box, 3 dimensions a bounding cube, and 4 dimensions a tesseract.
+ * <p>
+ * Multiple values for the same field in one document is supported, and open ended ranges can be defined using
+ * {@code Integer.MIN_VALUE} and {@code Integer.MAX_VALUE}.
+ *
+ * <p>
+ * This field defines the following static factory methods for common search operations over integer ranges:
+ * <ul>
+ * <li>{@link #newIntersectsQuery newIntersectsQuery()} matches ranges that intersect the defined search range.
+ * <li>{@link #newWithinQuery newWithinQuery()} matches ranges that are within the defined search range.
+ * <li>{@link #newContainsQuery newContainsQuery()} matches ranges that contain the defined search range.
+ * </ul>
+ */
+public class IntRange extends Field {
+ /** stores integer values so number of bytes is 4 */
+ public static final int BYTES = Integer.BYTES;
+
+ /**
+ * Create a new IntRange type, from min/max parallel arrays
+ *
+ * @param name field name. must not be null.
+ * @param min range min values; each entry is the min value for the dimension
+ * @param max range max values; each entry is the max value for the dimension
+ */
+ public IntRange(String name, final int[] min, final int[] max) {
+ super(name, getType(min.length));
+ setRangeValues(min, max);
+ }
+
+ /** set the field type */
+ private static FieldType getType(int dimensions) {
+ if (dimensions > 4) {
+ throw new IllegalArgumentException("IntRange does not support greater than 4 dimensions");
+ }
+
+ FieldType ft = new FieldType();
+ // dimensions is set as 2*dimension size (min/max per dimension)
+ ft.setDimensions(dimensions*2, BYTES);
+ ft.freeze();
+ return ft;
+ }
+
+ /**
+ * Changes the values of the field.
+ * @param min array of min values. (accepts {@code Integer.NEGATIVE_INFINITY})
+ * @param max array of max values. (accepts {@code Integer.POSITIVE_INFINITY})
+ * @throws IllegalArgumentException if {@code min} or {@code max} is invalid
+ */
+ public void setRangeValues(int[] min, int[] max) {
+ checkArgs(min, max);
+ if (min.length*2 != type.pointDimensionCount() || max.length*2 != type.pointDimensionCount()) {
+ throw new IllegalArgumentException("field (name=" + name + ") uses " + type.pointDimensionCount()/2
+ + " dimensions; cannot change to (incoming) " + min.length + " dimensions");
+ }
+
+ final byte[] bytes;
+ if (fieldsData == null) {
+ bytes = new byte[BYTES*2*min.length];
+ fieldsData = new BytesRef(bytes);
+ } else {
+ bytes = ((BytesRef)fieldsData).bytes;
+ }
+ verifyAndEncode(min, max, bytes);
+ }
+
+ /** validate the arguments */
+ private static void checkArgs(final int[] min, final int[] max) {
+ if (min == null || max == null || min.length == 0 || max.length == 0) {
+ throw new IllegalArgumentException("min/max range values cannot be null or empty");
+ }
+ if (min.length != max.length) {
+ throw new IllegalArgumentException("min/max ranges must agree");
+ }
+ if (min.length > 4) {
+ throw new IllegalArgumentException("IntRange does not support greater than 4 dimensions");
+ }
+ }
+
+ /**
+ * Encodes the min, max ranges into a byte array
+ */
+ private static byte[] encode(int[] min, int[] max) {
+ checkArgs(min, max);
+ byte[] b = new byte[BYTES*2*min.length];
+ verifyAndEncode(min, max, b);
+ return b;
+ }
+
+ /**
+ * encode the ranges into a sortable byte array ({@code Double.NaN} not allowed)
+ * <p>
+ * example for 4 dimensions (8 bytes per dimension value):
+ * minD1 ... minD4 | maxD1 ... maxD4
+ */
+ static void verifyAndEncode(int[] min, int[] max, byte[] bytes) {
+ for (int d=0,i=0,j=min.length*BYTES; d<min.length; ++d, i+=BYTES, j+=BYTES) {
+ if (Double.isNaN(min[d])) {
+ throw new IllegalArgumentException("invalid min value (" + Double.NaN + ")" + " in IntRange");
+ }
+ if (Double.isNaN(max[d])) {
+ throw new IllegalArgumentException("invalid max value (" + Double.NaN + ")" + " in IntRange");
+ }
+ if (min[d] > max[d]) {
+ throw new IllegalArgumentException("min value (" + min[d] + ") is greater than max value (" + max[d] + ")");
+ }
+ encode(min[d], bytes, i);
+ encode(max[d], bytes, j);
+ }
+ }
+
+ /** encode the given value into the byte array at the defined offset */
+ private static void encode(int val, byte[] bytes, int offset) {
+ NumericUtils.intToSortableBytes(val, bytes, offset);
+ }
+
+ /**
+ * Get the min value for the given dimension
+ * @param dimension the dimension, always positive
+ * @return the decoded min value
+ */
+ public int getMin(int dimension) {
+ if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+ throw new IllegalArgumentException("dimension request (" + dimension +
+ ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+ }
+ return decodeMin(((BytesRef)fieldsData).bytes, dimension);
+ }
+
+ /**
+ * Get the max value for the given dimension
+ * @param dimension the dimension, always positive
+ * @return the decoded max value
+ */
+ public int getMax(int dimension) {
+ if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+ throw new IllegalArgumentException("dimension request (" + dimension +
+ ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+ }
+ return decodeMax(((BytesRef)fieldsData).bytes, dimension);
+ }
+
+ /** decodes the min value (for the defined dimension) from the encoded input byte array */
+ static int decodeMin(byte[] b, int dimension) {
+ int offset = dimension*BYTES;
+ return NumericUtils.sortableBytesToInt(b, offset);
+ }
+
+ /** decodes the max value (for the defined dimension) from the encoded input byte array */
+ static int decodeMax(byte[] b, int dimension) {
+ int offset = b.length/2 + dimension*BYTES;
+ return NumericUtils.sortableBytesToInt(b, offset);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that intersect the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Integer.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Integer.MAX_VALUE})
+ * @return query for matching intersecting ranges (overlap, within, or contains)
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newIntersectsQuery(String field, final int[] min, final int[] max) {
+ return new RangeFieldQuery(field, encode(min, max), min.length, QueryType.INTERSECTS) {
+ @Override
+ protected String toString(byte[] ranges, int dimension) {
+ return IntRange.toString(ranges, dimension);
+ }
+ };
+ }
+
+ /**
+ * Create a query for matching indexed ranges that contain the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Integer.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Integer.MAX_VALUE})
+ * @return query for matching ranges that contain the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newContainsQuery(String field, final int[] min, final int[] max) {
+ return newRelationQuery(field, min, max, QueryType.CONTAINS);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that are within the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Integer.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Integer.MAX_VALUE})
+ * @return query for matching ranges within the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newWithinQuery(String field, final int[] min, final int[] max) {
+ return newRelationQuery(field, min, max, QueryType.WITHIN);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that cross the defined range.
+ * A CROSSES is defined as any set of ranges that are not disjoint and not wholly contained by
+ * the query. Effectively, its the complement of union(WITHIN, DISJOINT).
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Integer.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Integer.MAX_VALUE})
+ * @return query for matching ranges within the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newCrossesQuery(String field, final int[] min, final int[] max) {
+ return newRelationQuery(field, min, max, QueryType.CROSSES);
+ }
+
+ /** helper method for creating the desired relational query */
+ private static Query newRelationQuery(String field, final int[] min, final int[] max, QueryType relation) {
+ checkArgs(min, max);
+ return new RangeFieldQuery(field, encode(min, max), min.length, relation) {
+ @Override
+ protected String toString(byte[] ranges, int dimension) {
+ return IntRange.toString(ranges, dimension);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(" <");
+ sb.append(name);
+ sb.append(':');
+ byte[] b = ((BytesRef)fieldsData).bytes;
+ toString(b, 0);
+ for (int d=1; d<type.pointDimensionCount(); ++d) {
+ sb.append(' ');
+ toString(b, d);
+ }
+ sb.append('>');
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the String representation for the range at the given dimension
+ * @param ranges the encoded ranges, never null
+ * @param dimension the dimension of interest
+ * @return The string representation for the range at the provided dimension
+ */
+ private static String toString(byte[] ranges, int dimension) {
+ return "[" + Integer.toString(decodeMin(ranges, dimension)) + " : "
+ + Integer.toString(decodeMax(ranges, dimension)) + "]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/core/src/java/org/apache/lucene/document/LongRange.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/LongRange.java b/lucene/core/src/java/org/apache/lucene/document/LongRange.java
new file mode 100644
index 0000000..009f4a1
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/LongRange.java
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.document;
+
+import org.apache.lucene.document.RangeFieldQuery.QueryType;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * An indexed Long Range field.
+ * <p>
+ * This field indexes dimensional ranges defined as min/max pairs. It supports
+ * up to a maximum of 4 dimensions (indexed as 8 numeric values). With 1 dimension representing a single long range,
+ * 2 dimensions representing a bounding box, 3 dimensions a bounding cube, and 4 dimensions a tesseract.
+ * <p>
+ * Multiple values for the same field in one document is supported, and open ended ranges can be defined using
+ * {@code Long.MIN_VALUE} and {@code Long.MAX_VALUE}.
+ *
+ * <p>
+ * This field defines the following static factory methods for common search operations over long ranges:
+ * <ul>
+ * <li>{@link #newIntersectsQuery newIntersectsQuery()} matches ranges that intersect the defined search range.
+ * <li>{@link #newWithinQuery newWithinQuery()} matches ranges that are within the defined search range.
+ * <li>{@link #newContainsQuery newContainsQuery()} matches ranges that contain the defined search range.
+ * </ul>
+ */
+public class LongRange extends Field {
+ /** stores long values so number of bytes is 8 */
+ public static final int BYTES = Long.BYTES;
+
+ /**
+ * Create a new LongRange type, from min/max parallel arrays
+ *
+ * @param name field name. must not be null.
+ * @param min range min values; each entry is the min value for the dimension
+ * @param max range max values; each entry is the max value for the dimension
+ */
+ public LongRange(String name, final long[] min, final long[] max) {
+ super(name, getType(min.length));
+ setRangeValues(min, max);
+ }
+
+ /** set the field type */
+ private static FieldType getType(int dimensions) {
+ if (dimensions > 4) {
+ throw new IllegalArgumentException("LongRange does not support greater than 4 dimensions");
+ }
+
+ FieldType ft = new FieldType();
+ // dimensions is set as 2*dimension size (min/max per dimension)
+ ft.setDimensions(dimensions*2, BYTES);
+ ft.freeze();
+ return ft;
+ }
+
+ /**
+ * Changes the values of the field.
+ * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+ * @throws IllegalArgumentException if {@code min} or {@code max} is invalid
+ */
+ public void setRangeValues(long[] min, long[] max) {
+ checkArgs(min, max);
+ if (min.length*2 != type.pointDimensionCount() || max.length*2 != type.pointDimensionCount()) {
+ throw new IllegalArgumentException("field (name=" + name + ") uses " + type.pointDimensionCount()/2
+ + " dimensions; cannot change to (incoming) " + min.length + " dimensions");
+ }
+
+ final byte[] bytes;
+ if (fieldsData == null) {
+ bytes = new byte[BYTES*2*min.length];
+ fieldsData = new BytesRef(bytes);
+ } else {
+ bytes = ((BytesRef)fieldsData).bytes;
+ }
+ verifyAndEncode(min, max, bytes);
+ }
+
+ /** validate the arguments */
+ private static void checkArgs(final long[] min, final long[] max) {
+ if (min == null || max == null || min.length == 0 || max.length == 0) {
+ throw new IllegalArgumentException("min/max range values cannot be null or empty");
+ }
+ if (min.length != max.length) {
+ throw new IllegalArgumentException("min/max ranges must agree");
+ }
+ if (min.length > 4) {
+ throw new IllegalArgumentException("LongRange does not support greater than 4 dimensions");
+ }
+ }
+
+ /** Encodes the min, max ranges into a byte array */
+ private static byte[] encode(long[] min, long[] max) {
+ checkArgs(min, max);
+ byte[] b = new byte[BYTES*2*min.length];
+ verifyAndEncode(min, max, b);
+ return b;
+ }
+
+ /**
+ * encode the ranges into a sortable byte array ({@code Double.NaN} not allowed)
+ * <p>
+ * example for 4 dimensions (8 bytes per dimension value):
+ * minD1 ... minD4 | maxD1 ... maxD4
+ */
+ static void verifyAndEncode(long[] min, long[] max, byte[] bytes) {
+ for (int d=0,i=0,j=min.length*BYTES; d<min.length; ++d, i+=BYTES, j+=BYTES) {
+ if (Double.isNaN(min[d])) {
+ throw new IllegalArgumentException("invalid min value (" + Double.NaN + ")" + " in LongRange");
+ }
+ if (Double.isNaN(max[d])) {
+ throw new IllegalArgumentException("invalid max value (" + Double.NaN + ")" + " in LongRange");
+ }
+ if (min[d] > max[d]) {
+ throw new IllegalArgumentException("min value (" + min[d] + ") is greater than max value (" + max[d] + ")");
+ }
+ encode(min[d], bytes, i);
+ encode(max[d], bytes, j);
+ }
+ }
+
+ /** encode the given value into the byte array at the defined offset */
+ private static void encode(long val, byte[] bytes, int offset) {
+ NumericUtils.longToSortableBytes(val, bytes, offset);
+ }
+
+ /**
+ * Get the min value for the given dimension
+ * @param dimension the dimension, always positive
+ * @return the decoded min value
+ */
+ public long getMin(int dimension) {
+ if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+ throw new IllegalArgumentException("dimension request (" + dimension +
+ ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+ }
+ return decodeMin(((BytesRef)fieldsData).bytes, dimension);
+ }
+
+ /**
+ * Get the max value for the given dimension
+ * @param dimension the dimension, always positive
+ * @return the decoded max value
+ */
+ public long getMax(int dimension) {
+ if (dimension < 0 || dimension >= type.pointDimensionCount()/2) {
+ throw new IllegalArgumentException("dimension request (" + dimension +
+ ") out of bounds for field (name=" + name + " dimensions=" + type.pointDimensionCount()/2 + "). ");
+ }
+ return decodeMax(((BytesRef)fieldsData).bytes, dimension);
+ }
+
+ /** decodes the min value (for the defined dimension) from the encoded input byte array */
+ static long decodeMin(byte[] b, int dimension) {
+ int offset = dimension*BYTES;
+ return NumericUtils.sortableBytesToLong(b, offset);
+ }
+
+ /** decodes the max value (for the defined dimension) from the encoded input byte array */
+ static long decodeMax(byte[] b, int dimension) {
+ int offset = b.length/2 + dimension*BYTES;
+ return NumericUtils.sortableBytesToLong(b, offset);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that intersect the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+ * @return query for matching intersecting ranges (overlap, within, or contains)
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newIntersectsQuery(String field, final long[] min, final long[] max) {
+ return newRelationQuery(field, min, max, QueryType.INTERSECTS);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that contain the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+ * @return query for matching ranges that contain the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newContainsQuery(String field, final long[] min, final long[] max) {
+ return newRelationQuery(field, min, max, QueryType.CONTAINS);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that are within the defined range.
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+ * @return query for matching ranges within the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newWithinQuery(String field, final long[] min, final long[] max) {
+ return newRelationQuery(field, min, max, QueryType.WITHIN);
+ }
+
+ /**
+ * Create a query for matching indexed ranges that cross the defined range.
+ * A CROSSES is defined as any set of ranges that are not disjoint and not wholly contained by
+ * the query. Effectively, its the complement of union(WITHIN, DISJOINT).
+ * @param field field name. must not be null.
+ * @param min array of min values. (accepts {@code Long.MIN_VALUE})
+ * @param max array of max values. (accepts {@code Long.MAX_VALUE})
+ * @return query for matching ranges within the defined range
+ * @throws IllegalArgumentException if {@code field} is null, {@code min} or {@code max} is invalid
+ */
+ public static Query newCrossesQuery(String field, final long[] min, final long[] max) {
+ return newRelationQuery(field, min, max, QueryType.CROSSES);
+ }
+
+ /** helper method for creating the desired relational query */
+ private static Query newRelationQuery(String field, final long[] min, final long[] max, QueryType relation) {
+ checkArgs(min, max);
+ return new RangeFieldQuery(field, encode(min, max), min.length, relation) {
+ @Override
+ protected String toString(byte[] ranges, int dimension) {
+ return LongRange.toString(ranges, dimension);
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append(" <");
+ sb.append(name);
+ sb.append(':');
+ byte[] b = ((BytesRef)fieldsData).bytes;
+ toString(b, 0);
+ for (int d=1; d<type.pointDimensionCount(); ++d) {
+ sb.append(' ');
+ toString(b, d);
+ }
+ sb.append('>');
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the String representation for the range at the given dimension
+ * @param ranges the encoded ranges, never null
+ * @param dimension the dimension of interest
+ * @return The string representation for the range at the provided dimension
+ */
+ private static String toString(byte[] ranges, int dimension) {
+ return "[" + Long.toString(decodeMin(ranges, dimension)) + " : "
+ + Long.toString(decodeMax(ranges, dimension)) + "]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/core/src/java/org/apache/lucene/document/RangeFieldQuery.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/document/RangeFieldQuery.java b/lucene/core/src/java/org/apache/lucene/document/RangeFieldQuery.java
new file mode 100644
index 0000000..10f10fa
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/document/RangeFieldQuery.java
@@ -0,0 +1,340 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.document;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.IntPredicate;
+import java.util.function.Predicate;
+
+import org.apache.lucene.index.FieldInfo;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.PointValues;
+import org.apache.lucene.index.PointValues.Relation;
+import org.apache.lucene.index.PointValues.IntersectVisitor;
+import org.apache.lucene.search.ConstantScoreScorer;
+import org.apache.lucene.search.ConstantScoreWeight;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.util.DocIdSetBuilder;
+import org.apache.lucene.util.StringHelper;
+
+/**
+ * Query class for searching {@code RangeField} types by a defined {@link Relation}.
+ */
+abstract class RangeFieldQuery extends Query {
+ /** field name */
+ final String field;
+ /** query relation
+ * intersects: {@code CELL_CROSSES_QUERY},
+ * contains: {@code CELL_CONTAINS_QUERY},
+ * within: {@code CELL_WITHIN_QUERY} */
+ final QueryType queryType;
+ /** number of dimensions - max 4 */
+ final int numDims;
+ /** ranges encoded as a sortable byte array */
+ final byte[] ranges;
+ /** number of bytes per dimension */
+ final int bytesPerDim;
+
+ /** Used by {@code RangeFieldQuery} to check how each internal or leaf node relates to the query. */
+ enum QueryType {
+ /** Use this for intersects queries. */
+ INTERSECTS,
+ /** Use this for within queries. */
+ WITHIN,
+ /** Use this for contains */
+ CONTAINS,
+ /** Use this for crosses queries */
+ CROSSES
+ }
+
+ /**
+ * Create a query for searching indexed ranges that match the provided relation.
+ * @param field field name. must not be null.
+ * @param ranges encoded range values; this is done by the {@code RangeField} implementation
+ * @param queryType the query relation
+ */
+ RangeFieldQuery(String field, final byte[] ranges, final int numDims, final QueryType queryType) {
+ checkArgs(field, ranges, numDims);
+ if (queryType == null) {
+ throw new IllegalArgumentException("Query type cannot be null");
+ }
+ this.field = field;
+ this.queryType = queryType;
+ this.numDims = numDims;
+ this.ranges = ranges;
+ this.bytesPerDim = ranges.length / (2*numDims);
+ }
+
+ /** check input arguments */
+ private static void checkArgs(String field, final byte[] ranges, final int numDims) {
+ if (field == null) {
+ throw new IllegalArgumentException("field must not be null");
+ }
+ if (numDims > 4) {
+ throw new IllegalArgumentException("dimension size cannot be greater than 4");
+ }
+ if (ranges == null || ranges.length == 0) {
+ throw new IllegalArgumentException("encoded ranges cannot be null or empty");
+ }
+ }
+
+ /** Check indexed field info against the provided query data. */
+ private void checkFieldInfo(FieldInfo fieldInfo) {
+ if (fieldInfo.getPointDimensionCount()/2 != numDims) {
+ throw new IllegalArgumentException("field=\"" + field + "\" was indexed with numDims="
+ + fieldInfo.getPointDimensionCount()/2 + " but this query has numDims=" + numDims);
+ }
+ }
+
+ @Override
+ public final Weight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
+ return new ConstantScoreWeight(this, boost) {
+ final RangeFieldComparator target = new RangeFieldComparator();
+ private DocIdSet buildMatchingDocIdSet(LeafReader reader, PointValues values) throws IOException {
+ DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
+ values.intersect(
+ new IntersectVisitor() {
+ DocIdSetBuilder.BulkAdder adder;
+ @Override
+ public void grow(int count) {
+ adder = result.grow(count);
+ }
+ @Override
+ public void visit(int docID) throws IOException {
+ adder.add(docID);
+ }
+ @Override
+ public void visit(int docID, byte[] leaf) throws IOException {
+ if (target.matches(leaf)) {
+ adder.add(docID);
+ }
+ }
+ @Override
+ public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
+ byte[] node = getInternalRange(minPackedValue, maxPackedValue);
+ // compute range relation for BKD traversal
+ if (target.intersects(node) == false) {
+ return Relation.CELL_OUTSIDE_QUERY;
+ } else if (target.within(node)) {
+ // target within cell; continue traversing:
+ return Relation.CELL_CROSSES_QUERY;
+ } else if (target.contains(node)) {
+ // target contains cell; add iff queryType is not a CONTAINS or CROSSES query:
+ return (queryType == QueryType.CONTAINS || queryType == QueryType.CROSSES) ?
+ Relation.CELL_OUTSIDE_QUERY : Relation.CELL_INSIDE_QUERY;
+ }
+ // target intersects cell; continue traversing:
+ return Relation.CELL_CROSSES_QUERY;
+ }
+ });
+ return result.build();
+ }
+
+ @Override
+ public Scorer scorer(LeafReaderContext context) throws IOException {
+ LeafReader reader = context.reader();
+ PointValues values = reader.getPointValues(field);
+ if (values == null) {
+ // no docs in this segment indexed any ranges
+ return null;
+ }
+ FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
+ if (fieldInfo == null) {
+ // no docs in this segment indexed this field
+ return null;
+ }
+ checkFieldInfo(fieldInfo);
+ boolean allDocsMatch = true;
+ if (values.getDocCount() == reader.maxDoc()) {
+ // if query crosses, docs need to be further scrutinized
+ byte[] range = getInternalRange(values.getMinPackedValue(), values.getMaxPackedValue());
+ // if the internal node is not equal and not contained by the query, all docs do not match
+ if (queryType == QueryType.CROSSES || (!Arrays.equals(ranges, range)
+ && (target.contains(range) == false || queryType != QueryType.WITHIN))) {
+ allDocsMatch = false;
+ }
+ } else {
+ allDocsMatch = false;
+ }
+
+ DocIdSetIterator iterator = allDocsMatch == true ?
+ DocIdSetIterator.all(reader.maxDoc()) : buildMatchingDocIdSet(reader, values).iterator();
+ return new ConstantScoreScorer(this, score(), iterator);
+ }
+
+ /** get an encoded byte representation of the internal node; this is
+ * the lower half of the min array and the upper half of the max array */
+ private byte[] getInternalRange(byte[] min, byte[] max) {
+ byte[] range = new byte[min.length];
+ final int dimSize = numDims * bytesPerDim;
+ System.arraycopy(min, 0, range, 0, dimSize);
+ System.arraycopy(max, dimSize, range, dimSize, dimSize);
+ return range;
+ }
+ };
+ }
+
+ /**
+ * RangeFieldComparator class provides the core comparison logic for accepting or rejecting indexed
+ * {@code RangeField} types based on the defined query range and relation.
+ */
+ class RangeFieldComparator {
+ final Predicate<byte[]> predicate;
+
+ /** constructs the comparator based on the query type */
+ RangeFieldComparator() {
+ switch (queryType) {
+ case INTERSECTS:
+ predicate = this::intersects;
+ break;
+ case WITHIN:
+ predicate = this::contains;
+ break;
+ case CONTAINS:
+ predicate = this::within;
+ break;
+ case CROSSES:
+ // crosses first checks intersection (disjoint automatic fails),
+ // then ensures the query doesn't wholly contain the leaf:
+ predicate = (byte[] leaf) -> this.intersects(leaf)
+ && this.contains(leaf) == false;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid queryType [" + queryType + "] found.");
+ }
+ }
+
+ /** determines if the candidate range matches the query request */
+ private boolean matches(final byte[] candidate) {
+ return (Arrays.equals(ranges, candidate) && queryType != QueryType.CROSSES)
+ || predicate.test(candidate);
+ }
+
+ /** check if query intersects candidate range */
+ private boolean intersects(final byte[] candidate) {
+ return relate((int d) -> compareMinMax(candidate, d) > 0 || compareMaxMin(candidate, d) < 0);
+ }
+
+ /** check if query is within candidate range */
+ private boolean within(final byte[] candidate) {
+ return relate((int d) -> compareMinMin(candidate, d) < 0 || compareMaxMax(candidate, d) > 0);
+ }
+
+ /** check if query contains candidate range */
+ private boolean contains(final byte[] candidate) {
+ return relate((int d) -> compareMinMin(candidate, d) > 0 || compareMaxMax(candidate, d) < 0);
+ }
+
+ /** internal method used by each relation method to test range relation logic */
+ private boolean relate(IntPredicate predicate) {
+ for (int d=0; d<numDims; ++d) {
+ if (predicate.test(d)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** compare the encoded min value (for the defined query dimension) with the encoded min value in the byte array */
+ private int compareMinMin(byte[] b, int dimension) {
+ // convert dimension to offset:
+ dimension *= bytesPerDim;
+ return StringHelper.compare(bytesPerDim, ranges, dimension, b, dimension);
+ }
+
+ /** compare the encoded min value (for the defined query dimension) with the encoded max value in the byte array */
+ private int compareMinMax(byte[] b, int dimension) {
+ // convert dimension to offset:
+ dimension *= bytesPerDim;
+ return StringHelper.compare(bytesPerDim, ranges, dimension, b, numDims * bytesPerDim + dimension);
+ }
+
+ /** compare the encoded max value (for the defined query dimension) with the encoded min value in the byte array */
+ private int compareMaxMin(byte[] b, int dimension) {
+ // convert dimension to offset:
+ dimension *= bytesPerDim;
+ return StringHelper.compare(bytesPerDim, ranges, numDims * bytesPerDim + dimension, b, dimension);
+ }
+
+ /** compare the encoded max value (for the defined query dimension) with the encoded max value in the byte array */
+ private int compareMaxMax(byte[] b, int dimension) {
+ // convert dimension to max offset:
+ dimension = numDims * bytesPerDim + dimension * bytesPerDim;
+ return StringHelper.compare(bytesPerDim, ranges, dimension, b, dimension);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = classHash();
+ hash = 31 * hash + field.hashCode();
+ hash = 31 * hash + numDims;
+ hash = 31 * hash + queryType.hashCode();
+ hash = 31 * hash + Arrays.hashCode(ranges);
+
+ return hash;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ return sameClassAs(o) &&
+ equalsTo(getClass().cast(o));
+ }
+
+ protected boolean equalsTo(RangeFieldQuery other) {
+ return Objects.equals(field, other.field) &&
+ numDims == other.numDims &&
+ Arrays.equals(ranges, other.ranges) &&
+ other.queryType == queryType;
+ }
+
+ @Override
+ public String toString(String field) {
+ StringBuilder sb = new StringBuilder();
+ if (this.field.equals(field) == false) {
+ sb.append(this.field);
+ sb.append(':');
+ }
+ sb.append("<ranges:");
+ sb.append(toString(ranges, 0));
+ for (int d=1; d<numDims; ++d) {
+ sb.append(' ');
+ sb.append(toString(ranges, d));
+ }
+ sb.append('>');
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns a string of a single value in a human-readable format for debugging.
+ * This is used by {@link #toString()}.
+ *
+ * @param dimension dimension of the particular value
+ * @param ranges encoded ranges, never null
+ * @return human readable value for debugging
+ */
+ protected abstract String toString(byte[] ranges, int dimension);
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/core/src/java/org/apache/lucene/index/PointValues.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/PointValues.java b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
index dab9140..186dbd5 100644
--- a/lucene/core/src/java/org/apache/lucene/index/PointValues.java
+++ b/lucene/core/src/java/org/apache/lucene/index/PointValues.java
@@ -46,7 +46,7 @@ import org.apache.lucene.util.bkd.BKDWriter;
* <tr><td>{@code double}</td><td>{@link DoublePoint}</td></tr>
* <tr><td>{@code byte[]}</td><td>{@link BinaryPoint}</td></tr>
* <tr><td>{@link BigInteger}</td><td><a href="{@docRoot}/../sandbox/org/apache/lucene/document/BigIntegerPoint.html">BigIntegerPoint</a>*</td></tr>
- * <tr><td>{@link InetAddress}</td><td><a href="{@docRoot}/../sandbox/org/apache/lucene/document/InetAddressPoint.html">InetAddressPoint</a>*</td></tr>
+ * <tr><td>{@link InetAddress}</td><td><a href="{@docRoot}/../misc/org/apache/lucene/document/InetAddressPoint.html">InetAddressPoint</a>*</td></tr>
* </table>
* * in the <i>lucene-sandbox</i> jar<br>
* <p>
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/core/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java
new file mode 100644
index 0000000..49ca710
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestDoubleRangeFieldQueries.java
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.search;
+
+import java.util.Arrays;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.DoubleRange;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Random testing for RangeFieldQueries.
+ */
+public class TestDoubleRangeFieldQueries extends BaseRangeFieldQueryTestCase {
+ private static final String FIELD_NAME = "doubleRangeField";
+
+ private double nextDoubleInternal() {
+ if (rarely()) {
+ return random().nextBoolean() ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
+ }
+ double max = Double.MAX_VALUE / 2;
+ return (max + max) * random().nextDouble() - max;
+ }
+
+ @Override
+ protected Range nextRange(int dimensions) throws Exception {
+ double[] min = new double[dimensions];
+ double[] max = new double[dimensions];
+
+ double minV, maxV;
+ for (int d=0; d<dimensions; ++d) {
+ minV = nextDoubleInternal();
+ maxV = nextDoubleInternal();
+ min[d] = Math.min(minV, maxV);
+ max[d] = Math.max(minV, maxV);
+ }
+ return new DoubleTestRange(min, max);
+ }
+
+ @Override
+ protected DoubleRange newRangeField(Range r) {
+ return new DoubleRange(FIELD_NAME, ((DoubleTestRange)r).min, ((DoubleTestRange)r).max);
+ }
+
+ @Override
+ protected Query newIntersectsQuery(Range r) {
+ return DoubleRange.newIntersectsQuery(FIELD_NAME, ((DoubleTestRange)r).min, ((DoubleTestRange)r).max);
+ }
+
+ @Override
+ protected Query newContainsQuery(Range r) {
+ return DoubleRange.newContainsQuery(FIELD_NAME, ((DoubleTestRange)r).min, ((DoubleTestRange)r).max);
+ }
+
+ @Override
+ protected Query newWithinQuery(Range r) {
+ return DoubleRange.newWithinQuery(FIELD_NAME, ((DoubleTestRange)r).min, ((DoubleTestRange)r).max);
+ }
+
+ @Override
+ protected Query newCrossesQuery(Range r) {
+ return DoubleRange.newCrossesQuery(FIELD_NAME, ((DoubleTestRange)r).min, ((DoubleTestRange)r).max);
+ }
+
+ /** Basic test */
+ public void testBasics() throws Exception {
+ Directory dir = newDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+
+ // intersects (within)
+ Document document = new Document();
+ document.add(new DoubleRange(FIELD_NAME, new double[] {-10.0, -10.0}, new double[] {9.1, 10.1}));
+ writer.addDocument(document);
+
+ // intersects (crosses)
+ document = new Document();
+ document.add(new DoubleRange(FIELD_NAME, new double[] {10.0, -10.0}, new double[] {20.0, 10.0}));
+ writer.addDocument(document);
+
+ // intersects (contains, crosses)
+ document = new Document();
+ document.add(new DoubleRange(FIELD_NAME, new double[] {-20.0, -20.0}, new double[] {30.0, 30.1}));
+ writer.addDocument(document);
+
+ // intersects (crosses)
+ document = new Document();
+ document.add(new DoubleRange(FIELD_NAME, new double[] {-11.1, -11.2}, new double[] {1.23, 11.5}));
+ writer.addDocument(document);
+
+ // intersects (crosses)
+ document = new Document();
+ document.add(new DoubleRange(FIELD_NAME, new double[] {12.33, 1.2}, new double[] {15.1, 29.9}));
+ writer.addDocument(document);
+
+ // disjoint
+ document = new Document();
+ document.add(new DoubleRange(FIELD_NAME, new double[] {-122.33, 1.2}, new double[] {-115.1, 29.9}));
+ writer.addDocument(document);
+
+ // intersects (crosses)
+ document = new Document();
+ document.add(new DoubleRange(FIELD_NAME, new double[] {Double.NEGATIVE_INFINITY, 1.2}, new double[] {-11.0, 29.9}));
+ writer.addDocument(document);
+
+ // equal (within, contains, intersects)
+ document = new Document();
+ document.add(new DoubleRange(FIELD_NAME, new double[] {-11, -15}, new double[] {15, 20}));
+ writer.addDocument(document);
+
+ // search
+ IndexReader reader = writer.getReader();
+ IndexSearcher searcher = newSearcher(reader);
+ assertEquals(7, searcher.count(DoubleRange.newIntersectsQuery(FIELD_NAME,
+ new double[] {-11.0, -15.0}, new double[] {15.0, 20.0})));
+ assertEquals(2, searcher.count(DoubleRange.newWithinQuery(FIELD_NAME,
+ new double[] {-11.0, -15.0}, new double[] {15.0, 20.0})));
+ assertEquals(2, searcher.count(DoubleRange.newContainsQuery(FIELD_NAME,
+ new double[] {-11.0, -15.0}, new double[] {15.0, 20.0})));
+ assertEquals(5, searcher.count(DoubleRange.newCrossesQuery(FIELD_NAME,
+ new double[] {-11.0, -15.0}, new double[] {15.0, 20.0})));
+
+ reader.close();
+ writer.close();
+ dir.close();
+ }
+
+ /** DoubleRange test class implementation - use to validate DoubleRange */
+ private class DoubleTestRange extends Range {
+ double[] min;
+ double[] max;
+
+ DoubleTestRange(double[] min, double[] max) {
+ assert min != null && max != null && min.length > 0 && max.length > 0
+ : "test box: min/max cannot be null or empty";
+ assert min.length == max.length : "test box: min/max length do not agree";
+ this.min = min;
+ this.max = max;
+ }
+
+ @Override
+ protected int numDimensions() {
+ return min.length;
+ }
+
+ @Override
+ protected Double getMin(int dim) {
+ return min[dim];
+ }
+
+ @Override
+ protected void setMin(int dim, Object val) {
+ double v = (Double)val;
+ if (min[dim] < v) {
+ max[dim] = v;
+ } else {
+ min[dim] = v;
+ }
+ }
+
+ @Override
+ protected Double getMax(int dim) {
+ return max[dim];
+ }
+
+ @Override
+ protected void setMax(int dim, Object val) {
+ double v = (Double)val;
+ if (max[dim] > v) {
+ min[dim] = v;
+ } else {
+ max[dim] = v;
+ }
+ }
+
+ @Override
+ protected boolean isEqual(Range other) {
+ DoubleTestRange o = (DoubleTestRange)other;
+ return Arrays.equals(min, o.min) && Arrays.equals(max, o.max);
+ }
+
+ @Override
+ protected boolean isDisjoint(Range o) {
+ DoubleTestRange other = (DoubleTestRange)o;
+ for (int d=0; d<this.min.length; ++d) {
+ if (this.min[d] > other.max[d] || this.max[d] < other.min[d]) {
+ // disjoint:
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean isWithin(Range o) {
+ DoubleTestRange other = (DoubleTestRange)o;
+ for (int d=0; d<this.min.length; ++d) {
+ if ((this.min[d] >= other.min[d] && this.max[d] <= other.max[d]) == false) {
+ // not within:
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean contains(Range o) {
+ DoubleTestRange other = (DoubleTestRange) o;
+ for (int d=0; d<this.min.length; ++d) {
+ if ((this.min[d] <= other.min[d] && this.max[d] >= other.max[d]) == false) {
+ // not contains:
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("Box(");
+ b.append(min[0]);
+ b.append(" TO ");
+ b.append(max[0]);
+ for (int d=1; d<min.length; ++d) {
+ b.append(", ");
+ b.append(min[d]);
+ b.append(" TO ");
+ b.append(max[d]);
+ }
+ b.append(")");
+
+ return b.toString();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d34d81f9/lucene/core/src/test/org/apache/lucene/search/TestFloatRangeFieldQueries.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestFloatRangeFieldQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestFloatRangeFieldQueries.java
new file mode 100644
index 0000000..6dc5907
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestFloatRangeFieldQueries.java
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.search;
+
+import java.util.Arrays;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FloatRange;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Random testing for FloatRange Queries.
+ */
+public class TestFloatRangeFieldQueries extends BaseRangeFieldQueryTestCase {
+ private static final String FIELD_NAME = "floatRangeField";
+
+ private float nextFloatInternal() {
+ if (rarely()) {
+ return random().nextBoolean() ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
+ }
+ float max = Float.MAX_VALUE / 2;
+ return (max + max) * random().nextFloat() - max;
+ }
+
+ @Override
+ protected Range nextRange(int dimensions) throws Exception {
+ float[] min = new float[dimensions];
+ float[] max = new float[dimensions];
+
+ float minV, maxV;
+ for (int d=0; d<dimensions; ++d) {
+ minV = nextFloatInternal();
+ maxV = nextFloatInternal();
+ min[d] = Math.min(minV, maxV);
+ max[d] = Math.max(minV, maxV);
+ }
+ return new FloatTestRange(min, max);
+ }
+
+ @Override
+ protected FloatRange newRangeField(Range r) {
+ return new FloatRange(FIELD_NAME, ((FloatTestRange)r).min, ((FloatTestRange)r).max);
+ }
+
+ @Override
+ protected Query newIntersectsQuery(Range r) {
+ return FloatRange.newIntersectsQuery(FIELD_NAME, ((FloatTestRange)r).min, ((FloatTestRange)r).max);
+ }
+
+ @Override
+ protected Query newContainsQuery(Range r) {
+ return FloatRange.newContainsQuery(FIELD_NAME, ((FloatTestRange)r).min, ((FloatTestRange)r).max);
+ }
+
+ @Override
+ protected Query newWithinQuery(Range r) {
+ return FloatRange.newWithinQuery(FIELD_NAME, ((FloatTestRange)r).min, ((FloatTestRange)r).max);
+ }
+
+ @Override
+ protected Query newCrossesQuery(Range r) {
+ return FloatRange.newCrossesQuery(FIELD_NAME, ((FloatTestRange)r).min, ((FloatTestRange)r).max);
+ }
+
+ /** Basic test */
+ public void testBasics() throws Exception {
+ Directory dir = newDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(random(), dir);
+
+ // intersects (within)
+ Document document = new Document();
+ document.add(new FloatRange(FIELD_NAME, new float[] {-10.0f, -10.0f}, new float[] {9.1f, 10.1f}));
+ writer.addDocument(document);
+
+ // intersects (crosses)
+ document = new Document();
+ document.add(new FloatRange(FIELD_NAME, new float[] {10.0f, -10.0f}, new float[] {20.0f, 10.0f}));
+ writer.addDocument(document);
+
+ // intersects (contains, crosses)
+ document = new Document();
+ document.add(new FloatRange(FIELD_NAME, new float[] {-20.0f, -20.0f}, new float[] {30.0f, 30.1f}));
+ writer.addDocument(document);
+
+ // intersects (crosses)
+ document = new Document();
+ document.add(new FloatRange(FIELD_NAME, new float[] {-11.1f, -11.2f}, new float[] {1.23f, 11.5f}));
+ writer.addDocument(document);
+
+ // intersects (crosses)
+ document = new Document();
+ document.add(new FloatRange(FIELD_NAME, new float[] {12.33f, 1.2f}, new float[] {15.1f, 29.9f}));
+ writer.addDocument(document);
+
+ // disjoint
+ document = new Document();
+ document.add(new FloatRange(FIELD_NAME, new float[] {-122.33f, 1.2f}, new float[] {-115.1f, 29.9f}));
+ writer.addDocument(document);
+
+ // intersects (crosses)
+ document = new Document();
+ document.add(new FloatRange(FIELD_NAME, new float[] {Float.NEGATIVE_INFINITY, 1.2f}, new float[] {-11.0f, 29.9f}));
+ writer.addDocument(document);
+
+ // equal (within, contains, intersects)
+ document = new Document();
+ document.add(new FloatRange(FIELD_NAME, new float[] {-11f, -15f}, new float[] {15f, 20f}));
+ writer.addDocument(document);
+
+ // search
+ IndexReader reader = writer.getReader();
+ IndexSearcher searcher = newSearcher(reader);
+ assertEquals(7, searcher.count(FloatRange.newIntersectsQuery(FIELD_NAME,
+ new float[] {-11.0f, -15.0f}, new float[] {15.0f, 20.0f})));
+ assertEquals(2, searcher.count(FloatRange.newWithinQuery(FIELD_NAME,
+ new float[] {-11.0f, -15.0f}, new float[] {15.0f, 20.0f})));
+ assertEquals(2, searcher.count(FloatRange.newContainsQuery(FIELD_NAME,
+ new float[] {-11.0f, -15.0f}, new float[] {15.0f, 20.0f})));
+ assertEquals(5, searcher.count(FloatRange.newCrossesQuery(FIELD_NAME,
+ new float[] {-11.0f, -15.0f}, new float[] {15.0f, 20.0f})));
+
+ reader.close();
+ writer.close();
+ dir.close();
+ }
+
+ /** FloatRange test class implementation - use to validate FloatRange */
+ private class FloatTestRange extends Range {
+ float[] min;
+ float[] max;
+
+ FloatTestRange(float[] min, float[] max) {
+ assert min != null && max != null && min.length > 0 && max.length > 0
+ : "test box: min/max cannot be null or empty";
+ assert min.length == max.length : "test box: min/max length do not agree";
+ this.min = min;
+ this.max = max;
+ }
+
+ @Override
+ protected int numDimensions() {
+ return min.length;
+ }
+
+ @Override
+ protected Float getMin(int dim) {
+ return min[dim];
+ }
+
+ @Override
+ protected void setMin(int dim, Object val) {
+ float v = (Float)val;
+ if (min[dim] < v) {
+ max[dim] = v;
+ } else {
+ min[dim] = v;
+ }
+ }
+
+ @Override
+ protected Float getMax(int dim) {
+ return max[dim];
+ }
+
+ @Override
+ protected void setMax(int dim, Object val) {
+ float v = (Float)val;
+ if (max[dim] > v) {
+ min[dim] = v;
+ } else {
+ max[dim] = v;
+ }
+ }
+
+ @Override
+ protected boolean isEqual(Range other) {
+ FloatTestRange o = (FloatTestRange)other;
+ return Arrays.equals(min, o.min) && Arrays.equals(max, o.max);
+ }
+
+ @Override
+ protected boolean isDisjoint(Range o) {
+ FloatTestRange other = (FloatTestRange)o;
+ for (int d=0; d<this.min.length; ++d) {
+ if (this.min[d] > other.max[d] || this.max[d] < other.min[d]) {
+ // disjoint:
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean isWithin(Range o) {
+ FloatTestRange other = (FloatTestRange)o;
+ for (int d=0; d<this.min.length; ++d) {
+ if ((this.min[d] >= other.min[d] && this.max[d] <= other.max[d]) == false) {
+ // not within:
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean contains(Range o) {
+ FloatTestRange other = (FloatTestRange) o;
+ for (int d=0; d<this.min.length; ++d) {
+ if ((this.min[d] <= other.min[d] && this.max[d] >= other.max[d]) == false) {
+ // not contains:
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("Box(");
+ b.append(min[0]);
+ b.append(" TO ");
+ b.append(max[0]);
+ for (int d=1; d<min.length; ++d) {
+ b.append(", ");
+ b.append(min[d]);
+ b.append(" TO ");
+ b.append(max[d]);
+ }
+ b.append(")");
+
+ return b.toString();
+ }
+ }
+}