Index: lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/surround/query/SrndQueryTest.java
===================================================================
--- lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/surround/query/SrndQueryTest.java	(revision 0)
+++ lucene/contrib/queryparser/src/test/org/apache/lucene/queryParser/surround/query/SrndQueryTest.java	(revision 0)
@@ -0,0 +1,61 @@
+package org.apache.lucene.queryParser.surround.query;
+
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.lucene.queryParser.surround.parser.QueryParser;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.QueryUtils;
+import org.apache.lucene.util.LuceneTestCase;
+import org.junit.Test;
+
+/**
+ *
+ *
+ **/
+public class SrndQueryTest extends LuceneTestCase {
+
+  @Test
+  public void testHashEquals() throws Exception {
+    BasicQueryFactory qf = new BasicQueryFactory(16);
+    SrndQuery sq;
+    Query lq;
+    //grab some sample queries from Test02Boolean and Test03Distance and
+    //check there hashes and equals
+
+    sq = QueryParser.parse("word1 w word2");
+    lq = sq.makeLuceneQueryField("foo", qf);
+    QueryUtils.checkHashEquals(lq);
+
+    sq = QueryParser.parse("2N(w1,w2,w3)");
+    lq = sq.makeLuceneQueryField("foo", qf);
+    QueryUtils.checkHashEquals(lq);
+
+    sq = QueryParser.parse("w1");
+    lq = sq.makeLuceneQueryField("foo", qf);
+    QueryUtils.checkHashEquals(lq);
+
+    sq = QueryParser.parse("abc?");
+    lq = sq.makeLuceneQueryField("foo", qf);
+    QueryUtils.checkHashEquals(lq);
+
+    sq = QueryParser.parse("w*rd?");
+    lq = sq.makeLuceneQueryField("foo", qf);
+    QueryUtils.checkHashEquals(lq);
+  }
+}
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 1078278)
+++ 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 1078278)
+++ 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;}
   
@@ -73,19 +74,7 @@
 
   @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);
-      }
-      
-    };
+    return new InnerDistanceQuery(fieldName, qf, this.toString());
   }
   
   public Query getSpanNearQuery(
@@ -117,5 +106,46 @@
     r.setBoost(boost);
     return r;
   }
+
+  private class InnerDistanceQuery extends Query {
+
+    private final String fieldName;
+    private final BasicQueryFactory qf;
+    private final String syntax;
+
+    public InnerDistanceQuery(String fieldName, BasicQueryFactory qf,
+                              String syntax //TODO: not sure I like this, but we need something for equals and hashcode and this seems to be the only way
+          ) {
+      this.fieldName = fieldName;
+      this.qf = qf;
+      this.syntax = syntax;
+    }
+
+    @Override
+    public String toString(String field) {
+      return getClass().toString() + " " + fieldName + " (" + field + "?)";
+    }
+
+    @Override
+    public Query rewrite(IndexReader reader) throws IOException {
+      return getSpanNearQuery(reader, fieldName, getBoost(), qf);
+    }
+
+    @Override
+    public int hashCode() {
+      return Float.floatToIntBits(getBoost()) ^ fieldName.hashCode() ^ syntax.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof InnerDistanceQuery))
+        return false;
+      InnerDistanceQuery other = (InnerDistanceQuery)obj;
+      return (this.getBoost() == other.getBoost()) &&
+              this.fieldName.equals(other.fieldName) &&
+              this.syntax.equals(syntax)
+              ;
+    }
+  }
 }
 
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 1078278)
+++ 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());
@@ -75,28 +79,7 @@
   
   @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 */ 
-      }
-    };
+    return new InnerSimpleTerm(fieldName, qf, this.toString());
   }
     
   public void addSpanQueries(final SpanNearClauseFactory sncf) throws IOException {
@@ -109,6 +92,57 @@
             }
           });
   }
+
+  private class InnerSimpleTerm extends Query {
+    private final String fieldName;
+    private final BasicQueryFactory qf;
+    private final String syntax;
+
+    public InnerSimpleTerm(String fieldName, BasicQueryFactory qf,
+                           String syntax //TODO: is there a better way?  We need something unique for equals/hashcode
+        ) {
+      this.fieldName = fieldName;
+      this.qf = qf;
+      this.syntax = syntax;
+    }
+
+    @Override
+    public String toString(String field) {
+      return getClass().toString() + " " + fieldName + " (" + field + "?)";
+    }
+
+    @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 */
+    }
+
+    @Override
+    public int hashCode() {
+      return Float.floatToIntBits(getBoost()) ^ fieldName.hashCode() ^ syntax.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof InnerSimpleTerm))
+        return false;
+      InnerSimpleTerm other = (InnerSimpleTerm)obj;
+      return (this.getBoost() == other.getBoost()) &&
+              this.fieldName.equals(other.fieldName) &&
+              this.syntax.equals(syntax)
+              ;
+    }
+  }
 }
 
 
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 1078278)
+++ 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,31 @@
       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) {
