| package org.apache.lucene.search; |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import java.io.IOException; |
| |
| import org.apache.lucene.index.AtomicReader; |
| import org.apache.lucene.index.AtomicReaderContext; |
| import org.apache.lucene.index.DocValues; |
| import org.apache.lucene.index.SortedSetDocValues; |
| import org.apache.lucene.util.Bits; |
| import org.apache.lucene.util.BytesRef; |
| |
| /** |
| * A range filter built on top of a cached multi-valued term field (from {@link AtomicReader#getSortedSetDocValues}). |
| * |
| * <p>Like {@link DocValuesRangeFilter}, this is just a specialized range query versus |
| * using a TermRangeQuery with {@link DocTermOrdsRewriteMethod}: it will only do |
| * two ordinal to term lookups.</p> |
| */ |
| |
| public abstract class DocTermOrdsRangeFilter extends Filter { |
| final String field; |
| final BytesRef lowerVal; |
| final BytesRef upperVal; |
| final boolean includeLower; |
| final boolean includeUpper; |
| |
| private DocTermOrdsRangeFilter(String field, BytesRef lowerVal, BytesRef upperVal, boolean includeLower, boolean includeUpper) { |
| this.field = field; |
| this.lowerVal = lowerVal; |
| this.upperVal = upperVal; |
| this.includeLower = includeLower; |
| this.includeUpper = includeUpper; |
| } |
| |
| /** This method is implemented for each data type */ |
| @Override |
| public abstract DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws IOException; |
| |
| /** |
| * Creates a BytesRef range filter using {@link AtomicReader#getSortedSetDocValues}. This works with all |
| * fields containing zero or one term in the field. The range can be half-open by setting one |
| * of the values to <code>null</code>. |
| */ |
| public static DocTermOrdsRangeFilter newBytesRefRange(String field, BytesRef lowerVal, BytesRef upperVal, boolean includeLower, boolean includeUpper) { |
| return new DocTermOrdsRangeFilter(field, lowerVal, upperVal, includeLower, includeUpper) { |
| @Override |
| public DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws IOException { |
| final SortedSetDocValues docTermOrds = DocValues.getSortedSet(context.reader(), field); |
| final long lowerPoint = lowerVal == null ? -1 : docTermOrds.lookupTerm(lowerVal); |
| final long upperPoint = upperVal == null ? -1 : docTermOrds.lookupTerm(upperVal); |
| |
| final long inclusiveLowerPoint, inclusiveUpperPoint; |
| |
| // Hints: |
| // * binarySearchLookup returns -1, if value was null. |
| // * the value is <0 if no exact hit was found, the returned value |
| // is (-(insertion point) - 1) |
| if (lowerPoint == -1 && lowerVal == null) { |
| inclusiveLowerPoint = 0; |
| } else if (includeLower && lowerPoint >= 0) { |
| inclusiveLowerPoint = lowerPoint; |
| } else if (lowerPoint >= 0) { |
| inclusiveLowerPoint = lowerPoint + 1; |
| } else { |
| inclusiveLowerPoint = Math.max(0, -lowerPoint - 1); |
| } |
| |
| if (upperPoint == -1 && upperVal == null) { |
| inclusiveUpperPoint = Long.MAX_VALUE; |
| } else if (includeUpper && upperPoint >= 0) { |
| inclusiveUpperPoint = upperPoint; |
| } else if (upperPoint >= 0) { |
| inclusiveUpperPoint = upperPoint - 1; |
| } else { |
| inclusiveUpperPoint = -upperPoint - 2; |
| } |
| |
| if (inclusiveUpperPoint < 0 || inclusiveLowerPoint > inclusiveUpperPoint) { |
| return null; |
| } |
| |
| assert inclusiveLowerPoint >= 0 && inclusiveUpperPoint >= 0; |
| |
| return new DocValuesDocIdSet(context.reader().maxDoc(), acceptDocs) { |
| @Override |
| protected final boolean matchDoc(int doc) { |
| docTermOrds.setDocument(doc); |
| long ord; |
| while ((ord = docTermOrds.nextOrd()) != SortedSetDocValues.NO_MORE_ORDS) { |
| if (ord > inclusiveUpperPoint) { |
| return false; |
| } else if (ord >= inclusiveLowerPoint) { |
| return true; |
| } |
| } |
| return false; |
| } |
| }; |
| } |
| }; |
| } |
| |
| @Override |
| public final String toString() { |
| final StringBuilder sb = new StringBuilder(field).append(":"); |
| return sb.append(includeLower ? '[' : '{') |
| .append((lowerVal == null) ? "*" : lowerVal.toString()) |
| .append(" TO ") |
| .append((upperVal == null) ? "*" : upperVal.toString()) |
| .append(includeUpper ? ']' : '}') |
| .toString(); |
| } |
| |
| @Override |
| public final boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof DocTermOrdsRangeFilter)) return false; |
| DocTermOrdsRangeFilter other = (DocTermOrdsRangeFilter) o; |
| |
| if (!this.field.equals(other.field) |
| || 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 true; |
| } |
| |
| @Override |
| public final int hashCode() { |
| int h = field.hashCode(); |
| h ^= (lowerVal != null) ? lowerVal.hashCode() : 550356204; |
| h = (h << 1) | (h >>> 31); // rotate to distinguish lower from upper |
| h ^= (upperVal != null) ? upperVal.hashCode() : -1674416163; |
| h ^= (includeLower ? 1549299360 : -365038026) ^ (includeUpper ? 1721088258 : 1948649653); |
| return h; |
| } |
| |
| /** Returns the field name for this filter */ |
| public String getField() { return field; } |
| |
| /** 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; } |
| |
| /** Returns the lower value of this range filter */ |
| public BytesRef getLowerVal() { return lowerVal; } |
| |
| /** Returns the upper value of this range filter */ |
| public BytesRef getUpperVal() { return upperVal; } |
| } |