Index: lucene/src/test/org/apache/lucene/search/TestRewriteState.java
===================================================================
--- lucene/src/test/org/apache/lucene/search/TestRewriteState.java	Sun May 01 02:30:02 NZST 2011
+++ lucene/src/test/org/apache/lucene/search/TestRewriteState.java	Sun May 01 02:30:02 NZST 2011
@@ -0,0 +1,104 @@
+package org.apache.lucene.search;
+
+/*
+ * 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.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.RandomIndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.LuceneTestCase;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class TestRewriteState extends LuceneTestCase {
+
+  private Directory directory;
+  private IndexReader indexReader;
+  private IndexSearcher indexSearcher;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    directory = newDirectory();
+    RandomIndexWriter indexWriter = new RandomIndexWriter(random, directory);
+    indexWriter.addDocument(buildDocument("abc"));
+    indexWriter.addDocument(buildDocument("ghi"));
+    indexWriter.addDocument(buildDocument("xyz"));
+    indexWriter.commit();
+    indexWriter.close();
+    indexReader = IndexReader.open(directory);
+    indexSearcher = newSearcher(indexReader);
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    super.tearDown();
+    indexSearcher.close();
+    indexReader.close();
+    directory.close();
+  }
+
+  @Test
+  public void testGetRewrite_cachedFuzzyQueries() throws IOException {
+    RewriteCountFuzzyQuery a = new RewriteCountFuzzyQuery(new Term("value", "abd"));
+    RewriteCountFuzzyQuery b = new RewriteCountFuzzyQuery(new Term("value", "gji"));
+    RewriteCountFuzzyQuery c = new RewriteCountFuzzyQuery(new Term("value", "cyz"));
+
+    BooleanQuery ab = new BooleanQuery();
+    ab.add(a, BooleanClause.Occur.MUST);
+    ab.add(b, BooleanClause.Occur.MUST);
+
+    BooleanQuery ac = new BooleanQuery();
+    ab.add(a, BooleanClause.Occur.MUST);
+    ab.add(c, BooleanClause.Occur.MUST);
+
+    BooleanQuery abac = new BooleanQuery();
+    abac.add(ab, BooleanClause.Occur.SHOULD);
+    abac.add(ac, BooleanClause.Occur.SHOULD);
+
+    abac.rewrite(indexReader, new DefaultRewriteState());
+
+    assertEquals(1, a.rewriteCount);
+    assertEquals(1, b.rewriteCount);
+    assertEquals(1, c.rewriteCount);
+  }
+
+  private Document buildDocument(String value) {
+    Document document = new Document();
+    document.add(new Field("value", value, Field.Store.YES, Field.Index.NOT_ANALYZED));
+    return document;
+  }
+
+  private static class RewriteCountFuzzyQuery extends FuzzyQuery {
+
+    int rewriteCount;
+
+    private RewriteCountFuzzyQuery(Term term) {
+      super(term);
+    }
+
+    @Override
+    public Query rewrite(IndexReader reader) throws IOException {
+      rewriteCount++;
+      return super.rewrite(reader);
+    }
+  }
+}
Index: lucene/src/java/org/apache/lucene/search/Query.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/Query.java	(revision 1068526)
+++ lucene/src/java/org/apache/lucene/search/Query.java	Sat Apr 30 22:56:43 NZST 2011
@@ -111,6 +111,10 @@
    * of TermQuerys.
    */
   public Query rewrite(IndexReader reader) throws IOException {
+    return rewrite(reader, new DefaultRewriteState());
+  }
+
+  public Query rewrite(IndexReader reader, RewriteState rewriteState) throws IOException {
     return this;
   }
   
Index: lucene/src/java/org/apache/lucene/search/BooleanQuery.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/BooleanQuery.java	(revision 1097133)
+++ lucene/src/java/org/apache/lucene/search/BooleanQuery.java	Sat Apr 30 22:56:59 NZST 2011
@@ -359,12 +359,12 @@
   }
 
   @Override
-  public Query rewrite(IndexReader reader) throws IOException {
+  public Query rewrite(IndexReader reader, RewriteState rewriteState) throws IOException {
     if (minNrShouldMatch == 0 && clauses.size() == 1) {                    // optimize 1-clause queries
       BooleanClause c = clauses.get(0);
       if (!c.isProhibited()) {			  // just return clause
 
-        Query query = c.getQuery().rewrite(reader);    // rewrite first
+        Query query = rewriteState.getRewrite(c.getQuery(), reader);    // rewrite first
 
         if (getBoost() != 1.0f) {                 // incorporate boost
           if (query == c.getQuery()) {                   // if rewrite was no-op
@@ -383,7 +383,7 @@
     BooleanQuery clone = null;                    // recursively rewrite
     for (int i = 0 ; i < clauses.size(); i++) {
       BooleanClause c = clauses.get(i);
-      Query query = c.getQuery().rewrite(reader);
+      Query query = rewriteState.getRewrite(c.getQuery(), reader);
       if (query != c.getQuery()) {                     // clause rewrote: must clone
         if (clone == null) {
           // The BooleanQuery clone is lazily initialized so only initialize
Index: lucene/src/java/org/apache/lucene/search/DefaultRewriteState.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/DefaultRewriteState.java	Sat Apr 30 22:56:24 NZST 2011
+++ lucene/src/java/org/apache/lucene/search/DefaultRewriteState.java	Sat Apr 30 22:56:24 NZST 2011
@@ -0,0 +1,67 @@
+package org.apache.lucene.search;
+
+/*
+ * 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.index.IndexReader;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+public class DefaultRewriteState implements RewriteState {
+
+  private final Map<IndexReader, Map<Query, Query>> cachedQueriesByReader;
+
+  protected DefaultRewriteState(Map<IndexReader, Map<Query, Query>> cachedQueriesByReader) {
+    this.cachedQueriesByReader = cachedQueriesByReader;
+  }
+
+  protected DefaultRewriteState() {
+    this(new WeakHashMap<IndexReader, Map<Query, Query>>());
+  }
+
+  @Override
+  protected Object clone() {
+    return new DefaultRewriteState(new WeakHashMap<IndexReader, Map<Query, Query>>(cachedQueriesByReader));
+  }
+
+  public final Query getRewrite(Query query, IndexReader indexReader) throws IOException {
+    Query cachedRewrite = checkCachedState(query, indexReader);
+    if (cachedRewrite != null) {
+      return cachedRewrite;
+    }
+    Query rewrittenQuery = query.rewrite(indexReader);
+    updateCachedState(query, rewrittenQuery, indexReader);
+    return rewrittenQuery;
+  }
+
+  protected Query checkCachedState(Query query, IndexReader indexReader) {
+    Map<Query, Query> cachedQueries = cachedQueriesByReader.get(indexReader);
+    return cachedQueries != null ? cachedQueries.get(query) : null;
+  }
+
+  protected void updateCachedState(Query query, Query rewrittenQuery, IndexReader indexReader) {
+    Map<Query, Query> cachedQueries = cachedQueriesByReader.get(indexReader);
+    if (cachedQueries == null) {
+      cachedQueries = new HashMap<Query, Query>();
+      cachedQueriesByReader.put(indexReader, cachedQueries);
+    }
+    cachedQueries.put(query, rewrittenQuery);
+  }
+}
Index: lucene/src/java/org/apache/lucene/search/RewriteState.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/RewriteState.java	Sun May 01 01:49:39 NZST 2011
+++ lucene/src/java/org/apache/lucene/search/RewriteState.java	Sun May 01 01:49:39 NZST 2011
@@ -0,0 +1,27 @@
+package org.apache.lucene.search;
+
+/*
+ * 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.index.IndexReader;
+
+import java.io.IOException;
+
+public interface RewriteState {
+  
+  Query getRewrite(Query query, IndexReader indexReader) throws IOException;
+}
