
java.org.apache.lucene.search.spans.SpanNearQuery Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lucene Show documentation
Show all versions of lucene Show documentation
Libraries for Elasticsearch
The newest version!
/*
* 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.spans;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermContext;
import org.apache.lucene.index.Terms;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Weight;
/** Matches spans which are near one another. One can specify slop, the
* maximum number of intervening unmatched positions, as well as whether
* matches are required to be in-order.
*/
public class SpanNearQuery extends SpanQuery implements Cloneable {
/**
* A builder for SpanNearQueries
*/
public static class Builder {
private final boolean ordered;
private final String field;
private final List clauses = new LinkedList<>();
private int slop;
/**
* Construct a new builder
* @param field the field to search in
* @param ordered whether or not clauses must be in-order to match
*/
public Builder(String field, boolean ordered) {
this.field = field;
this.ordered = ordered;
}
/**
* Add a new clause
*/
public Builder addClause(SpanQuery clause) {
if (Objects.equals(clause.getField(), field) == false)
throw new IllegalArgumentException("Cannot add clause " + clause + " to SpanNearQuery for field " + field);
this.clauses.add(clause);
return this;
}
/**
* Add a gap after the previous clause of a defined width
*/
public Builder addGap(int width) {
if (!ordered)
throw new IllegalArgumentException("Gaps can only be added to ordered near queries");
this.clauses.add(new SpanGapQuery(field, width));
return this;
}
/**
* Set the slop for this query
*/
public Builder setSlop(int slop) {
this.slop = slop;
return this;
}
/**
* Build the query
*/
public SpanNearQuery build() {
return new SpanNearQuery(clauses.toArray(new SpanQuery[clauses.size()]), slop, ordered);
}
}
/**
* Returns a {@link Builder} for an ordered query on a particular field
*/
public static Builder newOrderedNearQuery(String field) {
return new Builder(field, true);
}
/**
* Returns a {@link Builder} for an unordered query on a particular field
*/
public static Builder newUnorderedNearQuery(String field) {
return new Builder(field, false);
}
protected List clauses;
protected int slop;
protected boolean inOrder;
protected String field;
/** Construct a SpanNearQuery. Matches spans matching a span from each
* clause, with up to slop
total unmatched positions between
* them.
*
When inOrder
is true, the spans from each clause
* must be in the same order as in clauses
and must be non-overlapping.
*
When inOrder
is false, the spans from each clause
* need not be ordered and may overlap.
* @param clausesIn the clauses to find near each other, in the same field, at least 2.
* @param slop The slop value
* @param inOrder true if order is important
*/
public SpanNearQuery(SpanQuery[] clausesIn, int slop, boolean inOrder) {
this.clauses = new ArrayList<>(clausesIn.length);
for (SpanQuery clause : clausesIn) {
if (this.field == null) { // check field
this.field = clause.getField();
} else if (clause.getField() != null && !clause.getField().equals(field)) {
throw new IllegalArgumentException("Clauses must have same field.");
}
this.clauses.add(clause);
}
this.slop = slop;
this.inOrder = inOrder;
}
/** Return the clauses whose spans are matched. */
public SpanQuery[] getClauses() {
return clauses.toArray(new SpanQuery[clauses.size()]);
}
/** Return the maximum number of intervening unmatched positions permitted.*/
public int getSlop() { return slop; }
/** Return true if matches are required to be in-order.*/
public boolean isInOrder() { return inOrder; }
@Override
public String getField() { return field; }
@Override
public String toString(String field) {
StringBuilder buffer = new StringBuilder();
buffer.append("spanNear([");
Iterator i = clauses.iterator();
while (i.hasNext()) {
SpanQuery clause = i.next();
buffer.append(clause.toString(field));
if (i.hasNext()) {
buffer.append(", ");
}
}
buffer.append("], ");
buffer.append(slop);
buffer.append(", ");
buffer.append(inOrder);
buffer.append(")");
return buffer.toString();
}
@Override
public SpanWeight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
List subWeights = new ArrayList<>();
for (SpanQuery q : clauses) {
subWeights.add(q.createWeight(searcher, false, boost));
}
return new SpanNearWeight(subWeights, searcher, needsScores ? getTermContexts(subWeights) : null, boost);
}
public class SpanNearWeight extends SpanWeight {
final List subWeights;
public SpanNearWeight(List subWeights, IndexSearcher searcher, Map terms, float boost) throws IOException {
super(SpanNearQuery.this, searcher, terms, boost);
this.subWeights = subWeights;
}
@Override
public void extractTermContexts(Map contexts) {
for (SpanWeight w : subWeights) {
w.extractTermContexts(contexts);
}
}
@Override
public Spans getSpans(final LeafReaderContext context, Postings requiredPostings) throws IOException {
Terms terms = context.reader().terms(field);
if (terms == null) {
return null; // field does not exist
}
ArrayList subSpans = new ArrayList<>(clauses.size());
for (SpanWeight w : subWeights) {
Spans subSpan = w.getSpans(context, requiredPostings);
if (subSpan != null) {
subSpans.add(subSpan);
} else {
return null; // all required
}
}
// all NearSpans require at least two subSpans
return (!inOrder) ? new NearSpansUnordered(slop, subSpans)
: new NearSpansOrdered(slop, subSpans);
}
@Override
public void extractTerms(Set terms) {
for (SpanWeight w : subWeights) {
w.extractTerms(terms);
}
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
for (Weight w : subWeights) {
if (w.isCacheable(ctx) == false)
return false;
}
return true;
}
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
boolean actuallyRewritten = false;
List rewrittenClauses = new ArrayList<>();
for (int i = 0 ; i < clauses.size(); i++) {
SpanQuery c = clauses.get(i);
SpanQuery query = (SpanQuery) c.rewrite(reader);
actuallyRewritten |= query != c;
rewrittenClauses.add(query);
}
if (actuallyRewritten) {
try {
SpanNearQuery rewritten = (SpanNearQuery) clone();
rewritten.clauses = rewrittenClauses;
return rewritten;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
return super.rewrite(reader);
}
@Override
public boolean equals(Object other) {
return sameClassAs(other) &&
equalsTo(getClass().cast(other));
}
private boolean equalsTo(SpanNearQuery other) {
return inOrder == other.inOrder &&
slop == other.slop &&
clauses.equals(other.clauses);
}
@Override
public int hashCode() {
int result = classHash();
result ^= clauses.hashCode();
result += slop;
int fac = 1 + (inOrder ? 8 : 4);
return fac * result;
}
private static class SpanGapQuery extends SpanQuery {
private final String field;
private final int width;
public SpanGapQuery(String field, int width) {
this.field = field;
this.width = width;
}
@Override
public String getField() {
return field;
}
@Override
public String toString(String field) {
return "SpanGap(" + field + ":" + width + ")";
}
@Override
public SpanWeight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
return new SpanGapWeight(searcher, boost);
}
private class SpanGapWeight extends SpanWeight {
SpanGapWeight(IndexSearcher searcher, float boost) throws IOException {
super(SpanGapQuery.this, searcher, null, boost);
}
@Override
public void extractTermContexts(Map contexts) {
}
@Override
public Spans getSpans(LeafReaderContext ctx, Postings requiredPostings) throws IOException {
return new GapSpans(width);
}
@Override
public void extractTerms(Set terms) {
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return true;
}
}
@Override
public boolean equals(Object other) {
return sameClassAs(other) &&
equalsTo(getClass().cast(other));
}
private boolean equalsTo(SpanGapQuery other) {
return width == other.width &&
field.equals(other.field);
}
@Override
public int hashCode() {
int result = classHash();
result -= 7 * width;
return result * 15 - field.hashCode();
}
}
static class GapSpans extends Spans {
int doc = -1;
int pos = -1;
final int width;
GapSpans(int width) {
this.width = width;
}
@Override
public int nextStartPosition() throws IOException {
return ++pos;
}
public int skipToPosition(int position) throws IOException {
return pos = position;
}
@Override
public int startPosition() {
return pos;
}
@Override
public int endPosition() {
return pos + width;
}
@Override
public int width() {
return width;
}
@Override
public void collect(SpanCollector collector) throws IOException {
}
@Override
public int docID() {
return doc;
}
@Override
public int nextDoc() throws IOException {
pos = -1;
return ++doc;
}
@Override
public int advance(int target) throws IOException {
pos = -1;
return doc = target;
}
@Override
public long cost() {
return 0;
}
@Override
public float positionsCost() {
return 0;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy