Index: lucene/src/java/org/apache/lucene/util/Visitor.java
===================================================================
--- lucene/src/java/org/apache/lucene/util/Visitor.java	Sat Apr 30 20:02:03 NZST 2011
+++ lucene/src/java/org/apache/lucene/util/Visitor.java	Sat Apr 30 20:02:03 NZST 2011
@@ -0,0 +1,78 @@
+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.util.HashMap;
+import java.util.Map;
+
+/**
+ * Abstract Visitor that supports visiting any kind of input data structure,
+ * producing any kind of output.
+ * <p/>
+ * Implementations should provide methods named 'visit', accepting a subtype
+ * of the specified input type, and returning subtypes of the specified output
+ * type.
+ * <p/>
+ * The following is an example of how to visit various Query implementations:
+ * <pre>
+ * public class QueryVisitor&lt;Query, Query&gt; {
+ *   public QueryVisitor() {
+ *     super(QueryVisitor.class, Query.class, Query.class);
+ *   }
+ *   Query visit(TermQuery termQuery) { ... }
+ *   Query visit(BooleanQuery booleanQuery) { ... }
+ *   Query visit(Query query) { ... }
+ * }
+ * </pre>
+ */
+public abstract class Visitor<I, O> {
+
+  private static final Map<Class, InvocationDispatcher> dispatcherByClass =
+      new HashMap<Class, InvocationDispatcher>();
+
+  protected final InvocationDispatcher<I, O> dispatcher;
+
+  /**
+   * Creates a new Visitor that will visit methods in the given visitor class,
+   * that accept parameters of the given input type, returning values of the
+   * given output type
+   *
+   * @param visitorClass Class whose methods will be visited by the Visitor
+   * @param inputType Type of the inputs to the visiting
+   * @param outputType Type of the outputs of the visiting
+   */
+  @SuppressWarnings("unchecked")
+  protected Visitor(Class visitorClass, Class<I> inputType, Class<O> outputType) {
+    InvocationDispatcher<I, O> dispatcher = dispatcherByClass.get(visitorClass);
+    if (dispatcher == null) {
+      dispatcher = new InvocationDispatcher<I, O>(visitorClass, "visit", inputType, outputType);
+      dispatcherByClass.put(visitorClass, dispatcher);
+    }
+    this.dispatcher = dispatcher;
+  }
+
+  /**
+   * Applies the given input to the visitor
+   *
+   * @param input Input value to apply to the visitor
+   * @return Return value from the visiting
+   */
+  public final O apply(I input) {
+    return dispatcher.dispatch(this, input);
+  }
+}
Index: lucene/src/test/org/apache/lucene/util/TestVisitor.java
===================================================================
--- lucene/src/test/org/apache/lucene/util/TestVisitor.java	Sat Apr 30 20:03:33 NZST 2011
+++ lucene/src/test/org/apache/lucene/util/TestVisitor.java	Sat Apr 30 20:03:33 NZST 2011
@@ -0,0 +1,114 @@
+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 org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestVisitor {
+
+  private interface TopLevel {}
+  private interface A extends TopLevel {}
+  private interface B extends TopLevel {}
+  private class AImpl implements A {}
+  private class ABImpl implements A, B {}
+
+  @Test
+  public void testVisitor_simpleDispatch() {
+    class AVisitor extends Visitor<A, String> {
+
+      AVisitor() {
+        super(AVisitor.class, A.class, String.class);
+      }
+
+      public String visit(A a) {
+        return "value";
+      }
+    }
+
+    Visitor<A, String> visitor = new AVisitor();
+    String visitorOutput = visitor.apply(new AImpl());
+    assertEquals("value", visitorOutput);
+  }
+
+  @Test
+  public void testVisitor_multipleMethodsOnlyOneDispatchable() {
+    class AVisitor extends Visitor<A, String> {
+
+      AVisitor() {
+        super(AVisitor.class, A.class, String.class);
+      }
+
+      public String visit(A a) {
+        return "Visited A";
+      }
+
+      public String visit(B b) {
+        return "Visited B";
+      }
+    }
+
+    Visitor<A, String> visitor = new AVisitor();
+    String visitorOutput = visitor.apply(new AImpl());
+    assertEquals("Visited A", visitorOutput);
+  }
+
+  @Test(expected = MethodDispatchException.class)
+  public void testVisitor_multipleDispatchableMethods() {
+    class TopLevelVisitor extends Visitor<TopLevel, String> {
+
+      TopLevelVisitor() {
+        super(TopLevelVisitor.class, TopLevel.class, String.class);
+      }
+
+      public String visit(A a) {
+        return "Visited A";
+      }
+
+      public String visit(B b) {
+        return "Visited B";
+      }
+    }
+
+    Visitor<TopLevel, String> visitor = new TopLevelVisitor();
+    visitor.apply(new ABImpl());
+  }
+
+  @Test
+  public void testVisitor_catchAll() {
+    class TopLevelVisitor extends Visitor<TopLevel, String> {
+
+      TopLevelVisitor() {
+        super(TopLevelVisitor.class, TopLevel.class, String.class);
+      }
+
+      public String visit(B b) {
+        return "Visited B";
+      }
+
+      public String visit(TopLevel topLevel) {
+        return "Visited Catchall";
+      }
+    }
+
+    Visitor<TopLevel, String> visitor = new TopLevelVisitor();
+    String visitorOutput = visitor.apply(new AImpl());
+    assertEquals("Visited Catchall", visitorOutput);
+  }
+}
Index: lucene/src/java/org/apache/lucene/util/InvocationDispatcher.java
===================================================================
--- lucene/src/java/org/apache/lucene/util/InvocationDispatcher.java	Sat Apr 30 20:02:03 NZST 2011
+++ lucene/src/java/org/apache/lucene/util/InvocationDispatcher.java	Sat Apr 30 20:02:03 NZST 2011
@@ -0,0 +1,163 @@
+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.*;
+
+public class InvocationDispatcher<P, R> {
+
+  private final List<Method> dispatchableMethods;
+  private final Map<Class, Method> methodByType = new HashMap<Class, Method>();
+
+  /**
+   * Creates a new {@link InvocationDispatcher} that will dispatch to methods
+   * of the given method name, belonging to the given Class and its supertypes
+   *
+   * @param classType Class whose methods will be dispatched to
+   * @param methodName Name of the methods to consider dispatching to
+   * @param parameterType Type that parameters will be when dispatched
+   * @param returnType Type that the return type of the method's is expected to be
+   */
+  public InvocationDispatcher(Class classType, String methodName, Class<P> parameterType, Class<R> returnType) {
+    this.dispatchableMethods = new ArrayList<Method>();
+
+    for (; classType != Object.class; classType = classType.getSuperclass()) {
+      for (Method method : classType.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 walker 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 walker, 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(walker, parameter);
+    } catch (Exception e) {
+      throw new MethodDispatchException("Exception dispatching call", e);
+    }
+  }
+
+  /**
+   * Determines whether the given method can receive dispatches.
+   *
+   * @param method Method to check if it can receive dispatches
+   * @param methodName Name method's must have to receive dispatches
+   * @param parameterType Type that the method's single parameter should be
+   * @param returnType Type that the method's return value should be
+   * @return {@code true} if the method 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/util/MethodDispatchException.java
===================================================================
--- lucene/src/java/org/apache/lucene/util/MethodDispatchException.java	Sat Apr 30 20:02:09 NZST 2011
+++ lucene/src/java/org/apache/lucene/util/MethodDispatchException.java	Sat Apr 30 20:02:09 NZST 2011
@@ -0,0 +1,29 @@
+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);
+  }
+}
