All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.lucene.search.spans.SpanNearQuery Maven / Gradle / Ivy

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.util.ToStringUtils;

/** 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 clauses 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[] clauses, int slop, boolean inOrder) { this(clauses, slop, inOrder, true); } /** * @deprecated Use {@link #SpanNearQuery(SpanQuery[], int, boolean)} */ @Deprecated public SpanNearQuery(SpanQuery[] clausesIn, int slop, boolean inOrder, boolean collectPayloads) { 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(")"); buffer.append(ToStringUtils.boost(getBoost())); return buffer.toString(); } @Override public SpanWeight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException { List subWeights = new ArrayList<>(); for (SpanQuery q : clauses) { subWeights.add(q.createWeight(searcher, false)); } return new SpanNearWeight(subWeights, searcher, needsScores ? getTermContexts(subWeights) : null); } public class SpanNearWeight extends SpanWeight { final List subWeights; public SpanNearWeight(List subWeights, IndexSearcher searcher, Map terms) throws IOException { super(SpanNearQuery.this, searcher, terms); 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 Query rewrite(IndexReader reader) throws IOException { if (getBoost() != 1f) { return super.rewrite(reader); } 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) { SpanNearQuery rewritten = (SpanNearQuery) clone(); rewritten.clauses = rewrittenClauses; return rewritten; } return super.rewrite(reader); } /** Returns true iff o is equal to this. */ @Override public boolean equals(Object o) { if (! super.equals(o)) { return false; } final SpanNearQuery spanNearQuery = (SpanNearQuery) o; return (inOrder == spanNearQuery.inOrder) && (slop == spanNearQuery.slop) && clauses.equals(spanNearQuery.clauses); } @Override public int hashCode() { int result = super.hashCode(); 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) throws IOException { return new SpanGapWeight(searcher); } private class SpanGapWeight extends SpanWeight { SpanGapWeight(IndexSearcher searcher) throws IOException { super(SpanGapQuery.this, searcher, null); } @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) { } } } 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