Index: lucene/src/java/org/apache/lucene/search/process/RewriteCachingQueryProcessor.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/process/RewriteCachingQueryProcessor.java	Wed Apr 27 19:07:16 NZST 2011
+++ lucene/src/java/org/apache/lucene/search/process/RewriteCachingQueryProcessor.java	Wed Apr 27 19:07:16 NZST 2011
@@ -0,0 +1,46 @@
+package org.apache.lucene.search.process;
+
+/*
+ * 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.search.Query;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class RewriteCachingQueryProcessor extends QueryProcessor {
+
+  private final Map<Query, Query> rewriteByQuery = new ConcurrentHashMap<Query, Query>();
+
+  public RewriteCachingQueryProcessor(Class<? extends QueryProcessor> processorClass) {
+    super(processorClass);
+  }
+
+  public RewriteCachingQueryProcessor() {
+    super(RewriteCachingQueryProcessor.class);
+  }
+
+  protected Query process(Query query) throws IOException {
+    Query rewrittenQuery = rewriteByQuery.get(query);
+    if (rewrittenQuery == null) {
+      rewrittenQuery = query.rewrite(readerContext.reader);
+      rewriteByQuery.put(query, rewrittenQuery);
+    }
+    return (rewrittenQuery != query) ? dispatchProcessing(rewrittenQuery) : rewrittenQuery;
+  }
+}
Index: lucene/src/java/org/apache/lucene/util/MethodDispatchException.java
===================================================================
--- lucene/src/java/org/apache/lucene/util/MethodDispatchException.java	Tue Apr 26 21:23:40 NZST 2011
+++ lucene/src/java/org/apache/lucene/util/MethodDispatchException.java	Tue Apr 26 21:23:40 NZST 2011
@@ -0,0 +1,28 @@
+package org.apache.lucene.util;
+
+/*
+ * 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.
+ */
+public class MethodDispatchException extends RuntimeException {
+
+    public MethodDispatchException(String message) {
+        super(message);
+    }
+
+    public MethodDispatchException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
Index: lucene/src/java/org/apache/lucene/util/InvocationDispatcher.java
===================================================================
--- lucene/src/java/org/apache/lucene/util/InvocationDispatcher.java	Tue Apr 26 22:40:32 NZST 2011
+++ lucene/src/java/org/apache/lucene/util/InvocationDispatcher.java	Tue Apr 26 22:40:32 NZST 2011
@@ -0,0 +1,174 @@
+package org.apache.lucene.util;
+
+/*
+ * 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.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * Generic Method invocation dispatcher that simulates simple multi dispatch.
+ * <p/>
+ * The dispatcher will scan all methods in a Class and its supertypes, identifying
+ * those of a given name.  When a method invocation is dispatched, the
+ * Dispatcher will resolve the method from those initially identified that
+ * accepts a parameter of the most specific type relative to the type of the
+ * given parameter.
+ */
+public class InvocationDispatcher<P, R> {
+
+  private final List<Method> dispatchableMethods;
+  private final Map<Class, Method> methodByType = new HashMap<Class, Method>();
+
+  /**
+   * Creates a new InvocationDispatcher that will dispatch to methods of the given
+   * method name, belonging to the given Class and its supertypes
+   *
+   * @param receiverType Class whose methods will be dispatched to
+   * @param methodName Name of the methods to consider dispatching to
+   * @param parameterType Type of the parameters that will be dispatched to the
+   *                      methods
+   * @param returnType Excepted return type of the dispatchable methods
+   */
+  public InvocationDispatcher(Class receiverType, String methodName, Class<P> parameterType, Class<R> returnType) {
+    this.dispatchableMethods = new ArrayList<Method>();
+
+    for (; receiverType != Object.class; receiverType = receiverType.getSuperclass()) {
+      for (Method method : receiverType.getDeclaredMethods()) {
+        if (isDispatchableMethod(method, methodName, parameterType, returnType)) {
+          dispatchableMethods.add(method);
+        }
+      }
+    }
+
+    Collections.sort(dispatchableMethods, new Comparator<Method>() {
+
+      public int compare(Method o1, Method o2) {
+        return isSuperType(o1, o2) ? 1 : -1;
+      }
+    });
+  }
+
+  /**
+   * Dispatches the invocation of the most specific method resolveable given
+   * the type of parameter.
+   *
+   * @param invokeReceiver Object to invoke the method on
+   * @param parameter Parameter to dispatch in the method invocation.  Also
+   *                  used to determine the method to dispatch to
+   * @return Return value of the method invocation
+   */
+  @SuppressWarnings("unchecked")
+  public final R dispatch(Object invokeReceiver, P parameter) {
+    Class parameterType = parameter.getClass();
+    Method method = methodByType.get(parameterType);
+    if (method == null) {
+      List<Method> possibleMethods = new ArrayList<Method>();
+      for (Method dispatchableMethod : dispatchableMethods) {
+        if (isSuperType(dispatchableMethod.getParameterTypes()[0], parameterType) &&
+            !isSuperType(dispatchableMethod, possibleMethods)) {
+          possibleMethods.add(dispatchableMethod);
+        }
+      }
+
+      if (possibleMethods.size() == 0) {
+        throw new MethodDispatchException("No methods were resolved for parameter type");
+      } else if (possibleMethods.size() > 1) {
+        throw new MethodDispatchException("Multiple methods resolved for parameter type, cannot disambiguate");
+      }
+
+      method = possibleMethods.get(0);
+      methodByType.put(parameterType, method);
+    }
+    try {
+      return (R) method.invoke(invokeReceiver, parameter);
+    } catch (Exception e) {
+      throw new MethodDispatchException("Exception dispatching call to resolved method", e);
+    }
+  }
+
+  /**
+   * Determines whether the given method is a dispatchable method by checking if it
+   * meets the given criteria
+   *
+   * @param method Methd to check if it is dispatchable
+   * @param methodName Name that the method must have
+   * @param parameterType Type that the method's single parameter should be equal
+   *                      to or subtype of
+   * @param returnType Type that the method's return value should be equal to or
+   *                   subtype of
+   * @return {@code true} if the method meets the criteria and can receive dispatches,
+   *         {@code false} otherwise
+   */
+  private boolean isDispatchableMethod(Method method,
+                                       String methodName,
+                                       Class<P> parameterType,
+                                       Class<R> returnType) {
+    return methodName.equals(method.getName()) &&
+        method.getParameterTypes().length == 1 &&
+        isSuperType(parameterType, method.getParameterTypes()[0]) &&
+        isSuperType(returnType, method.getReturnType());
+  }
+
+  /**
+   * Determines if superType is a supertype of subType.
+   *
+   * @param superType Type to see if its a supertype of subType
+   * @param subType Type to see if its a subtype of superType
+   * @return {@code true} if superType is the supertype, {@code false}
+   *         otherwise
+   */
+  private boolean isSuperType(Class superType, Class subType) {
+    return superType.isAssignableFrom(subType);
+  }
+
+  /**
+   * Determines if superTypeMethod is a supertype of subTypeMethod.  The
+   * supertype relationship is defined as method A is a supertype of
+   * method B iff the first parameter if A is a supertype of the first
+   * parameter of B.
+   *
+   * @param superTypeMethod Method to see if its a supertype of the other
+   * @param subTypeMethod Method to see if its a subtype of superTypeMethod
+   * @return {@code true} if superTypeMethod is a supertype of subTypeMethod,
+   *         {@code false} otherwise
+   * @see #isSuperType(Class, Class) for parameter supertype relation explaination
+   */
+  private boolean isSuperType(Method superTypeMethod, Method subTypeMethod) {
+    return isSuperType(superTypeMethod.getParameterTypes()[0], subTypeMethod.getParameterTypes()[0]);
+  }
+
+  /**
+   * Determines if the given method is a supertype of any of the methods
+   * provided in the given list.
+   *
+   * @param superTypeMethod Method to see if it is a supertype of the others
+   * @param methods Methods to check if they are a subtype of the provided
+   *                supertype method
+   * @return {@code true} if superTypeMethod is a supertype of any of the
+   *         methods, {@code false} otherwise
+   * @see #isSuperType(Method, Method) for Method supertype relation explaination
+   */
+  private boolean isSuperType(Method superTypeMethod, List<Method> methods) {
+    for (Method method : methods) {
+      if (isSuperType(superTypeMethod, method)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
Index: lucene/src/java/org/apache/lucene/search/process/QueryProcessor.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/process/QueryProcessor.java	Wed Apr 27 19:06:45 NZST 2011
+++ lucene/src/java/org/apache/lucene/search/process/QueryProcessor.java	Wed Apr 27 19:06:45 NZST 2011
@@ -0,0 +1,81 @@
+package org.apache.lucene.search.process;
+
+/*
+ * 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 org.apache.lucene.search.Query;
+import org.apache.lucene.util.InvocationDispatcher;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public abstract class QueryProcessor {
+
+  private static final Map<Class, InvocationDispatcher<Query, Query>> dispatcherByClass =
+      new ConcurrentHashMap<Class, InvocationDispatcher<Query, Query>>();
+
+  private final InvocationDispatcher<Query, Query> dispatcher;
+  protected IndexReader.AtomicReaderContext readerContext;
+
+  /**
+   * Creates a new QueryProcessor that will dispatch to process methods in the
+   * given Class
+   *
+   * @param processorClass Class whose process methods will be dispatched to
+   */
+  public QueryProcessor(Class<? extends QueryProcessor> processorClass) {
+    InvocationDispatcher<Query, Query> dispatcher = dispatcherByClass.get(processorClass);
+    if (dispatcher == null) {
+      dispatcher = new InvocationDispatcher<Query, Query>(processorClass, "process", Query.class, Query.class);
+      dispatcherByClass.put(processorClass, dispatcher);
+    }
+    this.dispatcher = dispatcher;
+  }
+
+  /**
+   * Dispatches the given Query to be processed
+   * 
+   * @param query Query to process
+   * @return Result of processing the query
+   * @throws IOException Can be thrown during the processing of a Query
+   */
+  public final Query dispatchProcessing(Query query) throws IOException {
+    return dispatcher.dispatch(this, query);
+  }
+
+  /**
+   * Sets the AtomicReaderContext to be used while processing Querys.  Note, this
+   * <b>must</b> be called before dispatching can begin 
+   *
+   * @param readerContext AtomicReaderContext to be used to process Querys
+   */
+  public void setNextReader(IndexReader.AtomicReaderContext readerContext) {
+    this.readerContext = readerContext;
+  }
+
+  /**
+   * Default receiver for dispatched Query process calls.
+   *
+   * @param query Query to process
+   * @return Result of processing
+   * @throws IOException Can be thrown while processing
+   */
+  protected abstract Query process(Query query) throws IOException;
+}
