Index: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SrndQuery.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SrndQuery.java	(revision 1084632)
+++ modules/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,32 @@
       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,
+   *  @see #toString().
+   */
+  @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,
+   *  @see #toString().
+   */
+  @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) {
Index: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/BasicQueryFactory.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/BasicQueryFactory.java	(revision 1084632)
+++ modules/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: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/ComposedQuery.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/ComposedQuery.java	(revision 1084632)
+++ modules/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: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SimpleTerm.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SimpleTerm.java	(revision 1084632)
+++ modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SimpleTerm.java	(working copy)
@@ -17,12 +17,9 @@
  */
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.Query;
 
 public abstract class SimpleTerm
@@ -38,7 +35,11 @@
   public String getFieldOperator() {return "/";}
   
   public abstract String toStringUnquoted();
-  
+
+  /** @deprecated (March 2011) Not normally used, to be removed from Lucene 4.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());
@@ -70,35 +71,10 @@
     void visitMatchingTerm(Term t)throws IOException;
   }
 
+  @Override
   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 +85,11 @@
             }
           });
   }
+
+  @Override
+  public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
+    return new SimpleTermRewriteQuery(this, fieldName, qf);
+  }
 }
 
 
Index: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/DistanceQuery.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/DistanceQuery.java	(revision 1084632)
+++ modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/DistanceQuery.java	(working copy)
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-
 import java.util.List;
 import java.util.Iterator;
 
@@ -39,12 +38,14 @@
     this.ordered = ordered;
   }
 
+
   private int opDistance;
   public int getOpDistance() {return opDistance;}
   
   private boolean ordered;
   public boolean subQueriesOrdered() {return ordered;}
   
+  @Override
   public String distanceSubQueryNotAllowed() {
     Iterator<?> sqi = getSubQueriesIterator();
     while (sqi.hasNext()) {
@@ -61,39 +62,22 @@
     }
     return null; /* subqueries acceptable */
   }
-
   
+  @Override
   public void addSpanQueries(SpanNearClauseFactory sncf) throws IOException {
     Query snq = getSpanNearQuery(sncf.getIndexReader(),
                                   sncf.getFieldName(),
                                   getWeight(),
                                   sncf.getBasicQueryFactory());
-    sncf.addSpanNearQuery(snq);
+    sncf.addSpanQuery(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,
           String fieldName,
           float boost,
           BasicQueryFactory qf) throws IOException {
-    SpanQuery[] spanNearClauses = new SpanQuery[getNrSubQueries()];
+    SpanQuery[] spanClauses = new SpanQuery[getNrSubQueries()];
     Iterator<?> sqi = getSubQueriesIterator();
     int qi = 0;
     while (sqi.hasNext()) {
@@ -108,14 +92,18 @@
         return SrndQuery.theEmptyLcnQuery;
       }
       
-      spanNearClauses[qi] = sncf.makeSpanNearClause();
-
+      spanClauses[qi] = sncf.makeSpanClause();
       qi++;
     }
-    
-    SpanNearQuery r = new SpanNearQuery(spanNearClauses, getOpDistance() - 1, subQueriesOrdered());
+
+    SpanNearQuery r = new SpanNearQuery(spanClauses, getOpDistance() - 1, subQueriesOrdered());
     r.setBoost(boost);
     return r;
   }
+
+  @Override
+  public Query makeLuceneQueryFieldNoBoost(final String fieldName, final BasicQueryFactory qf) {
+    return new DistanceRewriteQuery(this, fieldName, qf);
+  }
 }
 
Index: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SpanNearClauseFactory.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SpanNearClauseFactory.java	(revision 1084632)
+++ modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SpanNearClauseFactory.java	(working copy)
@@ -64,7 +64,7 @@
 import org.apache.lucene.search.spans.SpanTermQuery;
 
 
-public class SpanNearClauseFactory {
+public class SpanNearClauseFactory { // FIXME: rename to SpanClauseFactory
   public SpanNearClauseFactory(IndexReader reader, String fieldName, BasicQueryFactory qf) {
     this.reader = reader;
     this.fieldName = fieldName;
@@ -100,17 +100,16 @@
     /* CHECKME: wrap in Hashable...? */
     addSpanQueryWeighted(stq, weight);
   }
-  
-  public void addSpanNearQuery(Query q) {
+
+  public void addSpanQuery(Query q) {
     if (q == SrndQuery.theEmptyLcnQuery)
       return;
-    if (! (q instanceof SpanNearQuery))
-      throw new AssertionError("Expected SpanNearQuery: " + q.toString(getFieldName()));
-    /* CHECKME: wrap in Hashable...? */
-    addSpanQueryWeighted((SpanNearQuery)q, q.getBoost());
+    if (! (q instanceof SpanQuery))
+      throw new AssertionError("Expected SpanQuery: " + q.toString(getFieldName()));
+    addSpanQueryWeighted((SpanQuery)q, q.getBoost());
   }
-  
-  public SpanQuery makeSpanNearClause() {
+
+  public SpanQuery makeSpanClause() {
     SpanQuery [] spanQueries = new SpanQuery[size()];
     Iterator<SpanQuery> sqi = weightBySpanQuery.keySet().iterator();
     int i = 0;
Index: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/RewriteQuery.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/RewriteQuery.java	(revision 0)
+++ modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/RewriteQuery.java	(revision 0)
@@ -0,0 +1,81 @@
+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 java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.Query;
+
+abstract class RewriteQuery<SQ extends SrndQuery> extends Query {
+  protected final SQ srndQuery;
+  protected final String fieldName;
+  protected final BasicQueryFactory qf;
+
+  RewriteQuery(
+      SQ srndQuery,
+      String fieldName,
+      BasicQueryFactory qf) {
+    this.srndQuery = srndQuery;
+    this.fieldName = fieldName;
+    this.qf = qf;
+  }
+
+  @Override
+  abstract public Query rewrite(IndexReader reader) throws IOException;
+
+  @Override
+  public String toString() {
+    return toString(null);
+  }
+
+  @Override
+  public String toString(String field) {
+    return getClass().getName()
+	  + (field == null ? "" : "(unused: " + field + ")")
+	  + "(" + fieldName
+	  + ", " + srndQuery.toString()
+	  + ", " + qf.toString()
+	  + ")";
+  }
+
+  @Override
+  public int hashCode() {
+    return getClass().hashCode()
+	  ^ fieldName.hashCode()
+	  ^ qf.hashCode()
+	  ^ srndQuery.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null)
+      return false;
+    if (! getClass().equals(obj.getClass()))
+      return false;
+    RewriteQuery other = (RewriteQuery)obj;
+    return fieldName.equals(other.fieldName)
+	&& qf.equals(other.qf)
+	&& srndQuery.equals(other.srndQuery);
+  }
+
+  /** @throws UnsupportedOperationException */
+  @Override
+  public Object clone() {
+    throw new UnsupportedOperationException();
+  }
+}
+
Index: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SimpleTermRewriteQuery.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SimpleTermRewriteQuery.java	(revision 0)
+++ modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/SimpleTermRewriteQuery.java	(revision 0)
@@ -0,0 +1,52 @@
+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 java.io.IOException;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.index.Term;
+
+class SimpleTermRewriteQuery extends RewriteQuery<SimpleTerm> {
+
+  SimpleTermRewriteQuery(
+      SimpleTerm srndQuery,
+      String fieldName,
+      BasicQueryFactory qf) {
+    super(srndQuery, fieldName, qf);
+  }
+
+  @Override
+  public Query rewrite(IndexReader reader) throws IOException {
+    final List<Query> luceneSubQueries = new ArrayList<Query>();
+    srndQuery.visitMatchingTerms(reader, fieldName,
+	  new SimpleTerm.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 */
+  }
+}
+
Index: modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/DistanceRewriteQuery.java
===================================================================
--- modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/DistanceRewriteQuery.java	(revision 0)
+++ modules/queryparser/src/java/org/apache/lucene/queryparser/surround/query/DistanceRewriteQuery.java	(revision 0)
@@ -0,0 +1,38 @@
+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 java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.Query;
+
+class DistanceRewriteQuery extends RewriteQuery<DistanceQuery> {
+
+  DistanceRewriteQuery(
+      DistanceQuery srndQuery,
+      String fieldName,
+      BasicQueryFactory qf) {
+    super(srndQuery, fieldName, qf);
+  }
+
+  @Override
+  public Query rewrite(IndexReader reader) throws IOException {
+    return srndQuery.getSpanNearQuery(reader, fieldName, getBoost(), qf);
+  }
+}
+
Index: modules/queryparser/src/test/org/apache/lucene/queryparser/surround/query/SrndQueryTest.java
===================================================================
--- modules/queryparser/src/test/org/apache/lucene/queryparser/surround/query/SrndQueryTest.java	(revision 0)
+++ modules/queryparser/src/test/org/apache/lucene/queryparser/surround/query/SrndQueryTest.java	(revision 0)
@@ -0,0 +1,52 @@
+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 junit.framework.Assert;
+
+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 {
+  
+  void checkEqualParsings(String s1, String s2) throws Exception {
+    String fieldName = "foo";
+    BasicQueryFactory qf = new BasicQueryFactory(16);
+    Query lq1, lq2;
+    lq1 = QueryParser.parse(s1).makeLuceneQueryField(fieldName, qf);
+    lq2 = QueryParser.parse(s2).makeLuceneQueryField(fieldName, qf);
+    QueryUtils.checkEqual(lq1, lq2);
+  }
+
+  @Test
+  public void testHashEquals() throws Exception {
+    //grab some sample queries from Test02Boolean and Test03Distance and
+    //check there hashes and equals
+    checkEqualParsings("word1 w word2", " word1  w  word2 ");
+    checkEqualParsings("2N(w1,w2,w3)", " 2N(w1, w2 , w3)");
+    checkEqualParsings("abc?", " abc? ");
+    checkEqualParsings("w*rd?", " w*rd?");
+  }
+}
