You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by jp...@apache.org on 2015/07/28 12:01:18 UTC

svn commit: r1693059 [1/3] - in /lucene/dev/branches/branch_5x: ./ lucene/ lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ lucene/benchmark/ lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/ lucene/core/ lucene/cor...

Author: jpountz
Date: Tue Jul 28 10:01:17 2015
New Revision: 1693059

URL: http://svn.apache.org/r1693059
Log:
LUCENE-6531: Make PhraseQuery immutable.

Modified:
    lucene/dev/branches/branch_5x/   (props changed)
    lucene/dev/branches/branch_5x/lucene/   (props changed)
    lucene/dev/branches/branch_5x/lucene/CHANGES.txt   (contents, props changed)
    lucene/dev/branches/branch_5x/lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ShingleAnalyzerWrapperTest.java
    lucene/dev/branches/branch_5x/lucene/benchmark/   (props changed)
    lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/SimpleSloppyPhraseQueryMaker.java
    lucene/dev/branches/branch_5x/lucene/core/   (props changed)
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/NGramPhraseQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/similarities/TFIDFSimilarity.java
    lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestDemo.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestSearch.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/document/TestDocument.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestLazyProxSkipping.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestOmitTf.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBoolean2.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestMultiPhraseQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestNGramPhraseQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestPhraseQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestPositionIncrement.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestQueryRescorer.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestScorerPerf.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestSimilarity.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestSimpleSearchEquivalence.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestSloppyPhraseQuery.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestSloppyPhraseQuery2.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestSubScorerFreqs.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/spans/TestBasics.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/spans/TestSpanSearchEquivalence.java
    lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
    lucene/dev/branches/branch_5x/lucene/highlighter/   (props changed)
    lucene/dev/branches/branch_5x/lucene/highlighter/src/java/org/apache/lucene/search/vectorhighlight/FieldQuery.java
    lucene/dev/branches/branch_5x/lucene/highlighter/src/test/org/apache/lucene/search/highlight/HighlighterPhraseTest.java
    lucene/dev/branches/branch_5x/lucene/highlighter/src/test/org/apache/lucene/search/highlight/HighlighterTest.java
    lucene/dev/branches/branch_5x/lucene/highlighter/src/test/org/apache/lucene/search/highlight/MissesTest.java
    lucene/dev/branches/branch_5x/lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighter.java
    lucene/dev/branches/branch_5x/lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/AbstractTestCase.java
    lucene/dev/branches/branch_5x/lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/FastVectorHighlighterTest.java
    lucene/dev/branches/branch_5x/lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/SimpleFragListBuilderTest.java
    lucene/dev/branches/branch_5x/lucene/memory/   (props changed)
    lucene/dev/branches/branch_5x/lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndexAgainstRAMDir.java
    lucene/dev/branches/branch_5x/lucene/queryparser/   (props changed)
    lucene/dev/branches/branch_5x/lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/MultiFieldQueryParser.java
    lucene/dev/branches/branch_5x/lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java
    lucene/dev/branches/branch_5x/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/PhraseQueryNodeBuilder.java
    lucene/dev/branches/branch_5x/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/SlopQueryNodeBuilder.java
    lucene/dev/branches/branch_5x/lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/TestQPHelper.java
    lucene/dev/branches/branch_5x/lucene/queryparser/src/test/org/apache/lucene/queryparser/simple/TestSimpleQueryParser.java
    lucene/dev/branches/branch_5x/lucene/queryparser/src/test/org/apache/lucene/queryparser/util/QueryParserTestBase.java
    lucene/dev/branches/branch_5x/lucene/test-framework/   (props changed)
    lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/index/ThreadedIndexingAndSearchingTestCase.java
    lucene/dev/branches/branch_5x/lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java
    lucene/dev/branches/branch_5x/solr/   (props changed)
    lucene/dev/branches/branch_5x/solr/core/   (props changed)
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/parser/SolrQueryParserBase.java
    lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java

Modified: lucene/dev/branches/branch_5x/lucene/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/CHANGES.txt?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/CHANGES.txt (original)
+++ lucene/dev/branches/branch_5x/lucene/CHANGES.txt Tue Jul 28 10:01:17 2015
@@ -173,6 +173,9 @@ API Changes
 * LUCENE-6607: Factor out geo3d into its own spatial3d module.  (Karl
   Wright, Nick Knize, David Smiley, Mike McCandless)
 
+* LUCENE-6531: PhraseQuery is now immutable and can be built using the
+  PhraseQuery.Builder class. (Adrien Grand)
+
 Bug fixes
 
 * LUCENE-6500: ParallelCompositeReader did not always call

Modified: lucene/dev/branches/branch_5x/lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ShingleAnalyzerWrapperTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ShingleAnalyzerWrapperTest.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ShingleAnalyzerWrapperTest.java (original)
+++ lucene/dev/branches/branch_5x/lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ShingleAnalyzerWrapperTest.java Tue Jul 28 10:01:17 2015
@@ -36,7 +36,12 @@ import org.apache.lucene.index.IndexRead
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.search.*;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.store.Directory;
 
 /**
@@ -98,8 +103,7 @@ public class ShingleAnalyzerWrapperTest
    * This shows how to construct a phrase query containing shingles.
    */
   public void testShingleAnalyzerWrapperPhraseQuery() throws Exception {
-    PhraseQuery q = new PhraseQuery();
-
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
     try (TokenStream ts = analyzer.tokenStream("content", "this sentence")) {
       int j = -1;
     
@@ -110,11 +114,12 @@ public class ShingleAnalyzerWrapperTest
       while (ts.incrementToken()) {
         j += posIncrAtt.getPositionIncrement();
         String termText = termAtt.toString();
-        q.add(new Term("content", termText), j);
+        builder.add(new Term("content", termText), j);
       }
       ts.end();
     }
 
+    PhraseQuery q = builder.build();
     ScoreDoc[] hits = searcher.search(q, 1000).scoreDocs;
     int[] ranks = new int[] { 0 };
     compareRanks(hits, ranks);

Modified: lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/SimpleSloppyPhraseQueryMaker.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/SimpleSloppyPhraseQueryMaker.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/SimpleSloppyPhraseQueryMaker.java (original)
+++ lucene/dev/branches/branch_5x/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/SimpleSloppyPhraseQueryMaker.java Tue Jul 28 10:01:17 2015
@@ -50,29 +50,31 @@ public class SimpleSloppyPhraseQueryMake
         for (int wd=0; wd<words.length-qlen-slop; wd++) {
           // ordered
           int remainedSlop = slop;
-          PhraseQuery q = new PhraseQuery();
-          q.setSlop(slop);
           int wind = wd;
+          PhraseQuery.Builder builder = new PhraseQuery.Builder();
           for (int i=0; i<qlen; i++) {
-            q.add(new Term(DocMaker.BODY_FIELD,words[wind++]));
+            builder.add(new Term(DocMaker.BODY_FIELD, words[wind++]), i);
             if (remainedSlop>0) {
               remainedSlop--;
               wind++;
             }
           }
+          builder.setSlop(slop);
+          PhraseQuery q = builder.build();
           queries.add(q);
           // reversed
           remainedSlop = slop;
-          q = new PhraseQuery();
-          q.setSlop(slop+2*qlen);
           wind = wd+qlen+remainedSlop-1;
+          builder = new PhraseQuery.Builder();
           for (int i=0; i<qlen; i++) {
-            q.add(new Term(DocMaker.BODY_FIELD,words[wind--]));
+            builder.add(new Term(DocMaker.BODY_FIELD, words[wind--]), i);
             if (remainedSlop>0) {
               remainedSlop--;
               wind--;
             }
           }
+          builder.setSlop(slop + 2 * qlen);
+          q = builder.build();
           queries.add(q);
         }
       }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java Tue Jul 28 10:01:17 2015
@@ -54,7 +54,7 @@ public class MultiPhraseQuery extends Qu
   private int slop = 0;
 
   /** Sets the phrase slop for this query.
-   * @see PhraseQuery#setSlop(int)
+   * @see PhraseQuery#getSlop()
    */
   public void setSlop(int s) {
     if (s < 0) {
@@ -69,14 +69,11 @@ public class MultiPhraseQuery extends Qu
   public int getSlop() { return slop; }
 
   /** Add a single term at the next position in the phrase.
-   * @see PhraseQuery#add(Term)
    */
   public void add(Term term) { add(new Term[]{term}); }
 
   /** Add multiple terms at the next position in the phrase.  Any of the terms
    * may match.
-   *
-   * @see PhraseQuery#add(Term)
    */
   public void add(Term[] terms) {
     int position = 0;
@@ -88,8 +85,6 @@ public class MultiPhraseQuery extends Qu
 
   /**
    * Allows to specify the relative position of terms within the phrase.
-   * 
-   * @see PhraseQuery#add(Term, int)
    */
   public void add(Term[] terms, int position) {
     Objects.requireNonNull(terms, "Term array must not be null");

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/NGramPhraseQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/NGramPhraseQuery.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/NGramPhraseQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/NGramPhraseQuery.java Tue Jul 28 10:01:17 2015
@@ -18,6 +18,7 @@ package org.apache.lucene.search;
  */
 
 import java.io.IOException;
+import java.util.Objects;
 
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
@@ -30,70 +31,93 @@ import org.apache.lucene.index.Term;
  * will query "AB/0 BC/1 CD/2" (where term/position).
  *
  */
-public class NGramPhraseQuery extends PhraseQuery {
+public class NGramPhraseQuery extends Query {
+
   private final int n;
+  private final PhraseQuery phraseQuery;
   
   /**
    * Constructor that takes gram size.
    * @param n n-gram size
    */
-  public NGramPhraseQuery(int n){
+  public NGramPhraseQuery(int n, PhraseQuery query) {
     super();
     this.n = n;
+    this.phraseQuery = Objects.requireNonNull(query);
   }
 
   @Override
   public Query rewrite(IndexReader reader) throws IOException {
-    if(getSlop() != 0) return super.rewrite(reader);
+    final Term[] terms = phraseQuery.getTerms();
+    final int[] positions = phraseQuery.getPositions();
+
+    boolean isOptimizable = phraseQuery.getSlop() == 0
+        && n >= 2 // non-overlap n-gram cannot be optimized
+        && terms.length >= 3; // short ones can't be optimized
+
+    if (isOptimizable) {
+      for (int i = 1; i < positions.length; ++i) {
+        if (positions[i] != positions[i-1] + 1) {
+          isOptimizable = false;
+          break;
+        }
+      }
+    }
     
-    // check whether optimizable or not
-    if(n < 2 || // non-overlap n-gram cannot be optimized
-        getTerms().length < 3)  // too short to optimize
-      return super.rewrite(reader);
-
-    // check all posIncrement is 1
-    // if not, cannot optimize
-    int[] positions = getPositions();
-    Term[] terms = getTerms();
-    int prevPosition = positions[0];
-    for(int i = 1; i < positions.length; i++){
-      int pos = positions[i];
-      if(prevPosition + 1 != pos) return super.rewrite(reader);
-      prevPosition = pos;
+    if (isOptimizable == false) {
+      return phraseQuery.rewrite(reader);
     }
 
-    // now create the new optimized phrase query for n-gram
-    PhraseQuery optimized = new PhraseQuery();
-    optimized.setBoost(getBoost());
-    int pos = 0;
-    final int lastPos = terms.length - 1;
-    for(int i = 0; i < terms.length; i++){
-      if(pos % n == 0 || pos >= lastPos){
-        optimized.add(terms[i], positions[i]);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    for (int i = 0; i < terms.length; ++i) {
+      if (i % n == 0 || i == terms.length - 1) {
+        builder.add(terms[i], i);
       }
-      pos++;
     }
-    
-    return optimized;
+    PhraseQuery rewritten = builder.build();
+    rewritten.setBoost(phraseQuery.getBoost());
+    return rewritten;
   }
 
-  /** Returns true iff <code>o</code> is equal to this. */
   @Override
   public boolean equals(Object o) {
-    if (!(o instanceof NGramPhraseQuery))
+    if (super.equals(o) == false) {
       return false;
-    NGramPhraseQuery other = (NGramPhraseQuery)o;
-    if(this.n != other.n) return false;
-    return super.equals(other);
+    }
+    NGramPhraseQuery other = (NGramPhraseQuery) o;
+    return n == other.n && phraseQuery.equals(other.phraseQuery);
   }
 
-  /** Returns a hash code value for this object.*/
   @Override
   public int hashCode() {
-    return Float.floatToIntBits(getBoost())
-      ^ getSlop()
-      ^ getTerms().hashCode()
-      ^ getPositions().hashCode()
-      ^ n;
+    int h = super.hashCode();
+    h = 31 * h + phraseQuery.hashCode();
+    h = 31 * h + n;
+    return h;
+  }
+
+  /** Return the list of terms. */
+  public Term[] getTerms() {
+    return phraseQuery.getTerms();
+  }
+
+  /** Return the list of relative positions that each term should appear at. */
+  public int[] getPositions() {
+    return phraseQuery.getPositions();
+  }
+
+  @Override
+  public float getBoost() {
+    return phraseQuery.getBoost();
+  }
+
+  @Override
+  public void setBoost(float b) {
+    phraseQuery.setBoost(b);
+  }
+
+  @Override
+  public String toString(String field) {
+    return phraseQuery.toString(field);
   }
 }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java Tue Jul 28 10:01:17 2015
@@ -20,14 +20,16 @@ package org.apache.lucene.search;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
-import org.apache.lucene.index.PostingsEnum;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexReaderContext;
 import org.apache.lucene.index.LeafReader;
 import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.PostingsEnum;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermContext;
 import org.apache.lucene.index.TermState;
@@ -36,6 +38,7 @@ import org.apache.lucene.index.TermsEnum
 import org.apache.lucene.search.similarities.Similarity;
 import org.apache.lucene.search.similarities.Similarity.SimScorer;
 import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.ToStringUtils;
 
 /** A Query that matches documents containing a particular sequence of terms.
@@ -46,92 +49,212 @@ import org.apache.lucene.util.ToStringUt
  * <b>NOTE</b>: Leading holes don't have any particular meaning for this query
  * and will be ignored. For instance this query:
  * <pre class="prettyprint">
- * PhraseQuery pq = new PhraseQuery();
- * pq.add(new Term("body", "one"), 4);
- * pq.add(new Term("body", "two"), 5);
+ * PhraseQuery.Builder builder = new PhraseQuery.Builder();
+ * builder.add(new Term("body", "one"), 4);
+ * builder.add(new Term("body", "two"), 5);
+ * PhraseQuery pq = builder.build();
  * </pre>
  * is equivalent to the below query:
  * <pre class="prettyprint">
- * PhraseQuery pq = new PhraseQuery();
- * pq.add(new Term("body", "one"), 0);
- * pq.add(new Term("body", "two"), 1);
+ * PhraseQuery.Builder builder = new PhraseQuery.Builder();
+ * builder.add(new Term("body", "one"), 0);
+ * builder.add(new Term("body", "two"), 1);
+ * PhraseQuery pq = builder.build();
  * </pre>
  */
 public class PhraseQuery extends Query {
+
+  /** A builder for phrase queries. */
+  public static class Builder {
+
+    private int slop;
+    private final List<Term> terms;
+    private final List<Integer> positions;
+
+    /** Sole constructor. */
+    public Builder() {
+      slop = 0;
+      terms = new ArrayList<>();
+      positions = new ArrayList<>();
+    }
+
+    /**
+     * Set the slop.
+     * @see PhraseQuery#getSlop()
+     */
+    public void setSlop(int slop) {
+      this.slop = slop;
+    }
+
+    /**
+     * Adds a term to the end of the query phrase.
+     * The relative position of the term is the one immediately after the last term added.
+     */
+    public void add(Term term) {
+      add(term, positions.isEmpty() ? 0 : 1 + positions.get(positions.size() - 1));
+    }
+
+    /**
+     * Adds a term to the end of the query phrase.
+     * The relative position of the term within the phrase is specified explicitly.
+     * This allows e.g. phrases with more than one term at the same position
+     * or phrases with gaps (e.g. in connection with stopwords).
+     * 
+     */
+    public void add(Term term, int position) {
+      term = new Term(term.field(), BytesRef.deepCopyOf(term.bytes())); // be defensive
+      if (position < 0) {
+        throw new IllegalArgumentException("Positions must be >= 0, got " + position);
+      }
+      if (positions.isEmpty() == false) {
+        final int lastPosition = positions.get(positions.size() - 1);
+        if (position < lastPosition) {
+          throw new IllegalArgumentException("Positions must be added in order, got " + position + " after " + lastPosition);
+        }
+      }
+      if (terms.isEmpty() == false && term.field().equals(terms.get(0).field()) == false) {
+        throw new IllegalArgumentException("All terms must be on the same field, got " + term.field() + " and " + terms.get(0).field());
+      }
+      terms.add(term);
+      positions.add(position);
+    }
+
+    /**
+     * Build a phrase query based on the terms that have been added.
+     */
+    public PhraseQuery build() {
+      Term[] terms = this.terms.toArray(new Term[this.terms.size()]);
+      int[] positions = new int[this.positions.size()];
+      for (int i = 0; i < positions.length; ++i) {
+        positions[i] = this.positions.get(i);
+      }
+      return new PhraseQuery(slop, terms, positions);
+    }
+
+  }
+
+  private final boolean mutable;
+  private int slop;
   private String field;
-  private ArrayList<Term> terms = new ArrayList<>(4);
-  private ArrayList<Integer> positions = new ArrayList<>(4);
-  private int slop = 0;
-
-  /** Constructs an empty phrase query. */
-  public PhraseQuery() {}
-
-  /** Sets the number of other words permitted between words in query phrase.
-    If zero, then this is an exact phrase search.  For larger values this works
-    like a <code>WITHIN</code> or <code>NEAR</code> operator.
-
-    <p>The slop is in fact an edit-distance, where the units correspond to
-    moves of terms in the query phrase out of position.  For example, to switch
-    the order of two words requires two moves (the first move places the words
-    atop one another), so to permit re-orderings of phrases, the slop must be
-    at least two.
+  private final List<Term> terms;
+  private final List<Integer> positions;
 
-    <p>More exact matches are scored higher than sloppier matches, thus search
-    results are sorted by exactness.
+  private PhraseQuery(int slop, Term[] terms, int[] positions) {
+    if (terms.length != positions.length) {
+      throw new IllegalArgumentException("Must have as many terms as positions");
+    }
+    if (slop < 0) {
+      throw new IllegalArgumentException("Slop must be >= 0, got " + slop);
+    }
+    for (int i = 1; i < terms.length; ++i) {
+      if (terms[i-1].field().equals(terms[i].field()) == false) {
+        throw new IllegalArgumentException("All terms should have the same field");
+      }
+    }
+    for (int position : positions) {
+      if (position < 0) {
+        throw new IllegalArgumentException("Positions must be >= 0, got " + position);
+      }
+    }
+    for (int i = 1; i < positions.length; ++i) {
+      if (positions[i] < positions[i - 1]) {
+        throw new IllegalArgumentException("Positions should not go backwards, got "
+            + positions[i-1] + " before " + positions[i]);
+      }
+    }
+    this.slop = slop;
+    this.terms = Arrays.asList(terms);
+    this.positions = new ArrayList<>(positions.length);
+    for (int pos : positions) {
+      this.positions.add(pos);
+    }
+    this.field = terms.length == 0 ? null : terms[0].field();
+    this.mutable = false;
+  }
 
-    <p>The slop is zero by default, requiring exact matches.*/
-  public void setSlop(int s) {
-    if (s < 0) {
-      throw new IllegalArgumentException("slop value cannot be negative");
+  private static int[] incrementalPositions(int length) {
+    int[] positions = new int[length];
+    for (int i = 0; i < length; ++i) {
+      positions[i] = i;
     }
-    slop = s; 
+    return positions;
   }
-  /** Returns the slop.  See setSlop(). */
-  public int getSlop() { return slop; }
 
-  /**
-   * Adds a term to the end of the query phrase.
-   * The relative position of the term is the one immediately after the last term added.
-   */
-  public void add(Term term) {
-    int position = 0;
-    if (positions.size() > 0) {
-      position = positions.get(positions.size()-1) + 1;
+  private static Term[] toTerms(String field, String... termStrings) {
+    Term[] terms = new Term[termStrings.length];
+    for (int i = 0; i < terms.length; ++i) {
+      terms[i] = new Term(field, termStrings[i]);
     }
+    return terms;
+  }
 
-    add(term, position);
+  private static Term[] toTerms(String field, BytesRef... termBytes) {
+    Term[] terms = new Term[termBytes.length];
+    for (int i = 0; i < terms.length; ++i) {
+      terms[i] = new Term(field, BytesRef.deepCopyOf(termBytes[i]));
+    }
+    return terms;
   }
 
   /**
-   * Adds a term to the end of the query phrase.
-   * The relative position of the term within the phrase is specified explicitly.
-   * This allows e.g. phrases with more than one term at the same position
-   * or phrases with gaps (e.g. in connection with stopwords).
-   * 
+   * Create a phrase query which will match documents that contain the given
+   * list of terms at consecutive positions in {@code field}, and at a
+   * maximum edit distance of {@code slop}. For more complicated use-cases,
+   * use {@link PhraseQuery.Builder}.
+   * @see #getSlop()
    */
-  public void add(Term term, int position) {
-    Objects.requireNonNull(term, "Term must not be null");
-    if (positions.size() > 0) {
-      final int previousPosition = positions.get(positions.size()-1);
-      if (position < previousPosition) {
-        throw new IllegalArgumentException("Positions must be added in order. Got position="
-            + position + " while previous position was " + previousPosition);
-      }
-    } else if (position < 0) {
-      throw new IllegalArgumentException("Positions must be positive, got " + position);
-    }
+  public PhraseQuery(int slop, String field, String... terms) {
+    this(slop, toTerms(field, terms), incrementalPositions(terms.length));
+  }
 
-    if (terms.size() == 0) {
-      field = term.field();
-    } else if (!term.field().equals(field)) {
-      throw new IllegalArgumentException("All phrase terms must be in the same field: " + term);
-    }
+  /**
+   * Create a phrase query which will match documents that contain the given
+   * list of terms at consecutive positions in {@code field}.
+   */
+  public PhraseQuery(String field, String... terms) {
+    this(0, field, terms);
+  }
 
-    terms.add(term);
-    positions.add(Integer.valueOf(position));
+  /**
+   * Create a phrase query which will match documents that contain the given
+   * list of terms at consecutive positions in {@code field}, and at a
+   * maximum edit distance of {@code slop}. For more complicated use-cases,
+   * use {@link PhraseQuery.Builder}.
+   * @see #getSlop()
+   */
+  public PhraseQuery(int slop, String field, BytesRef... terms) {
+    this(slop, toTerms(field, terms), incrementalPositions(terms.length));
   }
 
-  /** Returns the set of terms in this phrase. */
+  /**
+   * Create a phrase query which will match documents that contain the given
+   * list of terms at consecutive positions in {@code field}.
+   */
+  public PhraseQuery(String field, BytesRef... terms) {
+    this(0, field, terms);
+  }
+
+  /**
+   * Return the slop for this {@link PhraseQuery}.
+   *
+   * <p>The slop is an edit distance between respective positions of terms as
+   * defined in this {@link PhraseQuery} and the positions of terms in a
+   * document.
+   *
+   * <p>For instance, when searching for {@code "quick fox"}, it is expected that
+   * the difference between the positions of {@code fox} and {@code quick} is 1.
+   * So {@code "a quick brown fox"} would be at an edit distance of 1 since the
+   * difference of the positions of {@code fox} and {@code quick} is 2.
+   * Similarly, {@code "the fox is quick"} would be at an edit distance of 3
+   * since the difference of the positions of {@code fox} and {@code quick} is -2.
+   * The slop defines the maximum edit distance for a document to match.
+   *
+   * <p>More exact matches are scored higher than sloppier matches, thus search
+   * results are sorted by exactness.
+   */
+  public int getSlop() { return slop; }
+
+  /** Returns the list of terms in this phrase. */
   public Term[] getTerms() {
     return terms.toArray(new Term[0]);
   }
@@ -140,10 +263,10 @@ public class PhraseQuery extends Query {
    * Returns the relative positions of terms in this phrase.
    */
   public int[] getPositions() {
-      int[] result = new int[positions.size()];
-      for(int i = 0; i < positions.size(); i++)
-          result[i] = positions.get(i).intValue();
-      return result;
+    int[] result = new int[positions.size()];
+    for(int i = 0; i < positions.size(); i++)
+        result[i] = positions.get(i).intValue();
+    return result;
   }
 
   @Override
@@ -157,16 +280,13 @@ public class PhraseQuery extends Query {
       tq.setBoost(getBoost());
       return tq;
     } else if (positions.get(0).intValue() != 0) {
-      // PhraseWeight requires that positions start at 0 so we need to rebase
-      // positions
-      final Term[] terms = getTerms();
-      final int[] positions = getPositions();
-      PhraseQuery rewritten = new PhraseQuery();
-      for (int i = 0; i < terms.length; ++i) {
-        rewritten.add(terms[i], positions[i] - positions[0]);
+      final int[] oldPositions = getPositions();
+      int[] newPositions = new int[oldPositions.length];
+      for (int i = 0; i < oldPositions.length; ++i) {
+        newPositions[i] = oldPositions[i] - oldPositions[0];
       }
+      PhraseQuery rewritten = new PhraseQuery(slop, getTerms(), newPositions);
       rewritten.setBoost(getBoost());
-      rewritten.setSlop(getSlop());
       return rewritten;
     } else {
       return super.rewrite(reader);
@@ -243,9 +363,11 @@ public class PhraseQuery extends Query {
     private final Similarity.SimWeight stats;
     private final boolean needsScores;
     private transient TermContext states[];
+    private final Term[] terms = getTerms();
+    private final int[] positions = getPositions();
 
     public PhraseWeight(IndexSearcher searcher, boolean needsScores)
-      throws IOException {
+        throws IOException {
       super(PhraseQuery.this);
       final int[] positions = PhraseQuery.this.getPositions();
       if (positions.length < 2) {
@@ -256,10 +378,10 @@ public class PhraseQuery extends Query {
       this.needsScores = needsScores;
       this.similarity = searcher.getSimilarity(needsScores);
       final IndexReaderContext context = searcher.getTopReaderContext();
-      states = new TermContext[terms.size()];
-      TermStatistics termStats[] = new TermStatistics[terms.size()];
-      for (int i = 0; i < terms.size(); i++) {
-        final Term term = terms.get(i);
+      states = new TermContext[terms.length];
+      TermStatistics termStats[] = new TermStatistics[terms.length];
+      for (int i = 0; i < terms.length; i++) {
+        final Term term = terms[i];
         states[i] = TermContext.build(context, term);
         termStats[i] = searcher.termStatistics(term, states[i]);
       }
@@ -268,7 +390,7 @@ public class PhraseQuery extends Query {
 
     @Override
     public void extractTerms(Set<Term> queryTerms) {
-      queryTerms.addAll(terms);
+      Collections.addAll(queryTerms, terms);
     }
 
     @Override
@@ -286,9 +408,9 @@ public class PhraseQuery extends Query {
 
     @Override
     public Scorer scorer(LeafReaderContext context) throws IOException {
-      assert !terms.isEmpty();
+      assert terms.length > 0;
       final LeafReader reader = context.reader();
-      PostingsAndFreq[] postingsFreqs = new PostingsAndFreq[terms.size()];
+      PostingsAndFreq[] postingsFreqs = new PostingsAndFreq[terms.length];
 
       final Terms fieldTerms = reader.terms(field);
       if (fieldTerms == null) {
@@ -302,8 +424,8 @@ public class PhraseQuery extends Query {
       // Reuse single TermsEnum below:
       final TermsEnum te = fieldTerms.iterator();
       
-      for (int i = 0; i < terms.size(); i++) {
-        final Term t = terms.get(i);
+      for (int i = 0; i < terms.length; i++) {
+        final Term t = terms[i];
         final TermState state = states[i].get(context.ord);
         if (state == null) { /* term doesnt exist in this segment */
           assert termNotInReader(reader, t): "no termstate found but term exists in reader";
@@ -311,7 +433,7 @@ public class PhraseQuery extends Query {
         }
         te.seekExact(t.bytes(), state);
         PostingsEnum postingsEnum = te.postings(null, PostingsEnum.POSITIONS);
-        postingsFreqs[i] = new PostingsAndFreq(postingsEnum, positions.get(i), t);
+        postingsFreqs[i] = new PostingsAndFreq(postingsEnum, positions[i], t);
       }
 
       // sort by increasing docFreq order
@@ -360,6 +482,9 @@ public class PhraseQuery extends Query {
   /** Prints a user-readable version of this query. */
   @Override
   public String toString(String f) {
+    final Term[] terms = getTerms();
+    final int[] positions = getPositions();
+
     StringBuilder buffer = new StringBuilder();
     if (field != null && !field.equals(f)) {
       buffer.append(field);
@@ -368,19 +493,19 @@ public class PhraseQuery extends Query {
 
     buffer.append("\"");
     final int maxPosition;
-    if (positions.isEmpty()) {
+    if (positions.length == 0) {
       maxPosition = -1;
     } else {
-      maxPosition = positions.get(positions.size() - 1);
+      maxPosition = positions[positions.length - 1];
     }
     String[] pieces = new String[maxPosition + 1];
-    for (int i = 0; i < terms.size(); i++) {
-      int pos = positions.get(i).intValue();
+    for (int i = 0; i < terms.length; i++) {
+      int pos = positions[i];
       String s = pieces[pos];
       if (s == null) {
-        s = (terms.get(i)).text();
+        s = (terms[i]).text();
       } else {
-        s = s + "|" + (terms.get(i)).text();
+        s = s + "|" + (terms[i]).text();
       }
       pieces[pos] = s;
     }
@@ -410,22 +535,105 @@ public class PhraseQuery extends Query {
   /** Returns true iff <code>o</code> is equal to this. */
   @Override
   public boolean equals(Object o) {
-    if (!(o instanceof PhraseQuery))
+    if (super.equals(o) == false) {
       return false;
-    PhraseQuery other = (PhraseQuery)o;
-    return super.equals(o)
-      && (this.slop == other.slop)
-      &&  this.terms.equals(other.terms)
-      && this.positions.equals(other.positions);
+    }
+    PhraseQuery that = (PhraseQuery) o;
+    return slop == that.slop
+        && terms.equals(that.terms)
+        && positions.equals(that.positions);
   }
 
   /** Returns a hash code value for this object.*/
   @Override
   public int hashCode() {
-    return super.hashCode()
-      ^ slop
-      ^ terms.hashCode()
-      ^ positions.hashCode();
+    int h = super.hashCode();
+    h = 31 * h + slop;
+    h = 31 * h + terms.hashCode();
+    h = 31 * h + positions.hashCode();
+    return h;
+  }
+
+  // Backward compatibility for pre-5.3 PhraseQuery APIs
+
+  /** Constructs an empty phrase query.
+   * @deprecated Use the {@link Builder} class to build phrase queries.
+   */
+  @Deprecated
+  public PhraseQuery() {
+    this.terms = new ArrayList<>();
+    this.positions = new ArrayList<>();
+    this.mutable = true;
+  }
+
+  private void ensureMutable(String method) {
+    if (mutable == false) {
+      throw new IllegalStateException("This PhraseQuery has been created with the new "
+          + "PhraseQuery.Builder API. It must not be modified afterwards. The "
+          + method + " method only exists for backward compatibility");
+    }
   }
 
+  /**
+   * Set the slop.
+   * @see #getSlop()
+   * @deprecated Phrase queries should be created once with {@link Builder}
+   *             and then considered immutable. See {@link Builder#setSlop(int)}.
+   */
+  @Deprecated
+  public void setSlop(int s) {
+    ensureMutable("setSlop");
+    if (s < 0) {
+      throw new IllegalArgumentException("slop value cannot be negative");
+    }
+    slop = s; 
+  }
+
+  /**
+   * Adds a term to the end of the query phrase.
+   * The relative position of the term is the one immediately after the last term added.
+   * @deprecated Phrase queries should be created once with {@link Builder}
+   *             and then considered immutable. See {@link Builder#add(Term)}.
+   */
+  @Deprecated
+  public void add(Term term) {
+    int position = 0;
+    if (positions.size() > 0) {
+      position = positions.get(positions.size()-1) + 1;
+    }
+
+    add(term, position);
+  }
+
+  /**
+   * Adds a term to the end of the query phrase.
+   * The relative position of the term within the phrase is specified explicitly.
+   * This allows e.g. phrases with more than one term at the same position
+   * or phrases with gaps (e.g. in connection with stopwords).
+   * @deprecated Phrase queries should be created once with {@link Builder}
+   *             and then considered immutable. See {@link Builder#add(Term, int)}.
+   */
+  @Deprecated
+  public void add(Term term, int position) {
+    ensureMutable("add");
+    Objects.requireNonNull(term, "Term must not be null");
+    if (positions.size() > 0) {
+      final int previousPosition = positions.get(positions.size()-1);
+      if (position < previousPosition) {
+        throw new IllegalArgumentException("Positions must be added in order. Got position="
+            + position + " while previous position was " + previousPosition);
+      }
+    } else if (position < 0) {
+      throw new IllegalArgumentException("Positions must be positive, got " + position);
+    }
+
+    if (terms.size() == 0) {
+      field = term.field();
+    } else if (!term.field().equals(field)) {
+      throw new IllegalArgumentException("All phrase terms must be in the same field: " + term);
+    }
+
+    terms.add(term);
+    positions.add(Integer.valueOf(position));
+  }
 }

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/similarities/TFIDFSimilarity.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/similarities/TFIDFSimilarity.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/similarities/TFIDFSimilarity.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/search/similarities/TFIDFSimilarity.java Tue Jul 28 10:01:17 2015
@@ -664,7 +664,7 @@ public abstract class TFIDFSimilarity ex
    * return larger values when the edit distance is small and smaller values
    * when it is large.
    *
-   * @see PhraseQuery#setSlop(int)
+   * @see PhraseQuery#getSlop()
    * @param distance the edit distance of this sloppy phrase match
    * @return the frequency increment for this match
    */

Modified: lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java Tue Jul 28 10:01:17 2015
@@ -323,8 +323,8 @@ public class QueryBuilder {
    * Creates simple phrase query from the cached tokenstream contents 
    */
   private Query analyzePhrase(String field, TokenStream stream, int slop) throws IOException {
-    PhraseQuery pq = newPhraseQuery();
-    pq.setSlop(slop);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.setSlop(slop);
     
     TermToBytesRefAttribute termAtt = stream.getAttribute(TermToBytesRefAttribute.class);
     PositionIncrementAttribute posIncrAtt = stream.getAttribute(PositionIncrementAttribute.class);
@@ -335,13 +335,13 @@ public class QueryBuilder {
       BytesRef bytes = termAtt.getBytesRef();
       if (enablePositionIncrements) {
         position += posIncrAtt.getPositionIncrement();
-        pq.add(new Term(field, BytesRef.deepCopyOf(bytes)), position);
       } else {
-        pq.add(new Term(field, BytesRef.deepCopyOf(bytes)));
+        position += 1;
       }
+      builder.add(new Term(field, bytes), position);
     }
-    
-    return pq;
+
+    return builder.build();
   }
   
   /** 
@@ -404,16 +404,6 @@ public class QueryBuilder {
   }
   
   /**
-   * Builds a new PhraseQuery instance.
-   * <p>
-   * This is intended for subclasses that wish to customize the generated queries.
-   * @return new PhraseQuery instance
-   */
-  protected PhraseQuery newPhraseQuery() {
-    return new PhraseQuery();
-  }
-  
-  /**
    * Builds a new MultiPhraseQuery instance.
    * <p>
    * This is intended for subclasses that wish to customize the generated queries.

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestDemo.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestDemo.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestDemo.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestDemo.java Tue Jul 28 10:01:17 2015
@@ -69,9 +69,7 @@ public class TestDemo extends LuceneTest
     }
 
     // Test simple phrase query
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term("fieldname", "to"));
-    phraseQuery.add(new Term("fieldname", "be"));
+    PhraseQuery phraseQuery = new PhraseQuery("fieldname", "to", "be");
     assertEquals(1, isearcher.search(phraseQuery, 1).totalHits);
 
     ireader.close();

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestSearch.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestSearch.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestSearch.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/TestSearch.java Tue Jul 28 10:01:17 2015
@@ -167,15 +167,10 @@ public class TestSearch extends LuceneTe
     booleanAB.add(new TermQuery(new Term("contents", "b")), BooleanClause.Occur.SHOULD);
     queries.add(booleanAB);
 
-    PhraseQuery phraseAB = new PhraseQuery();
-    phraseAB.add(new Term("contents", "a"));
-    phraseAB.add(new Term("contents", "b"));
+    PhraseQuery phraseAB = new PhraseQuery("contents", "a", "b");
     queries.add(phraseAB);
 
-    PhraseQuery phraseABC = new PhraseQuery();
-    phraseABC.add(new Term("contents", "a"));
-    phraseABC.add(new Term("contents", "b"));
-    phraseABC.add(new Term("contents", "c"));
+    PhraseQuery phraseABC = new PhraseQuery("contents", "a", "b", "c");
     queries.add(phraseABC);
 
     BooleanQuery booleanAC = new BooleanQuery();
@@ -183,15 +178,10 @@ public class TestSearch extends LuceneTe
     booleanAC.add(new TermQuery(new Term("contents", "c")), BooleanClause.Occur.SHOULD);
     queries.add(booleanAC);
 
-    PhraseQuery phraseAC = new PhraseQuery();
-    phraseAC.add(new Term("contents", "a"));
-    phraseAC.add(new Term("contents", "c"));
+    PhraseQuery phraseAC = new PhraseQuery("contents", "a", "c");
     queries.add(phraseAC);
 
-    PhraseQuery phraseACE = new PhraseQuery();
-    phraseACE.add(new Term("contents", "a"));
-    phraseACE.add(new Term("contents", "c"));
-    phraseACE.add(new Term("contents", "e"));
+    PhraseQuery phraseACE = new PhraseQuery("contents", "a", "c", "e");
     queries.add(phraseACE);
 
     return queries;

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/document/TestDocument.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/document/TestDocument.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/document/TestDocument.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/document/TestDocument.java Tue Jul 28 10:01:17 2015
@@ -220,9 +220,7 @@ public class TestDocument extends Lucene
     IndexReader reader = writer.getReader();
     
     IndexSearcher searcher = newSearcher(reader);
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("indexed_not_tokenized", "test1"));
-    query.add(new Term("indexed_not_tokenized", "test2"));
+    PhraseQuery query = new PhraseQuery("indexed_not_tokenized", "test1", "test2");
     
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals(1, hits.length);

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java Tue Jul 28 10:01:17 2015
@@ -172,9 +172,7 @@ public class TestAddIndexes extends Luce
       writer.updateDocument(new Term("id", "" + (i%10)), doc);
     }
     // Deletes one of the 10 added docs, leaving 9:
-    PhraseQuery q = new PhraseQuery();
-    q.add(new Term("content", "bbb"));
-    q.add(new Term("content", "14"));
+    PhraseQuery q = new PhraseQuery("content", "bbb", "14");
     writer.deleteDocuments(q);
 
     writer.forceMerge(1);
@@ -210,9 +208,7 @@ public class TestAddIndexes extends Luce
     writer.addIndexes(aux);
     
     // Deletes one of the 10 added docs, leaving 9:
-    PhraseQuery q = new PhraseQuery();
-    q.add(new Term("content", "bbb"));
-    q.add(new Term("content", "14"));
+    PhraseQuery q = new PhraseQuery("content", "bbb", "14");
     writer.deleteDocuments(q);
 
     writer.forceMerge(1);
@@ -246,9 +242,7 @@ public class TestAddIndexes extends Luce
     }
 
     // Deletes one of the 10 added docs, leaving 9:
-    PhraseQuery q = new PhraseQuery();
-    q.add(new Term("content", "bbb"));
-    q.add(new Term("content", "14"));
+    PhraseQuery q = new PhraseQuery("content", "bbb", "14");
     writer.deleteDocuments(q);
 
     writer.addIndexes(aux);

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java Tue Jul 28 10:01:17 2015
@@ -1869,9 +1869,10 @@ public class TestIndexWriter extends Luc
     IndexReader ir = iw.getReader();
     iw.close();
     IndexSearcher is = newSearcher(ir);
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("body", "just"), 0);
-    pq.add(new Term("body", "test"), 2);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term("body", "just"), 0);
+    builder.add(new Term("body", "test"), 2);
+    PhraseQuery pq = builder.build();
     // body:"just ? test"
     assertEquals(1, is.search(pq, 5).totalHits);
     ir.close();
@@ -1900,9 +1901,10 @@ public class TestIndexWriter extends Luc
     IndexReader ir = iw.getReader();
     iw.close();
     IndexSearcher is = newSearcher(ir);
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("body", "just"), 0);
-    pq.add(new Term("body", "test"), 3);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term("body", "just"), 0);
+    builder.add(new Term("body", "test"), 3);
+    PhraseQuery pq = builder.build();
     // body:"just ? ? test"
     assertEquals(1, is.search(pq, 5).totalHits);
     ir.close();

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java Tue Jul 28 10:01:17 2015
@@ -1427,14 +1427,10 @@ public class TestIndexWriterExceptions e
     w.close();
 
     final IndexSearcher s = newSearcher(r);
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("content", "silly"));
-    pq.add(new Term("content", "content"));
+    PhraseQuery pq = new PhraseQuery("content", "silly", "good");
     assertEquals(0, s.search(pq, 1).totalHits);
 
-    pq = new PhraseQuery();
-    pq.add(new Term("content", "good"));
-    pq.add(new Term("content", "content"));
+    pq = new PhraseQuery("content", "good", "content");
     assertEquals(numDocs1+numDocs2, s.search(pq, 1).totalHits);
     r.close();
     dir.close();
@@ -1508,14 +1504,10 @@ public class TestIndexWriterExceptions e
     w.close();
 
     final IndexSearcher s = newSearcher(r);
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("content", "silly"));
-    pq.add(new Term("content", "content"));
+    PhraseQuery pq = new PhraseQuery("content", "silly", "content");
     assertEquals(numDocs2, s.search(pq, 1).totalHits);
 
-    pq = new PhraseQuery();
-    pq.add(new Term("content", "good"));
-    pq.add(new Term("content", "content"));
+    pq = new PhraseQuery("content", "good", "content");
     assertEquals(numDocs1+numDocs3+numDocs4, s.search(pq, 1).totalHits);
     r.close();
     dir.close();

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestLazyProxSkipping.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestLazyProxSkipping.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestLazyProxSkipping.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestLazyProxSkipping.java Tue Jul 28 10:01:17 2015
@@ -111,9 +111,7 @@ public class TestLazyProxSkipping extend
     
     private ScoreDoc[] search() throws IOException {
         // create PhraseQuery "term1 term2" and search
-        PhraseQuery pq = new PhraseQuery();
-        pq.add(new Term(this.field, this.term1));
-        pq.add(new Term(this.field, this.term2));
+        PhraseQuery pq = new PhraseQuery(field, term1, term2);
         return this.searcher.search(pq, 1000).scoreDocs;        
     }
     

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestOmitTf.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestOmitTf.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestOmitTf.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/index/TestOmitTf.java Tue Jul 28 10:01:17 2015
@@ -308,9 +308,7 @@ public class TestOmitTf extends LuceneTe
     TermQuery q3 = new TermQuery(c);
     TermQuery q4 = new TermQuery(d);
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(a);
-    pq.add(c);
+    PhraseQuery pq = new PhraseQuery(a.field(), a.bytes(), c.bytes());
     try {
       searcher.search(pq, 10);
       fail("did not hit expected exception");

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBoolean2.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBoolean2.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBoolean2.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBoolean2.java Tue Jul 28 10:01:17 2015
@@ -31,6 +31,7 @@ import org.apache.lucene.search.similari
 import org.apache.lucene.search.similarities.Similarity;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.MockDirectoryWrapper;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.TestUtil;
 import org.junit.AfterClass;
@@ -332,13 +333,9 @@ public class TestBoolean2 extends Lucene
       if (qType < 3) {
         q = new TermQuery(new Term(field, vals[rnd.nextInt(vals.length)]));
       } else if (qType < 4) {
-        Term t1 = new Term(field, vals[rnd.nextInt(vals.length)]);
-        Term t2 = new Term(field, vals[rnd.nextInt(vals.length)]);
-        PhraseQuery pq = new PhraseQuery();
-        pq.add(t1);
-        pq.add(t2);
-        pq.setSlop(10); // increase possibility of matching
-        q = pq;
+        String t1 = vals[rnd.nextInt(vals.length)];
+        String t2 = vals[rnd.nextInt(vals.length)];
+        q = new PhraseQuery(10, field, t1, t2); // slop increases possibility of matching
       } else if (qType < 7) {
         q = new WildcardQuery(new Term(field, "w*"));
       } else {

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java Tue Jul 28 10:01:17 2015
@@ -110,9 +110,7 @@ public class TestBooleanQuery extends Lu
 
     // LUCENE-2617: make sure that a clause not in the index still contributes to the score via coord factor
     BooleanQuery qq = q.clone();
-    PhraseQuery phrase = new PhraseQuery();
-    phrase.add(new Term("field", "not_in_index"));
-    phrase.add(new Term("field", "another_not_in_index"));
+    PhraseQuery phrase = new PhraseQuery("field", "not_in_index", "another_not_in_index");
     phrase.setBoost(0);
     qq.add(phrase, BooleanClause.Occur.SHOULD);
     score2 = s.search(qq, 10).getMaxScore();
@@ -126,14 +124,14 @@ public class TestBooleanQuery extends Lu
     assertEquals(score*(2/3F), score2, 1e-6);
 
     // PhraseQuery w/ no terms added returns a null scorer
-    PhraseQuery pq = new PhraseQuery();
+    PhraseQuery pq = new PhraseQuery("field", new String[0]);
     q.add(pq, BooleanClause.Occur.SHOULD);
     assertEquals(1, s.search(q, 10).totalHits);
 
     // A required clause which returns null scorer should return null scorer to
     // IndexSearcher.
     q = new BooleanQuery();
-    pq = new PhraseQuery();
+    pq = new PhraseQuery("field", new String[0]);
     q.add(new TermQuery(new Term("field", "a")), BooleanClause.Occur.SHOULD);
     q.add(pq, BooleanClause.Occur.MUST);
     assertEquals(0, s.search(q, 10).totalHits);
@@ -610,9 +608,7 @@ public class TestBooleanQuery extends Lu
     final IndexSearcher searcher = new IndexSearcher(reader);
     searcher.setQueryCache(null); // to still have approximations
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "a"));
-    pq.add(new Term("field", "b"));
+    PhraseQuery pq = new PhraseQuery("field", "a", "b");
 
     BooleanQuery q = new BooleanQuery();
     q.add(pq, Occur.MUST);
@@ -641,9 +637,7 @@ public class TestBooleanQuery extends Lu
     final IndexSearcher searcher = new IndexSearcher(reader);
     searcher.setQueryCache(null); // to still have approximations
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "a"));
-    pq.add(new Term("field", "b"));
+    PhraseQuery pq = new PhraseQuery("field", "a", "b");
 
     BooleanQuery q = new BooleanQuery();
     q.add(pq, Occur.SHOULD);
@@ -674,9 +668,7 @@ public class TestBooleanQuery extends Lu
     final IndexSearcher searcher = new IndexSearcher(reader);
     searcher.setQueryCache(null); // to still have approximations
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "a"));
-    pq.add(new Term("field", "b"));
+    PhraseQuery pq = new PhraseQuery("field", "a", "b");
 
     BooleanQuery q = new BooleanQuery();
     q.add(pq, Occur.SHOULD);
@@ -705,9 +697,7 @@ public class TestBooleanQuery extends Lu
     final IndexSearcher searcher = new IndexSearcher(reader);
     searcher.setQueryCache(null); // to still have approximations
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "a"));
-    pq.add(new Term("field", "b"));
+    PhraseQuery pq = new PhraseQuery("field", "a", "b");
 
     BooleanQuery q = new BooleanQuery();
     q.add(pq, Occur.SHOULD);
@@ -736,9 +726,7 @@ public class TestBooleanQuery extends Lu
     final IndexSearcher searcher = new IndexSearcher(reader);
     searcher.setQueryCache(null); // to still have approximations
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "a"));
-    pq.add(new Term("field", "b"));
+    PhraseQuery pq = new PhraseQuery("field", "a", "b");
 
     BooleanQuery q = new BooleanQuery();
     q.add(pq, Occur.MUST);

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java Tue Jul 28 10:01:17 2015
@@ -60,10 +60,7 @@ public class TestComplexExplanations ext
     
     BooleanQuery q = new BooleanQuery();
 
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.setSlop(1);
-    phraseQuery.add(new Term(FIELD, "w1"));
-    phraseQuery.add(new Term(FIELD, "w2"));
+    PhraseQuery phraseQuery = new PhraseQuery(1, FIELD, "w1", "w2");
     q.add(phraseQuery, Occur.MUST);
     q.add(snear(st("w2"),
                 sor("w5","zz"),
@@ -123,10 +120,7 @@ public class TestComplexExplanations ext
     
     BooleanQuery q = new BooleanQuery();
 
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.setSlop(1);
-    phraseQuery.add(new Term(FIELD, "w1"));
-    phraseQuery.add(new Term(FIELD, "w2"));
+    PhraseQuery phraseQuery = new PhraseQuery(1, FIELD, "w1", "w2");
     q.add(phraseQuery, Occur.MUST);
     q.add(snear(st("w2"),
                 sor("w5","zz"),

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java Tue Jul 28 10:01:17 2015
@@ -231,9 +231,7 @@ public class TestConstantScoreQuery exte
     final IndexSearcher searcher = newSearcher(reader);
     searcher.setQueryCache(null); // to still have approximations
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "a"));
-    pq.add(new Term("field", "b"));
+    PhraseQuery pq = new PhraseQuery("field", "a", "b");
 
     ConstantScoreQuery q = new ConstantScoreQuery(pq);
 

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java Tue Jul 28 10:01:17 2015
@@ -871,10 +871,9 @@ public class TestLRUQueryCache extends L
         bq.setMinimumNumberShouldMatch(TestUtil.nextInt(random(), 0, numShould));
         return bq;
       case 2:
-        PhraseQuery pq = new PhraseQuery();
-        pq.add(randomTerm());
-        pq.add(randomTerm());
-        pq.setSlop(random().nextInt(2));
+        Term t1 = randomTerm();
+        Term t2 = randomTerm();
+        PhraseQuery pq = new PhraseQuery(random().nextInt(2), t1.field(), t1.bytes(), t2.bytes());
         return pq;
       case 3:
         return new MatchAllDocsQuery();

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestMultiPhraseQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestMultiPhraseQuery.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestMultiPhraseQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestMultiPhraseQuery.java Tue Jul 28 10:01:17 2015
@@ -496,17 +496,18 @@ public class TestMultiPhraseQuery extend
    * PQ AND Mode - Manually creating a phrase query
    */
   public void testZeroPosIncrSloppyPqAnd() throws IOException {
-    final PhraseQuery pq = new PhraseQuery();
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
     int pos = -1;
     for (Token tap : INCR_0_QUERY_TOKENS_AND) {
       pos += tap.getPositionIncrement();
-      pq.add(new Term("field",tap.toString()), pos);
+      builder.add(new Term("field", tap.toString()), pos);
     }
-    doTestZeroPosIncrSloppy(pq, 0);
-    pq.setSlop(1);
-    doTestZeroPosIncrSloppy(pq, 0);
-    pq.setSlop(2);
-    doTestZeroPosIncrSloppy(pq, 1);
+    builder.setSlop(0);
+    doTestZeroPosIncrSloppy(builder.build(), 0);
+    builder.setSlop(1);
+    doTestZeroPosIncrSloppy(builder.build(), 0);
+    builder.setSlop(2);
+    doTestZeroPosIncrSloppy(builder.build(), 1);
   }
 
   /**

Modified: lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestNGramPhraseQuery.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestNGramPhraseQuery.java?rev=1693059&r1=1693058&r2=1693059&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestNGramPhraseQuery.java (original)
+++ lucene/dev/branches/branch_5x/lucene/core/src/test/org/apache/lucene/search/TestNGramPhraseQuery.java Tue Jul 28 10:01:17 2015
@@ -49,51 +49,36 @@ public class TestNGramPhraseQuery extend
   
   public void testRewrite() throws Exception {
     // bi-gram test ABC => AB/BC => AB/BC
-    PhraseQuery pq1 = new NGramPhraseQuery(2);
-    pq1.add(new Term("f", "AB"));
-    pq1.add(new Term("f", "BC"));
+    NGramPhraseQuery pq1 = new NGramPhraseQuery(2, new PhraseQuery("f", "AB", "BC"));
     
     Query q = pq1.rewrite(reader);
-    assertTrue(q instanceof NGramPhraseQuery);
-    assertSame(pq1, q);
-    pq1 = (NGramPhraseQuery)q;
-    assertArrayEquals(new Term[]{new Term("f", "AB"), new Term("f", "BC")}, pq1.getTerms());
-    assertArrayEquals(new int[]{0, 1}, pq1.getPositions());
+    assertSame(q.rewrite(reader), q);
+    PhraseQuery rewritten1 = (PhraseQuery) q;
+    assertArrayEquals(new Term[]{new Term("f", "AB"), new Term("f", "BC")}, rewritten1.getTerms());
+    assertArrayEquals(new int[]{0, 1}, rewritten1.getPositions());
 
     // bi-gram test ABCD => AB/BC/CD => AB//CD
-    PhraseQuery pq2 = new NGramPhraseQuery(2);
-    pq2.add(new Term("f", "AB"));
-    pq2.add(new Term("f", "BC"));
-    pq2.add(new Term("f", "CD"));
+    NGramPhraseQuery pq2 = new NGramPhraseQuery(2, new PhraseQuery("f", "AB", "BC", "CD"));
     
     q = pq2.rewrite(reader);
     assertTrue(q instanceof PhraseQuery);
     assertNotSame(pq2, q);
-    pq2 = (PhraseQuery)q;
-    assertArrayEquals(new Term[]{new Term("f", "AB"), new Term("f", "CD")}, pq2.getTerms());
-    assertArrayEquals(new int[]{0, 2}, pq2.getPositions());
+    PhraseQuery rewritten2 = (PhraseQuery) q;
+    assertArrayEquals(new Term[]{new Term("f", "AB"), new Term("f", "CD")}, rewritten2.getTerms());
+    assertArrayEquals(new int[]{0, 2}, rewritten2.getPositions());
 
     // tri-gram test ABCDEFGH => ABC/BCD/CDE/DEF/EFG/FGH => ABC///DEF//FGH
-    PhraseQuery pq3 = new NGramPhraseQuery(3);
-    pq3.add(new Term("f", "ABC"));
-    pq3.add(new Term("f", "BCD"));
-    pq3.add(new Term("f", "CDE"));
-    pq3.add(new Term("f", "DEF"));
-    pq3.add(new Term("f", "EFG"));
-    pq3.add(new Term("f", "FGH"));
+    NGramPhraseQuery pq3 = new NGramPhraseQuery(3, new PhraseQuery("f", "ABC", "BCD", "CDE", "DEF", "EFG", "FGH"));
     
     q = pq3.rewrite(reader);
     assertTrue(q instanceof PhraseQuery);
     assertNotSame(pq3, q);
-    pq3 = (PhraseQuery)q;
-    assertArrayEquals(new Term[]{new Term("f", "ABC"), new Term("f", "DEF"), new Term("f", "FGH")}, pq3.getTerms());
-    assertArrayEquals(new int[]{0, 3, 5}, pq3.getPositions());
+    PhraseQuery rewritten3 = (PhraseQuery) q;
+    assertArrayEquals(new Term[]{new Term("f", "ABC"), new Term("f", "DEF"), new Term("f", "FGH")}, rewritten3.getTerms());
+    assertArrayEquals(new int[]{0, 3, 5}, rewritten3.getPositions());
     
     // LUCENE-4970: boosting test
-    PhraseQuery pq4 = new NGramPhraseQuery(2);
-    pq4.add(new Term("f", "AB"));
-    pq4.add(new Term("f", "BC"));
-    pq4.add(new Term("f", "CD"));
+    NGramPhraseQuery pq4 = new NGramPhraseQuery(2, new PhraseQuery("f", "AB", "BC", "CD"));
     pq4.setBoost(100.0F);
     
     q = pq4.rewrite(reader);