--- /dev/null
+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.IndexReader;
+import org.apache.lucene.util.PriorityQueue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * Similar to {@link NearSpansOrdered}, but for the unordered case.
+ *
+ * Expert:
+ * Only public for subclassing. Most implementations should not need this class
+ */
+public class NearSpansUnordered extends Spans {
+ private SpanNearQuery query;
+
+ private List<SpansCell> ordered = new ArrayList<SpansCell>(); // spans in query order
+ private Spans[] subSpans;
+ private int slop; // from query
+
+ private SpansCell first; // linked list of spans
+ private SpansCell last; // sorted by doc only
+
+ private int totalLength; // sum of current lengths
+
+ private CellQueue queue; // sorted queue of spans
+ private SpansCell max; // max element in queue
+
+ private boolean more = true; // true iff not done
+ private boolean firstTime = true; // true before first next()
+
+ private class CellQueue extends PriorityQueue<SpansCell> {
+ public CellQueue(int size) {
+ initialize(size);
+ }
+
+ @Override
+ protected final boolean lessThan(SpansCell spans1, SpansCell spans2) {
+ if (spans1.doc() == spans2.doc()) {
+ return NearSpansOrdered.docSpansOrdered(spans1, spans2);
+ } else {
+ return spans1.doc() < spans2.doc();
+ }
+ }
+ }
+
+
+ /** Wraps a Spans, and can be used to form a linked list. */
+ private class SpansCell extends Spans {
+ private Spans spans;
+ private SpansCell next;
+ private int length = -1;
+ private int index;
+
+ public SpansCell(Spans spans, int index) {
+ this.spans = spans;
+ this.index = index;
+ }
+
+ @Override
+ public boolean next() throws IOException {
+ return adjust(spans.next());
+ }
+
+ @Override
+ public boolean skipTo(int target) throws IOException {
+ return adjust(spans.skipTo(target));
+ }
+
+ private boolean adjust(boolean condition) {
+ if (length != -1) {
+ totalLength -= length; // subtract old length
+ }
+ if (condition) {
+ length = end() - start();
+ totalLength += length; // add new length
+
+ if (max == null || doc() > max.doc()
+ || (doc() == max.doc()) && (end() > max.end())) {
+ max = this;
+ }
+ }
+ more = condition;
+ return condition;
+ }
+
+ @Override
+ public int doc() { return spans.doc(); }
+
+ @Override
+ public int start() { return spans.start(); }
+
+ @Override
+ public int end() { return spans.end(); }
+ // TODO: Remove warning after API has been finalized
+ @Override
+ public Collection<byte[]> getPayload() throws IOException {
+ return new ArrayList<byte[]>(spans.getPayload());
+ }
+
+ // TODO: Remove warning after API has been finalized
+ @Override
+ public boolean isPayloadAvailable() {
+ return spans.isPayloadAvailable();
+ }
+
+ @Override
+ public String toString() { return spans.toString() + "#" + index; }
+ }
+
+
+ public NearSpansUnordered(SpanNearQuery query, IndexReader reader)
+ throws IOException {
+ this.query = query;
+ this.slop = query.getSlop();
+
+ SpanQuery[] clauses = query.getClauses();
+ queue = new CellQueue(clauses.length);
+ subSpans = new Spans[clauses.length];
+ for (int i = 0; i < clauses.length; i++) {
+ SpansCell cell =
+ new SpansCell(clauses[i].getSpans(reader), i);
+ ordered.add(cell);
+ subSpans[i] = cell.spans;
+ }
+ }
+ public Spans[] getSubSpans() {
+ return subSpans;
+ }
+ @Override
+ public boolean next() throws IOException {
+ if (firstTime) {
+ initList(true);
+ listToQueue(); // initialize queue
+ firstTime = false;
+ } else if (more) {
+ if (min().next()) { // trigger further scanning
+ queue.updateTop(); // maintain queue
+ } else {
+ more = false;
+ }
+ }
+
+ while (more) {
+
+ boolean queueStale = false;
+
+ if (min().doc() != max.doc()) { // maintain list
+ queueToList();
+ queueStale = true;
+ }
+
+ // skip to doc w/ all clauses
+
+ while (more && first.doc() < last.doc()) {
+ more = first.skipTo(last.doc()); // skip first upto last
+ firstToLast(); // and move it to the end
+ queueStale = true;
+ }
+
+ if (!more) return false;
+
+ // found doc w/ all clauses
+
+ if (queueStale) { // maintain the queue
+ listToQueue();
+ queueStale = false;
+ }
+
+ if (atMatch()) {
+ return true;
+ }
+
+ more = min().next();
+ if (more) {
+ queue.updateTop(); // maintain queue
+ }
+ }
+ return false; // no more matches
+ }
+
+ @Override
+ public boolean skipTo(int target) throws IOException {
+ if (firstTime) { // initialize
+ initList(false);
+ for (SpansCell cell = first; more && cell!=null; cell=cell.next) {
+ more = cell.skipTo(target); // skip all
+ }
+ if (more) {
+ listToQueue();
+ }
+ firstTime = false;
+ } else { // normal case
+ while (more && min().doc() < target) { // skip as needed
+ if (min().skipTo(target)) {
+ queue.updateTop();
+ } else {
+ more = false;
+ }
+ }
+ }
+ return more && (atMatch() || next());
+ }
+
+ private SpansCell min() { return queue.top(); }
+
+ @Override
+ public int doc() { return min().doc(); }
+ @Override
+ public int start() { return min().start(); }
+ @Override
+ public int end() { return max.end(); }
+
+ // TODO: Remove warning after API has been finalized
+ /**
+ * WARNING: The List is not necessarily in order of the the positions
+ * @return Collection of <code>byte[]</code> payloads
+ * @throws IOException
+ */
+ @Override
+ public Collection<byte[]> getPayload() throws IOException {
+ Set<byte[]> matchPayload = new HashSet<byte[]>();
+ for (SpansCell cell = first; cell != null; cell = cell.next) {
+ if (cell.isPayloadAvailable()) {
+ matchPayload.addAll(cell.getPayload());
+ }
+ }
+ return matchPayload;
+ }
+
+ // TODO: Remove warning after API has been finalized
+ @Override
+ public boolean isPayloadAvailable() {
+ SpansCell pointer = min();
+ while (pointer != null) {
+ if (pointer.isPayloadAvailable()) {
+ return true;
+ }
+ pointer = pointer.next;
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "("+query.toString()+")@"+
+ (firstTime?"START":(more?(doc()+":"+start()+"-"+end()):"END"));
+ }
+
+ private void initList(boolean next) throws IOException {
+ for (int i = 0; more && i < ordered.size(); i++) {
+ SpansCell cell = ordered.get(i);
+ if (next)
+ more = cell.next(); // move to first entry
+ if (more) {
+ addToList(cell); // add to list
+ }
+ }
+ }
+
+ private void addToList(SpansCell cell) throws IOException {
+ if (last != null) { // add next to end of list
+ last.next = cell;
+ } else
+ first = cell;
+ last = cell;
+ cell.next = null;
+ }
+
+ private void firstToLast() {
+ last.next = first; // move first to end of list
+ last = first;
+ first = first.next;
+ last.next = null;
+ }
+
+ private void queueToList() throws IOException {
+ last = first = null;
+ while (queue.top() != null) {
+ addToList(queue.pop());
+ }
+ }
+
+ private void listToQueue() {
+ queue.clear(); // rebuild queue
+ for (SpansCell cell = first; cell != null; cell = cell.next) {
+ queue.add(cell); // add to queue from list
+ }
+ }
+
+ private boolean atMatch() {
+ return (min().doc() == max.doc())
+ && ((max.end() - min().start() - totalLength) <= slop);
+ }
+}