Index: lucene/core/src/java/org/apache/lucene/search/positions/ConjunctionIntervalIterator.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/positions/ConjunctionIntervalIterator.java (revision 1400508) +++ lucene/core/src/java/org/apache/lucene/search/positions/ConjunctionIntervalIterator.java (working copy) @@ -16,13 +16,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import java.io.IOException; - import org.apache.lucene.search.Scorer; import org.apache.lucene.search.positions.IntervalQueue.IntervalRef; -import org.apache.lucene.util.ArrayUtil; -import org.apache.lucene.util.RamUsageEstimator; +import java.io.IOException; + /** * ConjuctionPositionIterator based on minimal interval semantics for AND * operator. @@ -104,7 +102,7 @@ for (int i = 0; i < iterators.length; i++) { int scorerAdvanced = iterators[i].scorerAdvanced(docId); if (scorerAdvanced != docId) { - System.out.println(); + System.out.println(); // nocommit! } assert scorerAdvanced == docId; final Interval interval = iterators[i].next(); @@ -146,91 +144,6 @@ snapshot.replay(collector); } } - - /* - * Due to the laziness of this position iterator and the minimizing algorithm - * we advance the underlying iterators before the consumer can call collect on - * the top level iterator. If we need to collect positions we need to record - * the last possible match in order to allow the consumer to get the right - * positions for the match. This is particularly important if leaf positions - * are required. - */ - private static final class SnapshotPositionCollector implements - IntervalCollector { - private SingleSnapshot[] snapshots; - private int index = 0; - - SnapshotPositionCollector(int subs) { - snapshots = new SingleSnapshot[subs]; - } - - @Override - public void collectLeafPosition(Scorer scorer, Interval interval, - int docID) { - collect(scorer, interval, docID, true); - - } - - private void collect(Scorer scorer, Interval interval, int docID, - boolean isLeaf) { - if (snapshots.length <= index) { - grow(ArrayUtil.oversize(index + 1, - (RamUsageEstimator.NUM_BYTES_OBJECT_REF * 2) - + RamUsageEstimator.NUM_BYTES_OBJECT_HEADER - + RamUsageEstimator.NUM_BYTES_BOOLEAN - + RamUsageEstimator.NUM_BYTES_INT)); - } - if (snapshots[index] == null) { - snapshots[index] = new SingleSnapshot(); - } - snapshots[index++].set(scorer, interval, isLeaf, docID); - } - - @Override - public void collectComposite(Scorer scorer, Interval interval, - int docID) { - collect(scorer, interval, docID, false); - } - - void replay(IntervalCollector collector) { - for (int i = 0; i < index; i++) { - SingleSnapshot singleSnapshot = snapshots[i]; - if (singleSnapshot.isLeaf) { - collector.collectLeafPosition(singleSnapshot.scorer, - singleSnapshot.interval, singleSnapshot.docID); - } else { - collector.collectComposite(singleSnapshot.scorer, - singleSnapshot.interval, singleSnapshot.docID); - } - } - } - - void reset() { - index = 0; - } - - private void grow(int size) { - final SingleSnapshot[] newArray = new SingleSnapshot[size]; - System.arraycopy(snapshots, 0, newArray, 0, index); - snapshots = newArray; - } - - private static final class SingleSnapshot { - Scorer scorer; - final Interval interval = new Interval(); - boolean isLeaf; - int docID; - - void set(Scorer scorer, Interval interval, boolean isLeaf, - int docID) { - this.scorer = scorer; - this.interval.copy(interval); - this.isLeaf = isLeaf; - this.docID = docID; - } - } - - } @Override public int matchDistance() { Index: lucene/core/src/java/org/apache/lucene/search/positions/OrderedConjunctionIntervalIterator.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/positions/OrderedConjunctionIntervalIterator.java (revision 1400508) +++ lucene/core/src/java/org/apache/lucene/search/positions/OrderedConjunctionIntervalIterator.java (working copy) @@ -16,10 +16,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import org.apache.lucene.search.Scorer; + import java.io.IOException; -import org.apache.lucene.search.Scorer; - /** * @lucene.experimental */ // nocommit - javadoc @@ -27,27 +27,20 @@ IntervalIterator { private final IntervalIterator[] iterators; - private static final Interval INFINITE_INTERVAL = new Interval( - Integer.MIN_VALUE, Integer.MIN_VALUE, -1, -1); private final Interval[] intervals; private final int lastIter; - private final Interval interval = new Interval( - Integer.MAX_VALUE, Integer.MAX_VALUE, -1, -1); + private final Interval interval = new Interval(); + private int index = 1; - private int lastTopEnd; - private int lastEndBegin; - - + private int matchDistance = 0; + + private SnapshotPositionCollector snapshot = null; + public OrderedConjunctionIntervalIterator(boolean collectPositions, IntervalIterator other) { - super(other.scorer, collectPositions); - assert other.subs(true) != null; - iterators = other.subs(true); - assert iterators.length > 1; - intervals = new Interval[iterators.length]; - lastIter = iterators.length - 1; + this(other.scorer, collectPositions, other.subs(true)); } - public OrderedConjunctionIntervalIterator(Scorer scorer, boolean collectPositions, IntervalIterator... iterators) throws IOException { + public OrderedConjunctionIntervalIterator(Scorer scorer, boolean collectPositions, IntervalIterator... iterators) { super(scorer, collectPositions); this.iterators = iterators; assert iterators.length > 1; @@ -60,10 +53,7 @@ if(intervals[0] == null) { return null; } - interval.begin = Integer.MAX_VALUE; - interval.end = Integer.MAX_VALUE; - interval.offsetBegin = -1; - interval.offsetEnd = -1; + interval.setMaximum(); int b = Integer.MAX_VALUE; while (true) { while (true) { @@ -84,20 +74,17 @@ } while (current.begin <= previous.end); index++; } - interval.begin = intervals[0].begin; - interval.end = intervals[lastIter].end; - interval.offsetBegin = intervals[0].offsetBegin; - interval.offsetEnd = intervals[lastIter].offsetEnd; - lastTopEnd = intervals[0].end; - lastEndBegin = intervals[lastIter].begin; + interval.update(intervals[0], intervals[lastIter]); + matchDistance = (intervals[lastIter].begin - lastIter) - intervals[0].end; b = intervals[lastIter].begin; index = 1; + if (collectPositions) + snapshotSubPositions(); intervals[0] = iterators[0].next(); if (intervals[0] == null) { return interval.begin == Integer.MAX_VALUE ? null : interval; } } - } @Override @@ -108,10 +95,29 @@ @Override public void collect(IntervalCollector collector) { assert collectPositions; + if (snapshot == null) { + // we might not be initialized if the first interval matches + collectInternal(collector); + } else { + snapshot.replay(collector); + } + } + + private void snapshotSubPositions() { + if (snapshot == null) { + snapshot = new SnapshotPositionCollector(iterators.length); + } + snapshot.reset(); + collectInternal(snapshot); + } + + private void collectInternal(IntervalCollector collector) { + assert collectPositions; collector.collectComposite(scorer, interval, docID()); for (IntervalIterator iter : iterators) { iter.collect(collector); } + } @Override @@ -120,7 +126,7 @@ for (int i = 0; i < iterators.length; i++) { int advanceTo = iterators[i].scorerAdvanced(docId); assert advanceTo == docId; - intervals[i] = INFINITE_INTERVAL; + intervals[i] = Interval.INFINITE_INTERVAL; } intervals[0] = iterators[0].next(); index = 1; @@ -129,7 +135,7 @@ @Override public int matchDistance() { - return (lastEndBegin-lastIter) - lastTopEnd; + return matchDistance; } } Index: lucene/core/src/java/org/apache/lucene/search/positions/IntervalFilterQuery.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/positions/IntervalFilterQuery.java (revision 1400508) +++ lucene/core/src/java/org/apache/lucene/search/positions/IntervalFilterQuery.java (working copy) @@ -20,9 +20,13 @@ import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; -import org.apache.lucene.search.*; +import org.apache.lucene.search.ComplexExplanation; +import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.Weight; import org.apache.lucene.search.Weight.PostingFeatures; -import org.apache.lucene.search.positions.IntervalIterator.IntervalCollector; import org.apache.lucene.search.positions.IntervalIterator.IntervalFilter; import org.apache.lucene.util.Bits; @@ -30,15 +34,21 @@ import java.util.Set; /** + * A Query that filters the results of an inner {@link Query} using an + * {@link IntervalFilter}. * * @lucene.experimental - */ // nocommit - javadoc + */ public class IntervalFilterQuery extends Query implements Cloneable { - private Query inner; private IntervalFilter filter; + /** + * Constructs a query using an inner query and an IntervalFilter + * @param inner the query to wrap + * @param filter the filter to restrict results by + */ public IntervalFilterQuery(Query inner, IntervalFilter filter) { this.inner = inner; this.filter = filter; @@ -82,8 +92,8 @@ @Override public Explanation explain(AtomicReaderContext context, int doc) throws IOException { - Scorer scorer = scorer(context, true, false, PostingFeatures.POSITIONS, context.reader() - .getLiveDocs()); + Scorer scorer = scorer(context, true, false, PostingFeatures.POSITIONS, + context.reader().getLiveDocs()); if (scorer != null) { int newDoc = scorer.advance(doc); if (newDoc == doc) { Index: lucene/core/src/java/org/apache/lucene/search/positions/Interval.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/positions/Interval.java (revision 1400508) +++ lucene/core/src/java/org/apache/lucene/search/positions/Interval.java (working copy) @@ -16,65 +16,146 @@ * limitations under the License. */ -import java.io.IOException; - import org.apache.lucene.util.BytesRef; +import java.io.IOException; +/** + * Represents a section of a document that matches a query + */ public class Interval implements Cloneable { - + + /** The position of the start of this Interval */ public int begin; + + /** The position of the end of this Interval */ public int end; + + /** The offset of the start of this Interval */ public int offsetBegin; + + /** The offset of the end of this Interval */ public int offsetEnd; - + + /** An interval that will always compare as less than any other interval */ + public static final Interval INFINITE_INTERVAL = new Interval(); + + /** + * Constructs a new Interval + * @param begin the start position + * @param end the end position + * @param offsetBegin the start offset + * @param offsetEnd the end offset + */ public Interval(int begin, int end, int offsetBegin, int offsetEnd) { this.begin = begin; this.end = end; this.offsetBegin = offsetBegin; this.offsetEnd = offsetEnd; } - + + /** + * Constructs a new Interval with no initial values. This + * will always compare as less than any other Interval. + */ public Interval() { this(Integer.MIN_VALUE, Integer.MIN_VALUE, -1, -1); } - + + /** + * Update to span the range defined by two other Intervals. + * @param start the first Interval + * @param end the second Interval + */ + public void update(Interval start, Interval end) { + this.begin = start.begin; + this.offsetBegin = start.offsetBegin; + this.end = end.end; + this.offsetEnd = end.offsetEnd; + } + + /** + * Compare with another Interval. + * @param other the comparator + * @return true if both start and end positions are less than + * the comparator. + */ public boolean lessThanExclusive(Interval other) { return begin < other.begin && end < other.end; } - + + /** + * Compare with another Interval. + * @param other the comparator + * @return true if both start and end positions are less than + * or equal to the comparator's. + */ public boolean lessThan(Interval other) { return begin <= other.begin && end <= other.end; } - + + /** + * Compare with another Interval + * @param other the comparator + * @return true if both start and end positions are greater then + * the comparator's. + */ public boolean greaterThanExclusive(Interval other) { return begin > other.begin && end > other.end; } - + + /** + * Compare with another Interval + * @param other the comparator + * @return true if both start and end positions are greater then + * of equal to the comparator's. + */ public boolean greaterThan(Interval other) { return begin >= other.begin && end >= other.end; } - + + /** + * Compare with another Interval + * @param other the comparator + * @return true if this Interval contains the comparator + */ public boolean contains(Interval other) { return begin <= other.begin && other.end <= end; } - + + /** + * Set all values of this Interval to be equal to another's + * @param other the Interval to copy + */ public void copy(Interval other) { begin = other.begin; end = other.end; offsetBegin = other.offsetBegin; offsetEnd = other.offsetEnd; } - + + // nocommit javadocs public BytesRef nextPayload() throws IOException { return null; } - - + + /** + * Set to a state that will always compare as less than any + * other Interval. + */ public void reset() { offsetBegin = offsetEnd = -1; begin = end = Integer.MIN_VALUE; } + + /** + * Set to a state that will always compare as more than any + * other Interval. + */ + public void setMaximum() { + offsetBegin = offsetEnd = -1; + begin = end = Integer.MAX_VALUE; + } @Override public Object clone() { @@ -90,5 +171,5 @@ return "Interval [begin=" + begin + "(" + offsetBegin + "), end=" + end + "(" + offsetEnd + ")]"; } - + } \ No newline at end of file Index: lucene/core/src/java/org/apache/lucene/search/positions/SnapshotPositionCollector.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/positions/SnapshotPositionCollector.java (revision 0) +++ lucene/core/src/java/org/apache/lucene/search/positions/SnapshotPositionCollector.java (revision 0) @@ -0,0 +1,107 @@ +package org.apache.lucene.search.positions; + +/* + * 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.search.Scorer; +import org.apache.lucene.util.ArrayUtil; +import org.apache.lucene.util.RamUsageEstimator; + +/* + * Due to the laziness of Conjunction position iterators and the minimizing algorithm + * we advance the underlying iterators before the consumer can call collect on + * the top level iterator. If we need to collect positions we need to record + * the last possible match in order to allow the consumer to get the right + * positions for the match. This is particularly important if leaf positions + * are required. + */ +final class SnapshotPositionCollector implements + IntervalIterator.IntervalCollector { + private SingleSnapshot[] snapshots; + private int index = 0; + + SnapshotPositionCollector(int subs) { + snapshots = new SingleSnapshot[subs]; + } + + @Override + public void collectLeafPosition(Scorer scorer, Interval interval, + int docID) { + collect(scorer, interval, docID, true); + + } + + private void collect(Scorer scorer, Interval interval, int docID, + boolean isLeaf) { + if (snapshots.length <= index) { + grow(ArrayUtil.oversize(index + 1, + (RamUsageEstimator.NUM_BYTES_OBJECT_REF * 2) + + RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + + RamUsageEstimator.NUM_BYTES_BOOLEAN + + RamUsageEstimator.NUM_BYTES_INT)); + } + if (snapshots[index] == null) { + snapshots[index] = new SingleSnapshot(); + } + snapshots[index++].set(scorer, interval, isLeaf, docID); + } + + @Override + public void collectComposite(Scorer scorer, Interval interval, + int docID) { + collect(scorer, interval, docID, false); + } + + void replay(IntervalIterator.IntervalCollector collector) { + for (int i = 0; i < index; i++) { + SingleSnapshot singleSnapshot = snapshots[i]; + if (singleSnapshot.isLeaf) { + collector.collectLeafPosition(singleSnapshot.scorer, + singleSnapshot.interval, singleSnapshot.docID); + } else { + collector.collectComposite(singleSnapshot.scorer, + singleSnapshot.interval, singleSnapshot.docID); + } + } + } + + void reset() { + index = 0; + } + + private void grow(int size) { + final SingleSnapshot[] newArray = new SingleSnapshot[size]; + System.arraycopy(snapshots, 0, newArray, 0, index); + snapshots = newArray; + } + + private static final class SingleSnapshot { + Scorer scorer; + final Interval interval = new Interval(); + boolean isLeaf; + int docID; + + void set(Scorer scorer, Interval interval, boolean isLeaf, + int docID) { + this.scorer = scorer; + this.interval.copy(interval); + this.isLeaf = isLeaf; + this.docID = docID; + } + } + +} Index: lucene/core/src/java/org/apache/lucene/search/positions/IntervalIterator.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/positions/IntervalIterator.java (revision 1400508) +++ lucene/core/src/java/org/apache/lucene/search/positions/IntervalIterator.java (working copy) @@ -30,7 +30,7 @@ public static final IntervalIterator[] EMPTY = new IntervalIterator[0]; public static final IntervalIterator NO_MORE_POSITIONS = new EmptyIntervalIterator(); public static final int NO_MORE_DOCS = Integer.MAX_VALUE; - + protected final Scorer scorer; protected final boolean collectPositions; @@ -38,11 +38,29 @@ this.scorer = scorer; this.collectPositions = collectPositions; } - + + /** + * Called after the parent scorer has been advanced. If the scorer is + * currently positioned on docId, then subsequent calls to next() will + * return Intervals for that document; otherwise, no Intervals are + * available + * @param docId the document the parent scorer was advanced to + * @return the docId that the scorer is currently positioned at + * @throws IOException if a low-level I/O error is encountered + */ public abstract int scorerAdvanced(int docId) throws IOException; - + + /** + * Get the next Interval on the current document. + * @return the next Interval, or null if there are no remaining Intervals + * @throws IOException if a low-level I/O error is encountered + */ public abstract Interval next() throws IOException; - + + /** + * Called once for each positional match on a document + * @param collector + */ public abstract void collect(IntervalCollector collector); public abstract IntervalIterator[] subs(boolean inOrder); Index: lucene/core/src/java/org/apache/lucene/search/Scorer.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/Scorer.java (revision 1400508) +++ lucene/core/src/java/org/apache/lucene/search/Scorer.java (working copy) @@ -64,8 +64,14 @@ collector.collect(doc); } } - - //nocommit javadocs + + /** + * Expert: Collects matching positions on the current document. + * + * @param collectPositions // nocommit, still not sure what this does! + * @return an {@link IntervalIterator} over matching positions + * @throws IOException if a low-level I/O error is encountered + */ public abstract IntervalIterator positions(boolean collectPositions) throws IOException; /** Index: lucene/highlighter/src/test/org/apache/lucene/search/highlight/positions/IntervalHighlighterTest.java =================================================================== --- lucene/highlighter/src/test/org/apache/lucene/search/highlight/positions/IntervalHighlighterTest.java (revision 1400508) +++ lucene/highlighter/src/test/org/apache/lucene/search/highlight/positions/IntervalHighlighterTest.java (working copy) @@ -34,21 +34,27 @@ import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.index.Term; -import org.apache.lucene.search.*; +import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.ConstantScoreQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MultiPhraseQuery; +import org.apache.lucene.search.MultiTermQuery; +import org.apache.lucene.search.PhraseQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.WildcardQuery; import org.apache.lucene.search.highlight.Highlighter; import org.apache.lucene.search.highlight.InvalidTokenOffsetsException; import org.apache.lucene.search.highlight.SimpleFragmenter; import org.apache.lucene.search.highlight.TextFragment; -import org.apache.lucene.search.highlight.positions.HighlightingIntervalCollector; -import org.apache.lucene.search.highlight.positions.IntervalTokenStream; -import org.apache.lucene.search.highlight.positions.ArrayIntervalIterator; -import org.apache.lucene.search.highlight.positions.DocAndPositions; import org.apache.lucene.search.positions.BlockIntervalIterator; import org.apache.lucene.search.positions.BrouwerianQuery; import org.apache.lucene.search.positions.IntervalFilterQuery; import org.apache.lucene.search.positions.IntervalIterator; import org.apache.lucene.search.positions.IntervalIterator.IntervalFilter; +import org.apache.lucene.search.positions.OrderedConjunctionQuery; import org.apache.lucene.store.Directory; import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util._TestUtil; @@ -122,6 +128,10 @@ } searcher = new IndexSearcher(DirectoryReader.open(dir)); } + + protected static TermQuery termQuery(String term) { + return new TermQuery(new Term(F, term)); + } private String[] doSearch(Query q) throws IOException, InvalidTokenOffsetsException { @@ -195,7 +205,7 @@ public void testTerm() throws Exception { insertDocs(analyzer, "This is a test test"); - String frags[] = doSearch(new TermQuery(new Term(F, "test"))); + String frags[] = doSearch(termQuery("test")); assertEquals("This is a test test", frags[0]); close(); } @@ -206,8 +216,7 @@ String gold = "this is some long text. It has the word long in many places. In fact, it has long on some different fragments. " + "Let us see what happens to long in this case."; insertDocs(analyzer, input); - String frags[] = doSearch(new TermQuery(new Term(F, "long")), - input.length()); + String frags[] = doSearch(termQuery("long"), input.length()); assertEquals(gold, frags[0]); close(); } @@ -215,8 +224,8 @@ public void testBooleanAnd() throws Exception { insertDocs(analyzer, "This is a test"); BooleanQuery bq = new BooleanQuery(); - bq.add(new BooleanClause(new TermQuery(new Term(F, "This")), Occur.MUST)); - bq.add(new BooleanClause(new TermQuery(new Term(F, "test")), Occur.MUST)); + bq.add(new BooleanClause(termQuery("This"), Occur.MUST)); + bq.add(new BooleanClause(termQuery("test"), Occur.MUST)); String frags[] = doSearch(bq); assertEquals("This is a test", frags[0]); close(); @@ -225,8 +234,8 @@ public void testConstantScore() throws Exception { insertDocs(analyzer, "This is a test"); BooleanQuery bq = new BooleanQuery(); - bq.add(new BooleanClause(new TermQuery(new Term(F, "This")), Occur.MUST)); - bq.add(new BooleanClause(new TermQuery(new Term(F, "test")), Occur.MUST)); + bq.add(new BooleanClause(termQuery("This"), Occur.MUST)); + bq.add(new BooleanClause(termQuery("test"), Occur.MUST)); String frags[] = doSearch(new ConstantScoreQuery(bq)); assertEquals("This is a test", frags[0]); close(); @@ -443,8 +452,30 @@ close(); } - - private Term[] terms(String field, String...tokens) { + + public void testNearPhraseQuery() throws Exception { + + insertDocs(analyzer, "pease porridge rather hot and pease porridge fairly cold"); + + Query firstQ = new OrderedConjunctionQuery(4, termQuery("pease"), termQuery("porridge"), termQuery("hot")); + { + String frags[] = doSearch(firstQ, Integer.MAX_VALUE); + assertEquals("pease porridge rather hot and pease porridge fairly cold", frags[0]); + } + + // near.3(near.4(pease, porridge, hot), near.4(pease, porridge, cold)) + Query q = new OrderedConjunctionQuery(3, + firstQ, + new OrderedConjunctionQuery(4, termQuery("pease"), termQuery("porridge"), termQuery("cold"))); + + String frags[] = doSearch(q, Integer.MAX_VALUE); + assertEquals("pease porridge rather hot and pease porridge fairly cold", + frags[0]); + + close(); + } + + private Term[] terms(String field, String...tokens) { Term[] terms = new Term[tokens.length]; for (int i = 0; i < tokens.length; i++) { terms[i] = new Term(field, tokens[i]); @@ -474,7 +505,8 @@ assertSloppyPhrase( "A A X A Y B A", null , 1, "X", "A", "A"); close(); } - + + private void assertSloppyPhrase(String doc, String expected, int slop, String...query) throws Exception { insertDocs(analyzer, doc); PhraseQuery pq = new PhraseQuery();