| package org.apache.lucene.search.spans; |
| |
| /* |
| * 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 org.apache.lucene.index.AtomicReaderContext; |
| import org.apache.lucene.index.IndexReader; |
| import org.apache.lucene.index.Term; |
| import org.apache.lucene.index.TermContext; |
| import org.apache.lucene.search.Query; |
| import org.apache.lucene.util.Bits; |
| import org.apache.lucene.util.ToStringUtils; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** Removes matches which overlap with another SpanQuery or |
| * within a x tokens before or y tokens after another SpanQuery. */ |
| public class SpanNotQuery extends SpanQuery implements Cloneable { |
| private SpanQuery include; |
| private SpanQuery exclude; |
| private final int pre; |
| private final int post; |
| |
| /** Construct a SpanNotQuery matching spans from <code>include</code> which |
| * have no overlap with spans from <code>exclude</code>.*/ |
| public SpanNotQuery(SpanQuery include, SpanQuery exclude) { |
| this(include, exclude, 0, 0); |
| } |
| |
| |
| /** Construct a SpanNotQuery matching spans from <code>include</code> which |
| * have no overlap with spans from <code>exclude</code> within |
| * <code>dist</code> tokens of <code>include</code>. */ |
| public SpanNotQuery(SpanQuery include, SpanQuery exclude, int dist) { |
| this(include, exclude, dist, dist); |
| } |
| |
| /** Construct a SpanNotQuery matching spans from <code>include</code> which |
| * have no overlap with spans from <code>exclude</code> within |
| * <code>pre</code> tokens before or <code>post</code> tokens of <code>include</code>. */ |
| public SpanNotQuery(SpanQuery include, SpanQuery exclude, int pre, int post) { |
| this.include = include; |
| this.exclude = exclude; |
| this.pre = (pre >=0) ? pre : 0; |
| this.post = (post >= 0) ? post : 0; |
| |
| if (include.getField() != null && exclude.getField() != null && !include.getField().equals(exclude.getField())) |
| throw new IllegalArgumentException("Clauses must have same field."); |
| } |
| |
| /** Return the SpanQuery whose matches are filtered. */ |
| public SpanQuery getInclude() { return include; } |
| |
| /** Return the SpanQuery whose matches must not overlap those returned. */ |
| public SpanQuery getExclude() { return exclude; } |
| |
| @Override |
| public String getField() { return include.getField(); } |
| |
| @Override |
| public void extractTerms(Set<Term> terms) { include.extractTerms(terms); } |
| |
| @Override |
| public String toString(String field) { |
| StringBuilder buffer = new StringBuilder(); |
| buffer.append("spanNot("); |
| buffer.append(include.toString(field)); |
| buffer.append(", "); |
| buffer.append(exclude.toString(field)); |
| buffer.append(", "); |
| buffer.append(Integer.toString(pre)); |
| buffer.append(", "); |
| buffer.append(Integer.toString(post)); |
| buffer.append(")"); |
| buffer.append(ToStringUtils.boost(getBoost())); |
| return buffer.toString(); |
| } |
| |
| @Override |
| public SpanNotQuery clone() { |
| SpanNotQuery spanNotQuery = new SpanNotQuery((SpanQuery)include.clone(), |
| (SpanQuery) exclude.clone(), pre, post); |
| spanNotQuery.setBoost(getBoost()); |
| return spanNotQuery; |
| } |
| |
| @Override |
| public Spans getSpans(final AtomicReaderContext context, final Bits acceptDocs, final Map<Term,TermContext> termContexts) throws IOException { |
| return new Spans() { |
| private Spans includeSpans = include.getSpans(context, acceptDocs, termContexts); |
| private boolean moreInclude = true; |
| |
| private Spans excludeSpans = exclude.getSpans(context, acceptDocs, termContexts); |
| private boolean moreExclude = excludeSpans.next(); |
| |
| @Override |
| public boolean next() throws IOException { |
| if (moreInclude) // move to next include |
| moreInclude = includeSpans.next(); |
| |
| while (moreInclude && moreExclude) { |
| |
| if (includeSpans.doc() > excludeSpans.doc()) // skip exclude |
| moreExclude = excludeSpans.skipTo(includeSpans.doc()); |
| |
| while (moreExclude // while exclude is before |
| && includeSpans.doc() == excludeSpans.doc() |
| && excludeSpans.end() <= includeSpans.start() - pre) { |
| moreExclude = excludeSpans.next(); // increment exclude |
| } |
| |
| if (!moreExclude // if no intersection |
| || includeSpans.doc() != excludeSpans.doc() |
| || includeSpans.end()+post <= excludeSpans.start()) |
| break; // we found a match |
| |
| moreInclude = includeSpans.next(); // intersected: keep scanning |
| } |
| return moreInclude; |
| } |
| |
| @Override |
| public boolean skipTo(int target) throws IOException { |
| if (moreInclude) // skip include |
| moreInclude = includeSpans.skipTo(target); |
| |
| if (!moreInclude) |
| return false; |
| |
| if (moreExclude // skip exclude |
| && includeSpans.doc() > excludeSpans.doc()) |
| moreExclude = excludeSpans.skipTo(includeSpans.doc()); |
| |
| while (moreExclude // while exclude is before |
| && includeSpans.doc() == excludeSpans.doc() |
| && excludeSpans.end() <= includeSpans.start()-pre) { |
| moreExclude = excludeSpans.next(); // increment exclude |
| } |
| |
| if (!moreExclude // if no intersection |
| || includeSpans.doc() != excludeSpans.doc() |
| || includeSpans.end()+post <= excludeSpans.start()) |
| return true; // we found a match |
| |
| return next(); // scan to next match |
| } |
| |
| @Override |
| public int doc() { return includeSpans.doc(); } |
| @Override |
| public int start() { return includeSpans.start(); } |
| @Override |
| public int end() { return includeSpans.end(); } |
| |
| // TODO: Remove warning after API has been finalized |
| @Override |
| public Collection<byte[]> getPayload() throws IOException { |
| ArrayList<byte[]> result = null; |
| if (includeSpans.isPayloadAvailable()) { |
| result = new ArrayList<>(includeSpans.getPayload()); |
| } |
| return result; |
| } |
| |
| // TODO: Remove warning after API has been finalized |
| @Override |
| public boolean isPayloadAvailable() throws IOException { |
| return includeSpans.isPayloadAvailable(); |
| } |
| |
| @Override |
| public long cost() { |
| return includeSpans.cost(); |
| } |
| |
| @Override |
| public String toString() { |
| return "spans(" + SpanNotQuery.this.toString() + ")"; |
| } |
| |
| }; |
| } |
| |
| @Override |
| public Query rewrite(IndexReader reader) throws IOException { |
| SpanNotQuery clone = null; |
| |
| SpanQuery rewrittenInclude = (SpanQuery) include.rewrite(reader); |
| if (rewrittenInclude != include) { |
| clone = this.clone(); |
| clone.include = rewrittenInclude; |
| } |
| SpanQuery rewrittenExclude = (SpanQuery) exclude.rewrite(reader); |
| if (rewrittenExclude != exclude) { |
| if (clone == null) clone = this.clone(); |
| clone.exclude = rewrittenExclude; |
| } |
| |
| if (clone != null) { |
| return clone; // some clauses rewrote |
| } else { |
| return this; // no clauses rewrote |
| } |
| } |
| |
| /** Returns true iff <code>o</code> is equal to this. */ |
| @Override |
| public boolean equals(Object o) { |
| if (!super.equals(o)) |
| return false; |
| |
| SpanNotQuery other = (SpanNotQuery)o; |
| return this.include.equals(other.include) |
| && this.exclude.equals(other.exclude) |
| && this.pre == other.pre |
| && this.post == other.post; |
| } |
| |
| @Override |
| public int hashCode() { |
| int h = super.hashCode(); |
| h = Integer.rotateLeft(h, 1); |
| h ^= include.hashCode(); |
| h = Integer.rotateLeft(h, 1); |
| h ^= exclude.hashCode(); |
| h = Integer.rotateLeft(h, 1); |
| h ^= pre; |
| h = Integer.rotateLeft(h, 1); |
| h ^= post; |
| return h; |
| } |
| |
| } |