package org.cdlib.xtf.textEngine;
/**
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Acknowledgements:
*
* A significant amount of new and/or modified code in this module
* was made possible by a grant from the Andrew W. Mellon Foundation,
* as part of the Melvyl Recommender Project.
*/
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.Weight;
import java.io.IOException;
import java.util.Set;
/**
* A query that implements efficient range searching on numeric data. Handles
* positive numbers up to 63 bits.
*/
public class NumericRangeQuery extends Query
{
private final String fieldName;
private final boolean includeLower;
private final String lowerVal;
private final boolean includeUpper;
private final String upperVal;
public NumericRangeQuery(String fieldName, String lowerVal, String upperVal,
boolean includeLower, boolean includeUpper)
{
// do a little bit of normalization...
if ("".equals(lowerVal)) {
lowerVal = null;
}
if ("".equals(upperVal)) {
upperVal = null;
}
this.fieldName = fieldName.intern(); // intern it, just like terms...
this.lowerVal = lowerVal;
this.upperVal = upperVal;
this.includeLower = includeLower;
this.includeUpper = includeUpper;
}
/** Returns the field name for this query */
public String getField() {
return fieldName;
}
/** Returns the value of the lower endpoint of this range query, null if open ended */
public String getLowerVal() {
return lowerVal;
}
/** Returns the value of the upper endpoint of this range query, null if open ended */
public String getUpperVal() {
return upperVal;
}
/** Returns <code>true</code> if the lower endpoint is inclusive */
public boolean includesLower() {
return includeLower;
}
/** Returns <code>true</code> if the upper endpoint is inclusive */
public boolean includesUpper() {
return includeUpper;
}
public Query rewrite(IndexReader reader)
throws IOException
{
return this;
}
public void extractTerms(Set terms)
{
// OK to not add any terms when used for MultiSearcher,
// but may not be OK for highlighting
}
private class NumericRangeWeight implements Weight
{
private NumericRangeQuery query;
private Similarity similarity;
private float queryNorm;
private float queryWeight;
public NumericRangeWeight(NumericRangeQuery query, Searcher searcher) {
this.similarity = getSimilarity(searcher);
}
public Query getQuery() {
return NumericRangeQuery.this;
}
public float getValue() {
return queryWeight;
}
public float sumOfSquaredWeights()
throws IOException
{
queryWeight = getBoost();
return queryWeight * queryWeight;
}
public void normalize(float norm) {
this.queryNorm = norm;
queryWeight *= this.queryNorm;
}
public Scorer scorer(IndexReader reader)
throws IOException
{
return new NumericRangeScorer(similarity, reader, this);
}
public Explanation explain(IndexReader reader, int doc)
throws IOException
{
NumericRangeScorer cs = (NumericRangeScorer)scorer(reader);
int docPos = cs.docPos(doc);
boolean inRange = docPos >= 0 ? cs.inRange(docPos) : false;
Explanation result = new Explanation();
if (inRange) {
result.setDescription(
"NumericRangeQuery(" + query.toString() + "), product of:");
result.setValue(queryWeight);
result.addDetail(new Explanation(getBoost(), "boost"));
result.addDetail(new Explanation(queryNorm, "queryNorm"));
}
else {
result.setDescription(
"NumericRangeQuery(" + query.toString() + ") doesn't match id " +
doc);
result.setValue(0);
}
return result;
}
}
private class NumericRangeScorer extends Scorer
{
final NumericFieldData data;
final float theScore;
final int dataSize;
final boolean checkLower;
final long lowerNum;
final boolean checkUpper;
final long upperNum;
int dataPos = -1;
public NumericRangeScorer(Similarity similarity, IndexReader reader,
Weight w)
throws IOException
{
super(similarity);
theScore = w.getValue();
data = NumericFieldData.getCachedData(reader, fieldName);
dataSize = data.size();
checkLower = (lowerVal != null);
this.lowerNum = checkLower ? NumericFieldData.parseVal(lowerVal) : -1;
checkUpper = (upperVal != null);
this.upperNum = checkUpper ? NumericFieldData.parseVal(upperVal) : -1;
}
public boolean next()
throws IOException
{
while (true) {
++dataPos;
if (dataPos >= dataSize)
break;
if (inRange(dataPos))
break;
}
return dataPos < dataSize;
}
public final boolean inRange(int dataPos)
{
long value = data.value(dataPos);
if (checkLower) {
if (value < lowerNum)
return false;
if (value == lowerNum && !includeLower)
return false;
}
if (checkUpper) {
if (value > upperNum)
return false;
if (value == upperNum && !includeUpper)
return false;
}
return true;
}
public int docPos(int doc) {
return data.docPos(doc);
}
public int doc() {
return data.doc(dataPos);
}
public float score()
throws IOException
{
return theScore;
}
public boolean skipTo(int target)
throws IOException
{
dataPos = Math.max(dataPos, data.findDocIndex(target) - 1);
return next();
}
public Explanation explain(int doc)
throws IOException
{
throw new UnsupportedOperationException();
}
}
protected Weight createWeight(Searcher searcher) {
return new NumericRangeQuery.NumericRangeWeight(this, searcher);
}
/** Prints a user-readable version of this query. */
public String toString(String field)
{
StringBuffer buffer = new StringBuffer();
if (!getField().equals(field)) {
buffer.append(getField());
buffer.append(":");
}
buffer.append(includeLower ? '[' : '{');
buffer.append(lowerVal != null ? lowerVal : "*");
buffer.append(" TO ");
buffer.append(upperVal != null ? upperVal : "*");
buffer.append(includeUpper ? ']' : '}');
if (getBoost() != 1.0f) {
buffer.append("^");
buffer.append(Float.toString(getBoost()));
}
return buffer.toString();
}
/** Returns true if <code>o</code> is equal to this. */
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof NumericRangeQuery))
return false;
NumericRangeQuery other = (NumericRangeQuery)o;
if (this.fieldName != other.fieldName // interned comparison
||
this.includeLower != other.includeLower ||
this.includeUpper != other.includeUpper)
{
return false;
}
if (this.lowerVal != null ? !this.lowerVal.equals(other.lowerVal)
: other.lowerVal != null)
return false;
if (this.upperVal != null ? !this.upperVal.equals(other.upperVal)
: other.upperVal != null)
return false;
return this.getBoost() == other.getBoost();
}
/** Returns a hash code value for this object.*/
public int hashCode()
{
int h = Float.floatToIntBits(getBoost()) ^ fieldName.hashCode();
// hashCode of "" is 0, so don't use that for null...
h ^= lowerVal != null ? lowerVal.hashCode() : 0x965a965a;
// don't just XOR upperVal with out mixing either it or h, as it will cancel
// out lowerVal if they are equal.
h ^= (h << 17) | (h >>> 16); // a reversible (one to one) 32 bit mapping mix
h ^= (upperVal != null ? (upperVal.hashCode()) : 0x5a695a69);
h ^= (includeLower ? 0x665599aa : 0) ^ (includeUpper ? 0x99aa5566 : 0);
return h;
}
} // class NumericRangeQuery