Index: lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ShingleAnalyzerWrapperTest.java
===================================================================
--- lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ShingleAnalyzerWrapperTest.java	(revision 1684461)
+++ lucene/analysis/common/src/test/org/apache/lucene/analysis/shingle/ShingleAnalyzerWrapperTest.java	(working copy)
@@ -36,7 +36,12 @@
 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 @@
    * 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 @@
       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);
Index: lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/SimpleSloppyPhraseQueryMaker.java
===================================================================
--- lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/SimpleSloppyPhraseQueryMaker.java	(revision 1684461)
+++ lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/feeds/SimpleSloppyPhraseQueryMaker.java	(working copy)
@@ -50,29 +50,31 @@
         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);
         }
       }
Index: lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java	(revision 1684461)
+++ lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java	(working copy)
@@ -55,7 +55,7 @@
   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) {
@@ -70,14 +70,11 @@
   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;
@@ -89,8 +86,6 @@
 
   /**
    * 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");
Index: lucene/core/src/java/org/apache/lucene/search/NGramPhraseQuery.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/NGramPhraseQuery.java	(revision 1684461)
+++ lucene/core/src/java/org/apache/lucene/search/NGramPhraseQuery.java	(working copy)
@@ -18,6 +18,7 @@
  */
 
 import java.io.IOException;
+import java.util.Objects;
 
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
@@ -30,70 +31,93 @@
  * 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);
-    
-    // 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);
+    final Term[] terms = phraseQuery.getTerms();
+    final int[] positions = phraseQuery.getPositions();
 
-    // 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;
+    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;
+        }
+      }
     }
+    
+    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);
+  }
 }
Index: lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java	(revision 1684461)
+++ lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java	(working copy)
@@ -20,14 +20,15 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Objects;
+import java.util.Collections;
+import java.util.List;
 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;
@@ -37,6 +38,7 @@
 import org.apache.lucene.search.similarities.Similarity.SimScorer;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.ToStringUtils;
 
 /** A Query that matches documents containing a particular sequence of terms.
@@ -47,94 +49,209 @@
  * <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 {
-  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() {}
+  /** A builder for phrase queries. */
+  public static class Builder {
 
-  /** 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.
+    private int slop;
+    private final List<Term> terms;
+    private final List<Integer> positions;
 
-    <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.
+    /** Sole constructor. */
+    public Builder() {
+      slop = 0;
+      terms = new ArrayList<>();
+      positions = new ArrayList<>();
+    }
 
-    <p>More exact matches are scored higher than sloppier matches, thus search
-    results are sorted by exactness.
+    /**
+     * Set the slop.
+     * @see PhraseQuery#getSlop()
+     */
+    public void setSlop(int slop) {
+      this.slop = slop;
+    }
 
-    <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");
+    /**
+     * 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));
     }
-    slop = s; 
-  }
-  /** 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;
+    /**
+     * 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);
     }
 
-    add(term, 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);
+    }
+
   }
 
-  /**
-   * 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) {
-    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);
+  private final int slop;
+  private final String field;
+  private final Term[] terms;
+  private final int[] positions;
+
+  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");
       }
-    } else if (position < 0) {
-      throw new IllegalArgumentException("Positions must be positive, got " + position);
     }
+    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 = terms;
+    this.positions = positions;
+    this.field = terms.length == 0 ? null : terms[0].field();
+  }
 
-    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);
+  private static int[] incrementalPositions(int length) {
+    int[] positions = new int[length];
+    for (int i = 0; i < length; ++i) {
+      positions[i] = i;
     }
+    return positions;
+  }
 
-    terms.add(term);
-    positions.add(Integer.valueOf(position));
+  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;
   }
 
-  /** Returns the set of terms in this phrase. */
+  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;
+  }
+
+  /**
+   * 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, String... terms) {
+    this(slop, toTerms(field, terms), incrementalPositions(terms.length));
+  }
+
+  /**
+   * 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);
+  }
+
+  /**
+   * 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));
+  }
+
+  /**
+   * 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]);
+    return terms;
   }
 
   /**
@@ -141,33 +258,26 @@
    * 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;
+      return positions;
   }
 
   @Override
   public Query rewrite(IndexReader reader) throws IOException {
-    if (terms.isEmpty()) {
+    if (terms.length == 0) {
       BooleanQuery bq = new BooleanQuery();
       bq.setBoost(getBoost());
       return bq;
-    } else if (terms.size() == 1) {
-      TermQuery tq = new TermQuery(terms.get(0));
+    } else if (terms.length == 1) {
+      TermQuery tq = new TermQuery(terms[0]);
       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]);
+    } else if (positions[0] != 0) {
+      int[] newPositions = new int[positions.length];
+      for (int i = 0; i < positions.length; ++i) {
+        newPositions[i] = positions[i] - positions[0];
       }
+      PhraseQuery rewritten = new PhraseQuery(slop, terms, newPositions);
       rewritten.setBoost(getBoost());
-      rewritten.setSlop(getSlop());
       return rewritten;
     } else {
       return super.rewrite(reader);
@@ -257,10 +367,10 @@
       this.needsScores = needsScores;
       this.similarity = searcher.getSimilarity();
       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]);
       }
@@ -269,7 +379,7 @@
 
     @Override
     public void extractTerms(Set<Term> queryTerms) {
-      queryTerms.addAll(terms);
+      Collections.addAll(queryTerms, terms);
     }
 
     @Override
@@ -287,10 +397,10 @@
 
     @Override
     public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
-      assert !terms.isEmpty();
+      assert terms.length > 0;
       final LeafReader reader = context.reader();
       final Bits liveDocs = acceptDocs;
-      PostingsAndFreq[] postingsFreqs = new PostingsAndFreq[terms.size()];
+      PostingsAndFreq[] postingsFreqs = new PostingsAndFreq[terms.length];
 
       final Terms fieldTerms = reader.terms(field);
       if (fieldTerms == null) {
@@ -304,8 +414,8 @@
       // 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";
@@ -313,7 +423,7 @@
         }
         te.seekExact(t.bytes(), state);
         PostingsEnum postingsEnum = te.postings(liveDocs, 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
@@ -370,19 +480,19 @@
 
     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;
     }
@@ -412,22 +522,23 @@
   /** 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
+        && Arrays.equals(terms, that.terms)
+        && Arrays.equals(positions, 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 + Arrays.hashCode(terms);
+    h = 31 * h + Arrays.hashCode(positions);
+    return h;
   }
 
 }
Index: lucene/core/src/java/org/apache/lucene/search/similarities/TFIDFSimilarity.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/search/similarities/TFIDFSimilarity.java	(revision 1684461)
+++ lucene/core/src/java/org/apache/lucene/search/similarities/TFIDFSimilarity.java	(working copy)
@@ -664,7 +664,7 @@
    * 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
    */
Index: lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java	(revision 1684461)
+++ lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java	(working copy)
@@ -329,8 +329,8 @@
    * 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);
     BytesRef bytes = termAtt.getBytesRef();
@@ -344,13 +344,13 @@
       
       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();
   }
   
   /** 
@@ -415,16 +415,6 @@
   }
   
   /**
-   * 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.
Index: lucene/core/src/test/org/apache/lucene/TestDemo.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/TestDemo.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/TestDemo.java	(working copy)
@@ -70,9 +70,7 @@
     }
 
     // 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();
Index: lucene/core/src/test/org/apache/lucene/TestSearch.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/TestSearch.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/TestSearch.java	(working copy)
@@ -167,15 +167,10 @@
     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 @@
     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;
Index: lucene/core/src/test/org/apache/lucene/document/TestDocument.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/document/TestDocument.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/document/TestDocument.java	(working copy)
@@ -246,9 +246,7 @@
     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);
Index: lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/index/TestAddIndexes.java	(working copy)
@@ -172,9 +172,7 @@
       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 @@
     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 @@
     }
 
     // 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);
Index: lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/index/TestIndexWriter.java	(working copy)
@@ -1894,9 +1894,10 @@
     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();
@@ -1925,9 +1926,10 @@
     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();
Index: lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/index/TestIndexWriterExceptions.java	(working copy)
@@ -1410,14 +1410,10 @@
     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();
@@ -1491,14 +1487,10 @@
     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();
Index: lucene/core/src/test/org/apache/lucene/index/TestLazyProxSkipping.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/index/TestLazyProxSkipping.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/index/TestLazyProxSkipping.java	(working copy)
@@ -111,9 +111,7 @@
     
     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;        
     }
     
Index: lucene/core/src/test/org/apache/lucene/index/TestOmitTf.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/index/TestOmitTf.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/index/TestOmitTf.java	(working copy)
@@ -308,9 +308,7 @@
     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");
Index: lucene/core/src/test/org/apache/lucene/search/TestBoolean2.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestBoolean2.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestBoolean2.java	(working copy)
@@ -31,6 +31,7 @@
 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 @@
       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 {
Index: lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestBooleanQuery.java	(working copy)
@@ -110,9 +110,7 @@
 
     // 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,7 +124,7 @@
     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);
 
@@ -133,7 +131,7 @@
     // 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 @@
     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 @@
     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 @@
     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 @@
     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 @@
     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);
Index: lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestComplexExplanations.java	(working copy)
@@ -60,10 +60,7 @@
     
     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 @@
     
     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"),
Index: lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestConstantScoreQuery.java	(working copy)
@@ -231,9 +231,7 @@
     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);
 
Index: lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestLRUQueryCache.java	(working copy)
@@ -850,10 +850,9 @@
         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();
Index: lucene/core/src/test/org/apache/lucene/search/TestMultiPhraseQuery.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestMultiPhraseQuery.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestMultiPhraseQuery.java	(working copy)
@@ -496,17 +496,18 @@
    * 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);
   }
 
   /**
Index: lucene/core/src/test/org/apache/lucene/search/TestNGramPhraseQuery.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestNGramPhraseQuery.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestNGramPhraseQuery.java	(working copy)
@@ -49,51 +49,36 @@
   
   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);
Index: lucene/core/src/test/org/apache/lucene/search/TestPhraseQuery.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestPhraseQuery.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestPhraseQuery.java	(working copy)
@@ -97,7 +97,6 @@
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    query = new PhraseQuery();
   }
 
   @AfterClass
@@ -110,9 +109,7 @@
   }
 
   public void testNotCloseEnough() throws Exception {
-    query.setSlop(2);
-    query.add(new Term("field", "one"));
-    query.add(new Term("field", "five"));
+    query = new PhraseQuery(2, "field", "one", "five");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals(0, hits.length);
     QueryUtils.check(random(), query,searcher);
@@ -119,9 +116,7 @@
   }
 
   public void testBarelyCloseEnough() throws Exception {
-    query.setSlop(3);
-    query.add(new Term("field", "one"));
-    query.add(new Term("field", "five"));
+    query = new PhraseQuery(3, "field", "one", "five");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals(1, hits.length);
     QueryUtils.check(random(), query,searcher);
@@ -132,16 +127,13 @@
    */
   public void testExact() throws Exception {
     // slop is zero by default
-    query.add(new Term("field", "four"));
-    query.add(new Term("field", "five"));
+    query = new PhraseQuery("field", "four", "five");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("exact match", 1, hits.length);
     QueryUtils.check(random(), query,searcher);
 
 
-    query = new PhraseQuery();
-    query.add(new Term("field", "two"));
-    query.add(new Term("field", "one"));
+    query = new PhraseQuery("field", "two", "one");
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("reverse not exact", 0, hits.length);
     QueryUtils.check(random(), query,searcher);
@@ -149,9 +141,7 @@
 
   public void testSlop1() throws Exception {
     // Ensures slop of 1 works with terms in order.
-    query.setSlop(1);
-    query.add(new Term("field", "one"));
-    query.add(new Term("field", "two"));
+    query = new PhraseQuery(1, "field", "one", "two");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("in order", 1, hits.length);
     QueryUtils.check(random(), query,searcher);
@@ -159,10 +149,7 @@
 
     // Ensures slop of 1 does not work for phrases out of order;
     // must be at least 2.
-    query = new PhraseQuery();
-    query.setSlop(1);
-    query.add(new Term("field", "two"));
-    query.add(new Term("field", "one"));
+    query = new PhraseQuery(1, "field", "two", "one");
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("reversed, slop not 2 or more", 0, hits.length);
     QueryUtils.check(random(), query,searcher);
@@ -172,18 +159,14 @@
    * As long as slop is at least 2, terms can be reversed
    */
   public void testOrderDoesntMatter() throws Exception {
-    query.setSlop(2); // must be at least two for reverse order match
-    query.add(new Term("field", "two"));
-    query.add(new Term("field", "one"));
+    // must be at least two for reverse order match
+    query = new PhraseQuery(2, "field", "two", "one");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("just sloppy enough", 1, hits.length);
     QueryUtils.check(random(), query,searcher);
 
 
-    query = new PhraseQuery();
-    query.setSlop(2);
-    query.add(new Term("field", "three"));
-    query.add(new Term("field", "one"));
+    query = new PhraseQuery(2, "field", "three", "one");
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("not sloppy enough", 0, hits.length);
     QueryUtils.check(random(), query,searcher);
@@ -195,26 +178,19 @@
    * to line up a phrase
    */
   public void testMultipleTerms() throws Exception {
-    query.setSlop(2);
-    query.add(new Term("field", "one"));
-    query.add(new Term("field", "three"));
-    query.add(new Term("field", "five"));
+    query = new PhraseQuery(2, "field", "one", "three", "five");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("two total moves", 1, hits.length);
     QueryUtils.check(random(), query,searcher);
 
-
-    query = new PhraseQuery();
-    query.setSlop(5); // it takes six moves to match this phrase
-    query.add(new Term("field", "five"));
-    query.add(new Term("field", "three"));
-    query.add(new Term("field", "one"));
+    // it takes six moves to match this phrase
+    query = new PhraseQuery(5, "field", "five", "three", "one");
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("slop of 5 not close enough", 0, hits.length);
     QueryUtils.check(random(), query,searcher);
 
 
-    query.setSlop(6);
+    query = new PhraseQuery(6, "field", "five", "three", "one");
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("slop of 6 just right", 1, hits.length);
     QueryUtils.check(random(), query,searcher);
@@ -235,9 +211,7 @@
     IndexSearcher searcher = newSearcher(reader);
 
     // valid exact phrase query
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("field","stop"));
-    query.add(new Term("field","words"));
+    PhraseQuery query = new PhraseQuery("field", "stop", "words");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals(1, hits.length);
     QueryUtils.check(random(), query,searcher);
@@ -264,9 +238,7 @@
     
     IndexSearcher searcher = newSearcher(reader);
     
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term("source", "marketing"));
-    phraseQuery.add(new Term("source", "info"));
+    PhraseQuery phraseQuery = new PhraseQuery("source", "marketing", "info");
     ScoreDoc[] hits = searcher.search(phraseQuery, 1000).scoreDocs;
     assertEquals(2, hits.length);
     QueryUtils.check(random(), phraseQuery,searcher);
@@ -303,9 +275,7 @@
     searcher = newSearcher(reader);
     
     termQuery = new TermQuery(new Term("contents","woo"));
-    phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term("contents","map"));
-    phraseQuery.add(new Term("contents","entry"));
+    phraseQuery = new PhraseQuery("contents", "map", "entry");
     
     hits = searcher.search(termQuery, 1000).scoreDocs;
     assertEquals(3, hits.length);
@@ -355,10 +325,7 @@
 
     IndexSearcher searcher = newSearcher(reader);
     searcher.setSimilarity(new DefaultSimilarity());
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("field", "firstname"));
-    query.add(new Term("field", "lastname"));
-    query.setSlop(Integer.MAX_VALUE);
+    PhraseQuery query = new PhraseQuery(Integer.MAX_VALUE, "field", "firstname", "lastname");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals(3, hits.length);
     // Make sure that those matches where the terms appear closer to
@@ -375,25 +342,35 @@
   }
   
   public void testToString() throws Exception {
-    PhraseQuery q = new PhraseQuery();
+    PhraseQuery q = new PhraseQuery("field", new String[0]);
     assertEquals("\"\"", q.toString());
 
-    q.add(new Term("field", "hi"), 1);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "hi"), 1);
+    q = builder.build();
     assertEquals("field:\"? hi\"", q.toString());
 
-    q = new PhraseQuery(); // Query "this hi this is a test is"
-    q.add(new Term("field", "hi"), 1);
-    q.add(new Term("field", "test"), 5);
     
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "hi"), 1);
+    builder.add(new Term("field", "test"), 5);
+    q = builder.build(); // Query "this hi this is a test is"
+
     assertEquals("field:\"? hi ? ? ? test\"", q.toString());
 
-    q = new PhraseQuery();
-    q.add(new Term("field", "hi"), 1);
-    q.add(new Term("field", "hello"), 1);
-    q.add(new Term("field", "test"), 5);
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "hi"), 1);
+    builder.add(new Term("field", "hello"), 1);
+    builder.add(new Term("field", "test"), 5);
+    q = builder.build();
     assertEquals("field:\"? hi|hello ? ? ? test\"", q.toString());
 
-    q.setSlop(5);
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "hi"), 1);
+    builder.add(new Term("field", "hello"), 1);
+    builder.add(new Term("field", "test"), 5);
+    builder.setSlop(5);
+    q = builder.build();
     assertEquals("field:\"? hi|hello ? ? ? test\"~5", q.toString());
 
     q.setBoost(2);
@@ -401,17 +378,13 @@
   }
 
   public void testWrappedPhrase() throws IOException {
-    query.add(new Term("repeated", "first"));
-    query.add(new Term("repeated", "part"));
-    query.add(new Term("repeated", "second"));
-    query.add(new Term("repeated", "part"));
-    query.setSlop(100);
+    query = new PhraseQuery(100, "repeated", "first", "part", "second", "part");
 
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("slop of 100 just right", 1, hits.length);
     QueryUtils.check(random(), query,searcher);
 
-    query.setSlop(99);
+    query = new PhraseQuery(99, "repeated", "first", "part", "second", "part");
 
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("slop of 99 not enough", 0, hits.length);
@@ -421,10 +394,7 @@
   // work on two docs like this: "phrase exist notexist exist found"
   public void testNonExistingPhrase() throws IOException {
     // phrase without repetitions that exists in 2 docs
-    query.add(new Term("nonexist", "phrase"));
-    query.add(new Term("nonexist", "notexist"));
-    query.add(new Term("nonexist", "found"));
-    query.setSlop(2); // would be found this way
+    query = new PhraseQuery(2, "nonexist", "phrase", "notexist", "found");
 
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("phrase without repetitions exists in 2 docs", 2, hits.length);
@@ -431,11 +401,7 @@
     QueryUtils.check(random(), query,searcher);
 
     // phrase with repetitions that exists in 2 docs
-    query = new PhraseQuery();
-    query.add(new Term("nonexist", "phrase"));
-    query.add(new Term("nonexist", "exist"));
-    query.add(new Term("nonexist", "exist"));
-    query.setSlop(1); // would be found 
+    query = new PhraseQuery(1, "nonexist", "phrase", "exist", "exist");
 
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("phrase with repetitions exists in two docs", 2, hits.length);
@@ -442,11 +408,7 @@
     QueryUtils.check(random(), query,searcher);
 
     // phrase I with repetitions that does not exist in any doc
-    query = new PhraseQuery();
-    query.add(new Term("nonexist", "phrase"));
-    query.add(new Term("nonexist", "notexist"));
-    query.add(new Term("nonexist", "phrase"));
-    query.setSlop(1000); // would not be found no matter how high the slop is
+    query = new PhraseQuery(1000, "nonexist", "phrase", "notexist", "phrase");
 
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("nonexisting phrase with repetitions does not exist in any doc", 0, hits.length);
@@ -453,12 +415,7 @@
     QueryUtils.check(random(), query,searcher);
 
     // phrase II with repetitions that does not exist in any doc
-    query = new PhraseQuery();
-    query.add(new Term("nonexist", "phrase"));
-    query.add(new Term("nonexist", "exist"));
-    query.add(new Term("nonexist", "exist"));
-    query.add(new Term("nonexist", "exist"));
-    query.setSlop(1000); // would not be found no matter how high the slop is
+    query = new PhraseQuery(1000, "nonexist", "phrase", "exist", "exist", "exist");
 
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("nonexisting phrase with repetitions does not exist in any doc", 0, hits.length);
@@ -478,9 +435,7 @@
   public void testPalyndrome2() throws Exception {
     
     // search on non palyndrome, find phrase with no slop, using exact phrase scorer
-    query.setSlop(0); // to use exact phrase scorer
-    query.add(new Term("field", "two"));
-    query.add(new Term("field", "three"));
+    query = new PhraseQuery("field", "two", "three"); // to use exact phrase scorer
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("phrase found with exact phrase scorer", 1, hits.length);
     float score0 = hits[0].score;
@@ -488,7 +443,7 @@
     QueryUtils.check(random(), query,searcher);
 
     // search on non palyndrome, find phrase with slop 2, though no slop required here.
-    query.setSlop(2); // to use sloppy scorer 
+    query = new PhraseQuery("field", "two", "three"); // to use sloppy scorer 
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("just sloppy enough", 1, hits.length);
     float score1 = hits[0].score;
@@ -497,10 +452,7 @@
     QueryUtils.check(random(), query,searcher);
 
     // search ordered in palyndrome, find it twice
-    query = new PhraseQuery();
-    query.setSlop(2); // must be at least two for both ordered and reversed to match
-    query.add(new Term("palindrome", "two"));
-    query.add(new Term("palindrome", "three"));
+    query = new PhraseQuery(2, "palindrome", "two", "three"); // must be at least two for both ordered and reversed to match
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("just sloppy enough", 1, hits.length);
     //float score2 = hits[0].score;
@@ -511,10 +463,7 @@
     //assertTrue("ordered scores higher in palindrome",score1+SCORE_COMP_THRESH<score2);
 
     // search reveresed in palyndrome, find it twice
-    query = new PhraseQuery();
-    query.setSlop(2); // must be at least two for both ordered and reversed to match
-    query.add(new Term("palindrome", "three"));
-    query.add(new Term("palindrome", "two"));
+    query = new PhraseQuery(2, "palindrome", "three", "two"); // must be at least two for both ordered and reversed to match
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("just sloppy enough", 1, hits.length);
     //float score3 = hits[0].score;
@@ -538,10 +487,8 @@
   public void testPalyndrome3() throws Exception {
     
     // search on non palyndrome, find phrase with no slop, using exact phrase scorer
-    query.setSlop(0); // to use exact phrase scorer
-    query.add(new Term("field", "one"));
-    query.add(new Term("field", "two"));
-    query.add(new Term("field", "three"));
+    // slop=0 to use exact phrase scorer
+    query = new PhraseQuery(0, "field", "one", "two", "three");
     ScoreDoc[] hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("phrase found with exact phrase scorer", 1, hits.length);
     float score0 = hits[0].score;
@@ -552,7 +499,8 @@
     searcher.explain(query, 0);
 
     // search on non palyndrome, find phrase with slop 3, though no slop required here.
-    query.setSlop(4); // to use sloppy scorer 
+    // slop=4 to use sloppy scorer
+    query = new PhraseQuery(4, "field", "one", "two", "three");
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("just sloppy enough", 1, hits.length);
     float score1 = hits[0].score;
@@ -561,11 +509,8 @@
     QueryUtils.check(random(), query,searcher);
 
     // search ordered in palyndrome, find it twice
-    query = new PhraseQuery();
-    query.setSlop(4); // must be at least four for both ordered and reversed to match
-    query.add(new Term("palindrome", "one"));
-    query.add(new Term("palindrome", "two"));
-    query.add(new Term("palindrome", "three"));
+    // slop must be at least four for both ordered and reversed to match
+    query = new PhraseQuery(4, "palindrome", "one", "two", "three");
     hits = searcher.search(query, 1000).scoreDocs;
 
     // just make sure no exc:
@@ -580,11 +525,8 @@
     //assertTrue("ordered scores higher in palindrome",score1+SCORE_COMP_THRESH<score2);
 
     // search reveresed in palyndrome, find it twice
-    query = new PhraseQuery();
-    query.setSlop(4); // must be at least four for both ordered and reversed to match
-    query.add(new Term("palindrome", "three"));
-    query.add(new Term("palindrome", "two"));
-    query.add(new Term("palindrome", "one"));
+    // must be at least four for both ordered and reversed to match
+    query = new PhraseQuery(4, "palindrome", "three", "two", "one");
     hits = searcher.search(query, 1000).scoreDocs;
     assertEquals("just sloppy enough", 1, hits.length);
     //float score3 = hits[0].score;
@@ -599,14 +541,13 @@
   // LUCENE-1280
   public void testEmptyPhraseQuery() throws Throwable {
     final BooleanQuery q2 = new BooleanQuery();
-    q2.add(new PhraseQuery(), BooleanClause.Occur.MUST);
+    q2.add(new PhraseQuery("field", new String[0]), BooleanClause.Occur.MUST);
     q2.toString();
   }
   
   /* test that a single term is rewritten to a term query */
   public void testRewrite() throws IOException {
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("foo", "bar"));
+    PhraseQuery pq = new PhraseQuery("foo", "bar");
     Query rewritten = pq.rewrite(searcher.getIndexReader());
     assertTrue(rewritten instanceof TermQuery);
   }
@@ -680,12 +621,13 @@
       
       final int numTerm = TestUtil.nextInt(r, 2, 20);
       final int start = r.nextInt(doc.size()-numTerm);
-      PhraseQuery pq = new PhraseQuery();
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
       StringBuilder sb = new StringBuilder();
       for(int t=start;t<start+numTerm;t++) {
-        pq.add(new Term("f", doc.get(t)));
+        builder.add(new Term("f", doc.get(t)), t);
         sb.append(doc.get(t)).append(' ');
       }
+      PhraseQuery pq = builder.build();
 
       TopDocs hits = s.search(pq, NUM_DOCS);
       boolean found = false;
@@ -704,11 +646,8 @@
   }
   
   public void testNegativeSlop() throws Exception {
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("field", "two"));
-    query.add(new Term("field", "one"));
     try {
-      query.setSlop(-2);
+      new PhraseQuery(-2, "field", "two", "one");
       fail("didn't get expected exception");
     } catch (IllegalArgumentException expected) {
       // expected exception
@@ -716,9 +655,9 @@
   }
 
   public void testNegativePosition() throws Exception {
-    PhraseQuery query = new PhraseQuery();
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
     try {
-      query.add(new Term("field", "two"), -42);
+      builder.add(new Term("field", "two"), -42);
       fail("didn't get expected exception");
     } catch (IllegalArgumentException expected) {
       // expected exception
@@ -726,11 +665,11 @@
   }
 
   public void testBackwardPositions() throws Exception {
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("field", "one"), 1);
-    query.add(new Term("field", "two"), 5);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "one"), 1);
+    builder.add(new Term("field", "two"), 5);
     try {
-      query.add(new Term("field", "three"), 4);
+      builder.add(new Term("field", "three"), 4);
       fail("didn't get expected exception");
     } catch (IllegalArgumentException expected) {
       // expected exception
Index: lucene/core/src/test/org/apache/lucene/search/TestPositionIncrement.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestPositionIncrement.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestPositionIncrement.java	(working copy)
@@ -17,6 +17,11 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.MockPayloadAnalyzer;
 import org.apache.lucene.analysis.Tokenizer;
@@ -44,11 +49,6 @@
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.LuceneTestCase;
 
-import java.io.IOException;
-import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-
 /**
  * Term position unit test.
  *
@@ -122,50 +122,56 @@
     PhraseQuery q;
     ScoreDoc[] hits;
 
-    q = new PhraseQuery();
-    q.add(new Term("field", "1"));
-    q.add(new Term("field", "2"));
+    q = new PhraseQuery("field", "1", "2");
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(0, hits.length);
 
+    // same as previous, using the builder with implicit positions
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "1"));
+    builder.add(new Term("field", "2"));
+    q = builder.build();
+    hits = searcher.search(q, 1000).scoreDocs;
+    assertEquals(0, hits.length);
+
     // same as previous, just specify positions explicitely.
-    q = new PhraseQuery(); 
-    q.add(new Term("field", "1"),0);
-    q.add(new Term("field", "2"),1);
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "1"), 0);
+    builder.add(new Term("field", "2"), 1);
+    q = builder.build();
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(0, hits.length);
 
     // specifying correct positions should find the phrase.
-    q = new PhraseQuery();
-    q.add(new Term("field", "1"),0);
-    q.add(new Term("field", "2"),2);
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "1"), 0);
+    builder.add(new Term("field", "2"), 2);
+    q = builder.build();
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(1, hits.length);
 
-    q = new PhraseQuery();
-    q.add(new Term("field", "2"));
-    q.add(new Term("field", "3"));
+    q = new PhraseQuery("field", "2", "3");
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(1, hits.length);
 
-    q = new PhraseQuery();
-    q.add(new Term("field", "3"));
-    q.add(new Term("field", "4"));
+    q = new PhraseQuery("field", "3", "4");
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(0, hits.length);
 
     // phrase query would find it when correct positions are specified. 
-    q = new PhraseQuery();
-    q.add(new Term("field", "3"),0);
-    q.add(new Term("field", "4"),0);
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "3"), 0);
+    builder.add(new Term("field", "4"), 0);
+    q = builder.build();
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(1, hits.length);
 
     // phrase query should fail for non existing searched term 
     // even if there exist another searched terms in the same searched position. 
-    q = new PhraseQuery();
-    q.add(new Term("field", "3"),0);
-    q.add(new Term("field", "9"),0);
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term("field", "3"), 0);
+    builder.add(new Term("field", "9"), 0);
+    q = builder.build();
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(0, hits.length);
 
@@ -176,27 +182,19 @@
     hits = searcher.search(mq, 1000).scoreDocs;
     assertEquals(1, hits.length);
 
-    q = new PhraseQuery();
-    q.add(new Term("field", "2"));
-    q.add(new Term("field", "4"));
+    q = new PhraseQuery("field", "2", "4");
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(1, hits.length);
 
-    q = new PhraseQuery();
-    q.add(new Term("field", "3"));
-    q.add(new Term("field", "5"));
+    q = new PhraseQuery("field", "3", "5");
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(1, hits.length);
 
-    q = new PhraseQuery();
-    q.add(new Term("field", "4"));
-    q.add(new Term("field", "5"));
+    q = new PhraseQuery("field", "4", "5");
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(1, hits.length);
 
-    q = new PhraseQuery();
-    q.add(new Term("field", "2"));
-    q.add(new Term("field", "5"));
+    q = new PhraseQuery("field", "2", "5");
     hits = searcher.search(q, 1000).scoreDocs;
     assertEquals(0, hits.length);
     
Index: lucene/core/src/test/org/apache/lucene/search/TestQueryRescorer.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestQueryRescorer.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestQueryRescorer.java	(working copy)
@@ -79,10 +79,7 @@
     assertEquals("1", searcher.doc(hits.scoreDocs[1].doc).get("id"));
 
     // Now, resort using PhraseQuery:
-    PhraseQuery pq = new PhraseQuery();
-    pq.setSlop(5);
-    pq.add(new Term("field", "wizard"));
-    pq.add(new Term("field", "oz"));
+    PhraseQuery pq = new PhraseQuery(5, "field", "wizard", "oz");
 
     TopDocs hits2 = QueryRescorer.rescore(searcher, hits, pq, 2.0, 10);
 
@@ -176,10 +173,7 @@
 
     // Now, resort using PhraseQuery, but with an
     // opposite-world combine:
-    PhraseQuery pq = new PhraseQuery();
-    pq.setSlop(5);
-    pq.add(new Term("field", "wizard"));
-    pq.add(new Term("field", "oz"));
+    PhraseQuery pq = new PhraseQuery(5, "field", "wizard", "oz");
     
     TopDocs hits2 = new QueryRescorer(pq) {
         @Override
@@ -229,9 +223,7 @@
     assertEquals("1", searcher.doc(hits.scoreDocs[1].doc).get("id"));
 
     // Now, resort using PhraseQuery:
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "wizard"));
-    pq.add(new Term("field", "oz"));
+    PhraseQuery pq = new PhraseQuery("field", "wizard", "oz");
 
     Rescorer rescorer = new QueryRescorer(pq) {
         @Override
@@ -306,9 +298,7 @@
     assertEquals("1", searcher.doc(hits.scoreDocs[1].doc).get("id"));
 
     // Now, resort using PhraseQuery, no slop:
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "wizard"));
-    pq.add(new Term("field", "oz"));
+    PhraseQuery pq = new PhraseQuery("field", "wizard", "oz");
 
     TopDocs hits2 = QueryRescorer.rescore(searcher, hits, pq, 2.0, 10);
 
Index: lucene/core/src/test/org/apache/lucene/search/TestScorerPerf.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestScorerPerf.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestScorerPerf.java	(working copy)
@@ -13,8 +13,8 @@
 import org.apache.lucene.index.LeafReaderContext;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.BitDocIdSet;
 import org.apache.lucene.util.Bits;
-import org.apache.lucene.util.BitDocIdSet;
 import org.apache.lucene.util.FixedBitSet;
 import org.apache.lucene.util.LuceneTestCase;
 
@@ -324,12 +324,14 @@
 
     for (int i=0; i<iter; i++) {
       int nClauses = random().nextInt(maxClauses-1)+2; // min 2 clauses
-      PhraseQuery q = new PhraseQuery();
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
       for (int j=0; j<nClauses; j++) {
         int tnum = random().nextInt(termsInIndex);
-        q.add(new Term("f",Character.toString((char)(tnum+'A'))), j);
+        builder.add(new Term("f", Character.toString((char)(tnum+'A'))));
       }
-      q.setSlop(termsInIndex);  // this could be random too
+      // slop could be random too
+      builder.setSlop(termsInIndex);
+      PhraseQuery q = builder.build();
 
       CountingHitCollector hc = new CountingHitCollector();
       s.search(q, hc);
Index: lucene/core/src/test/org/apache/lucene/search/TestSimilarity.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestSimilarity.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestSimilarity.java	(working copy)
@@ -118,9 +118,7 @@
          }
        });
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(a);
-    pq.add(c);
+    PhraseQuery pq = new PhraseQuery(a.field(), a.bytes(), c.bytes());
     //System.out.println(pq.toString("field"));
     searcher.search(pq,
        new SimpleCollector() {
@@ -140,7 +138,7 @@
          }
        });
 
-    pq.setSlop(2);
+    pq = new PhraseQuery(2, a.field(), a.bytes(), c.bytes());
     //System.out.println(pq.toString("field"));
     searcher.search(pq, new SimpleCollector() {
       private Scorer scorer;
Index: lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestSimpleExplanations.java	(working copy)
@@ -54,50 +54,31 @@
   /* some simple phrase tests */
   
   public void testP1() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD, "w1"));
-    phraseQuery.add(new Term(FIELD, "w2"));
+    PhraseQuery phraseQuery = new PhraseQuery(FIELD, "w1", "w2");
     qtest(phraseQuery, new int[] { 0 });
   }
   public void testP2() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD, "w1"));
-    phraseQuery.add(new Term(FIELD, "w3"));
+    PhraseQuery phraseQuery = new PhraseQuery(FIELD, "w1", "w3");
     qtest(phraseQuery, new int[] { 1,3 });
   }
   public void testP3() throws Exception {
-    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");
     qtest(phraseQuery, new int[] { 0,1,2 });
   }
   public void testP4() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.setSlop(1);
-    phraseQuery.add(new Term(FIELD, "w2"));
-    phraseQuery.add(new Term(FIELD, "w3"));
+    PhraseQuery phraseQuery = new PhraseQuery(1, FIELD, "w2", "w3");
     qtest(phraseQuery, new int[] { 0,1,2,3 });
   }
   public void testP5() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.setSlop(1);
-    phraseQuery.add(new Term(FIELD, "w3"));
-    phraseQuery.add(new Term(FIELD, "w2"));
+    PhraseQuery phraseQuery = new PhraseQuery(1, FIELD, "w3", "w2");
     qtest(phraseQuery, new int[] { 1,3 });
   }
   public void testP6() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.setSlop(2);
-    phraseQuery.add(new Term(FIELD, "w3"));
-    phraseQuery.add(new Term(FIELD, "w2"));
+    PhraseQuery phraseQuery = new PhraseQuery(2, FIELD, "w3", "w2");
     qtest(phraseQuery, new int[] { 0,1,3 });
   }
   public void testP7() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.setSlop(3);
-    phraseQuery.add(new Term(FIELD, "w3"));
-    phraseQuery.add(new Term(FIELD, "w2"));
+    PhraseQuery phraseQuery = new PhraseQuery(3, FIELD, "w3", "w2");
     qtest(phraseQuery, new int[] { 0,1,2,3 });
   }
 
@@ -657,14 +638,10 @@
   public void testMultiFieldBQofPQ1() throws Exception {
     BooleanQuery query = new BooleanQuery();
 
-    PhraseQuery leftChild = new PhraseQuery();
-    leftChild.add(new Term(FIELD, "w1"));
-    leftChild.add(new Term(FIELD, "w2"));
+    PhraseQuery leftChild = new PhraseQuery(FIELD, "w1", "w2");
     query.add(leftChild, BooleanClause.Occur.SHOULD);
 
-    PhraseQuery rightChild = new PhraseQuery();
-    rightChild.add(new Term(ALTFIELD, "w1"));
-    rightChild.add(new Term(ALTFIELD, "w2"));
+    PhraseQuery rightChild = new PhraseQuery(ALTFIELD, "w1", "w2");
     query.add(rightChild, BooleanClause.Occur.SHOULD);
 
     qtest(query, new int[] { 0 });
@@ -672,14 +649,10 @@
   public void testMultiFieldBQofPQ2() throws Exception {
     BooleanQuery query = new BooleanQuery();
 
-    PhraseQuery leftChild = new PhraseQuery();
-    leftChild.add(new Term(FIELD, "w1"));
-    leftChild.add(new Term(FIELD, "w3"));
+    PhraseQuery leftChild = new PhraseQuery(FIELD, "w1", "w3");
     query.add(leftChild, BooleanClause.Occur.SHOULD);
 
-    PhraseQuery rightChild = new PhraseQuery();
-    rightChild.add(new Term(ALTFIELD, "w1"));
-    rightChild.add(new Term(ALTFIELD, "w3"));
+    PhraseQuery rightChild = new PhraseQuery(ALTFIELD, "w1", "w3");
     query.add(rightChild, BooleanClause.Occur.SHOULD);
 
     qtest(query, new int[] { 1,3 });
@@ -687,16 +660,10 @@
   public void testMultiFieldBQofPQ3() throws Exception {
     BooleanQuery query = new BooleanQuery();
 
-    PhraseQuery leftChild = new PhraseQuery();
-    leftChild.setSlop(1);
-    leftChild.add(new Term(FIELD, "w1"));
-    leftChild.add(new Term(FIELD, "w2"));
+    PhraseQuery leftChild = new PhraseQuery(1, FIELD, "w1", "w2");
     query.add(leftChild, BooleanClause.Occur.SHOULD);
 
-    PhraseQuery rightChild = new PhraseQuery();
-    rightChild.setSlop(1);
-    rightChild.add(new Term(ALTFIELD, "w1"));
-    rightChild.add(new Term(ALTFIELD, "w2"));
+    PhraseQuery rightChild = new PhraseQuery(1, ALTFIELD, "w1", "w2");
     query.add(rightChild, BooleanClause.Occur.SHOULD);
 
     qtest(query, new int[] { 0,1,2 });
@@ -704,16 +671,10 @@
   public void testMultiFieldBQofPQ4() throws Exception {
     BooleanQuery query = new BooleanQuery();
 
-    PhraseQuery leftChild = new PhraseQuery();
-    leftChild.setSlop(1);
-    leftChild.add(new Term(FIELD, "w2"));
-    leftChild.add(new Term(FIELD, "w3"));
+    PhraseQuery leftChild = new PhraseQuery(1, FIELD, "w2", "w3");
     query.add(leftChild, BooleanClause.Occur.SHOULD);
 
-    PhraseQuery rightChild = new PhraseQuery();
-    rightChild.setSlop(1);
-    rightChild.add(new Term(ALTFIELD, "w2"));
-    rightChild.add(new Term(ALTFIELD, "w3"));
+    PhraseQuery rightChild = new PhraseQuery(1, ALTFIELD, "w2", "w3");
     query.add(rightChild, BooleanClause.Occur.SHOULD);
 
     qtest(query, new int[] { 0,1,2,3 });
@@ -721,16 +682,10 @@
   public void testMultiFieldBQofPQ5() throws Exception {
     BooleanQuery query = new BooleanQuery();
 
-    PhraseQuery leftChild = new PhraseQuery();
-    leftChild.setSlop(1);
-    leftChild.add(new Term(FIELD, "w3"));
-    leftChild.add(new Term(FIELD, "w2"));
+    PhraseQuery leftChild = new PhraseQuery(1, FIELD, "w3", "w2");
     query.add(leftChild, BooleanClause.Occur.SHOULD);
 
-    PhraseQuery rightChild = new PhraseQuery();
-    rightChild.setSlop(1);
-    rightChild.add(new Term(ALTFIELD, "w3"));
-    rightChild.add(new Term(ALTFIELD, "w2"));
+    PhraseQuery rightChild = new PhraseQuery(1, ALTFIELD, "w3", "w2");
     query.add(rightChild, BooleanClause.Occur.SHOULD);
 
     qtest(query, new int[] { 1,3 });
@@ -738,16 +693,10 @@
   public void testMultiFieldBQofPQ6() throws Exception {
     BooleanQuery query = new BooleanQuery();
 
-    PhraseQuery leftChild = new PhraseQuery();
-    leftChild.setSlop(2);
-    leftChild.add(new Term(FIELD, "w3"));
-    leftChild.add(new Term(FIELD, "w2"));
+    PhraseQuery leftChild = new PhraseQuery(2, FIELD, "w3", "w2");
     query.add(leftChild, BooleanClause.Occur.SHOULD);
 
-    PhraseQuery rightChild = new PhraseQuery();
-    rightChild.setSlop(2);
-    rightChild.add(new Term(ALTFIELD, "w3"));
-    rightChild.add(new Term(ALTFIELD, "w2"));
+    PhraseQuery rightChild = new PhraseQuery(2, ALTFIELD, "w3", "w2");
     query.add(rightChild, BooleanClause.Occur.SHOULD);
 
     qtest(query, new int[] { 0,1,3 });
@@ -755,16 +704,10 @@
   public void testMultiFieldBQofPQ7() throws Exception {
     BooleanQuery query = new BooleanQuery();
 
-    PhraseQuery leftChild = new PhraseQuery();
-    leftChild.setSlop(3);
-    leftChild.add(new Term(FIELD, "w3"));
-    leftChild.add(new Term(FIELD, "w2"));
+    PhraseQuery leftChild = new PhraseQuery(3, FIELD, "w3", "w2");
     query.add(leftChild, BooleanClause.Occur.SHOULD);
 
-    PhraseQuery rightChild = new PhraseQuery();
-    rightChild.setSlop(1);
-    rightChild.add(new Term(ALTFIELD, "w3"));
-    rightChild.add(new Term(ALTFIELD, "w2"));
+    PhraseQuery rightChild = new PhraseQuery(1, ALTFIELD, "w3", "w2");
     query.add(rightChild, BooleanClause.Occur.SHOULD);
 
     qtest(query, new int[] { 0,1,2,3 });
Index: lucene/core/src/test/org/apache/lucene/search/TestSimpleSearchEquivalence.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestSimpleSearchEquivalence.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestSimpleSearchEquivalence.java	(working copy)
@@ -92,9 +92,7 @@
   public void testExactPhraseVersusBooleanAnd() throws Exception {
     Term t1 = randomTerm();
     Term t2 = randomTerm();
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2);
+    PhraseQuery q1 = new PhraseQuery(t1.field(), t1.bytes(), t2.bytes());
     BooleanQuery q2 = new BooleanQuery();
     q2.add(new TermQuery(t1), Occur.MUST);
     q2.add(new TermQuery(t2), Occur.MUST);
@@ -105,9 +103,10 @@
   public void testExactPhraseVersusBooleanAndWithHoles() throws Exception {
     Term t1 = randomTerm();
     Term t2 = randomTerm();
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2, 2);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(t1, 0);
+    builder.add(t2, 2);
+    PhraseQuery q1 = builder.build();
     BooleanQuery q2 = new BooleanQuery();
     q2.add(new TermQuery(t1), Occur.MUST);
     q2.add(new TermQuery(t2), Occur.MUST);
@@ -118,13 +117,8 @@
   public void testPhraseVersusSloppyPhrase() throws Exception {
     Term t1 = randomTerm();
     Term t2 = randomTerm();
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2);
-    PhraseQuery q2 = new PhraseQuery();
-    q2.add(t1);
-    q2.add(t2);
-    q2.setSlop(1);
+    PhraseQuery q1 = new PhraseQuery(t1.field(), t1.bytes(), t2.bytes());
+    PhraseQuery q2 = new PhraseQuery(1, t1.field(), t1.bytes(), t2.bytes());
     assertSubsetOf(q1, q2);
   }
   
@@ -132,13 +126,12 @@
   public void testPhraseVersusSloppyPhraseWithHoles() throws Exception {
     Term t1 = randomTerm();
     Term t2 = randomTerm();
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2, 2);
-    PhraseQuery q2 = new PhraseQuery();
-    q2.add(t1);
-    q2.add(t2, 2);
-    q2.setSlop(1);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(t1, 0);
+    builder.add(t2, 2);
+    PhraseQuery q1 = builder.build();
+    builder.setSlop(2);
+    PhraseQuery q2 = builder.build();
     assertSubsetOf(q1, q2);
   }
   
@@ -146,9 +139,7 @@
   public void testExactPhraseVersusMultiPhrase() throws Exception {
     Term t1 = randomTerm();
     Term t2 = randomTerm();
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2);
+    PhraseQuery q1 = new PhraseQuery(t1.field(), t1.bytes(), t2.bytes());
     Term t3 = randomTerm();
     MultiPhraseQuery q2 = new MultiPhraseQuery();
     q2.add(t1);
@@ -160,9 +151,10 @@
   public void testExactPhraseVersusMultiPhraseWithHoles() throws Exception {
     Term t1 = randomTerm();
     Term t2 = randomTerm();
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2, 2);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(t1, 0);
+    builder.add(t2, 2);
+    PhraseQuery q1 = builder.build();
     Term t3 = randomTerm();
     MultiPhraseQuery q2 = new MultiPhraseQuery();
     q2.add(t1);
@@ -179,10 +171,7 @@
     do {
       t2 = randomTerm();
     } while (t1.equals(t2));
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2);
-    q1.setSlop(Integer.MAX_VALUE);
+    PhraseQuery q1 = new PhraseQuery(Integer.MAX_VALUE, t1.field(), t1.bytes(), t2.bytes());
     BooleanQuery q2 = new BooleanQuery();
     q2.add(new TermQuery(t1), Occur.MUST);
     q2.add(new TermQuery(t2), Occur.MUST);
@@ -193,12 +182,11 @@
   public void testPhraseRelativePositions() throws Exception {
     Term t1 = randomTerm();
     Term t2 = randomTerm();
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2);
-    PhraseQuery q2 = new PhraseQuery();
-    q2.add(t1, 10000);
-    q2.add(t2, 10001);
+    PhraseQuery q1 = new PhraseQuery(t1.field(), t1.bytes(), t2.bytes());
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(t1, 10000);
+    builder.add(t2, 10001);
+    PhraseQuery q2 = builder.build();
     assertSameScores(q1, q2);
   }
 
@@ -206,14 +194,12 @@
   public void testSloppyPhraseRelativePositions() throws Exception {
     Term t1 = randomTerm();
     Term t2 = randomTerm();
-    PhraseQuery q1 = new PhraseQuery();
-    q1.add(t1);
-    q1.add(t2);
-    q1.setSlop(2);
-    PhraseQuery q2 = new PhraseQuery();
-    q2.add(t1, 10000);
-    q2.add(t2, 10001);
-    q2.setSlop(2);
+    PhraseQuery q1 = new PhraseQuery(2, t1.field(), t1.bytes(), t2.bytes());
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(t1, 10000);
+    builder.add(t2, 10001);
+    builder.setSlop(2);
+    PhraseQuery q2 = builder.build();
     assertSameScores(q1, q2);
   }
 }
Index: lucene/core/src/test/org/apache/lucene/search/TestSloppyPhraseQuery.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestSloppyPhraseQuery.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestSloppyPhraseQuery.java	(working copy)
@@ -17,6 +17,8 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
+
 import org.apache.lucene.analysis.MockAnalyzer;
 import org.apache.lucene.analysis.MockTokenizer;
 import org.apache.lucene.document.Document;
@@ -31,8 +33,6 @@
 import org.apache.lucene.store.RAMDirectory;
 import org.apache.lucene.util.LuceneTestCase;
 
-import java.io.IOException;
-
 public class TestSloppyPhraseQuery extends LuceneTestCase {
 
   private static final String S_1 = "A A A";
@@ -135,7 +135,14 @@
   }
   
   private float  checkPhraseQuery(Document doc, PhraseQuery query, int slop, int expectedNumResults) throws Exception {
-    query.setSlop(slop);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    Term[] terms = query.getTerms();
+    int[] positions = query.getPositions();
+    for (int i = 0; i < terms.length; ++i) {
+      builder.add(terms[i], positions[i]);
+    }
+    builder.setSlop(slop);
+    query = builder.build();
 
     MockDirectoryWrapper ramDir = new MockDirectoryWrapper(random(), new RAMDirectory());
     RandomIndexWriter writer = new RandomIndexWriter(random(), ramDir, new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false));
@@ -168,12 +175,8 @@
   }
 
   private static PhraseQuery makePhraseQuery(String terms) {
-    PhraseQuery query = new PhraseQuery();
     String[] t = terms.split(" +");
-    for (int i=0; i<t.length; i++) {
-      query.add(new Term("f", t[i]));
-    }
-    return query;
+    return new PhraseQuery("f", t);
   }
 
   static class MaxFreqCollector extends SimpleCollector {
@@ -242,16 +245,18 @@
     IndexReader ir = iw.getReader();
     iw.close();
     IndexSearcher is = newSearcher(ir);
-    
-    PhraseQuery pq = new PhraseQuery();
+
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term("lyrics", "drug"), 1);
+    builder.add(new Term("lyrics", "drug"), 4);
+    PhraseQuery pq = builder.build();
     // "drug the drug"~1
-    pq.add(new Term("lyrics", "drug"), 1);
-    pq.add(new Term("lyrics", "drug"), 4);
-    pq.setSlop(0);
     assertEquals(1, is.search(pq, 4).totalHits);
-    pq.setSlop(1);
+    builder.setSlop(1);
+    pq = builder.build();
     assertEquals(3, is.search(pq, 4).totalHits);
-    pq.setSlop(2);
+    builder.setSlop(2);
+    pq = builder.build();
     assertEquals(4, is.search(pq, 4).totalHits);
     ir.close();
     dir.close();
@@ -270,11 +275,12 @@
     iw.close();
     
     IndexSearcher is = newSearcher(ir);
-    PhraseQuery pq = new PhraseQuery();
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term("lyrics", "drug"), 1);
+    builder.add(new Term("lyrics", "drug"), 3);
+    builder.setSlop(1);
+    PhraseQuery pq = builder.build();
     // "drug the drug"~1
-    pq.add(new Term("lyrics", "drug"), 1);
-    pq.add(new Term("lyrics", "drug"), 3);
-    pq.setSlop(1);
     assertSaneScoring(pq, is);
     ir.close();
     dir.close();
@@ -324,11 +330,12 @@
         
      IndexSearcher is = newSearcher(ir);
      
-     PhraseQuery pq = new PhraseQuery();
+     PhraseQuery.Builder builder = new PhraseQuery.Builder();
+     builder.add(new Term("lyrics", "drug"), 1);
+     builder.add(new Term("lyrics", "drug"), 4);
+     builder.setSlop(5);
+     PhraseQuery pq = builder.build();
      // "drug the drug"~5
-     pq.add(new Term("lyrics", "drug"), 1);
-     pq.add(new Term("lyrics", "drug"), 3);
-     pq.setSlop(5);
      assertSaneScoring(pq, is);
      ir.close();
      dir.close();
Index: lucene/core/src/test/org/apache/lucene/search/TestSloppyPhraseQuery2.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestSloppyPhraseQuery2.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestSloppyPhraseQuery2.java	(working copy)
@@ -31,15 +31,8 @@
     Term t1 = randomTerm();
     Term t2 = randomTerm();
     for (int i = 0; i < 10; i++) {
-      PhraseQuery q1 = new PhraseQuery();
-      q1.add(t1); 
-      q1.add(t2);
-      q1.setSlop(i);
-      PhraseQuery q2 = new PhraseQuery();
-      q2.add(t1); 
-      q2.add(t2);
-      q1.setSlop(i);
-      q2.setSlop(i+1);
+      PhraseQuery q1 = new PhraseQuery(i, t1.field(), t1.bytes(), t2.bytes());
+      PhraseQuery q2 = new PhraseQuery(i + 1, t1.field(), t1.bytes(), t2.bytes());
       assertSubsetOf(q1, q2);
     }
   }
@@ -49,14 +42,13 @@
     Term t1 = randomTerm();
     Term t2 = randomTerm();
     for (int i = 0; i < 10; i++) {
-      PhraseQuery q1 = new PhraseQuery();
-      q1.add(t1); 
-      q1.add(t2, 2);
-      q1.setSlop(i);
-      PhraseQuery q2 = new PhraseQuery();
-      q2.add(t1);
-      q2.add(t2, 2);
-      q2.setSlop(i+1);
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
+      builder.add(t1, 0);
+      builder.add(t2, 2);
+      builder.setSlop(i);
+      PhraseQuery q1 = builder.build();
+      builder.setSlop(i + 1);
+      PhraseQuery q2 = builder.build();
       assertSubsetOf(q1, q2);
     }
   }
@@ -67,17 +59,10 @@
     Term t2 = randomTerm();
     Term t3 = randomTerm();
     for (int i = 0; i < 10; i++) {
-      PhraseQuery q1 = new PhraseQuery();
-      q1.add(t1); 
-      q1.add(t2); 
-      q1.add(t3);
-      q1.setSlop(i);
-      PhraseQuery q2 = new PhraseQuery();
-      q2.add(t1);
-      q2.add(t2);
-      q2.add(t3);
-      q2.setSlop(i+1);
+      PhraseQuery q1 = new PhraseQuery(i, t1.field(), t1.bytes(), t2.bytes(), t3.bytes());
+      PhraseQuery q2 = new PhraseQuery(i + 1, t1.field(), t1.bytes(), t2.bytes(), t3.bytes());
       assertSubsetOf(q1, q2);
+      assertSubsetOf(q1, q2);
     }
   }
   
@@ -89,16 +74,14 @@
     int pos1 = 1 + random().nextInt(3);
     int pos2 = pos1 + 1 + random().nextInt(3);
     for (int i = 0; i < 10; i++) {
-      PhraseQuery q1 = new PhraseQuery();
-      q1.add(t1);
-      q1.add(t2, pos1);
-      q1.add(t3, pos2);
-      q1.setSlop(i);
-      PhraseQuery q2 = new PhraseQuery();
-      q2.add(t1);
-      q2.add(t2, pos1);
-      q2.add(t3, pos2);
-      q2.setSlop(i+1);
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
+      builder.add(t1, 0);
+      builder.add(t2, pos1);
+      builder.add(t3, pos2);
+      builder.setSlop(i);
+      PhraseQuery q1 = builder.build();
+      builder.setSlop(i + 1);
+      PhraseQuery q2 = builder.build();
       assertSubsetOf(q1, q2);
     }
   }
@@ -107,14 +90,8 @@
   public void testRepetitiveIncreasingSloppiness() throws Exception {
     Term t = randomTerm();
     for (int i = 0; i < 10; i++) {
-      PhraseQuery q1 = new PhraseQuery();
-      q1.add(t);
-      q1.add(t);
-      q1.setSlop(i);
-      PhraseQuery q2 = new PhraseQuery();
-      q2.add(t);
-      q2.add(t);
-      q2.setSlop(i+1);
+      PhraseQuery q1 = new PhraseQuery(i, t.field(), t.bytes(), t.bytes());
+      PhraseQuery q2 = new PhraseQuery(i + 1, t.field(), t.bytes(), t.bytes());
       assertSubsetOf(q1, q2);
     }
   }
@@ -123,14 +100,13 @@
   public void testRepetitiveIncreasingSloppinessWithHoles() throws Exception {
     Term t = randomTerm();
     for (int i = 0; i < 10; i++) {
-      PhraseQuery q1 = new PhraseQuery();
-      q1.add(t);
-      q1.add(t, 2);
-      q1.setSlop(i);
-      PhraseQuery q2 = new PhraseQuery();
-      q2.add(t);
-      q2.add(t, 2);
-      q2.setSlop(i+1);
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
+      builder.add(t, 0);
+      builder.add(t, 2);
+      builder.setSlop(i);
+      PhraseQuery q1 = builder.build();
+      builder.setSlop(i + 1);
+      PhraseQuery q2 = builder.build();
       assertSubsetOf(q1, q2);
     }
   }
@@ -139,17 +115,10 @@
   public void testRepetitiveIncreasingSloppiness3() throws Exception {
     Term t = randomTerm();
     for (int i = 0; i < 10; i++) {
-      PhraseQuery q1 = new PhraseQuery();
-      q1.add(t);
-      q1.add(t);
-      q1.add(t);
-      q1.setSlop(i);
-      PhraseQuery q2 = new PhraseQuery();
-      q2.add(t);
-      q2.add(t);
-      q2.add(t);
-      q2.setSlop(i+1);
+      PhraseQuery q1 = new PhraseQuery(i, t.field(), t.bytes(), t.bytes(), t.bytes());
+      PhraseQuery q2 = new PhraseQuery(i + 1, t.field(), t.bytes(), t.bytes(), t.bytes());
       assertSubsetOf(q1, q2);
+      assertSubsetOf(q1, q2);
     }
   }
   
@@ -159,17 +128,16 @@
     int pos1 = 1 + random().nextInt(3);
     int pos2 = pos1 + 1 + random().nextInt(3);
     for (int i = 0; i < 10; i++) {
-      PhraseQuery q1 = new PhraseQuery();
-      q1.add(t);
-      q1.add(t, pos1);
-      q1.add(t, pos2);
-      q1.setSlop(i);
-      PhraseQuery q2 = new PhraseQuery();
-      q2.add(t);
-      q2.add(t, pos1);
-      q2.add(t, pos2);
-      q2.setSlop(i+1);
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
+      builder.add(t, 0);
+      builder.add(t, pos1);
+      builder.add(t, pos2);
+      builder.setSlop(i);
+      PhraseQuery q1 = builder.build();
+      builder.setSlop(i + 1);
+      PhraseQuery q2 = builder.build();
       assertSubsetOf(q1, q2);
+      assertSubsetOf(q1, q2);
     }
   }
   
Index: lucene/core/src/test/org/apache/lucene/search/TestSubScorerFreqs.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/TestSubScorerFreqs.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/TestSubScorerFreqs.java	(working copy)
@@ -192,9 +192,7 @@
 
   @Test
   public void testPhraseQuery() throws Exception {
-    PhraseQuery q = new PhraseQuery();
-    q.add(new Term("f", "b"));
-    q.add(new Term("f", "c"));
+    PhraseQuery q = new PhraseQuery("f", "b", "c");
     CountingCollector c = new CountingCollector(TopScoreDocCollector.create(10));
     s.search(q, c);
     final int maxDocs = s.getIndexReader().maxDoc();
Index: lucene/core/src/test/org/apache/lucene/search/spans/TestBasics.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/spans/TestBasics.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/spans/TestBasics.java	(working copy)
@@ -113,9 +113,7 @@
   }
 
   public void testPhrase() throws Exception {
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("field", "seventy"));
-    query.add(new Term("field", "seven"));
+    PhraseQuery query = new PhraseQuery("field", "seventy", "seven");
     checkHits(query, new int[]
       {77, 177, 277, 377, 477, 577, 677, 777, 877,
               977, 1077, 1177, 1277, 1377, 1477, 1577, 1677, 1777, 1877, 1977});
@@ -122,9 +120,7 @@
   }
 
   public void testPhrase2() throws Exception {
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("field", "seventish"));
-    query.add(new Term("field", "sevenon"));
+    PhraseQuery query = new PhraseQuery("field", "seventish", "sevenon");
     checkHits(query, new int[] {});
   }
 
Index: lucene/core/src/test/org/apache/lucene/search/spans/TestSpanSearchEquivalence.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/search/spans/TestSpanSearchEquivalence.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/search/spans/TestSpanSearchEquivalence.java	(working copy)
@@ -153,9 +153,7 @@
                              spanQuery(new SpanTermQuery(t2)) 
                            };
     SpanQuery q1 = spanQuery(new SpanNearQuery(subquery, 0, true));
-    PhraseQuery q2 = new PhraseQuery();
-    q2.add(t1);
-    q2.add(t2);
+    PhraseQuery q2 = new PhraseQuery(t1.field(), t1.bytes(), t2.bytes());
     assertSameSet(q1, q2);
   }
   
Index: lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java	(revision 1684461)
+++ lucene/core/src/test/org/apache/lucene/util/TestQueryBuilder.java	(working copy)
@@ -18,8 +18,6 @@
  */
 
 import java.io.IOException;
-import java.io.Reader;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.MockAnalyzer;
@@ -100,10 +98,10 @@
   }
   
   public void testPhraseQueryPositionIncrements() throws Exception {
-    PhraseQuery expected = new PhraseQuery();
-    expected.add(new Term("field", "1"));
-    expected.add(new Term("field", "2"), 2);
-    
+    PhraseQuery.Builder pqBuilder = new PhraseQuery.Builder();
+    pqBuilder.add(new Term("field", "1"), 0);
+    pqBuilder.add(new Term("field", "2"), 2);
+    PhraseQuery expected = pqBuilder.build();
     CharacterRunAutomaton stopList = new CharacterRunAutomaton(new RegExp("[sS][tT][oO][pP]").toAutomaton());
 
     Analyzer analyzer = new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false, stopList);
@@ -219,9 +217,7 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer();
     
-    PhraseQuery expected = new PhraseQuery();
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
+    PhraseQuery expected = new PhraseQuery("field", "中", "国");
     
     QueryBuilder builder = new QueryBuilder(analyzer);
     assertEquals(expected, builder.createPhraseQuery("field", "中国"));
@@ -231,10 +227,7 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer();
     
-    PhraseQuery expected = new PhraseQuery();
-    expected.setSlop(3);
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
+    PhraseQuery expected = new PhraseQuery(3, "field", "中", "国");
     
     QueryBuilder builder = new QueryBuilder(analyzer);
     assertEquals(expected, builder.createPhraseQuery("field", "中国", 3));
Index: lucene/highlighter/src/java/org/apache/lucene/search/vectorhighlight/FieldQuery.java
===================================================================
--- lucene/highlighter/src/java/org/apache/lucene/search/vectorhighlight/FieldQuery.java	(revision 1684461)
+++ lucene/highlighter/src/java/org/apache/lucene/search/vectorhighlight/FieldQuery.java	(working copy)
@@ -226,13 +226,14 @@
         }
       }
       if( overlap && src.length - i < dest.length ){
-        PhraseQuery pq = new PhraseQuery();
+        PhraseQuery.Builder pqBuilder = new PhraseQuery.Builder();
         for( Term srcTerm : src )
-          pq.add( srcTerm );
+          pqBuilder.add( srcTerm );
         for( int k = src.length - i; k < dest.length; k++ ){
-          pq.add( new Term( src[0].field(), dest[k].text() ) );
+          pqBuilder.add( new Term( src[0].field(), dest[k].text() ) );
         }
-        pq.setSlop( slop );
+        pqBuilder.setSlop( slop );
+        PhraseQuery pq = pqBuilder.build();
         pq.setBoost( boost );
         if(!expandQueries.contains( pq ) )
           expandQueries.add( pq );
Index: lucene/highlighter/src/test/org/apache/lucene/search/highlight/HighlighterPhraseTest.java
===================================================================
--- lucene/highlighter/src/test/org/apache/lucene/search/highlight/HighlighterPhraseTest.java	(revision 1684461)
+++ lucene/highlighter/src/test/org/apache/lucene/search/highlight/HighlighterPhraseTest.java	(working copy)
@@ -70,10 +70,7 @@
     try {
       assertEquals(1, indexReader.numDocs());
       final IndexSearcher indexSearcher = newSearcher(indexReader);
-      final PhraseQuery phraseQuery = new PhraseQuery();
-      phraseQuery.add(new Term(FIELD, "fox"));
-      phraseQuery.add(new Term(FIELD, "jumped"));
-      phraseQuery.setSlop(0);
+      final PhraseQuery phraseQuery = new PhraseQuery(FIELD, "fox", "jumped");
       TopDocs hits = indexSearcher.search(phraseQuery, 1);
       assertEquals(1, hits.totalHits);
       final Highlighter highlighter = new Highlighter(
@@ -178,10 +175,7 @@
     try {
       assertEquals(1, indexReader.numDocs());
       final IndexSearcher indexSearcher = newSearcher(indexReader);
-      final PhraseQuery phraseQuery = new PhraseQuery();
-      phraseQuery.add(new Term(FIELD, "did"));
-      phraseQuery.add(new Term(FIELD, "jump"));
-      phraseQuery.setSlop(0);
+      final PhraseQuery phraseQuery = new PhraseQuery(FIELD, "did", "jump");
       TopDocs hits = indexSearcher.search(phraseQuery, 1);
       assertEquals(0, hits.totalHits);
       final Highlighter highlighter = new Highlighter(
@@ -218,10 +212,7 @@
     try {
       assertEquals(1, indexReader.numDocs());
       final IndexSearcher indexSearcher = newSearcher(indexReader);
-      final PhraseQuery phraseQuery = new PhraseQuery();
-      phraseQuery.add(new Term(FIELD, "did"));
-      phraseQuery.add(new Term(FIELD, "jump"));
-      phraseQuery.setSlop(1);
+      final PhraseQuery phraseQuery = new PhraseQuery(1, FIELD, "did", "jump");
       TopDocs hits = indexSearcher.search(phraseQuery, 1);
       assertEquals(1, hits.totalHits);
       final Highlighter highlighter = new Highlighter(
Index: lucene/highlighter/src/test/org/apache/lucene/search/highlight/HighlighterTest.java
===================================================================
--- lucene/highlighter/src/test/org/apache/lucene/search/highlight/HighlighterTest.java	(revision 1684461)
+++ lucene/highlighter/src/test/org/apache/lucene/search/highlight/HighlighterTest.java	(working copy)
@@ -17,8 +17,6 @@
  * limitations under the License.
  */
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -31,6 +29,9 @@
 import java.util.Map;
 import java.util.StringTokenizer;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.BaseTokenStreamTestCase;
 import org.apache.lucene.analysis.CachingTokenFilter;
@@ -67,6 +68,7 @@
 import org.apache.lucene.search.MultiTermQuery;
 import org.apache.lucene.search.NumericRangeQuery;
 import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.PhraseQuery.Builder;
 import org.apache.lucene.search.PrefixQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.QueryWrapperFilter;
@@ -81,11 +83,11 @@
 import org.apache.lucene.search.join.ScoreMode;
 import org.apache.lucene.search.join.ToChildBlockJoinQuery;
 import org.apache.lucene.search.join.ToParentBlockJoinQuery;
+import org.apache.lucene.search.payloads.SpanPayloadCheckQuery;
 import org.apache.lucene.search.spans.SpanMultiTermQueryWrapper;
 import org.apache.lucene.search.spans.SpanNearQuery;
 import org.apache.lucene.search.spans.SpanNotQuery;
 import org.apache.lucene.search.spans.SpanOrQuery;
-import org.apache.lucene.search.payloads.SpanPayloadCheckQuery;
 import org.apache.lucene.search.spans.SpanQuery;
 import org.apache.lucene.search.spans.SpanTermQuery;
 import org.apache.lucene.store.Directory;
@@ -140,9 +142,7 @@
   }
 
   public void testQueryScorerHits() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD_NAME, "very"));
-    phraseQuery.add(new Term(FIELD_NAME, "long"));
+    PhraseQuery phraseQuery = new PhraseQuery(FIELD_NAME, "very", "long");
 
     query = phraseQuery;
     searcher = newSearcher(reader);
@@ -264,10 +264,7 @@
     // highlighted
     // regardless of the field name.
 
-    PhraseQuery q = new PhraseQuery();
-    q.setSlop(3);
-    q.add(new Term(FIELD_NAME, "world"));
-    q.add(new Term(FIELD_NAME, "flatland"));
+    PhraseQuery q = new PhraseQuery(3, FIELD_NAME, "world", "flatland");
 
     String expected = "I call our <B>world</B> <B>Flatland</B>, not because we call it so,";
     String observed = highlightField(q, "SOME_FIELD_NAME", s1);
@@ -280,10 +277,7 @@
     // when the query field name differs from the name of the field being
     // highlighted,
     // which in this example happens to be the default field name.
-    q = new PhraseQuery();
-    q.setSlop(3);
-    q.add(new Term("text", "world"));
-    q.add(new Term("text", "flatland"));
+    q = new PhraseQuery(3, "text", "world", "flatland");
 
     expected = s1;
     observed = highlightField(q, FIELD_NAME, s1);
@@ -337,27 +331,13 @@
     String f1 = "f1";
     String f2 = "f2";
 
-    PhraseQuery f1ph1 = new PhraseQuery();
-    f1ph1.add(new Term(f1, "a"));
-    f1ph1.add(new Term(f1, "b"));
-    f1ph1.add(new Term(f1, "c"));
-    f1ph1.add(new Term(f1, "d"));
+    PhraseQuery f1ph1 = new PhraseQuery(f1, "a", "b", "c", "d");
 
-    PhraseQuery f2ph1 = new PhraseQuery();
-    f2ph1.add(new Term(f2, "a"));
-    f2ph1.add(new Term(f2, "b"));
-    f2ph1.add(new Term(f2, "c"));
-    f2ph1.add(new Term(f2, "d"));
+    PhraseQuery f2ph1 = new PhraseQuery(f2, "a", "b", "c", "d");
 
-    PhraseQuery f1ph2 = new PhraseQuery();
-    f1ph2.add(new Term(f1, "b"));
-    f1ph2.add(new Term(f1, "c"));
-    f1ph2.add(new Term(f1, "g"));
+    PhraseQuery f1ph2 = new PhraseQuery(f1, "b", "c", "g");
 
-    PhraseQuery f2ph2 = new PhraseQuery();
-    f2ph2.add(new Term(f2, "b"));
-    f2ph2.add(new Term(f2, "c"));
-    f2ph2.add(new Term(f2, "g"));
+    PhraseQuery f2ph2 = new PhraseQuery(f2, "b", "c", "g");
 
     BooleanQuery booleanQuery = new BooleanQuery();
     BooleanQuery leftChild = new BooleanQuery();
@@ -384,10 +364,11 @@
   }
 
   public void testSimpleQueryScorerPhraseHighlighting() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD_NAME, "very"));
-    phraseQuery.add(new Term(FIELD_NAME, "long"));
-    phraseQuery.add(new Term(FIELD_NAME, "contains"), 3);
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term(FIELD_NAME, "very"), 0);
+    builder.add(new Term(FIELD_NAME, "long"), 1);
+    builder.add(new Term(FIELD_NAME, "contains"), 3);
+    PhraseQuery phraseQuery = builder.build();
     doSearching(phraseQuery);
 
     int maxNumFragmentsRequired = 2;
@@ -413,11 +394,12 @@
     
     numHighlights = 0;
 
-    phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD_NAME, "piece"), 1);
-    phraseQuery.add(new Term(FIELD_NAME, "text"), 3);
-    phraseQuery.add(new Term(FIELD_NAME, "refers"), 4);
-    phraseQuery.add(new Term(FIELD_NAME, "kennedy"), 6);
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term(FIELD_NAME, "piece"), 1);
+    builder.add(new Term(FIELD_NAME, "text"), 3);
+    builder.add(new Term(FIELD_NAME, "refers"), 4);
+    builder.add(new Term(FIELD_NAME, "kennedy"), 6);
+    phraseQuery = builder.build();
 
     doSearching(phraseQuery);
 
@@ -444,11 +426,12 @@
     
     numHighlights = 0;
 
-    phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD_NAME, "lets"));
-    phraseQuery.add(new Term(FIELD_NAME, "lets"), 4);
-    phraseQuery.add(new Term(FIELD_NAME, "lets"), 8);
-    phraseQuery.add(new Term(FIELD_NAME, "lets"), 12);
+    builder = new PhraseQuery.Builder();
+    builder.add(new Term(FIELD_NAME, "lets"), 0);
+    builder.add(new Term(FIELD_NAME, "lets"), 4);
+    builder.add(new Term(FIELD_NAME, "lets"), 8);
+    builder.add(new Term(FIELD_NAME, "lets"), 12);
+    phraseQuery = builder.build();
 
     doSearching(phraseQuery);
 
@@ -636,11 +619,7 @@
   }
 
   public void testSimpleQueryScorerPhraseHighlighting2() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.setSlop(5);
-    phraseQuery.add(new Term(FIELD_NAME, "text"));
-    phraseQuery.add(new Term(FIELD_NAME, "piece"));
-    phraseQuery.add(new Term(FIELD_NAME, "long"));
+    PhraseQuery phraseQuery = new PhraseQuery(5, FIELD_NAME, "text", "piece", "long");
     doSearching(phraseQuery);
 
     int maxNumFragmentsRequired = 2;
@@ -665,10 +644,7 @@
   }
 
   public void testSimpleQueryScorerPhraseHighlighting3() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD_NAME, "x"));
-    phraseQuery.add(new Term(FIELD_NAME, "y"));
-    phraseQuery.add(new Term(FIELD_NAME, "z"));
+    PhraseQuery phraseQuery = new PhraseQuery(FIELD_NAME, "x", "y", "z");
     doSearching(phraseQuery);
 
     int maxNumFragmentsRequired = 2;
@@ -693,11 +669,12 @@
   }
   
   public void testSimpleSpanFragmenter() throws Exception {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD_NAME, "piece"));
-    phraseQuery.add(new Term(FIELD_NAME, "text"), 2);
-    phraseQuery.add(new Term(FIELD_NAME, "very"), 5);
-    phraseQuery.add(new Term(FIELD_NAME, "long"), 6);
+    Builder builder = new PhraseQuery.Builder();
+    builder.add(new Term(FIELD_NAME, "piece"), 0);
+    builder.add(new Term(FIELD_NAME, "text"), 2);
+    builder.add(new Term(FIELD_NAME, "very"), 5);
+    builder.add(new Term(FIELD_NAME, "long"), 6);
+    PhraseQuery phraseQuery = builder.build();
     doSearching(phraseQuery);
 
     int maxNumFragmentsRequired = 2;
@@ -719,9 +696,7 @@
 
     }
 
-    phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD_NAME, "been"));
-    phraseQuery.add(new Term(FIELD_NAME, "shot"));
+    phraseQuery = new PhraseQuery(FIELD_NAME, "been", "shot");
 
     doSearching(query);
 
@@ -748,10 +723,7 @@
     BooleanQuery booleanQuery = new BooleanQuery();
     booleanQuery.add(new TermQuery(new Term(FIELD_NAME, "y")), Occur.SHOULD);
 
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(FIELD_NAME, "x"));
-    phraseQuery.add(new Term(FIELD_NAME, "y"));
-    phraseQuery.add(new Term(FIELD_NAME, "z"));
+    PhraseQuery phraseQuery = new PhraseQuery(FIELD_NAME, "x", "y", "z");
     booleanQuery.add(phraseQuery, Occur.SHOULD);
 
     doSearching(booleanQuery);
@@ -1109,9 +1081,7 @@
       @Override
       public void run() throws Exception {
         numHighlights = 0;
-        PhraseQuery phraseQuery = new PhraseQuery();
-        phraseQuery.add(new Term(FIELD_NAME, "john"));
-        phraseQuery.add(new Term(FIELD_NAME, "kennedy"));
+        PhraseQuery phraseQuery = new PhraseQuery(FIELD_NAME, "john", "kennedy");
         doSearching(phraseQuery);
         doStandardHighlights(analyzer, searcher, hits, query, HighlighterTest.this);
         // Currently highlights "John" and "Kennedy" separately
@@ -1192,9 +1162,7 @@
       @Override
       public void run() throws Exception {
         numHighlights = 0;
-        PhraseQuery pq = new PhraseQuery();
-        pq.add(new Term("contents", "john"));
-        pq.add(new Term("contents", "kennedy"));
+        PhraseQuery pq = new PhraseQuery("contents", "john", "kennedy");
         BooleanQuery bq = new BooleanQuery();
         bq.add(pq, Occur.MUST);
         bq.add(TermRangeQuery.newStringRange("contents", "john", "john", true, true), Occur.FILTER);
Index: lucene/highlighter/src/test/org/apache/lucene/search/highlight/MissesTest.java
===================================================================
--- lucene/highlighter/src/test/org/apache/lucene/search/highlight/MissesTest.java	(revision 1684461)
+++ lucene/highlighter/src/test/org/apache/lucene/search/highlight/MissesTest.java	(working copy)
@@ -58,9 +58,7 @@
 
   public void testPhraseQuery() throws IOException, InvalidTokenOffsetsException {
     try (Analyzer analyzer = new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false)) {
-      final PhraseQuery query = new PhraseQuery();
-      query.add(new Term("test", "foo"));
-      query.add(new Term("test", "bar"));
+      final PhraseQuery query = new PhraseQuery("test", "foo", "bar");
       final Highlighter highlighter = new Highlighter(new SimpleHTMLFormatter(), new QueryScorer(query));
       assertEquals("this is a <B>foo</B> <B>bar</B> example",
           highlighter.getBestFragment(analyzer, "test", "this is a foo bar example"));
Index: lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighter.java
===================================================================
--- lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighter.java	(revision 1684461)
+++ lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighter.java	(working copy)
@@ -446,9 +446,7 @@
     IndexReader ir = iw.getReader();
     iw.close();
     IndexSearcher searcher = newSearcher(ir);
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("body", "buddhist"));
-    query.add(new Term("body", "origins"));
+    PhraseQuery query = new PhraseQuery("body", "buddhist", "origins");
     TopDocs topDocs = searcher.search(query, 10);
     assertEquals(1, topDocs.totalHits);
     PostingsHighlighter highlighter = new PostingsHighlighter();
@@ -476,9 +474,7 @@
     IndexReader ir = iw.getReader();
     iw.close();
     IndexSearcher searcher = newSearcher(ir);
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("body", "curious"));
-    query.add(new Term("body", "george"));
+    PhraseQuery query = new PhraseQuery("body", "curious", "george");
     TopDocs topDocs = searcher.search(query, 10);
     assertEquals(1, topDocs.totalHits);
     PostingsHighlighter highlighter = new PostingsHighlighter();
Index: lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/AbstractTestCase.java
===================================================================
--- lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/AbstractTestCase.java	(revision 1684461)
+++ lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/AbstractTestCase.java	(working copy)
@@ -138,12 +138,8 @@
   }
   
   protected Query pq( float boost, int slop, String field, String... texts ){
-    PhraseQuery query = new PhraseQuery();
-    for( String text : texts ){
-      query.add( new Term( field, text ) );
-    }
+    PhraseQuery query = new PhraseQuery(slop, field, texts);
     query.setBoost( boost );
-    query.setSlop( slop );
     return query;
   }
   
@@ -188,11 +184,7 @@
   }
 
   protected PhraseQuery toPhraseQuery(List<BytesRef> bytesRefs, String field) {
-    PhraseQuery phraseQuery = new PhraseQuery();
-    for (BytesRef bytesRef : bytesRefs) {
-      phraseQuery.add(new Term(field, bytesRef));
-    }
-    return phraseQuery;
+    return new PhraseQuery(field, bytesRefs.toArray(new BytesRef[0]));
   }
 
   static final class BigramAnalyzer extends Analyzer {
Index: lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/FastVectorHighlighterTest.java
===================================================================
--- lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/FastVectorHighlighterTest.java	(revision 1684461)
+++ lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/FastVectorHighlighterTest.java	(working copy)
@@ -117,9 +117,7 @@
     }
     
     {
-      PhraseQuery query = new PhraseQuery();
-      query.add(new Term(field, "internet"));
-      query.add(new Term(field, "explorer"));
+      PhraseQuery query = new PhraseQuery(field, "internet", "explorer");
       FieldQuery fieldQuery = highlighter.getFieldQuery(query, reader);
       String[] bestFragments = highlighter.getBestFragments(fieldQuery, reader,
           docId, field, 128, 1);
@@ -166,11 +164,7 @@
     }
     {
       BooleanQuery query = new BooleanQuery();
-      PhraseQuery pq = new PhraseQuery();
-      pq.add(new Term(field, "test"));
-      pq.add(new Term(field, "foo"));
-      pq.add(new Term(field, "highlighed"));
-      pq.setSlop(5);
+      PhraseQuery pq = new PhraseQuery(5, field, "test", "foo", "highlighed");
       query.add(new TermQuery(new Term(field, "foo")), Occur.MUST);
       query.add(pq, Occur.MUST);
       query.add(new TermQuery(new Term(field, "highlighed")), Occur.MUST);
@@ -187,11 +181,7 @@
       
     }
     {
-      PhraseQuery query = new PhraseQuery();
-      query.add(new Term(field, "test"));
-      query.add(new Term(field, "foo"));
-      query.add(new Term(field, "highlighed"));
-      query.setSlop(3);
+      PhraseQuery query = new PhraseQuery(3, field, "test", "foo", "highlighed");
       FieldQuery fieldQuery = highlighter.getFieldQuery(query, reader);
       String[] bestFragments = highlighter.getBestFragments(fieldQuery, reader,
           docId, field, 18, 1);
@@ -205,11 +195,7 @@
       
     }
     {
-      PhraseQuery query = new PhraseQuery();
-      query.add(new Term(field, "test"));
-      query.add(new Term(field, "foo"));
-      query.add(new Term(field, "highlighted"));
-      query.setSlop(30);
+      PhraseQuery query = new PhraseQuery(30, field, "test", "foo", "highlighed");
       FieldQuery fieldQuery = highlighter.getFieldQuery(query, reader);
       String[] bestFragments = highlighter.getBestFragments(fieldQuery, reader,
           docId, field, 18, 1);
@@ -217,11 +203,7 @@
     }
     {
       BooleanQuery query = new BooleanQuery();
-      PhraseQuery pq = new PhraseQuery();
-      pq.add(new Term(field, "test"));
-      pq.add(new Term(field, "foo"));
-      pq.add(new Term(field, "highlighed"));
-      pq.setSlop(5);
+      PhraseQuery pq = new PhraseQuery(5, field, "test", "foo", "highlighed");
       BooleanQuery inner = new BooleanQuery();
       inner.add(pq, Occur.MUST);
       inner.add(new TermQuery(new Term(field, "foo")), Occur.MUST);
@@ -536,23 +518,13 @@
     int docId = 0;
     
     // query1: match
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "test"));
-    pq.add(new Term("field", "http"));
-    pq.add(new Term("field", "www"));
-    pq.add(new Term("field", "facebook"));
-    pq.add(new Term("field", "com"));
+    PhraseQuery pq = new PhraseQuery("field", "test", "http", "www", "facebook", "com");
     FieldQuery fieldQuery  = highlighter.getFieldQuery(pq, reader);
     String[] bestFragments = highlighter.getBestFragments(fieldQuery, reader, docId, "field", 54, 1);
     assertEquals("<b>Test: http://www.facebook.com</b>", bestFragments[0]);
     
     // query2: match
-    PhraseQuery pq2 = new PhraseQuery();
-    pq2.add(new Term("field", "test"));
-    pq2.add(new Term("field", "httpwwwfacebookcom"));
-    pq2.add(new Term("field", "www"));
-    pq2.add(new Term("field", "facebook"));
-    pq2.add(new Term("field", "com"));
+    PhraseQuery pq2 = new PhraseQuery("field", "test", "httpwwwfacebookcom", "www", "facebook", "com");
     fieldQuery  = highlighter.getFieldQuery(pq2, reader);
     bestFragments = highlighter.getBestFragments(fieldQuery, reader, docId, "field", 54, 1);
     assertEquals("<b>Test: http://www.facebook.com</b>", bestFragments[0]);
@@ -661,11 +633,7 @@
     if ( terms.length == 1 ) {
       q = new TermQuery( new Term( field, terms[ 0 ] ) );
     } else {
-      PhraseQuery pq = new PhraseQuery();
-      for ( String term: terms ) {
-        pq.add( new Term( field, term ) );
-      }
-      q = pq;
+      q = new PhraseQuery(field, terms);
     }
     q.setBoost( boost );
     return q;
Index: lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/SimpleFragListBuilderTest.java
===================================================================
--- lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/SimpleFragListBuilderTest.java	(revision 1684461)
+++ lucene/highlighter/src/test/org/apache/lucene/search/vectorhighlight/SimpleFragListBuilderTest.java	(working copy)
@@ -48,9 +48,7 @@
   public void testSmallerFragSizeThanPhraseQuery() throws Exception {
     SimpleFragListBuilder sflb = new SimpleFragListBuilder();
 
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(F, "abcdefgh"));
-    phraseQuery.add(new Term(F, "jklmnopqrs"));
+    PhraseQuery phraseQuery = new PhraseQuery(F, "abcdefgh", "jklmnopqrs");
 
     FieldFragList ffl = sflb.createFieldFragList( fpl(phraseQuery, "abcdefgh   jklmnopqrs" ), sflb.minFragCharSize );
     assertEquals( 1, ffl.getFragInfos().size() );
@@ -120,9 +118,7 @@
   public void testPhraseQuery() throws Exception {
     SimpleFragListBuilder sflb = new SimpleFragListBuilder();
 
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.add(new Term(F, "a"));
-    phraseQuery.add(new Term(F, "b"));
+    PhraseQuery phraseQuery = new PhraseQuery(F, "a", "b");
 
     FieldFragList ffl = sflb.createFieldFragList( fpl(phraseQuery, "c d e" ), 20 );
     assertEquals( 0, ffl.getFragInfos().size() );
@@ -138,10 +134,7 @@
   public void testPhraseQuerySlop() throws Exception {
     SimpleFragListBuilder sflb = new SimpleFragListBuilder();
 
-    PhraseQuery phraseQuery = new PhraseQuery();
-    phraseQuery.setSlop(1);
-    phraseQuery.add(new Term(F, "a"));
-    phraseQuery.add(new Term(F, "b"));
+    PhraseQuery phraseQuery = new PhraseQuery(1, F, "a", "b");
 
     FieldFragList ffl = sflb.createFieldFragList( fpl(phraseQuery, "a c b" ), 20 );
     assertEquals( 1, ffl.getFragInfos().size() );
Index: lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndexAgainstRAMDir.java
===================================================================
--- lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndexAgainstRAMDir.java	(revision 1684461)
+++ lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndexAgainstRAMDir.java	(working copy)
@@ -410,9 +410,7 @@
     LeafReader reader = (LeafReader) mindex.createSearcher().getIndexReader();
     TestUtil.checkReader(reader);
     assertEquals(7, reader.terms("field").getSumTotalTermFreq());
-    PhraseQuery query = new PhraseQuery();
-    query.add(new Term("field", "fox"));
-    query.add(new Term("field", "jumps"));
+    PhraseQuery query = new PhraseQuery("field", "fox", "jumps");
     assertTrue(mindex.search(query) > 0.1);
     mindex.reset();
     mockAnalyzer.setPositionIncrementGap(1 + random().nextInt(10));
@@ -419,7 +417,7 @@
     mindex.addField("field", "the quick brown fox", mockAnalyzer);
     mindex.addField("field", "jumps over the", mockAnalyzer);
     assertEquals(0, mindex.search(query), 0.00001f);
-    query.setSlop(10);
+    query = new PhraseQuery(10, "field", "fox", "jumps");
     assertTrue("posGap" + mockAnalyzer.getPositionIncrementGap("field") , mindex.search(query) > 0.0001);
     TestUtil.checkReader(mindex.createSearcher().getIndexReader());
   }
Index: lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/MultiFieldQueryParser.java
===================================================================
--- lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/MultiFieldQueryParser.java	(revision 1684461)
+++ lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/MultiFieldQueryParser.java	(working copy)
@@ -109,7 +109,7 @@
               q.setBoost(boost.floatValue());
             }
           }
-          applySlop(q,slop);
+          q = applySlop(q,slop);
           clauses.add(new BooleanClause(q, BooleanClause.Occur.SHOULD));
         }
       }
@@ -118,16 +118,26 @@
       return getBooleanQuery(clauses, true);
     }
     Query q = super.getFieldQuery(field, queryText, true);
-    applySlop(q,slop);
+    q = applySlop(q,slop);
     return q;
   }
 
-  private void applySlop(Query q, int slop) {
+  private Query applySlop(Query q, int slop) {
     if (q instanceof PhraseQuery) {
-      ((PhraseQuery) q).setSlop(slop);
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
+      builder.setSlop(slop);
+      PhraseQuery pq = (PhraseQuery) q;
+      org.apache.lucene.index.Term[] terms = pq.getTerms();
+      int[] positions = pq.getPositions();
+      for (int i = 0; i < terms.length; ++i) {
+        builder.add(terms[i], positions[i]);
+      }
+      q = builder.build();
+      q.setBoost(pq.getBoost());
     } else if (q instanceof MultiPhraseQuery) {
       ((MultiPhraseQuery) q).setSlop(slop);
     }
+    return q;
   }
   
 
Index: lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java
===================================================================
--- lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java	(revision 1684461)
+++ lucene/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java	(working copy)
@@ -493,7 +493,16 @@
     Query query = getFieldQuery(field, queryText, true);
 
     if (query instanceof PhraseQuery) {
-      ((PhraseQuery) query).setSlop(slop);
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
+      builder.setSlop(slop);
+      PhraseQuery pq = (PhraseQuery) query;
+      org.apache.lucene.index.Term[] terms = pq.getTerms();
+      int[] positions = pq.getPositions();
+      for (int i = 0; i < terms.length; ++i) {
+        builder.add(terms[i], positions[i]);
+      }
+      query = builder.build();
+      query.setBoost(pq.getBoost());
     }
     if (query instanceof MultiPhraseQuery) {
       ((MultiPhraseQuery) query).setSlop(slop);
Index: lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/PhraseQueryNodeBuilder.java
===================================================================
--- lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/PhraseQueryNodeBuilder.java	(revision 1684461)
+++ lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/PhraseQueryNodeBuilder.java	(working copy)
@@ -25,6 +25,7 @@
 import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode;
 import org.apache.lucene.queryparser.flexible.core.nodes.TokenizedPhraseQueryNode;
 import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 
 /**
@@ -38,10 +39,10 @@
   }
 
   @Override
-  public PhraseQuery build(QueryNode queryNode) throws QueryNodeException {
+  public Query build(QueryNode queryNode) throws QueryNodeException {
     TokenizedPhraseQueryNode phraseNode = (TokenizedPhraseQueryNode) queryNode;
 
-    PhraseQuery phraseQuery = new PhraseQuery();
+    PhraseQuery.Builder builder = new PhraseQuery.Builder();
 
     List<QueryNode> children = phraseNode.getChildren();
 
@@ -52,13 +53,12 @@
             .getTag(QueryTreeBuilder.QUERY_TREE_BUILDER_TAGID);
         FieldQueryNode termNode = (FieldQueryNode) child;
 
-        phraseQuery.add(termQuery.getTerm(), termNode.getPositionIncrement());
-
+        builder.add(termQuery.getTerm(), termNode.getPositionIncrement());
       }
 
     }
 
-    return phraseQuery;
+    return builder.build();
 
   }
 
Index: lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/SlopQueryNodeBuilder.java
===================================================================
--- lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/SlopQueryNodeBuilder.java	(revision 1684461)
+++ lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/SlopQueryNodeBuilder.java	(working copy)
@@ -45,7 +45,16 @@
         QueryTreeBuilder.QUERY_TREE_BUILDER_TAGID);
 
     if (query instanceof PhraseQuery) {
-      ((PhraseQuery) query).setSlop(phraseSlopNode.getValue());
+      PhraseQuery.Builder builder = new PhraseQuery.Builder();
+      builder.setSlop(phraseSlopNode.getValue());
+      PhraseQuery pq = (PhraseQuery) query;
+      org.apache.lucene.index.Term[] terms = pq.getTerms();
+      int[] positions = pq.getPositions();
+      for (int i = 0; i < terms.length; ++i) {
+        builder.add(terms[i], positions[i]);
+      }
+      query = builder.build();
+      query.setBoost(pq.getBoost());
 
     } else {
       ((MultiPhraseQuery) query).setSlop(phraseSlopNode.getValue());
Index: lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/TestQPHelper.java
===================================================================
--- lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/TestQPHelper.java	(revision 1684461)
+++ lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/TestQPHelper.java	(working copy)
@@ -28,7 +28,13 @@
 import java.util.Map;
 import java.util.TimeZone;
 
-import org.apache.lucene.analysis.*;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.analysis.MockTokenFilter;
+import org.apache.lucene.analysis.MockTokenizer;
+import org.apache.lucene.analysis.TokenFilter;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.Tokenizer;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
@@ -48,8 +54,8 @@
 import org.apache.lucene.queryparser.flexible.messages.MessageImpl;
 import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler;
 import org.apache.lucene.queryparser.flexible.standard.nodes.WildcardQueryNode;
+import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanClause.Occur;
-import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.FuzzyQuery;
 import org.apache.lucene.search.IndexSearcher;
@@ -401,9 +407,7 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer();
     
-    PhraseQuery expected = new PhraseQuery();
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
+    PhraseQuery expected = new PhraseQuery("field", "中", "国");
     
     assertEquals(expected, getQuery("\"中国\"", analyzer));
   }
@@ -412,10 +416,8 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer();
     
-    PhraseQuery expected = new PhraseQuery();
+    PhraseQuery expected = new PhraseQuery("field", "中", "国");
     expected.setBoost(0.5f);
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
     
     assertEquals(expected, getQuery("\"中国\"^0.5", analyzer));
   }
@@ -424,10 +426,7 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer(); 
     
-    PhraseQuery expected = new PhraseQuery();
-    expected.setSlop(3);
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
+    PhraseQuery expected = new PhraseQuery(3, "field", "中", "国");
     
     assertEquals(expected, getQuery("\"中国\"~3", analyzer));
   }
Index: lucene/queryparser/src/test/org/apache/lucene/queryparser/simple/TestSimpleQueryParser.java
===================================================================
--- lucene/queryparser/src/test/org/apache/lucene/queryparser/simple/TestSimpleQueryParser.java	(revision 1684461)
+++ lucene/queryparser/src/test/org/apache/lucene/queryparser/simple/TestSimpleQueryParser.java	(working copy)
@@ -103,9 +103,7 @@
 
   /** test a simple phrase */
   public void testPhrase() throws Exception {
-    PhraseQuery expected = new PhraseQuery();
-    expected.add(new Term("field", "foo"));
-    expected.add(new Term("field", "bar"));
+    PhraseQuery expected = new PhraseQuery("field", "foo", "bar");
 
     assertEquals(expected, parse("\"foo bar\""));
   }
@@ -112,23 +110,15 @@
 
   /** test a simple phrase with various slop settings */
   public void testPhraseWithSlop() throws Exception {
-    PhraseQuery expectedWithSlop = new PhraseQuery();
-    expectedWithSlop.add(new Term("field", "foo"));
-    expectedWithSlop.add(new Term("field", "bar"));
-    expectedWithSlop.setSlop(2);
+    PhraseQuery expectedWithSlop = new PhraseQuery(2, "field", "foo", "bar");
 
     assertEquals(expectedWithSlop, parse("\"foo bar\"~2"));
 
-    PhraseQuery expectedWithMultiDigitSlop = new PhraseQuery();
-    expectedWithMultiDigitSlop.add(new Term("field", "foo"));
-    expectedWithMultiDigitSlop.add(new Term("field", "bar"));
-    expectedWithMultiDigitSlop.setSlop(10);
+    PhraseQuery expectedWithMultiDigitSlop = new PhraseQuery(10, "field", "foo", "bar");
 
     assertEquals(expectedWithMultiDigitSlop, parse("\"foo bar\"~10"));
 
-    PhraseQuery expectedNoSlop = new PhraseQuery();
-    expectedNoSlop.add(new Term("field", "foo"));
-    expectedNoSlop.add(new Term("field", "bar"));
+    PhraseQuery expectedNoSlop = new PhraseQuery("field", "foo", "bar");
 
     assertEquals("Ignore trailing tilde with no slop", expectedNoSlop, parse("\"foo bar\"~"));
     assertEquals("Ignore non-numeric trailing slop", expectedNoSlop, parse("\"foo bar\"~a"));
@@ -135,10 +125,7 @@
     assertEquals("Ignore non-numeric trailing slop", expectedNoSlop, parse("\"foo bar\"~1a"));
     assertEquals("Ignore negative trailing slop", expectedNoSlop, parse("\"foo bar\"~-1"));
 
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("field", "foo"));
-    pq.add(new Term("field", "bar"));
-    pq.setSlop(12);
+    PhraseQuery pq = new PhraseQuery(12, "field", "foo", "bar");
 
     BooleanQuery expectedBoolean = new BooleanQuery();
     expectedBoolean.add(pq, Occur.MUST);
@@ -165,12 +152,8 @@
 
   /** test some AND'd phrases using '+' operator */
   public void testANDPhrase() throws Exception {
-    PhraseQuery phrase1 = new PhraseQuery();
-    phrase1.add(new Term("field", "foo"));
-    phrase1.add(new Term("field", "bar"));
-    PhraseQuery phrase2 = new PhraseQuery();
-    phrase2.add(new Term("field", "star"));
-    phrase2.add(new Term("field", "wars"));
+    PhraseQuery phrase1 = new PhraseQuery("field", "foo", "bar");
+    PhraseQuery phrase2 = new PhraseQuery("field", "star", "wars");
     BooleanQuery expected = new BooleanQuery();
     expected.add(phrase1, Occur.MUST);
     expected.add(phrase2, Occur.MUST);
@@ -209,12 +192,8 @@
 
   /** test some OR'd phrases using '|' operator */
   public void testORPhrase() throws Exception {
-    PhraseQuery phrase1 = new PhraseQuery();
-    phrase1.add(new Term("field", "foo"));
-    phrase1.add(new Term("field", "bar"));
-    PhraseQuery phrase2 = new PhraseQuery();
-    phrase2.add(new Term("field", "star"));
-    phrase2.add(new Term("field", "wars"));
+    PhraseQuery phrase1 = new PhraseQuery("field", "foo", "bar");
+    PhraseQuery phrase2 = new PhraseQuery("field", "star", "wars");
     BooleanQuery expected = new BooleanQuery();
     expected.add(phrase1, Occur.SHOULD);
     expected.add(phrase2, Occur.SHOULD);
@@ -324,9 +303,7 @@
   }
 
   public void testGarbagePhrase() throws Exception {
-    PhraseQuery expected = new PhraseQuery();
-    expected.add(new Term("field", "star"));
-    expected.add(new Term("field", "wars"));
+    PhraseQuery expected = new PhraseQuery("field", "star", "wars");
 
     assertEquals(expected, parse("\"star wars\""));
     assertEquals(expected, parse("\"star wars\\ \""));
@@ -614,9 +591,7 @@
   }
 
   public void testDisableSlop() {
-    PhraseQuery expectedPhrase = new PhraseQuery();
-    expectedPhrase.add(new Term("field", "foo"));
-    expectedPhrase.add(new Term("field", "bar"));
+    PhraseQuery expectedPhrase = new PhraseQuery("field", "foo", "bar");
 
     BooleanQuery expected = new BooleanQuery();
     expected.add(expectedPhrase, Occur.MUST);
Index: lucene/queryparser/src/test/org/apache/lucene/queryparser/util/QueryParserTestBase.java
===================================================================
--- lucene/queryparser/src/test/org/apache/lucene/queryparser/util/QueryParserTestBase.java	(revision 1684461)
+++ lucene/queryparser/src/test/org/apache/lucene/queryparser/util/QueryParserTestBase.java	(working copy)
@@ -298,9 +298,7 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer();
     
-    PhraseQuery expected = new PhraseQuery();
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
+    PhraseQuery expected = new PhraseQuery("field", "中", "国");
     
     assertEquals(expected, getQuery("\"中国\"", analyzer));
   }
@@ -309,10 +307,8 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer();
     
-    PhraseQuery expected = new PhraseQuery();
+    PhraseQuery expected = new PhraseQuery("field", "中", "国");
     expected.setBoost(0.5f);
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
     
     assertEquals(expected, getQuery("\"中国\"^0.5", analyzer));
   }
@@ -321,10 +317,7 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer();
     
-    PhraseQuery expected = new PhraseQuery();
-    expected.setSlop(3);
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
+    PhraseQuery expected = new PhraseQuery(3, "field", "中", "国");
     
     assertEquals(expected, getQuery("\"中国\"~3", analyzer));
   }
@@ -333,9 +326,7 @@
     // individual CJK chars as terms
     SimpleCJKAnalyzer analyzer = new SimpleCJKAnalyzer(); 
   
-    PhraseQuery expected = new PhraseQuery();
-    expected.add(new Term("field", "中"));
-    expected.add(new Term("field", "国"));
+    PhraseQuery expected = new PhraseQuery("field", "中", "国");
     CommonQueryParserConfiguration qp = getParserConfig(analyzer);
     setAutoGeneratePhraseQueries(qp, true);
     assertEquals(expected, getQuery("中国",qp));
@@ -1259,10 +1250,10 @@
                          new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false, stopStopList));
     qp.setEnablePositionIncrements(true);
 
-    PhraseQuery phraseQuery = new PhraseQuery();
+    PhraseQuery.Builder phraseQuery = new PhraseQuery.Builder();
     phraseQuery.add(new Term("field", "1"));
     phraseQuery.add(new Term("field", "2"), 2);
-    assertEquals(phraseQuery, getQuery("\"1 stop 2\"",qp));
+    assertEquals(phraseQuery.build(), getQuery("\"1 stop 2\"",qp));
   }
 
   public void testMatchAllQueryParsing() throws Exception {
Index: lucene/test-framework/src/java/org/apache/lucene/index/ThreadedIndexingAndSearchingTestCase.java
===================================================================
--- lucene/test-framework/src/java/org/apache/lucene/index/ThreadedIndexingAndSearchingTestCase.java	(revision 1684461)
+++ lucene/test-framework/src/java/org/apache/lucene/index/ThreadedIndexingAndSearchingTestCase.java	(working copy)
@@ -673,9 +673,7 @@
   protected void smokeTestSearcher(IndexSearcher s) throws Exception {
     runQuery(s, new TermQuery(new Term("body", "united")));
     runQuery(s, new TermQuery(new Term("titleTokenized", "states")));
-    PhraseQuery pq = new PhraseQuery();
-    pq.add(new Term("body", "united"));
-    pq.add(new Term("body", "states"));
+    PhraseQuery pq = new PhraseQuery("body", "united", "states");
     runQuery(s, pq);
   }
 }
Index: lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java
===================================================================
--- lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java	(revision 1684461)
+++ lucene/test-framework/src/java/org/apache/lucene/search/SearchEquivalenceTestBase.java	(working copy)
@@ -151,10 +151,7 @@
       query = TermRangeQuery.newStringRange("field", "a", "" + randomChar(), true, true);
     } else {
       // use a query with a two-phase approximation
-      PhraseQuery phrase = new PhraseQuery();
-      phrase.add(new Term("field", "" + randomChar()));
-      phrase.add(new Term("field", "" + randomChar()));
-      phrase.setSlop(100);
+      PhraseQuery phrase = new PhraseQuery(100, "field", "" + randomChar(), "" + randomChar());
       query = phrase;
     }
     
Index: solr/core/src/java/org/apache/solr/parser/SolrQueryParserBase.java
===================================================================
--- solr/core/src/java/org/apache/solr/parser/SolrQueryParserBase.java	(revision 1684461)
+++ solr/core/src/java/org/apache/solr/parser/SolrQueryParserBase.java	(working copy)
@@ -387,7 +387,16 @@
     if (subQParser == null) {
 
       if (query instanceof PhraseQuery) {
-        ((PhraseQuery) query).setSlop(slop);
+        PhraseQuery pq = (PhraseQuery) query;
+        Term[] terms = pq.getTerms();
+        int[] positions = pq.getPositions();
+        PhraseQuery.Builder builder = new PhraseQuery.Builder();
+        for (int i = 0; i < terms.length; ++i) {
+          builder.add(terms[i], positions[i]);
+        }
+        builder.setSlop(slop);
+        query = builder.build();
+        query.setBoost(pq.getBoost());
       }
       if (query instanceof MultiPhraseQuery) {
         ((MultiPhraseQuery) query).setSlop(slop);
Index: solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java
===================================================================
--- solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java	(revision 1684461)
+++ solr/core/src/java/org/apache/solr/search/ExtendedDismaxQParser.java	(working copy)
@@ -17,12 +17,22 @@
 
 package org.apache.solr.search;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Multimaps;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.core.StopFilterFactory;
 import org.apache.lucene.analysis.util.TokenFilterFactory;
+import org.apache.lucene.index.Term;
 import org.apache.lucene.queries.function.BoostedQuery;
 import org.apache.lucene.queries.function.FunctionQuery;
 import org.apache.lucene.queries.function.ValueSource;
@@ -45,17 +55,9 @@
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.util.SolrPluginUtils;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import com.google.common.base.Function;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
 
 /**
  * Query parser that generates DisjunctionMaxQueries based on user configuration.
@@ -1240,7 +1242,14 @@
             if (query instanceof PhraseQuery) {
               PhraseQuery pq = (PhraseQuery)query;
               if (minClauseSize > 1 && pq.getTerms().length < minClauseSize) return null;
-              ((PhraseQuery)query).setSlop(slop);
+              PhraseQuery.Builder builder = new PhraseQuery.Builder();
+              Term[] terms = pq.getTerms();
+              int[] positions = pq.getPositions();
+              for (int i = 0; i < terms.length; ++i) {
+                builder.add(terms[i], positions[i]);
+              }
+              builder.setSlop(slop);
+              query = builder.build();
             } else if (query instanceof MultiPhraseQuery) {
               MultiPhraseQuery pq = (MultiPhraseQuery)query;
               if (minClauseSize > 1 && pq.getTermArrays().size() < minClauseSize) return null;
