You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2014/12/14 05:33:01 UTC
svn commit: r1645381 - in /lucene/dev/trunk:
lucene/spatial/src/java/org/apache/lucene/spatial/
lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/
lucene/spatial/src/test/org/apache/lucene/spatial/prefix/
lucene/spatial/src/test/org/apache/...
Author: dsmiley
Date: Sun Dec 14 04:33:01 2014
New Revision: 1645381
URL: http://svn.apache.org/r1645381
Log:
LUCENE-5648: spatial NumberRangePrefixTree refactorings, mostly:
* rename NRPT.LevelledValue -> UnitNRShape, NRPT.NRShape -> SpanUnitsNRShape, both subclass new NRPT.NRShape marker class; make all 3 public and use these types in method signatures as appropriate.
* remove NRPT pass-through convenience methods on NRPTStrategy (not needed; maintenance burden)
* re-order some factory/parsing methods in NRPT to keep together at the top
* UnitNRShape implements Comparable, and both NRShape implements Cloneable
* improve randomized test to pick better random calendars
* more docs
Modified:
lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/NumberRangePrefixTreeStrategy.java
lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTree.java
lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java
lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/DateNRStrategyTest.java
lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTreeTest.java
lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/DateRangeField.java
Modified: lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/NumberRangePrefixTreeStrategy.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/NumberRangePrefixTreeStrategy.java?rev=1645381&r1=1645380&r2=1645381&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/NumberRangePrefixTreeStrategy.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/NumberRangePrefixTreeStrategy.java Sun Dec 14 04:33:01 2014
@@ -25,10 +25,11 @@ import org.apache.lucene.queries.functio
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree;
-import java.text.ParseException;
-
/** A PrefixTree based on Number/Date ranges. This isn't very "spatial" on the surface (to the user) but
- * it's implemented using spatial so that's why it's here extending a SpatialStrategy.
+ * it's implemented using spatial so that's why it's here extending a SpatialStrategy. When using this class, you will
+ * use various utility methods on the prefix tree implementation to convert objects/strings to/from shapes.
+ *
+ * To use with dates, pass in {@link org.apache.lucene.spatial.prefix.tree.DateRangePrefixTree}.
*
* @lucene.experimental
*/
@@ -55,22 +56,6 @@ public class NumberRangePrefixTreeStrate
return new Field[]{field};
}
- /** For a Date based tree, pass in a Calendar, with unspecified fields marked as cleared.
- * See {@link NumberRangePrefixTree#toShape(Object)}. */
- public Shape toShape(Object value) {
- return getGrid().toShape(value);
- }
-
- /** See {@link NumberRangePrefixTree#toRangeShape(Shape, Shape)}. */
- public Shape toRangeShape(Shape min, Shape max) {
- return getGrid().toRangeShape(min, max);
- }
-
- /** See {@link NumberRangePrefixTree#parseShape(String)}. */
- public Shape parseShape(String str) throws ParseException {
- return getGrid().parseShape(str);
- }
-
/** Unsupported. */
@Override
public ValueSource makeDistanceValueSource(Point queryPoint, double multiplier) {
Modified: lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTree.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTree.java?rev=1645381&r1=1645380&r2=1645381&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTree.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTree.java Sun Dec 14 04:33:01 2014
@@ -17,8 +17,6 @@ package org.apache.lucene.spatial.prefix
* limitations under the License.
*/
-import com.spatial4j.core.shape.Shape;
-
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
@@ -27,11 +25,13 @@ import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
+import com.spatial4j.core.shape.Shape;
+
/**
* A PrefixTree for date ranges in which the levels of the tree occur at natural periods of time (e.g. years,
* months, ...). You pass in {@link Calendar} objects with the desired fields set and the unspecified
- * fields unset, which conveys the precision. The implementation tries to be generic to the Calendar
- * abstraction, making some optimizations when a Gregorian is used, but no others have been tested.
+ * fields unset, which conveys the precision. The implementation makes some optimization assumptions about a
+ * {@link java.util.GregorianCalendar}; others could probably be supported easily.
* <p/>
* Warning: If you construct a Calendar and then get something from the object like a field (e.g. year) or
* milliseconds, then every field is fully set by side-effect. So after setting the fields, pass it to this
@@ -42,7 +42,7 @@ public class DateRangePrefixTree extends
/*
WARNING java.util.Calendar is tricky to work with:
- * If you "get" any field value, every fields because "set". This can introduce a Heisenbug effect,
+ * If you "get" any field value, every field becomes "set". This can introduce a Heisenbug effect,
when in a debugger in some cases. Fortunately, Calendar.toString() doesn't apply.
* Beware Calendar underflow of the underlying long. If you create a Calendar from LONG.MIN_VALUE, and clear
a field, it will underflow and appear close to LONG.MAX_VALUE (BC to AD).
@@ -94,10 +94,10 @@ public class DateRangePrefixTree extends
public static final DateRangePrefixTree INSTANCE = new DateRangePrefixTree();
- private final LevelledValue minLV, maxLV;
- private final LevelledValue gregorianChangeDateLV;
+ private final UnitNRShape minLV, maxLV;
+ private final UnitNRShape gregorianChangeDateLV;
- private DateRangePrefixTree() {
+ protected DateRangePrefixTree() {
super(new int[]{//sublevels by level
NUM_MYEARS,
1000,//1 thousand thousand-years in a million years
@@ -109,27 +109,27 @@ public class DateRangePrefixTree extends
calFieldLen(Calendar.SECOND),
calFieldLen(Calendar.MILLISECOND),
});
- maxLV = (LevelledValue) toShape((Calendar)MAXCAL.clone());
- minLV = (LevelledValue) toShape((Calendar)MINCAL.clone());
+ maxLV = toShape((Calendar)MAXCAL.clone());
+ minLV = toShape((Calendar)MINCAL.clone());
if (MAXCAL instanceof GregorianCalendar) {
- //TODO this should be a configurable param by passing a Calendar surving as a template.
+ //TODO this should be a configurable param by passing a Calendar serving as a template.
GregorianCalendar gCal = (GregorianCalendar)MAXCAL;
- gregorianChangeDateLV = (LevelledValue) toShape(gCal.getGregorianChange());
+ gregorianChangeDateLV = toUnitShape(gCal.getGregorianChange());
} else {
gregorianChangeDateLV = null;
}
}
@Override
- protected int getNumSubCells(LevelledValue lv) {
- int cmp = comparePrefixLV(lv, maxLV);
+ public int getNumSubCells(UnitNRShape lv) {
+ int cmp = comparePrefix(lv, maxLV);
assert cmp <= 0;
if (cmp == 0)//edge case (literally!)
return maxLV.getValAtLevel(lv.getLevel()+1);
// if using GregorianCalendar and we're after the "Gregorian change date" then we'll compute
// the sub-cells ourselves more efficiently without the need to construct a Calendar.
- cmp = gregorianChangeDateLV != null ? comparePrefixLV(lv, gregorianChangeDateLV) : -1;
+ cmp = gregorianChangeDateLV != null ? comparePrefix(lv, gregorianChangeDateLV) : -1;
//TODO consider also doing fast-path if field is <= hours even if before greg change date
if (cmp >= 0) {
int result = fastSubCells(lv);
@@ -140,7 +140,7 @@ public class DateRangePrefixTree extends
}
}
- private int fastSubCells(LevelledValue lv) {
+ private int fastSubCells(UnitNRShape lv) {
if (lv.getLevel() == yearLevel+1) {//month
switch (lv.getValAtLevel(lv.getLevel())) {
case Calendar.SEPTEMBER:
@@ -166,12 +166,12 @@ public class DateRangePrefixTree extends
}
}
- private int slowSubCells(LevelledValue lv) {
+ private int slowSubCells(UnitNRShape lv) {
int field = FIELD_BY_LEVEL[lv.getLevel()+1];
//short-circuit optimization (GregorianCalendar assumptions)
if (field == -1 || field == Calendar.YEAR || field >= Calendar.HOUR_OF_DAY)//TODO make configurable
return super.getNumSubCells(lv);
- Calendar cal = toCalendarLV(lv);//somewhat heavyweight op; ideally should be stored on LevelledValue somehow
+ Calendar cal = toCalendar(lv);//somewhat heavyweight op; ideally should be stored on UnitNRShape somehow
return cal.getActualMaximum(field) - cal.getActualMinimum(field) + 1;
}
@@ -182,6 +182,21 @@ public class DateRangePrefixTree extends
}
/** Calendar utility method:
+ * Returns the spatial prefix tree level for the corresponding {@link java.util.Calendar} field, such as
+ * {@link java.util.Calendar#YEAR}. If there's no match, the next greatest level is returned as a negative value.
+ */
+ public int getTreeLevelForCalendarField(int calField) {
+ for (int i = yearLevel; i < FIELD_BY_LEVEL.length; i++) {
+ if (FIELD_BY_LEVEL[i] == calField) {
+ return i;
+ } else if (FIELD_BY_LEVEL[i] > calField) {
+ return -1 * i;
+ }
+ }
+ throw new IllegalArgumentException("Bad calendar field?: " + calField);
+ }
+
+ /** Calendar utility method:
* Gets the Calendar field code of the last field that is set prior to an unset field. It only
* examines fields relevant to the prefix tree. If no fields are set, it returns -1. */
public int getCalPrecisionField(Calendar cal) {
@@ -214,7 +229,7 @@ public class DateRangePrefixTree extends
* result in a {@link java.lang.IllegalArgumentException}.
*/
@Override
- public Shape toShape(Object value) {
+ public UnitNRShape toUnitShape(Object value) {
if (value instanceof Calendar) {
return toShape((Calendar) value);
} else if (value instanceof Date) {
@@ -227,7 +242,7 @@ public class DateRangePrefixTree extends
/** Converts the Calendar into a Shape.
* The isSet() state of the Calendar is re-instated when done. */
- public Shape toShape(Calendar cal) {
+ public UnitNRShape toShape(Calendar cal) {
// Convert a Calendar into a stack of cell numbers
final int calPrecField = getCalPrecisionField(cal);//must call first; getters set all fields
try {
@@ -256,19 +271,21 @@ public class DateRangePrefixTree extends
}
}
- public Calendar toCalendar(Shape shape) {
- if (shape instanceof LevelledValue)
- return toCalendarLV((LevelledValue) shape);
- throw new IllegalArgumentException("Can't be converted to Calendar: "+shape);
+ /** Calls {@link #toCalendar(org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape)}. */
+ @Override
+ public Object toObject(UnitNRShape shape) {
+ return toCalendar(shape);
}
- private Calendar toCalendarLV(LevelledValue lv) {
+ /** Converts the {@link org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape} shape to a
+ * corresponding Calendar that is cleared below its level. */
+ public Calendar toCalendar(UnitNRShape lv) {
if (lv.getLevel() == 0)
return newCal();
- if (comparePrefixLV(lv, minLV) <= 0) {//shouldn't typically happen; sometimes in a debugger
+ if (comparePrefix(lv, minLV) <= 0) {//shouldn't typically happen; sometimes in a debugger
return (Calendar) MINCAL.clone();//full precision; truncation would cause underflow
}
- assert comparePrefixLV(lv, maxLV) <= 0;
+ assert comparePrefix(lv, maxLV) <= 0;
Calendar cal = newCal();
int yearAdj = lv.getValAtLevel(1) * 1_000_000;
@@ -294,23 +311,23 @@ public class DateRangePrefixTree extends
}
@Override
- protected String toStringLV(LevelledValue lv) {
- return toString(toCalendarLV(lv));
+ protected String toString(UnitNRShape lv) {
+ return toString(toCalendar(lv));
}
/** Calendar utility method:
- * Converts to calendar to ISO-8601, to include proper BC handling (1BC is "0000", 2BC is "-0001", etc.);
+ * Formats the calendar to ISO-8601 format, to include proper BC handling (1BC is "0000", 2BC is "-0001", etc.);
* and WITHOUT a trailing 'Z'.
* A fully cleared calendar will yield the string "*".
* The isSet() state of the Calendar is re-instated when done. */
@SuppressWarnings("fallthrough")
- public String toString(Calendar cal) {
+ public String toString(Calendar cal) {
final int calPrecField = getCalPrecisionField(cal);//must call first; getters set all fields
if (calPrecField == -1)
return "*";
try {
- //TODO not fully optimized because I only expect this to be used in tests / debugging.
- // Borrow code from Solr DateUtil, and have it reference this back?
+ //TODO not fully optimized; but it's at least not used in 'search'.
+ //TODO maybe borrow code from Solr DateUtil (put in Lucene util somewhere), and have it reference this back?
String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS";
int ptnLen = 0;
switch (calPrecField) {//switch fall-through is deliberate
@@ -352,8 +369,8 @@ public class DateRangePrefixTree extends
}
@Override
- protected LevelledValue parseShapeLV(String str) throws ParseException {
- return (LevelledValue) toShape(parseCalendar(str));
+ protected UnitNRShape parseUnitShape(String str) throws ParseException {
+ return toShape(parseCalendar(str));
}
/** Calendar utility method:
Modified: lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java?rev=1645381&r1=1645380&r2=1645381&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/NumberRangePrefixTree.java Sun Dec 14 04:33:01 2014
@@ -17,6 +17,8 @@ package org.apache.lucene.spatial.prefix
* limitations under the License.
*/
+import java.text.ParseException;
+
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.shape.Point;
@@ -27,11 +29,28 @@ import com.spatial4j.core.shape.impl.Rec
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.StringHelper;
-import java.text.ParseException;
-
/**
- * A special SpatialPrefixTree for single-dimensional number ranges of integral values. It's based
- * on a stack of integers, and thus it's not limited to a long.
+ * A SpatialPrefixTree for single-dimensional numbers and number ranges of fixed precision values (not floating point).
+ * Despite its name, the indexed values (and queries) need not actually be ranges, they can be unit instance/values.
+ * <p />
+ * Why might you use this instead of Lucene's built-in integer/long support? Here are some reasons with features based
+ * on code in this class, <em>or are possible based on this class but require a subclass to fully realize it</em>.
+ * <ul>
+ * <li>Index ranges, not just unit instances. This is especially useful when the requirement calls for a
+ * multi-valued range.</li>
+ * <li>Instead of a fixed "precisionStep", this prefixTree can have a customizable number of child values for any
+ * prefix (up to 32768). This allows exact alignment of the prefix-tree with typical/expected values, which
+ * results in better performance. For example in a Date implementation, every month can get its own dedicated prefix,
+ * every day, etc., even though months vary in duration.</li>
+ * <li>Arbitrary precision, like {@link java.math.BigDecimal}.</li>
+ * <li>Standard Lucene integer/long indexing always indexes the full precision of those data types but this one
+ * is customizable.</li>
+ * </ul>
+ *
+ * Unlike "normal" spatial components in this module, this special-purpose one only works with {@link Shape}s
+ * created by the methods on this class, not from any {@link com.spatial4j.core.context.SpatialContext}.
+ *
+ * @see org.apache.lucene.spatial.NumberRangePrefixTreeStrategy
* @see <a href="https://issues.apache.org/jira/browse/LUCENE-5648">LUCENE-5648</a>
* @lucene.experimental
*/
@@ -49,34 +68,93 @@ public abstract class NumberRangePrefixT
DUMMY_CTX = factory.newSpatialContext();
}
+ /** Base interface for {@link Shape}s this prefix tree supports. It extends {@link Shape} (Spatial4j) for compatibility
+ * with the spatial API even though it doesn't intermix with conventional 2D shapes.
+ * @lucene.experimental
+ */
+ public static interface NRShape extends Shape, Cloneable {
+ /** The result should be parseable by {@link #parseShape(String)}. */
+ abstract String toString();
+
+ /** Returns this shape rounded to the target level. If we are already more course than the level then the shape is
+ * simply returned. The result may refer to internal state of the argument so you may want to clone it.
+ */
+ public NRShape roundToLevel(int targetLevel);
+ }
+
//
- // LevelledValue
+ // Factory / Conversions / parsing relating to NRShapes
//
- /** A value implemented as a stack of numbers. Spatially speaking, it's
- * analogous to a Point but 1D yet has some precision width.
- * @lucene.internal */
- protected static interface LevelledValue extends Shape {
- int getLevel();//0 means the world (universe).
- int getValAtLevel(int level);//level >= 0 && <= getLevel()
- LevelledValue getLVAtLevel(int level);
+ /** Converts the value to a unit shape. Doesn't parse strings; see {@link #parseShape(String)} for
+ * that. This is the reverse of {@link #toObject(org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape)}. */
+ public abstract UnitNRShape toUnitShape(Object value);
+
+ /** Returns a shape that represents the continuous range between {@code start} and {@code end}. It will
+ * be normalized, and so sometimes a {@link org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape}
+ * will be returned, other times a
+ * {@link org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.SpanUnitsNRShape} will be.
+ *
+ * @throws IllegalArgumentException if the arguments are in the wrong order, or if either contains the other (yet they
+ * aren't equal).
+ */
+ public NRShape toRangeShape(UnitNRShape startUnit, UnitNRShape endUnit) {
+ //note: this normalization/optimization process is actually REQUIRED based on assumptions elsewhere.
+ //Normalize start & end
+ startUnit = startUnit.getShapeAtLevel(truncateStartVals(startUnit, 0)); // chops off trailing min-vals (zeroes)
+ endUnit = endUnit.getShapeAtLevel(truncateEndVals(endUnit, 0)); // chops off trailing max-vals
+ //Optimize to just start or end if it's equivalent, e.g. April to April 1st is April 1st.
+ int cmp = comparePrefix(startUnit, endUnit);
+ if (cmp > 0) {
+ throw new IllegalArgumentException("Wrong order: "+ startUnit +" TO "+ endUnit);
+ }
+ if (cmp == 0) {//one is a prefix of the other
+ if (startUnit.getLevel() == endUnit.getLevel()) {
+ //same
+ return startUnit;
+ } else if (endUnit.getLevel() > startUnit.getLevel()) {
+ // e.g. April to April 1st
+ if (truncateStartVals(endUnit, startUnit.getLevel()) == startUnit.getLevel()) {
+ return endUnit;
+ }
+ } else {//minLV level > maxLV level
+ // e.g. April 30 to April
+ if (truncateEndVals(startUnit, endUnit.getLevel()) == endUnit.getLevel()) {
+ return startUnit;
+ }
+ }
+ }
+ return new SpanUnitsNRShape(startUnit, endUnit);
}
- /** Compares a to b, returning less than 0, 0, or greater than 0, if a is less than, equal to, or
- * greater than b, respectively. Only min(a.levels,b.levels) are compared.
- * @lucene.internal */
- protected static int comparePrefixLV(LevelledValue a, LevelledValue b) {
- int minLevel = Math.min(a.getLevel(), b.getLevel());
- for (int level = 1; level <= minLevel; level++) {
- int diff = a.getValAtLevel(level) - b.getValAtLevel(level);
- if (diff != 0)
- return diff;
+ /** From lv.getLevel on up, it returns the first Level seen with val != 0. It doesn't check past endLevel. */
+ private int truncateStartVals(UnitNRShape lv, int endLevel) {
+ for (int level = lv.getLevel(); level > endLevel; level--) {
+ if (lv.getValAtLevel(level) != 0)
+ return level;
}
- return 0;
+ return endLevel;
}
- protected String toStringLV(LevelledValue lv) {
- StringBuilder buf = new StringBuilder();
+ private int truncateEndVals(UnitNRShape lv, int endLevel) {
+ for (int level = lv.getLevel(); level > endLevel; level--) {
+ int max = getNumSubCells(lv.getShapeAtLevel(level - 1)) - 1;
+ if (lv.getValAtLevel(level) != max)
+ return level;
+ }
+ return endLevel;
+ }
+
+ /** Converts a UnitNRShape shape to the corresponding type supported by this class, such as a Calendar/BigDecimal.
+ * This is the reverse of {@link #toUnitShape(Object)}.
+ */
+ public abstract Object toObject(UnitNRShape shape);
+
+ /** A string representation of the UnitNRShape that is parse-able by {@link #parseUnitShape(String)}. */
+ protected abstract String toString(UnitNRShape lv);
+
+ protected static String toStringUnitRaw(UnitNRShape lv) {
+ StringBuilder buf = new StringBuilder(100);
buf.append('[');
for (int level = 1; level <= lv.getLevel(); level++) {
buf.append(lv.getValAtLevel(level)).append(',');
@@ -86,20 +164,96 @@ public abstract class NumberRangePrefixT
return buf.toString();
}
+ /** Detects a range pattern and parses it, otherwise it's parsed as one shape via
+ * {@link #parseUnitShape(String)}. The range pattern looks like this BNF:
+ * <pre>
+ * '[' + parseShapeLV + ' TO ' + parseShapeLV + ']'
+ * </pre>
+ * It's the same thing as the toString() of the range shape, notwithstanding range optimization.
+ *
+ * @param str not null or empty
+ * @return not null
+ * @throws java.text.ParseException If there is a problem
+ */
+ public NRShape parseShape(String str) throws ParseException {
+ if (str == null || str.isEmpty())
+ throw new IllegalArgumentException("str is null or blank");
+ if (str.charAt(0) == '[') {
+ if (str.charAt(str.length()-1) != ']')
+ throw new ParseException("If starts with [ must end with ]; got "+str, str.length()-1);
+ int middle = str.indexOf(" TO ");
+ if (middle < 0)
+ throw new ParseException("If starts with [ must contain ' TO '; got "+str, -1);
+ String leftStr = str.substring(1, middle);
+ String rightStr = str.substring(middle + " TO ".length(), str.length()-1);
+ return toRangeShape(parseUnitShape(leftStr), parseUnitShape(rightStr));
+ } else if (str.charAt(0) == '{') {
+ throw new ParseException("Exclusive ranges not supported; got "+str, 0);
+ } else {
+ return parseUnitShape(str);
+ }
+ }
+
+ /** Parse a String to a UnitNRShape. "*" should be the full-range (level 0 shape). */
+ protected abstract UnitNRShape parseUnitShape(String str) throws ParseException;
+
+
//
- // NRShape
+ // UnitNRShape
//
- /** Number Range Shape; based on a pair of {@link LevelledValue}.
- * Spatially speaking, it's analogous to a Rectangle but 1D.
+ /**
+ * A unit value Shape implemented as a stack of numbers, one for each level in the prefix tree. It directly
+ * corresponds to a {@link Cell}. Spatially speaking, it's analogous to a Point but 1D and has some precision width.
+ * @lucene.experimental
+ */
+ public static interface UnitNRShape extends NRShape, Comparable<UnitNRShape> {
+ //note: formerly known as LevelledValue; thus some variables still use 'lv'
+
+ /** Get the prefix tree level, the higher the more precise. 0 means the world (universe). */
+ int getLevel();
+ /** Gets the value at the specified level of this unit. level must be >= 0 and <= getLevel(). */
+ int getValAtLevel(int level);
+ /** Gets an ancestor at the specified level. It shares state, so you may want to clone() it. */
+ UnitNRShape getShapeAtLevel(int level);
+ @Override
+ UnitNRShape roundToLevel(int targetLevel);
+
+ /** Deep clone */
+ UnitNRShape clone();
+ }
+
+ /** Compares a to b, returning less than 0, 0, or greater than 0, if a is less than, equal to, or
+ * greater than b, respectively, up to their common prefix (i.e. only min(a.levels,b.levels) are compared).
* @lucene.internal */
- protected class NRShape implements Shape {
+ protected static int comparePrefix(UnitNRShape a, UnitNRShape b) {
+ int minLevel = Math.min(a.getLevel(), b.getLevel());
+ for (int level = 1; level <= minLevel; level++) {
+ int diff = a.getValAtLevel(level) - b.getValAtLevel(level);
+ if (diff != 0)
+ return diff;
+ }
+ return 0;
+ }
+
+
+ //
+ // SpanUnitsNRShape
+ //
- private final LevelledValue minLV, maxLV;
+ /** A range Shape; based on a pair of {@link org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape}.
+ * Spatially speaking, it's analogous to a Rectangle but 1D. It might have been named with Range in the name but it
+ * may be confusing since even the {@link org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape}
+ * is in some sense a range.
+ * @lucene.experimental */
+ public class SpanUnitsNRShape implements NRShape {
+
+ private final UnitNRShape minLV, maxLV;
private final int lastLevelInCommon;//computed; not part of identity
- /** Don't call directly; see {@link #toRangeShape(com.spatial4j.core.shape.Shape, com.spatial4j.core.shape.Shape)}. */
- private NRShape(LevelledValue minLV, LevelledValue maxLV) {
+ /** Don't call directly; see
+ * {@link #toRangeShape(org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape, org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape)}. */
+ private SpanUnitsNRShape(UnitNRShape minLV, UnitNRShape maxLV) {
this.minLV = minLV;
this.maxLV = maxLV;
@@ -112,37 +266,42 @@ public abstract class NumberRangePrefixT
lastLevelInCommon = level - 1;
}
- public LevelledValue getMinLV() { return minLV; }
+ public UnitNRShape getMinUnit() { return minLV; }
+
+ public UnitNRShape getMaxUnit() { return maxLV; }
- public LevelledValue getMaxLV() { return maxLV; }
+ /** How many levels are in common between minUnit and maxUnit, not including level 0. */
+ private int getLevelsInCommon() { return lastLevelInCommon; }
- /** How many levels are in common between minLV and maxLV. */
- private int getLastLevelInCommon() { return lastLevelInCommon; }
+ @Override
+ public NRShape roundToLevel(int targetLevel) {
+ return toRangeShape(minLV.roundToLevel(targetLevel), maxLV.roundToLevel(targetLevel));
+ }
@Override
public SpatialRelation relate(Shape shape) {
-// if (shape instanceof LevelledValue)
-// return relate((LevelledValue)shape);
- if (shape instanceof NRShape)
- return relate((NRShape) shape);
- return shape.relate(this).transpose();//probably a LevelledValue
+// if (shape instanceof UnitNRShape)
+// return relate((UnitNRShape)shape);
+ if (shape instanceof SpanUnitsNRShape)
+ return relate((SpanUnitsNRShape) shape);
+ return shape.relate(this).transpose();//probably a UnitNRShape
}
- public SpatialRelation relate(NRShape ext) {
+ public SpatialRelation relate(SpanUnitsNRShape ext) {
//This logic somewhat mirrors RectangleImpl.relate_range()
- int extMin_intMax = comparePrefixLV(ext.getMinLV(), getMaxLV());
+ int extMin_intMax = comparePrefix(ext.getMinUnit(), getMaxUnit());
if (extMin_intMax > 0)
return SpatialRelation.DISJOINT;
- int extMax_intMin = comparePrefixLV(ext.getMaxLV(), getMinLV());
+ int extMax_intMin = comparePrefix(ext.getMaxUnit(), getMinUnit());
if (extMax_intMin < 0)
return SpatialRelation.DISJOINT;
- int extMin_intMin = comparePrefixLV(ext.getMinLV(), getMinLV());
- int extMax_intMax = comparePrefixLV(ext.getMaxLV(), getMaxLV());
- if ((extMin_intMin > 0 || extMin_intMin == 0 && ext.getMinLV().getLevel() >= getMinLV().getLevel())
- && (extMax_intMax < 0 || extMax_intMax == 0 && ext.getMaxLV().getLevel() >= getMaxLV().getLevel()))
+ int extMin_intMin = comparePrefix(ext.getMinUnit(), getMinUnit());
+ int extMax_intMax = comparePrefix(ext.getMaxUnit(), getMaxUnit());
+ if ((extMin_intMin > 0 || extMin_intMin == 0 && ext.getMinUnit().getLevel() >= getMinUnit().getLevel())
+ && (extMax_intMax < 0 || extMax_intMax == 0 && ext.getMaxUnit().getLevel() >= getMaxUnit().getLevel()))
return SpatialRelation.CONTAINS;
- if ((extMin_intMin < 0 || extMin_intMin == 0 && ext.getMinLV().getLevel() <= getMinLV().getLevel())
- && (extMax_intMax > 0 || extMax_intMax == 0 && ext.getMaxLV().getLevel() <= getMaxLV().getLevel()))
+ if ((extMin_intMin < 0 || extMin_intMin == 0 && ext.getMinUnit().getLevel() <= getMinUnit().getLevel())
+ && (extMax_intMax > 0 || extMax_intMax == 0 && ext.getMaxUnit().getLevel() <= getMaxUnit().getLevel()))
return SpatialRelation.WITHIN;
return SpatialRelation.INTERSECTS;
}
@@ -165,18 +324,27 @@ public abstract class NumberRangePrefixT
@Override
public boolean isEmpty() { return false; }
+ /** A deep clone. */
+ @Override
+ public SpanUnitsNRShape clone() {
+ return new SpanUnitsNRShape(minLV.clone(), maxLV.clone());
+ }
+
@Override
- public String toString() { return "[" + toStringLV(minLV) + " TO " + toStringLV(maxLV) + "]"; }
+ public String toString() {
+ return "[" + NumberRangePrefixTree.this.toString(minLV) + " TO "
+ + NumberRangePrefixTree.this.toString(maxLV) + "]";
+ }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- NRShape nrShape = (NRShape) o;
+ SpanUnitsNRShape spanShape = (SpanUnitsNRShape) o;
- if (!maxLV.equals(nrShape.maxLV)) return false;
- if (!minLV.equals(nrShape.minLV)) return false;
+ if (!maxLV.equals(spanShape.maxLV)) return false;
+ if (!minLV.equals(spanShape.minLV)) return false;
return true;
}
@@ -187,98 +355,7 @@ public abstract class NumberRangePrefixT
result = 31 * result + maxLV.hashCode();
return result;
}
- }// class NRShapeImpl
-
- /** Converts the value to a shape (usually not a range). If it's a JDK object (e.g. Number, Calendar)
- * that could be parsed from a String, this class won't do it; you must parse it. */
- public abstract Shape toShape(Object value);
-
- /** Detects a range pattern and parses it, otherwise it's parsed as one shape via
- * {@link #parseShapeLV(String)}. The range pattern looks like this BNF:
- * <pre>
- * '[' + parseShapeLV + ' TO ' + parseShapeLV + ']'
- * </pre>
- * It's the same thing as the toString() of the range shape, notwithstanding range optimization.
- * @param str not null or empty
- * @return not null
- * @throws java.text.ParseException If there is a problem
- */
- public Shape parseShape(String str) throws ParseException {
- if (str == null || str.isEmpty())
- throw new IllegalArgumentException("str is null or blank");
- if (str.charAt(0) == '[') {
- if (str.charAt(str.length()-1) != ']')
- throw new ParseException("If starts with [ must end with ]; got "+str, str.length()-1);
- int middle = str.indexOf(" TO ");
- if (middle < 0)
- throw new ParseException("If starts with [ must contain ' TO '; got "+str, -1);
- String leftStr = str.substring(1, middle);
- String rightStr = str.substring(middle + " TO ".length(), str.length()-1);
- return toRangeShape(parseShapeLV(leftStr), parseShapeLV(rightStr));
- } else if (str.charAt(0) == '{') {
- throw new ParseException("Exclusive ranges not supported; got "+str, 0);
- } else {
- return parseShapeLV(str);
- }
- }
-
- /** Parse a String to a LevelledValue. "*" should be the full-range. */
- protected abstract LevelledValue parseShapeLV(String str) throws ParseException;
-
- /** Returns a shape that represents the continuous range between {@code start} and {@code end}. It will
- * be optimized.
- * @throws IllegalArgumentException if the arguments are in the wrong order, or if either contains the other.
- */
- public Shape toRangeShape(Shape start, Shape end) {
- if (!(start instanceof LevelledValue && end instanceof LevelledValue))
- throw new IllegalArgumentException("Must pass "+LevelledValue.class+" but got "+start.getClass());
- LevelledValue startLV = (LevelledValue) start;
- LevelledValue endLV = (LevelledValue) end;
- //note: this normalization/optimization process is actually REQUIRED based on assumptions elsewhere.
- //Normalize start & end
- startLV = startLV.getLVAtLevel(truncateStartVals(startLV, 0)); // chops off trailing min-vals (zeroes)
- endLV = endLV.getLVAtLevel(truncateEndVals(endLV, 0)); // chops off trailing max-vals
- //Optimize to just start or end if it's equivalent, e.g. April to April 1st is April 1st.
- int cmp = comparePrefixLV(startLV, endLV);
- if (cmp > 0) {
- throw new IllegalArgumentException("Wrong order: "+start+" TO "+end);
- }
- if (cmp == 0) {//one is a prefix of the other
- if (startLV.getLevel() == endLV.getLevel()) {
- //same
- return startLV;
- } else if (endLV.getLevel() > startLV.getLevel()) {
- // e.g. April to April 1st
- if (truncateStartVals(endLV, startLV.getLevel()) == startLV.getLevel()) {
- return endLV;
- }
- } else {//minLV level > maxLV level
- // e.g. April 30 to April
- if (truncateEndVals(startLV, endLV.getLevel()) == endLV.getLevel()) {
- return startLV;
- }
- }
- }
- return new NRShape(startLV, endLV);
- }
-
- /** From lv.getLevel on up, it returns the first Level seen with val != 0. It doesn't check past endLevel. */
- private int truncateStartVals(LevelledValue lv, int endLevel) {
- for (int level = lv.getLevel(); level > endLevel; level--) {
- if (lv.getValAtLevel(level) != 0)
- return level;
- }
- return endLevel;
- }
-
- private int truncateEndVals(LevelledValue lv, int endLevel) {
- for (int level = lv.getLevel(); level > endLevel; level--) {
- int max = getNumSubCells(lv.getLVAtLevel(level-1)) - 1;
- if (lv.getValAtLevel(level) != max)
- return level;
- }
- return endLevel;
- }
+ }// class SpanUnitsNRShape
//
// NumberRangePrefixTree
@@ -332,15 +409,19 @@ public abstract class NumberRangePrefixT
@Override
public int getLevelForDistance(double dist) {
- return maxLevels;
+ //note: it might be useful to compute which level has a raw width (counted in
+ // bottom units, e.g. milliseconds), that covers the provided dist in those units?
+ return maxLevels; // thus always use full precision. We don't do approximations in this tree/strategy.
+ //throw new UnsupportedOperationException("Not applicable.");
}
@Override
public double getDistanceForLevel(int level) {
+ //note: we could compute this... should we?
throw new UnsupportedOperationException("Not applicable.");
}
- protected Shape toShape(int[] valStack, int len) {
+ protected UnitNRShape toShape(int[] valStack, int len) {
final NRCell[] cellStack = newCellStack(len);
for (int i = 0; i < len; i++) {
cellStack[i+1].resetCellWithCellNum(valStack[i]);
@@ -367,7 +448,7 @@ public abstract class NumberRangePrefixT
if (scratch == null)
scratch = getWorldCell();
- //We decode level, leaf, and populate bytes.
+ //We decode level #, leaf boolean, and populate bytes by reference. We don't decode the stack.
//reverse lookup term length to the level and hence the cell
NRCell[] cellsByLevel = ((NRCell) scratch).cellsByLevel;
@@ -389,7 +470,8 @@ public abstract class NumberRangePrefixT
return result;
}
- protected int getNumSubCells(LevelledValue lv) {
+ /** Returns the number of sub-cells beneath the given UnitNRShape. */
+ public int getNumSubCells(UnitNRShape lv) {
return maxSubCellsByLevel[lv.getLevel()];
}
@@ -402,7 +484,7 @@ public abstract class NumberRangePrefixT
* of Cells at adjacent levels, that all have a reference back to the cell array to traverse. They also share a common
* BytesRef for the term.
* @lucene.internal */
- protected class NRCell extends CellIterator implements Cell, LevelledValue {
+ protected class NRCell extends CellIterator implements Cell, UnitNRShape {
//Shared: (TODO put this in a new class)
final NRCell[] cellsByLevel;
@@ -484,11 +566,15 @@ public abstract class NumberRangePrefixT
cell.cellNumber = (term.bytes[term.offset + termLen - 1] & 0xFF) - 1;
assert cell.cellNumber < 255;
}
- assert cell.cellNumber >= 0;
+ cell.assertDecoded();
}
}
- @Override // for Cell & for LevelledValue
+ private void assertDecoded() {
+ assert cellNumber >= 0 : "Illegal state; ensureDecoded() wasn't called";
+ }
+
+ @Override // for Cell & for UnitNRShape
public int getLevel() {
return cellLevel;
}
@@ -514,8 +600,9 @@ public abstract class NumberRangePrefixT
}
@Override
- public Shape getShape() {
- ensureDecoded(); return this;
+ public UnitNRShape getShape() {
+ ensureDecoded();
+ return this;
}
@Override
@@ -579,7 +666,7 @@ public abstract class NumberRangePrefixT
//----------- CellIterator
- Shape iterFilter;//LevelledValue or NRShape
+ Shape iterFilter;//UnitNRShape or NRShape
boolean iterFirstIsIntersects;
boolean iterLastIsIntersects;
int iterFirstCellNumber;
@@ -587,11 +674,11 @@ public abstract class NumberRangePrefixT
private void initIter(Shape filter) {
cellNumber = -1;
- if (filter instanceof LevelledValue && ((LevelledValue) filter).getLevel() == 0)
+ if (filter instanceof UnitNRShape && ((UnitNRShape) filter).getLevel() == 0)
filter = null;//world means everything -- no filter
iterFilter = filter;
- NRCell parent = getLVAtLevel(getLevel() - 1);
+ NRCell parent = getShapeAtLevel(getLevel() - 1);
// Initialize iter* members.
@@ -604,16 +691,16 @@ public abstract class NumberRangePrefixT
return;
}
- final LevelledValue minLV;
- final LevelledValue maxLV;
+ final UnitNRShape minLV;
+ final UnitNRShape maxLV;
final int lastLevelInCommon;//between minLV & maxLV
- if (filter instanceof NRShape) {
- NRShape nrShape = (NRShape) iterFilter;
- minLV = nrShape.getMinLV();
- maxLV = nrShape.getMaxLV();
- lastLevelInCommon = nrShape.getLastLevelInCommon();
+ if (filter instanceof SpanUnitsNRShape) {
+ SpanUnitsNRShape spanShape = (SpanUnitsNRShape) iterFilter;
+ minLV = spanShape.getMinUnit();
+ maxLV = spanShape.getMaxUnit();
+ lastLevelInCommon = spanShape.getLevelsInCommon();
} else {
- minLV = (LevelledValue) iterFilter;
+ minLV = (UnitNRShape) iterFilter;
maxLV = minLV;
lastLevelInCommon = minLV.getLevel();
}
@@ -649,7 +736,7 @@ public abstract class NumberRangePrefixT
//not common to get here, except for level 1 which always happens
- int startCmp = comparePrefixLV(minLV, parent);
+ int startCmp = comparePrefix(minLV, parent);
if (startCmp > 0) {//start comes after this cell
iterFirstCellNumber = 0;
iterFirstIsIntersects = false;
@@ -657,7 +744,7 @@ public abstract class NumberRangePrefixT
iterLastIsIntersects = false;
return;
}
- int endCmp = comparePrefixLV(maxLV, parent);//compare to end cell
+ int endCmp = comparePrefix(maxLV, parent);//compare to end cell
if (endCmp < 0) {//end comes before this cell
iterFirstCellNumber = 0;
iterFirstIsIntersects = false;
@@ -719,56 +806,65 @@ public abstract class NumberRangePrefixT
//TODO override nextFrom to be more efficient
- //----------- LevelledValue / Shape
+ //----------- UnitNRShape
@Override
public int getValAtLevel(int level) {
final int result = cellsByLevel[level].cellNumber;
- assert result >= 0;//initialized
+ assert result >= 0;//initialized (decoded)
return result;
}
@Override
- public NRCell getLVAtLevel(int level) {
+ public NRCell getShapeAtLevel(int level) {
assert level <= cellLevel;
return cellsByLevel[level];
}
@Override
+ public UnitNRShape roundToLevel(int targetLevel) {
+ if (getLevel() <= targetLevel) {
+ return this;
+ } else {
+ return getShapeAtLevel(targetLevel);
+ }
+ }
+
+ @Override
public SpatialRelation relate(Shape shape) {
- ensureDecoded();
+ assertDecoded();
if (shape == iterFilter && cellShapeRel != null)
return cellShapeRel;
- if (shape instanceof LevelledValue)
- return relate((LevelledValue)shape);
- if (shape instanceof NRShape)
- return relate((NRShape)shape);
+ if (shape instanceof UnitNRShape)
+ return relate((UnitNRShape)shape);
+ if (shape instanceof SpanUnitsNRShape)
+ return relate((SpanUnitsNRShape)shape);
return shape.relate(this).transpose();
}
- public SpatialRelation relate(LevelledValue lv) {
- ensureDecoded();
- int cmp = comparePrefixLV(this, lv);
+ public SpatialRelation relate(UnitNRShape lv) {
+ assertDecoded();
+ int cmp = comparePrefix(this, lv);
if (cmp != 0)
return SpatialRelation.DISJOINT;
if (getLevel() > lv.getLevel())
- return SpatialRelation.WITHIN;//or equals
- return SpatialRelation.CONTAINS;
+ return SpatialRelation.WITHIN;
+ return SpatialRelation.CONTAINS;//or equals
//no INTERSECTS; that won't happen.
}
- public SpatialRelation relate(NRShape nrShape) {
- ensureDecoded();
- int startCmp = comparePrefixLV(nrShape.getMinLV(), this);
+ public SpatialRelation relate(SpanUnitsNRShape spanShape) {
+ assertDecoded();
+ int startCmp = comparePrefix(spanShape.getMinUnit(), this);
if (startCmp > 0) {//start comes after this cell
return SpatialRelation.DISJOINT;
}
- int endCmp = comparePrefixLV(nrShape.getMaxLV(), this);
+ int endCmp = comparePrefix(spanShape.getMaxUnit(), this);
if (endCmp < 0) {//end comes before this cell
return SpatialRelation.DISJOINT;
}
- int nrMinLevel = nrShape.getMinLV().getLevel();
- int nrMaxLevel = nrShape.getMaxLV().getLevel();
+ int nrMinLevel = spanShape.getMinUnit().getLevel();
+ int nrMaxLevel = spanShape.getMaxUnit().getLevel();
if ((startCmp < 0 || startCmp == 0 && nrMinLevel <= getLevel())
&& (endCmp > 0 || endCmp == 0 && nrMaxLevel <= getLevel()))
return SpatialRelation.WITHIN;//or equals
@@ -781,13 +877,33 @@ public abstract class NumberRangePrefixT
return SpatialRelation.INTERSECTS;
}
for (;nrMaxLevel < getLevel(); nrMaxLevel++) {
- if (getValAtLevel(nrMaxLevel + 1) != getNumSubCells(getLVAtLevel(nrMaxLevel)) - 1)
+ if (getValAtLevel(nrMaxLevel + 1) != getNumSubCells(getShapeAtLevel(nrMaxLevel)) - 1)
return SpatialRelation.INTERSECTS;
}
return SpatialRelation.CONTAINS;
}
@Override
+ public UnitNRShape clone() {
+ //no leaf distinction; this is purely based on UnitNRShape
+ NRCell cell = (NRCell) readCell(getTokenBytesNoLeaf(null), null);
+ cell.ensureOwnTermBytes();
+ return cell.getShape();
+ }
+
+ @Override
+ public int compareTo(UnitNRShape o) {
+ assertDecoded();
+ //no leaf distinction; this is purely based on UnitNRShape
+ int cmp = comparePrefix(this, o);
+ if (cmp != 0) {
+ return cmp;
+ } else {
+ return getLevel() - o.getLevel();
+ }
+ }
+
+ @Override
public Rectangle getBoundingBox() {
throw new UnsupportedOperationException();
}
@@ -848,11 +964,7 @@ public abstract class NumberRangePrefixT
@Override
public String toString() {
- ensureDecoded();
- String str = NumberRangePrefixTree.this.toStringLV(this);
- if (isLeaf())
- str += "â¢";//bullet (won't be confused with textual representation)
- return str;
+ return NumberRangePrefixTree.this.toString(getShape());
}
/** Configure your IDE to use this. */
@@ -860,16 +972,7 @@ public abstract class NumberRangePrefixT
String pretty = toString();
if (getLevel() == 0)
return pretty;
- //now prefix it by an array of integers of the cell levels
- StringBuilder buf = new StringBuilder(100);
- buf.append('[');
- for (int level = 1; level <= getLevel(); level++) {
- if (level != 1)
- buf.append(',');
- buf.append(getLVAtLevel(level).cellNumber);
- }
- buf.append("] ").append(pretty);
- return buf.toString();
+ return toStringUnitRaw(this) + (isLeaf() ? "â¢" : "") + " " + pretty;
}
} // END OF NRCell
Modified: lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/DateNRStrategyTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/DateNRStrategyTest.java?rev=1645381&r1=1645380&r2=1645381&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/DateNRStrategyTest.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/DateNRStrategyTest.java Sun Dec 14 04:33:01 2014
@@ -17,18 +17,19 @@ package org.apache.lucene.spatial.prefix
* limitations under the License.
*/
+import java.io.IOException;
+import java.util.Calendar;
+
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.spatial.NumberRangePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.DateRangePrefixTree;
+import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
-import java.io.IOException;
-import java.text.ParseException;
-import java.util.Calendar;
+import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
public class DateNRStrategyTest extends RandomSpatialOpStrategyTestCase {
@@ -36,16 +37,17 @@ public class DateNRStrategyTest extends
DateRangePrefixTree tree;
- int era;
- int year;
+ long randomCalWindowMs;
@Before
public void setUp() throws Exception {
super.setUp();
tree = DateRangePrefixTree.INSTANCE;
strategy = new NumberRangePrefixTreeStrategy(tree, "dateRange");
- era = random().nextBoolean() ? 0 : 1;
- year = 1 + random().nextInt(2_000_000);
+ Calendar tmpCal = tree.newCal();
+ int randomCalWindowField = randomIntBetween(1, Calendar.ZONE_OFFSET - 1);//we're not allowed to add zone offset
+ tmpCal.add(randomCalWindowField, 2_000);
+ randomCalWindowMs = Math.max(2000L, tmpCal.getTimeInMillis());
}
@Test
@@ -69,9 +71,6 @@ public class DateNRStrategyTest extends
@Test
public void testWithinSame() throws IOException {
final Calendar cal = tree.newCal();
- cal.set(Calendar.ERA, era);
- cal.set(Calendar.YEAR, year);
-
testOperation(
tree.toShape(cal),
SpatialOperation.IsWithin,
@@ -98,10 +97,13 @@ public class DateNRStrategyTest extends
@Override
protected Shape randomIndexedShape() {
Calendar cal1 = randomCalendar();
- Shape s1 = tree.toShape(cal1);
+ UnitNRShape s1 = tree.toShape(cal1);
+ if (rarely()) {
+ return s1;
+ }
try {
Calendar cal2 = randomCalendar();
- Shape s2 = tree.toShape(cal2);
+ UnitNRShape s2 = tree.toShape(cal2);
if (cal1.compareTo(cal2) < 0) {
return tree.toRangeShape(s1, s2);
} else {
@@ -115,9 +117,7 @@ public class DateNRStrategyTest extends
private Calendar randomCalendar() {
Calendar cal = tree.newCal();
- cal.setTimeInMillis(random().nextLong());
- cal.set(Calendar.ERA, era);
- cal.set(Calendar.YEAR, year);
+ cal.setTimeInMillis(random().nextLong() % randomCalWindowMs);
try {
tree.clearFieldsAfter(cal, random().nextInt(Calendar.FIELD_COUNT+1)-1);
} catch (AssertionError e) {
Modified: lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTreeTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTreeTest.java?rev=1645381&r1=1645380&r2=1645381&view=diff
==============================================================================
--- lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTreeTest.java (original)
+++ lucene/dev/trunk/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/DateRangePrefixTreeTest.java Sun Dec 14 04:33:01 2014
@@ -17,16 +17,17 @@ package org.apache.lucene.spatial.prefix
* limitations under the License.
*/
-import com.spatial4j.core.shape.Shape;
-import com.spatial4j.core.shape.SpatialRelation;
-import org.apache.lucene.util.BytesRef;
-import org.apache.lucene.util.LuceneTestCase;
-
import java.text.ParseException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
+import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.SpatialRelation;
+import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.LuceneTestCase;
+
public class DateRangePrefixTreeTest extends LuceneTestCase {
private DateRangePrefixTree tree = DateRangePrefixTree.INSTANCE;
@@ -94,7 +95,7 @@ public class DateRangePrefixTreeTest ext
assertEquals(cal, tree.parseCalendar(calString));
//to Shape and back to Cal
- Shape shape = tree.toShape(cal);
+ UnitNRShape shape = tree.toShape(cal);
Calendar cal2 = tree.toCalendar(shape);
assertEquals(calString, tree.toString(cal2));
@@ -104,7 +105,7 @@ public class DateRangePrefixTreeTest ext
BytesRef term = cell.getTokenBytesNoLeaf(null);
Cell cell2 = tree.readCell(BytesRef.deepCopyOf(term), null);
assertEquals(calString, cell, cell2);
- Calendar cal3 = tree.toCalendar(cell2.getShape());
+ Calendar cal3 = tree.toCalendar((UnitNRShape) cell2.getShape());
assertEquals(calString, tree.toString(cal3));
// setLeaf comparison
Modified: lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/DateRangeField.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/DateRangeField.java?rev=1645381&r1=1645380&r2=1645381&view=diff
==============================================================================
--- lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/DateRangeField.java (original)
+++ lucene/dev/trunk/solr/core/src/java/org/apache/solr/schema/DateRangeField.java Sun Dec 14 04:33:01 2014
@@ -17,23 +17,25 @@ package org.apache.solr.schema;
* limitations under the License.
*/
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
import com.spatial4j.core.shape.Shape;
import org.apache.lucene.index.StorableField;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.NumberRangePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.DateRangePrefixTree;
+import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.NRShape;
+import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.search.QParser;
-import java.text.ParseException;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
/**
* @see NumberRangePrefixTreeStrategy
* @see DateRangePrefixTree
@@ -62,12 +64,12 @@ public class DateRangeField extends Abst
@Override
public List<StorableField> createFields(SchemaField field, Object val, float boost) {
if (val instanceof Date || val instanceof Calendar)//From URP
- val = tree.toShape(val);
+ val = tree.toUnitShape(val);
return super.createFields(field, val, boost);
}
@Override
- protected Shape parseShape(String str) {
+ protected NRShape parseShape(String str) {
try {
return tree.parseShape(str);
} catch (ParseException e) {
@@ -103,7 +105,7 @@ public class DateRangeField extends Abst
part1 = "*";
if (part2 == null)
part2 = "*";
- Shape shape = tree.toRangeShape(parseShape(part1), parseShape(part2));
+ Shape shape = tree.toRangeShape((UnitNRShape) parseShape(part1), (UnitNRShape) parseShape(part2));
SpatialArgs spatialArgs = new SpatialArgs(SpatialOperation.Intersects, shape);
return getQueryFromSpatialArgs(parser, field, spatialArgs);
}