Index: lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/BasicQueryFactory.java
===================================================================
--- lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/BasicQueryFactory.java	(revision 1079883)
+++ lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/BasicQueryFactory.java	(working copy)
@@ -45,8 +45,19 @@
   public int getNrQueriesMade() {return queriesMade;}
   public int getMaxBasicQueries() {return maxBasicQueries;}
   
-  private synchronized void checkMax() throws TooManyBasicQueries {
-    if (queriesMade >= maxBasicQueries)
+  public String toString() {
+    return getClass().getName()
+	  + "(maxBasicQueries: " + maxBasicQueries
+	  + ", queriesMade: " + queriesMade
+	  + ")";
+  }
+
+  private boolean atMax() {
+    return queriesMade >= maxBasicQueries;
+  }
+
+  protected synchronized void checkMax() throws TooManyBasicQueries {
+    if (atMax())
       throw new TooManyBasicQueries(getMaxBasicQueries());
     queriesMade++;
   }
@@ -60,6 +71,22 @@
     checkMax();
     return new SpanTermQuery(term);
   }
+
+  @Override
+  public int hashCode() {
+    return getClass().hashCode() ^ (atMax() ? 7 : 31*32);
+  }
+
+  /** Two BasicQueryFactory's are equal when they generate
+   *  the same types of basic queries, or both cannot generate queries anymore.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (! (obj instanceof BasicQueryFactory))
+      return false;
+    BasicQueryFactory other = (BasicQueryFactory) obj;
+    return atMax() == other.atMax();
+  }
 }
 
 
Index: lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/ComposedQuery.java
===================================================================
--- lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/ComposedQuery.java	(revision 1079883)
+++ lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/ComposedQuery.java	(working copy)
@@ -24,36 +24,36 @@
 
 public abstract class ComposedQuery extends SrndQuery { 
   
-  public ComposedQuery(List qs, boolean operatorInfix, String opName) {
+  public ComposedQuery(List<SrndQuery> qs, boolean operatorInfix, String opName) {
     recompose(qs);
     this.operatorInfix = operatorInfix;
     this.opName = opName;
   }
   
-  protected void recompose(List queries) {
+  protected void recompose(List<SrndQuery> queries) {
     if (queries.size() < 2) throw new AssertionError("Too few subqueries"); 
     this.queries = queries;
   }
   
-  private String opName;
+  protected String opName;
   public String getOperatorName() {return opName;}
   
-  private List queries;
+  protected List<SrndQuery> queries;
   
-  public Iterator getSubQueriesIterator() {return queries.listIterator();}
+  public Iterator<SrndQuery> getSubQueriesIterator() {return queries.listIterator();}
 
   public int getNrSubQueries() {return queries.size();}
   
-  public SrndQuery getSubQuery(int qn) {return (SrndQuery) queries.get(qn);}
+  public SrndQuery getSubQuery(int qn) {return queries.get(qn);}
 
   private boolean operatorInfix; 
   public boolean isOperatorInfix() { return operatorInfix; } /* else prefix operator */
   
   public List<Query> makeLuceneSubQueriesField(String fn, BasicQueryFactory qf) {
     List<Query> luceneSubQueries = new ArrayList<Query>();
-    Iterator sqi = getSubQueriesIterator();
+    Iterator<SrndQuery> sqi = getSubQueriesIterator();
     while (sqi.hasNext()) {
-      luceneSubQueries.add( ((SrndQuery) sqi.next()).makeLuceneQueryField(fn, qf));
+      luceneSubQueries.add( (sqi.next()).makeLuceneQueryField(fn, qf));
     }
     return luceneSubQueries;
   }
@@ -77,7 +77,7 @@
   
   protected void infixToString(StringBuilder r) {
     /* Brackets are possibly redundant in the result. */
-    Iterator sqi = getSubQueriesIterator();
+    Iterator<SrndQuery> sqi = getSubQueriesIterator();
     r.append(getBracketOpen());
     if (sqi.hasNext()) {
       r.append(sqi.next().toString());
@@ -92,7 +92,7 @@
   }
 
   protected void prefixToString(StringBuilder r) {
-    Iterator sqi = getSubQueriesIterator();
+    Iterator<SrndQuery> sqi = getSubQueriesIterator();
     r.append(getOperatorName()); /* prefix operator */
     r.append(getBracketOpen());
     if (sqi.hasNext()) {
@@ -109,9 +109,9 @@
   @Override
   public boolean isFieldsSubQueryAcceptable() {
     /* at least one subquery should be acceptable */
-    Iterator sqi = getSubQueriesIterator();
+    Iterator<SrndQuery> sqi = getSubQueriesIterator();
     while (sqi.hasNext()) {
-      if (((SrndQuery) sqi.next()).isFieldsSubQueryAcceptable()) {
+      if ((sqi.next()).isFieldsSubQueryAcceptable()) {
         return true;
       }
     }
Index: lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/DistanceQuery.java
===================================================================
--- lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/DistanceQuery.java	(revision 1079883)
+++ lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/DistanceQuery.java	(working copy)
@@ -39,6 +39,7 @@
     this.ordered = ordered;
   }
 
+
   private int opDistance;
   public int getOpDistance() {return opDistance;}
   
@@ -70,23 +71,6 @@
                                   sncf.getBasicQueryFactory());
     sncf.addSpanNearQuery(snq);
   }
-
-  @Override
-  public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
-    return new Query () {
-      
-      @Override
-      public String toString(String fn) {
-        return getClass().toString() + " " + fieldName + " (" + fn + "?)";
-      }
-      
-      @Override
-      public Query rewrite(IndexReader reader) throws IOException {
-        return getSpanNearQuery(reader, fieldName, getBoost(), qf);
-      }
-      
-    };
-  }
   
   public Query getSpanNearQuery(
           IndexReader reader,
@@ -117,5 +101,69 @@
     r.setBoost(boost);
     return r;
   }
+
+  @Override
+  public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
+    return new DistanceLuceneQuery(this, fieldName, qf);
+  }
+
+  private static class DistanceLuceneQuery extends Query {
+    private final DistanceQuery distanceQuery; // for equals() argument 
+    private final String fieldName;
+    private final BasicQueryFactory qf;
+
+    public DistanceLuceneQuery(
+	DistanceQuery distanceQuery,
+	String fieldName,
+	BasicQueryFactory qf) {
+      this.distanceQuery = distanceQuery;
+      this.fieldName = fieldName;
+      this.qf = qf;
+    }
+
+    @Override
+    public Query rewrite(IndexReader reader) throws IOException {
+      return distanceQuery.getSpanNearQuery(reader, fieldName, getBoost(), qf);
+    }
+
+    @Override
+    public String toString() {
+      return toString(null);
+    }
+
+    @Override
+    public String toString(String field) {
+      return getClass().getName()
+	    + (field == null ? "" : "(unused: " + field + ")")
+	    + "(" + fieldName
+	    + ", " + distanceQuery.toString()
+	    + ", " + qf.toString()
+	    + ")";
+    }
+
+    @Override
+    public int hashCode() {
+      return getClass().hashCode()
+	    ^ fieldName.hashCode()
+	    ^ qf.hashCode()
+	    ^ distanceQuery.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof DistanceLuceneQuery))
+        return false;
+      DistanceLuceneQuery other = (DistanceLuceneQuery)obj;
+      return fieldName.equals(other.fieldName)
+          && qf.equals(other.qf)
+	  && distanceQuery.equals(other.distanceQuery);
+    }
+
+    /** @throws RuntimeException */
+    @Override
+    public Object clone() {
+      throw new RuntimeException(new CloneNotSupportedException());
+    }
+  }
 }
 
Index: lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/SimpleTerm.java
===================================================================
--- lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/SimpleTerm.java	(revision 1079883)
+++ lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/SimpleTerm.java	(working copy)
@@ -39,6 +39,10 @@
   
   public abstract String toStringUnquoted();
   
+  /** @deprecated Not normally used, to be removed from Lucene 5.0.
+   *   This class implementing Comparable is to be removed at the same time.
+   */
+  @Deprecated
   public int compareTo(SimpleTerm ost) {
     /* for ordering terms and prefixes before using an index, not used */
     return this.toStringUnquoted().compareTo( ost.toStringUnquoted());
@@ -71,34 +75,7 @@
   }
 
   public String distanceSubQueryNotAllowed() {return null;}
-
   
-  @Override
-  public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
-    return new Query() {
-      @Override
-      public String toString(String fn) {
-        return getClass().toString() + " " + fieldName + " (" + fn + "?)";
-      }
-      
-      @Override
-      public Query rewrite(IndexReader reader) throws IOException {
-        final List<Query> luceneSubQueries = new ArrayList<Query>();
-        visitMatchingTerms( reader, fieldName,
-            new MatchingTermVisitor() {
-              public void visitMatchingTerm(Term term) throws IOException {
-                luceneSubQueries.add(qf.newTermQuery(term));
-              }
-            });
-        return  (luceneSubQueries.size() == 0) ? SrndQuery.theEmptyLcnQuery
-              : (luceneSubQueries.size() == 1) ? luceneSubQueries.get(0)
-              : SrndBooleanQuery.makeBooleanQuery(
-                  /* luceneSubQueries all have default weight */
-                  luceneSubQueries, BooleanClause.Occur.SHOULD); /* OR the subquery terms */ 
-      }
-    };
-  }
-    
   public void addSpanQueries(final SpanNearClauseFactory sncf) throws IOException {
     visitMatchingTerms(
           sncf.getIndexReader(),
@@ -109,6 +86,81 @@
             }
           });
   }
+
+  @Override
+  public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
+    return new SimpleTermLuceneQuery(this, fieldName, qf);
+  }
+    
+  private static class SimpleTermLuceneQuery extends Query {
+    private final SimpleTerm simpleTerm; // for equals() argument
+    private final String fieldName;
+    private final BasicQueryFactory qf;
+
+    public SimpleTermLuceneQuery(
+	SimpleTerm simpleTerm,
+	String fieldName,
+	BasicQueryFactory qf) {
+      this.simpleTerm = simpleTerm;
+      this.fieldName = fieldName;
+      this.qf = qf;
+    }
+
+    @Override
+    public String toString() {
+      return toString(null);
+    }
+
+    @Override
+    public String toString(String field) {
+      return getClass().getName()
+	    + (field == null ? "" : "(unused: " + field + ")")
+	    + "(" + fieldName
+	    + ", " + simpleTerm.toString()
+	    + ", " + qf.toString()
+	    + ")";
+    }
+      
+    @Override
+    public Query rewrite(IndexReader reader) throws IOException {
+      final List<Query> luceneSubQueries = new ArrayList<Query>();
+      simpleTerm.visitMatchingTerms(reader, fieldName,
+	    new MatchingTermVisitor() {
+	      public void visitMatchingTerm(Term term) throws IOException {
+		luceneSubQueries.add(qf.newTermQuery(term));
+	      }
+	    });
+      return  (luceneSubQueries.size() == 0) ? SrndQuery.theEmptyLcnQuery
+            : (luceneSubQueries.size() == 1) ? luceneSubQueries.get(0)
+            : SrndBooleanQuery.makeBooleanQuery(
+              /* luceneSubQueries all have default weight */
+              luceneSubQueries, BooleanClause.Occur.SHOULD); /* OR the subquery terms */
+    }
+
+    @Override
+    public int hashCode() {
+      return getClass().hashCode()
+	    ^ fieldName.hashCode()
+	    ^ qf.hashCode()
+	    ^ simpleTerm.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof SimpleTermLuceneQuery))
+        return false;
+      SimpleTermLuceneQuery other = (SimpleTermLuceneQuery)obj;
+      return fieldName.equals(other.fieldName)
+          && qf.equals(other.qf)
+	  && simpleTerm.equals(other.simpleTerm);
+    }
+
+    /** @throws RuntimeException */
+    @Override
+    public Object clone() {
+      throw new RuntimeException(new CloneNotSupportedException());
+    }
+  }
 }
 
 
Index: lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/SrndQuery.java
===================================================================
--- lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/SrndQuery.java	(revision 1079883)
+++ lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/surround/query/SrndQuery.java	(working copy)
@@ -53,6 +53,9 @@
   
   public abstract Query makeLuceneQueryFieldNoBoost(String fieldName, BasicQueryFactory qf);
   
+  /** This method is used by {@link #hashCode()} and {@link #equals(Object)},
+   *  see LUCENE-2945.
+   */
   @Override
   public abstract String toString();
   
@@ -66,8 +69,30 @@
       throw new Error(cns);
     }
   }
-  
-/* An empty Lucene query */
+
+  /** For subclasses of {@link SrndQuery} within the package
+   *  {@link org.apache.lucene.queryParser.surround.query}
+   *  it is not necessary to override this method.
+   */
+  @Override
+  public int hashCode() {
+    return getClass().hashCode() ^ toString().hashCode();
+  }
+
+  /** For subclasses of {@link SrndQuery} within the package
+   *  {@link org.apache.lucene.queryParser.surround.query}
+   *  it is not necessary to override this method.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null)
+      return false;
+    if (! getClass().equals(obj.getClass()))
+      return false;
+    return toString().equals(obj.toString());
+  }
+
+  /** An empty Lucene query */
   public final static Query theEmptyLcnQuery = new BooleanQuery() { /* no changes allowed */
     @Override
     public void setBoost(float boost) {
