Index: lucene/src/java/org/apache/lucene/search/process/DispatchingQueryProcessor.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/process/DispatchingQueryProcessor.java	Tue Apr 26 21:28:13 NZST 2011
+++ lucene/src/java/org/apache/lucene/search/process/DispatchingQueryProcessor.java	Tue Apr 26 21:28:13 NZST 2011
@@ -0,0 +1,66 @@
+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 org.apache.lucene.util.InvocationDispatcher;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of {@link QueryProcessor} that supports dispatching Querys to
+ * the process method that is most closely related to the actual Query type.
+ * <p/>
+ * Users should call {@link #dispatch(org.apache.lucene.search.Query)} to initial
+ * the dispatching process.  Concrete implementations should also call dispatch
+ * to re-dispatch a Query if appropriate (such as a new Query returning from a
+ * rewrite request).
+ */
+public abstract class DispatchingQueryProcessor implements QueryProcessor {
+
+  private static final Map<Class, InvocationDispatcher<Query, Query>> dispatcherByClass =
+      new HashMap<Class, InvocationDispatcher<Query, Query>>();
+
+  private final InvocationDispatcher<Query, Query> dispatcher;
+
+  /**
+   * Creates a new DispatchingQueryProcessor that will dispatch to process
+   * methods in the given Class
+   *
+   * @param processorClass Class whose process methods will be dispatched to
+   */
+  public DispatchingQueryProcessor(Class processorClass) {
+    InvocationDispatcher<Query, Query> dispatcher = dispatcherByClass.get(processorClass);
+    if (dispatcher == null) {
+      dispatcher = new InvocationDispatcher<Query, Query>(processorClass, "process");
+      dispatcherByClass.put(processorClass, dispatcher);
+    }
+    this.dispatcher = dispatcher;
+  }
+
+  /**
+   * Dispatches the processing of the given Query
+   *
+   * @param query Query to process
+   * @return Result of the processing
+   */
+  public final Query dispatch(Query query) {
+    return dispatcher.dispatch(this, query);
+  }
+}
Index: lucene/src/java/org/apache/lucene/search/process/QueryProcessor.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/process/QueryProcessor.java	Tue Apr 26 21:27:14 NZST 2011
+++ lucene/src/java/org/apache/lucene/search/process/QueryProcessor.java	Tue Apr 26 21:27:14 NZST 2011
@@ -0,0 +1,28 @@
+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;
+
+public interface QueryProcessor {
+
+  Query process(Query query) throws IOException;
+}
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 21:27:37 NZST 2011
+++ lucene/src/java/org/apache/lucene/util/InvocationDispatcher.java	Tue Apr 26 21:27:37 NZST 2011
@@ -0,0 +1,149 @@
+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
+ * like that used in the visitor pattern.
+ * <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 VisitDispatcher that will dispatch to methods of the given
+   * method name, belonging to the given Class and its supertypes
+   *
+   * @param visitorClass Class whose methods will be dispatched to
+   * @param methodName Name of the methods to consider dispatching to
+   */
+  public InvocationDispatcher(Class visitorClass, String methodName) {
+    this.dispatchableMethods = new ArrayList<Method>();
+
+    for (; visitorClass != Object.class; visitorClass = visitorClass.getSuperclass()) {
+      for (Method method : visitorClass.getDeclaredMethods()) {
+        if (methodName.equals(method.getName()) && method.getParameterTypes().length == 1) {
+          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 visitor 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 visitor, P parameter) {
+    Class parameterType = parameter.getClass();
+    Method method = methodByType.get(parameterType);
+    if (method == null) {
+      List<Method> possibleMethods = new ArrayList<Method>();
+      for (Method visitMethod : dispatchableMethods) {
+        if (isSuperType(visitMethod.getParameterTypes()[0], parameterType) &&
+            !isSuperType(visitMethod, possibleMethods)) {
+          possibleMethods.add(visitMethod);
+        }
+      }
+
+      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(visitor, parameter);
+    } catch (Exception e) {
+      throw new MethodDispatchException("Exception dispatching call to resolved method", e);
+    }
+  }
+
+  /**
+   * 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/DefaultQueryProcessor.java
===================================================================
--- lucene/src/java/org/apache/lucene/search/process/DefaultQueryProcessor.java	Tue Apr 26 21:28:28 NZST 2011
+++ lucene/src/java/org/apache/lucene/search/process/DefaultQueryProcessor.java	Tue Apr 26 21:28:28 NZST 2011
@@ -0,0 +1,43 @@
+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 java.io.IOException;
+
+public class DefaultQueryProcessor extends DispatchingQueryProcessor {
+
+  private final IndexReader indexReader;
+
+  public DefaultQueryProcessor(Class visitorClass, IndexReader indexReader) {
+    super(visitorClass);
+    this.indexReader = indexReader;
+  }
+
+  public DefaultQueryProcessor(IndexReader indexReader) {
+    super(DefaultQueryProcessor.class);
+    this.indexReader = indexReader;
+  }
+
+  public Query process(Query query) throws IOException {
+    Query rewrittenQuery = query.rewrite(indexReader);
+    return (rewrittenQuery != query) ? dispatch(rewrittenQuery) : rewrittenQuery;
+  }
+}
