1 package org.apache.lucene.search.spans;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import org.apache.lucene.index.IndexReader;
21 import org.apache.lucene.index.Term;
22 import org.apache.lucene.search.Query;
23 import org.apache.lucene.util.ToStringUtils;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Collection;
30 /** Removes matches which overlap with another SpanQuery. */
31 public class SpanNotQuery extends SpanQuery implements Cloneable {
32 private SpanQuery include;
33 private SpanQuery exclude;
35 /** Construct a SpanNotQuery matching spans from <code>include</code> which
36 * have no overlap with spans from <code>exclude</code>.*/
37 public SpanNotQuery(SpanQuery include, SpanQuery exclude) {
38 this.include = include;
39 this.exclude = exclude;
41 if (!include.getField().equals(exclude.getField()))
42 throw new IllegalArgumentException("Clauses must have same field.");
45 /** Return the SpanQuery whose matches are filtered. */
46 public SpanQuery getInclude() { return include; }
48 /** Return the SpanQuery whose matches must not overlap those returned. */
49 public SpanQuery getExclude() { return exclude; }
52 public String getField() { return include.getField(); }
55 public void extractTerms(Set<Term> terms) { include.extractTerms(terms); }
58 public String toString(String field) {
59 StringBuilder buffer = new StringBuilder();
60 buffer.append("spanNot(");
61 buffer.append(include.toString(field));
63 buffer.append(exclude.toString(field));
65 buffer.append(ToStringUtils.boost(getBoost()));
66 return buffer.toString();
70 public Object clone() {
71 SpanNotQuery spanNotQuery = new SpanNotQuery((SpanQuery)include.clone(),(SpanQuery) exclude.clone());
72 spanNotQuery.setBoost(getBoost());
77 public Spans getSpans(final IndexReader reader) throws IOException {
79 private Spans includeSpans = include.getSpans(reader);
80 private boolean moreInclude = true;
82 private Spans excludeSpans = exclude.getSpans(reader);
83 private boolean moreExclude = excludeSpans.next();
86 public boolean next() throws IOException {
87 if (moreInclude) // move to next include
88 moreInclude = includeSpans.next();
90 while (moreInclude && moreExclude) {
92 if (includeSpans.doc() > excludeSpans.doc()) // skip exclude
93 moreExclude = excludeSpans.skipTo(includeSpans.doc());
95 while (moreExclude // while exclude is before
96 && includeSpans.doc() == excludeSpans.doc()
97 && excludeSpans.end() <= includeSpans.start()) {
98 moreExclude = excludeSpans.next(); // increment exclude
101 if (!moreExclude // if no intersection
102 || includeSpans.doc() != excludeSpans.doc()
103 || includeSpans.end() <= excludeSpans.start())
104 break; // we found a match
106 moreInclude = includeSpans.next(); // intersected: keep scanning
112 public boolean skipTo(int target) throws IOException {
113 if (moreInclude) // skip include
114 moreInclude = includeSpans.skipTo(target);
119 if (moreExclude // skip exclude
120 && includeSpans.doc() > excludeSpans.doc())
121 moreExclude = excludeSpans.skipTo(includeSpans.doc());
123 while (moreExclude // while exclude is before
124 && includeSpans.doc() == excludeSpans.doc()
125 && excludeSpans.end() <= includeSpans.start()) {
126 moreExclude = excludeSpans.next(); // increment exclude
129 if (!moreExclude // if no intersection
130 || includeSpans.doc() != excludeSpans.doc()
131 || includeSpans.end() <= excludeSpans.start())
132 return true; // we found a match
134 return next(); // scan to next match
138 public int doc() { return includeSpans.doc(); }
140 public int start() { return includeSpans.start(); }
142 public int end() { return includeSpans.end(); }
144 // TODO: Remove warning after API has been finalized
146 public Collection<byte[]> getPayload() throws IOException {
147 ArrayList<byte[]> result = null;
148 if (includeSpans.isPayloadAvailable()) {
149 result = new ArrayList<byte[]>(includeSpans.getPayload());
154 // TODO: Remove warning after API has been finalized
156 public boolean isPayloadAvailable() {
157 return includeSpans.isPayloadAvailable();
161 public String toString() {
162 return "spans(" + SpanNotQuery.this.toString() + ")";
169 public Query rewrite(IndexReader reader) throws IOException {
170 SpanNotQuery clone = null;
172 SpanQuery rewrittenInclude = (SpanQuery) include.rewrite(reader);
173 if (rewrittenInclude != include) {
174 clone = (SpanNotQuery) this.clone();
175 clone.include = rewrittenInclude;
177 SpanQuery rewrittenExclude = (SpanQuery) exclude.rewrite(reader);
178 if (rewrittenExclude != exclude) {
179 if (clone == null) clone = (SpanNotQuery) this.clone();
180 clone.exclude = rewrittenExclude;
184 return clone; // some clauses rewrote
186 return this; // no clauses rewrote
190 /** Returns true iff <code>o</code> is equal to this. */
192 public boolean equals(Object o) {
193 if (this == o) return true;
194 if (!(o instanceof SpanNotQuery)) return false;
196 SpanNotQuery other = (SpanNotQuery)o;
197 return this.include.equals(other.include)
198 && this.exclude.equals(other.exclude)
199 && this.getBoost() == other.getBoost();
203 public int hashCode() {
204 int h = include.hashCode();
205 h = (h<<1) | (h >>> 31); // rotate left
206 h ^= exclude.hashCode();
207 h = (h<<1) | (h >>> 31); // rotate left
208 h ^= Float.floatToRawIntBits(getBoost());