[CALCITE-4550] Simplify JaninoRelMetadataProvider API for binding methods (James Starr)

Decoupling MetadataDef and JaninoMetadataProvider.
Reworking binding RelMdPercentageOriginalRows to handlers. Adding
DescriptiveCacheKey to replace the method in metadata cache list.
Adding a handlers method to RelMetadataProvider keys on the class
of MetadataHandler.  Reworking RelMetadataQuery to not use defs.
Using MetadataHandler methods instead of Metadata methods in code
generation.

Close apache/calcite#2570
diff --git a/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java
index 7b71053..e243c0f 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java
@@ -23,12 +23,14 @@
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.UnboundMetadata;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Multimap;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.lang.reflect.Method;
+import java.util.List;
 
 import static java.util.Objects.requireNonNull;
 
@@ -68,8 +70,14 @@
     };
   }
 
+  @Deprecated // to be removed before 2.0
   @Override public <M extends Metadata> Multimap<Method, MetadataHandler<M>> handlers(
       MetadataDef<M> def) {
     return ImmutableMultimap.of();
   }
+
+  @Override public List<MetadataHandler<?>> handlers(
+      Class<? extends MetadataHandler<?>> handlerClass) {
+    return ImmutableList.of();
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java
index a99a6dc..499b4fc 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java
@@ -23,12 +23,14 @@
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.UnboundMetadata;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Multimap;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.lang.reflect.Method;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -119,8 +121,14 @@
     };
   }
 
+  @Deprecated
   @Override public <M extends Metadata> Multimap<Method, MetadataHandler<M>> handlers(
       MetadataDef<M> def) {
     return ImmutableMultimap.of();
   }
+
+  @Override public List<MetadataHandler<?>> handlers(
+      Class<? extends MetadataHandler<?>> handlerClass) {
+    return ImmutableList.of();
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java
index 0c7349f..01e53a7 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java
@@ -83,11 +83,17 @@
     };
   }
 
+  @Deprecated // to be removed before 2.0
   @Override public <M extends Metadata> Multimap<Method, MetadataHandler<M>> handlers(
       MetadataDef<M> def) {
     return underlyingProvider.handlers(def);
   }
 
+  @Override public List<MetadataHandler<?>> handlers(
+      Class<? extends MetadataHandler<?>> handlerClass) {
+    return underlyingProvider.handlers(handlerClass);
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /** An entry in the cache. Consists of the cached object and the timestamp
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java
index a0c4520..5377b06 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java
@@ -105,6 +105,7 @@
     }
   }
 
+  @Deprecated // to be removed before 2.0
   @Override public <M extends Metadata> Multimap<Method, MetadataHandler<M>> handlers(
       MetadataDef<M> def) {
     final ImmutableMultimap.Builder<Method, MetadataHandler<M>> builder =
@@ -115,6 +116,16 @@
     return builder.build();
   }
 
+  @Override public List<MetadataHandler<?>> handlers(
+      Class<? extends MetadataHandler<?>> handlerClass) {
+    final ImmutableList.Builder<MetadataHandler<?>> builder =
+        ImmutableList.builder();
+    for (RelMetadataProvider provider : providers) {
+      builder.addAll(provider.handlers(handlerClass));
+    }
+    return builder.build();
+  }
+
   /** Creates a chain. */
   public static RelMetadataProvider of(List<RelMetadataProvider> list) {
     return new ChainedRelMetadataProvider(ImmutableList.copyOf(list));
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
index f3eb478..6d5639e 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
@@ -21,8 +21,8 @@
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.metadata.janino.DescriptiveCacheKey;
 import org.apache.calcite.rel.metadata.janino.DispatchGenerator;
-import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.ControlFlowException;
 import org.apache.calcite.util.Util;
 
@@ -45,6 +45,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -66,13 +67,13 @@
   /** Cache of pre-generated handlers by provider and kind of metadata.
    * For the cache to be effective, providers should implement identity
    * correctly. */
-  @SuppressWarnings("unchecked")
-  private static final LoadingCache<Key, MetadataHandler> HANDLERS =
+  private static final LoadingCache<Key, MetadataHandler<?>> HANDLERS =
       maxSize(CacheBuilder.newBuilder(),
           CalciteSystemProperty.METADATA_HANDLER_CACHE_MAXIMUM_SIZE.value())
           .build(
               CacheLoader.from(key ->
-                  load3(key.def, key.provider.handlers(key.def))));
+                  generateCompileAndInstantiate(key.handlerClass,
+                      key.provider.handlers(key.handlerClass))));
 
   /** Private constructor; use {@link #of}. */
   private JaninoRelMetadataProvider(RelMetadataProvider provider) {
@@ -115,69 +116,105 @@
     throw new UnsupportedOperationException();
   }
 
+  @Deprecated // to be removed before 2.0
   @Override public <M extends Metadata> Multimap<Method, MetadataHandler<M>>
       handlers(MetadataDef<M> def) {
     return provider.handlers(def);
   }
 
-  private static <M extends Metadata> MetadataHandler<M> load3(
-      MetadataDef<M> def, Multimap<Method, ? extends MetadataHandler<?>> map) {
+  @Override public List<MetadataHandler<?>> handlers(
+      Class<? extends MetadataHandler<?>> handlerClass) {
+    return provider.handlers(handlerClass);
+  }
+
+  private static <MH extends MetadataHandler<?>> MH generateCompileAndInstantiate(
+      Class<MH> handlerClass,
+      List<? extends MetadataHandler<? extends Metadata>> handlers) {
+    final LinkedHashSet<? extends MetadataHandler<? extends Metadata>> handlerSet =
+        new LinkedHashSet<>(handlers);
     final StringBuilder buff = new StringBuilder();
     final String name =
-        "GeneratedMetadata_" + simpleNameForHandler(def.handlerClass);
+        "GeneratedMetadata_" + simpleNameForHandler(handlerClass);
 
     final Map<MetadataHandler<?>, String> handlerToName = new LinkedHashMap<>();
-    for (MetadataHandler<?> provider : map.values()) {
+    for (MetadataHandler<?> provider : handlerSet) {
       if (!handlerToName.containsKey(provider)) {
         handlerToName.put(provider, "provider" + handlerToName.size());
       }
     }
-
-    buff.append("  private final ").append(MetadataDef.class.getName()).append(" def;\n");
+    //Properties
     for (Map.Entry<MetadataHandler<?>, String> handlerAndName : handlerToName.entrySet()) {
       buff.append("  public final ").append(handlerAndName.getKey().getClass().getName())
           .append(' ').append(handlerAndName.getValue()).append(";\n");
     }
-    buff.append("  public ").append(name).append("(\n")
-        .append("      ").append(MetadataDef.class.getName()).append(" def");
+    //Constructor
+    buff.append("  public ").append(name).append("(\n");
     for (Map.Entry<MetadataHandler<?>, String> handlerAndName : handlerToName.entrySet()) {
-      buff.append(",\n")
-          .append("      ")
+      buff.append("      ")
           .append(handlerAndName.getKey().getClass().getName())
           .append(' ')
-          .append(handlerAndName.getValue());
+          .append(handlerAndName.getValue())
+          .append(",\n");
     }
-    buff.append(") {\n")
-        .append("    this.def = def;\n");
-
+    if (!handlerToName.isEmpty()) {
+      //Remove trailing comma and new line
+      buff.setLength(buff.length() - 2);
+    }
+    buff.append(") {\n");
     for (String handlerName : handlerToName.values()) {
       buff.append("    this.").append(handlerName).append(" = ").append(handlerName)
           .append(";\n");
     }
-    buff.append("  }\n")
-        .append("  public ")
-        .append(MetadataDef.class.getName())
-        .append(" getDef() {\n")
-        .append("    return ")
-        .append(def.metadataClass.getName())
-        .append(".DEF;\n")
-        .append("  }\n");
+    buff.append("  }\n");
+    getDefMethod(buff, handlerToName.values()
+        .stream()
+        .findFirst()
+        .orElse(null));
+
     DispatchGenerator dispatchGenerator = new DispatchGenerator(handlerToName);
-    for (Ord<Method> method : Ord.zip(map.keySet())) {
+    for (Ord<Method> method : Ord.zip(handlerClass.getDeclaredMethods())) {
+      cacheProperties(buff, method.e, method.i);
       generateCachedMethod(buff, method.e, method.i);
-      dispatchGenerator.dispatchMethod(buff, method.e, map.get(method.e));
+      dispatchGenerator.dispatchMethod(buff, method.e, handlerSet);
     }
-    final List<Object> argList = new ArrayList<>();
-    argList.add(def);
-    argList.addAll(handlerToName.keySet());
+    final List<Object> argList = new ArrayList<>(handlerToName.keySet());
     try {
-      return compile(name, buff.toString(), def, argList);
+      return compile(name, buff.toString(), handlerClass, argList);
     } catch (CompileException | IOException e) {
       throw new RuntimeException("Error compiling:\n"
           + buff, e);
     }
   }
 
+  static void cacheProperties(StringBuilder buff, Method method, int methodIndex) {
+    buff.append("  private final Object ");
+    appendKeyName(buff, methodIndex);
+    buff.append(" = new ")
+        .append(DescriptiveCacheKey.class.getName())
+        .append("(\"")
+        .append(method.toString())
+        .append("\");\n");
+  }
+
+  private static void appendKeyName(StringBuilder buff, int methodIndex) {
+    buff.append("methodKey").append(methodIndex);
+  }
+
+  private static void getDefMethod(StringBuilder buff, @Nullable String handlerName) {
+    buff.append("  public ")
+        .append(MetadataDef.class.getName())
+        .append(" getDef() {\n");
+
+    if (handlerName == null) {
+      buff.append("    return null;");
+    } else {
+      buff.append("    return ")
+          .append(handlerName)
+          .append(".getDef();\n");
+    }
+    buff.append("  }\n");
+  }
+
   private static void generateCachedMethod(StringBuilder buff, Method method, int methodIndex) {
     String delRelClass = DelegatingMetadataRel.class.getName();
     buff.append("  public ")
@@ -202,13 +239,7 @@
                 ? org.apache.calcite.runtime.FlatLists.class
                 : ImmutableList.class).getName())
         .append(".of(");
-    if (methodIndex == 0) {
-      buff.append("def");
-    } else {
-      buff.append("def.methods.get(")
-          .append(methodIndex)
-          .append(")");
-    }
+    appendKeyName(buff, methodIndex);
     safeArgList(buff, method)
         .append(");\n")
         .append("    final Object v = mq.map.get(r, key);\n")
@@ -268,20 +299,23 @@
 
   /** Returns e.g. ", ignoreNulls". */
   private static StringBuilder argList(StringBuilder buff, Method method) {
-    for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
-      buff.append(", a").append(t.i);
+    Class<?>[] paramTypes = method.getParameterTypes();
+    for (int i = 2; i < paramTypes.length; i++) {
+      buff.append(", a").append(i - 2);
     }
     return buff;
   }
 
   /** Returns e.g. ", ignoreNulls". */
   private static StringBuilder safeArgList(StringBuilder buff, Method method) {
-    for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
-      if (Primitive.is(t.e) || RexNode.class.isAssignableFrom(t.e)) {
-        buff.append(", a").append(t.i);
+    Class<?>[] paramTypes = method.getParameterTypes();
+    for (int i = 2; i < paramTypes.length; i++) {
+      Class<?> t = paramTypes[i];
+      if (Primitive.is(t)) {
+        buff.append(", a").append(i - 2);
       } else {
         buff.append(", ") .append(NullSentinel.class.getName())
-            .append(".mask(a").append(t.i).append(")");
+            .append(".mask(a").append(i - 2).append(")");
       }
     }
     return buff;
@@ -289,14 +323,15 @@
 
   /** Returns e.g. ",\n boolean ignoreNulls". */
   private static StringBuilder paramList(StringBuilder buff, Method method) {
-    for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
-      buff.append(",\n      ").append(t.e.getName()).append(" a").append(t.i);
+    Class<?>[] paramTypes = method.getParameterTypes();
+    for (int i = 2; i < paramTypes.length; i++) {
+      buff.append(",\n      ").append(paramTypes[i].getName()).append(" a").append(i - 2);
     }
     return buff;
   }
 
-  static <M extends Metadata> MetadataHandler<M> compile(String className,
-      String classBody, MetadataDef<M> def,
+  static  <MH extends MetadataHandler<?>> MH compile(String className,
+      String classBody, Class<MH> handlerClass,
       List<Object> argList) throws CompileException, IOException {
     final ICompilerFactory compilerFactory;
     ClassLoader classLoader =
@@ -312,7 +347,7 @@
     compiler.setParentClassLoader(JaninoRexCompiler.class.getClassLoader());
 
     final String s = "public final class " + className
-        + " implements " + def.handlerClass.getCanonicalName() + " {\n"
+        + " implements " + handlerClass.getCanonicalName() + " {\n"
         + classBody
         + "\n"
         + "}";
@@ -328,7 +363,7 @@
     final Object o;
     try {
       constructor = compiler.getClassLoader().loadClass(className)
-              .getDeclaredConstructors()[0];
+          .getDeclaredConstructors()[0];
       o = constructor.newInstance(argList.toArray());
     } catch (InstantiationException
         | IllegalAccessException
@@ -336,26 +371,19 @@
         | ClassNotFoundException e) {
       throw new RuntimeException(e);
     }
-    return def.handlerClass.cast(o);
+    return handlerClass.cast(o);
   }
 
-  synchronized <M extends Metadata, H extends MetadataHandler<M>> H create(
-      MetadataDef<M> def) {
+  synchronized <H extends MetadataHandler<?>> H revise(Class<H> handlerClass) {
     try {
-      final Key key = new Key((MetadataDef) def, provider);
+      final Key key = new Key(handlerClass, provider);
       //noinspection unchecked
-      return (H) HANDLERS.get(key);
+      return handlerClass.cast(HANDLERS.get(key));
     } catch (UncheckedExecutionException | ExecutionException e) {
       throw Util.throwAsRuntime(Util.causeOrSelf(e));
     }
   }
 
-  synchronized <M extends Metadata, H extends MetadataHandler<M>> H revise(
-      Class<? extends RelNode> rClass, MetadataDef<M> def) {
-    //noinspection unchecked
-    return (H) create(def);
-  }
-
   /** Registers some classes. Does not flush the providers, but next time we
    * need to generate a provider, it will handle all of these classes. So,
    * calling this method reduces the number of times we need to re-generate. */
@@ -376,23 +404,24 @@
 
   /** Key for the cache. */
   private static class Key {
-    public final MetadataDef def;
-    public final RelMetadataProvider provider;
+    final Class<? extends MetadataHandler<? extends Metadata>> handlerClass;
+    final RelMetadataProvider provider;
 
-    private Key(MetadataDef def, RelMetadataProvider provider) {
-      this.def = def;
+    private Key(Class<? extends MetadataHandler<?>> handlerClass,
+        RelMetadataProvider provider) {
+      this.handlerClass = handlerClass;
       this.provider = provider;
     }
 
     @Override public int hashCode() {
-      return (def.hashCode() * 37
+      return (handlerClass.hashCode() * 37
           + provider.hashCode()) * 37;
     }
 
     @Override public boolean equals(@Nullable Object obj) {
       return this == obj
           || obj instanceof Key
-          && ((Key) obj).def.equals(def)
+          && ((Key) obj).handlerClass.equals(handlerClass)
           && ((Key) obj).provider.equals(provider);
     }
   }
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java
index 122367b..ab01fc2 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java
@@ -68,7 +68,10 @@
   private final ConcurrentMap<Class<RelNode>, UnboundMetadata> map;
   @Deprecated // to be removed before 2.0
   private final Class<? extends Metadata> metadataClass0;
+  @Deprecated // to be removed before 2.0
   private final ImmutableMultimap<Method, MetadataHandler> handlerMap;
+  private final Class<? extends MetadataHandler<?>> handlerClass;
+  private final ImmutableList<MetadataHandler<?>> handlers;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -82,12 +85,15 @@
   protected ReflectiveRelMetadataProvider(
       ConcurrentMap<Class<RelNode>, UnboundMetadata> map,
       Class<? extends Metadata> metadataClass0,
-      Multimap<Method, MetadataHandler> handlerMap) {
+      Multimap<Method, MetadataHandler<?>> handlerMap,
+      Class<? extends MetadataHandler<?>> handlerClass) {
     Preconditions.checkArgument(!map.isEmpty(), "ReflectiveRelMetadataProvider "
         + "methods map is empty; are your methods named wrong?");
     this.map = map;
     this.metadataClass0 = metadataClass0;
     this.handlerMap = ImmutableMultimap.copyOf(handlerMap);
+    this.handlerClass = handlerClass;
+    this.handlers = ImmutableList.copyOf(handlerMap.values());
   }
 
   /** Returns an implementation of {@link RelMetadataProvider} that scans for
@@ -107,20 +113,31 @@
    * that extend {@link org.apache.calcite.rel.core.Union}
    * or {@link org.apache.calcite.rel.core.Filter}.</p>
    */
+  @Deprecated // to be removed before 2.0
   public static RelMetadataProvider reflectiveSource(Method method,
       MetadataHandler target) {
-    return reflectiveSource(target, ImmutableList.of(method));
+    return reflectiveSource(target, ImmutableList.of(method), target.getDef().handlerClass);
   }
 
   /** Returns a reflective metadata provider that implements several
    * methods. */
+  @Deprecated // to be removed before 2.0
   public static RelMetadataProvider reflectiveSource(MetadataHandler target,
       Method... methods) {
-    return reflectiveSource(target, ImmutableList.copyOf(methods));
+    return reflectiveSource(target, ImmutableList.copyOf(methods), target.getDef().handlerClass);
   }
 
+  @SuppressWarnings("deprecation")
+  public static <M extends Metadata> RelMetadataProvider reflectiveSource(
+      MetadataHandler<? extends M> handler, Class<? extends MetadataHandler<M>> handlerClass) {
+    //When deprecated code is removed, handler.getDef().methods will no longer be required
+    return reflectiveSource(handler, handler.getDef().methods, handlerClass);
+  }
+
+  @Deprecated // to be removed before 2.0
   private static RelMetadataProvider reflectiveSource(
-      final MetadataHandler target, final ImmutableList<Method> methods) {
+      final MetadataHandler target, final ImmutableList<Method> methods,
+      final Class<? extends MetadataHandler<?>> handlerClass) {
     final Space2 space = Space2.create(target, methods);
 
     // This needs to be a concurrent map since RelMetadataProvider are cached in static
@@ -199,9 +216,10 @@
       methodsMap.put(key, function);
     }
     return new ReflectiveRelMetadataProvider(methodsMap, space.metadataClass0,
-        space.providerMap);
+        space.providerMap, handlerClass);
   }
 
+  @Deprecated // to be removed before 2.0
   @Override public <M extends Metadata> Multimap<Method, MetadataHandler<M>> handlers(
       MetadataDef<M> def) {
     final ImmutableMultimap.Builder<Method, MetadataHandler<M>> builder =
@@ -215,6 +233,16 @@
     return builder.build();
   }
 
+  @Override public List<MetadataHandler<?>> handlers(
+      Class<? extends MetadataHandler<?>> handlerClass) {
+    if (this.handlerClass.isAssignableFrom(handlerClass)) {
+      return handlers;
+    } else {
+      return ImmutableList.of();
+    }
+  }
+
+  @Deprecated // to be removed before 2.0
   private static boolean couldImplement(Method handlerMethod, Method method) {
     if (!handlerMethod.getName().equals(method.getName())
         || (handlerMethod.getModifiers() & Modifier.STATIC) != 0
@@ -278,19 +306,20 @@
 
   /** Workspace for computing which methods can act as handlers for
    * given metadata methods. */
+  @Deprecated // to be removed before 2.0
   static class Space {
     final Set<Class<RelNode>> classes = new HashSet<>();
     final Map<Pair<Class<RelNode>, Method>, Method> handlerMap = new HashMap<>();
-    final ImmutableMultimap<Method, MetadataHandler> providerMap;
+    final ImmutableMultimap<Method, MetadataHandler<?>> providerMap;
 
-    Space(Multimap<Method, MetadataHandler> providerMap) {
+    Space(Multimap<Method, MetadataHandler<?>> providerMap) {
       this.providerMap = ImmutableMultimap.copyOf(providerMap);
 
       // Find the distinct set of RelNode classes handled by this provider,
       // ordered base-class first.
-      for (Map.Entry<Method, MetadataHandler> entry : providerMap.entries()) {
+      for (Map.Entry<Method, MetadataHandler<?>> entry : providerMap.entries()) {
         final Method method = entry.getKey();
-        final MetadataHandler provider = entry.getValue();
+        final MetadataHandler<?> provider = entry.getValue();
         for (final Method handlerMethod : provider.getClass().getMethods()) {
           if (couldImplement(handlerMethod, method)) {
             @SuppressWarnings("unchecked") final Class<RelNode> relNodeClass =
@@ -332,16 +361,19 @@
   }
 
   /** Extended work space. */
+  @Deprecated // to be removed before 2.0
   static class Space2 extends Space {
     private Class<Metadata> metadataClass0;
 
     Space2(Class<Metadata> metadataClass0,
-        ImmutableMultimap<Method, MetadataHandler> providerMap) {
+        ImmutableMultimap<Method, MetadataHandler<?>> providerMap) {
       super(providerMap);
       this.metadataClass0 = metadataClass0;
     }
 
-    public static Space2 create(MetadataHandler target,
+    @Deprecated // to be removed before 2.0
+    public static Space2 create(
+        MetadataHandler<?> target,
         ImmutableList<Method> methods) {
       assert methods.size() > 0;
       final Method method0 = methods.get(0);
@@ -352,7 +384,7 @@
         assert method.getDeclaringClass() == metadataClass0;
       }
 
-      final ImmutableMultimap.Builder<Method, MetadataHandler> providerBuilder =
+      final ImmutableMultimap.Builder<Method, MetadataHandler<?>> providerBuilder =
           ImmutableMultimap.builder();
       for (final Method method : methods) {
         providerBuilder.put(method, target);
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdAllPredicates.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdAllPredicates.java
index ef02613..0ca61be 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdAllPredicates.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdAllPredicates.java
@@ -41,7 +41,6 @@
 import org.apache.calcite.rex.RexTableInputRef.RelTableRef;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Util;
 
@@ -78,8 +77,9 @@
  */
 public class RelMdAllPredicates
     implements MetadataHandler<BuiltInMetadata.AllPredicates> {
-  public static final RelMetadataProvider SOURCE = ReflectiveRelMetadataProvider
-      .reflectiveSource(BuiltInMethod.ALL_PREDICATES.method, new RelMdAllPredicates());
+  public static final RelMetadataProvider SOURCE =
+      ReflectiveRelMetadataProvider.reflectiveSource(
+          new RelMdAllPredicates(), BuiltInMetadata.AllPredicates.Handler.class);
 
   @Override public MetadataDef<BuiltInMetadata.AllPredicates> getDef() {
     return BuiltInMetadata.AllPredicates.DEF;
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
index 850f99a..e32ad62 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
@@ -51,7 +51,6 @@
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.sql.validate.SqlMonotonicity;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Pair;
@@ -84,7 +83,7 @@
     implements MetadataHandler<BuiltInMetadata.Collation> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.COLLATIONS.method, new RelMdCollation());
+          new RelMdCollation(), BuiltInMetadata.Collation.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java
index 560f5ff..c8a56e2 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java
@@ -35,7 +35,6 @@
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.rex.RexVisitor;
 import org.apache.calcite.rex.RexVisitorImpl;
-import org.apache.calcite.util.BuiltInMethod;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.checkerframework.checker.nullness.qual.PolyNull;
@@ -53,7 +52,7 @@
     implements MetadataHandler<BuiltInMetadata.ColumnOrigin> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.COLUMN_ORIGIN.method, new RelMdColumnOrigins());
+          new RelMdColumnOrigins(), BuiltInMetadata.ColumnOrigin.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
index 5a9d12d..7e912f0 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
@@ -45,7 +45,6 @@
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
@@ -67,7 +66,7 @@
     implements MetadataHandler<BuiltInMetadata.ColumnUniqueness> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.COLUMN_UNIQUENESS.method, new RelMdColumnUniqueness());
+          new RelMdColumnUniqueness(), BuiltInMetadata.ColumnUniqueness.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
index a8b42cf..d172f37 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
@@ -33,7 +33,6 @@
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.util.Bug;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.NumberUtil;
 
@@ -55,7 +54,7 @@
     implements MetadataHandler<BuiltInMetadata.DistinctRowCount> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.DISTINCT_ROW_COUNT.method, new RelMdDistinctRowCount());
+          new RelMdDistinctRowCount(), BuiltInMetadata.DistinctRowCount.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java
index e06cab5..5133dbb 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java
@@ -35,7 +35,6 @@
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexProgram;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.collect.ImmutableList;
@@ -53,7 +52,7 @@
     implements MetadataHandler<BuiltInMetadata.Distribution> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.DISTRIBUTION.method, new RelMdDistribution());
+          new RelMdDistribution(), BuiltInMetadata.Distribution.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java
index 6795522..1f5d8f5 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java
@@ -18,7 +18,6 @@
 
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.sql.SqlExplainLevel;
-import org.apache.calcite.util.BuiltInMethod;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -30,8 +29,7 @@
     implements MetadataHandler<BuiltInMetadata.ExplainVisibility> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.EXPLAIN_VISIBILITY.method,
-          new RelMdExplainVisibility());
+          new RelMdExplainVisibility(), BuiltInMetadata.ExplainVisibility.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExpressionLineage.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExpressionLineage.java
index 6cc92bf..cc9d0e4 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExpressionLineage.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExpressionLineage.java
@@ -40,7 +40,6 @@
 import org.apache.calcite.rex.RexTableInputRef.RelTableRef;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
@@ -84,7 +83,7 @@
     implements MetadataHandler<BuiltInMetadata.ExpressionLineage> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.EXPRESSION_LINEAGE.method, new RelMdExpressionLineage());
+          new RelMdExpressionLineage(), BuiltInMetadata.ExpressionLineage.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdLowerBoundCost.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdLowerBoundCost.java
index 6e59655..b54f2d0 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdLowerBoundCost.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdLowerBoundCost.java
@@ -21,7 +21,6 @@
 import org.apache.calcite.plan.volcano.VolcanoPlanner;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.metadata.BuiltInMetadata.LowerBoundCost;
-import org.apache.calcite.util.BuiltInMethod;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -34,7 +33,7 @@
 
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          new RelMdLowerBoundCost(), BuiltInMethod.LOWER_BOUND_COST.method);
+          new RelMdLowerBoundCost(), LowerBoundCost.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
index 31c632b..e4801b6 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
@@ -36,7 +36,6 @@
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.util.Bug;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.Util;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -49,7 +48,7 @@
     implements MetadataHandler<BuiltInMetadata.MaxRowCount> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.MAX_ROW_COUNT.method, new RelMdMaxRowCount());
+          new RelMdMaxRowCount(), BuiltInMetadata.MaxRowCount.Handler.class);
 
   //~ Methods ----------------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java
index a1019f0..b8333fc 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java
@@ -17,7 +17,6 @@
 package org.apache.calcite.rel.metadata;
 
 import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.util.BuiltInMethod;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -34,9 +33,7 @@
    * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory}. */
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(new RelMdMemory(),
-          BuiltInMethod.MEMORY.method,
-          BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE.method,
-          BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE_SPLIT.method);
+          BuiltInMetadata.Memory.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java
index eb4a1d9..21d55ad 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMinRowCount.java
@@ -34,7 +34,6 @@
 import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.util.Bug;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.Util;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -47,7 +46,7 @@
     implements MetadataHandler<BuiltInMetadata.MinRowCount> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.MIN_ROW_COUNT.method, new RelMdMinRowCount());
+          new RelMdMinRowCount(), BuiltInMetadata.MinRowCount.Handler.class);
 
   //~ Methods ----------------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdNodeTypes.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdNodeTypes.java
index 04a71e0..a7d160f 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdNodeTypes.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdNodeTypes.java
@@ -35,7 +35,6 @@
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rel.core.Window;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ArrayListMultimap;
@@ -51,7 +50,7 @@
     implements MetadataHandler<BuiltInMetadata.NodeTypes> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.NODE_TYPES.method, new RelMdNodeTypes());
+          new RelMdNodeTypes(), BuiltInMetadata.NodeTypes.Handler.class);
 
   //~ Methods ----------------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java
index 4f2a371..2bd1747 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java
@@ -20,7 +20,6 @@
 import org.apache.calcite.rel.core.Exchange;
 import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.Values;
-import org.apache.calcite.util.BuiltInMethod;
 
 /**
  * Default implementations of the
@@ -36,8 +35,7 @@
    * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism}. */
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(new RelMdParallelism(),
-          BuiltInMethod.IS_PHASE_TRANSITION.method,
-          BuiltInMethod.SPLIT_COUNT.method);
+          BuiltInMetadata.Parallelism.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java
index 949931f..a25d2ee 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java
@@ -22,7 +22,6 @@
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.Union;
-import org.apache.calcite.util.BuiltInMethod;
 
 import com.google.common.collect.ImmutableList;
 
@@ -36,30 +35,26 @@
  * {@link RelMetadataQuery#getPercentageOriginalRows} for the standard logical
  * algebra.
  */
-public class RelMdPercentageOriginalRows
-    implements MetadataHandler<BuiltInMetadata.PercentageOriginalRows> {
-  private static final RelMdPercentageOriginalRows INSTANCE =
-      new RelMdPercentageOriginalRows();
-
+public class RelMdPercentageOriginalRows {
   public static final RelMetadataProvider SOURCE =
       ChainedRelMetadataProvider.of(
           ImmutableList.of(
               ReflectiveRelMetadataProvider.reflectiveSource(
-                  BuiltInMethod.PERCENTAGE_ORIGINAL_ROWS.method, INSTANCE),
-
+                  new RelMdPercentageOriginalRowsHandler(),
+                  BuiltInMetadata.PercentageOriginalRows.Handler.class),
               ReflectiveRelMetadataProvider.reflectiveSource(
-                  BuiltInMethod.CUMULATIVE_COST.method, INSTANCE),
-
+                  new RelMdCumulativeCost(),
+                  BuiltInMetadata.CumulativeCost.Handler.class),
               ReflectiveRelMetadataProvider.reflectiveSource(
-                  BuiltInMethod.NON_CUMULATIVE_COST.method, INSTANCE)));
+                  new RelMdNonCumulativeCost(),
+                  BuiltInMetadata.NonCumulativeCost.Handler.class
+              )
+          ));
 
   //~ Methods ----------------------------------------------------------------
 
   private RelMdPercentageOriginalRows() {}
 
-  @Override public MetadataDef<BuiltInMetadata.PercentageOriginalRows> getDef() {
-    return BuiltInMetadata.PercentageOriginalRows.DEF;
-  }
 
   public @Nullable Double getPercentageOriginalRows(Aggregate rel, RelMetadataQuery mq) {
     // REVIEW jvs 28-Mar-2006: The assumption here seems to be that
@@ -199,4 +194,41 @@
       return numerator / denominator;
     }
   }
+
+  /**
+   * Binds {@link RelMdPercentageOriginalRows} to {@link BuiltInMetadata.CumulativeCost}.
+   */
+  private static final class RelMdCumulativeCost
+      extends RelMdPercentageOriginalRows
+      implements MetadataHandler<BuiltInMetadata.CumulativeCost> {
+    @Deprecated // to be removed before 2.0
+    @Override public MetadataDef<BuiltInMetadata.CumulativeCost> getDef() {
+      return BuiltInMetadata.CumulativeCost.DEF;
+    }
+  }
+
+  /**
+   * Binds {@link RelMdPercentageOriginalRows} to {@link BuiltInMetadata.NonCumulativeCost}.
+   */
+  private static final class RelMdNonCumulativeCost
+      extends RelMdPercentageOriginalRows
+      implements MetadataHandler<BuiltInMetadata.NonCumulativeCost> {
+
+    @Deprecated // to be removed before 2.0
+    @Override public MetadataDef<BuiltInMetadata.NonCumulativeCost> getDef() {
+      return BuiltInMetadata.NonCumulativeCost.DEF;
+    }
+  }
+
+  /**
+   * Binds {@link RelMdPercentageOriginalRows} to {@link BuiltInMetadata.PercentageOriginalRows}.
+   */
+  private static final class RelMdPercentageOriginalRowsHandler
+      extends RelMdPercentageOriginalRows
+      implements MetadataHandler<BuiltInMetadata.PercentageOriginalRows> {
+    @Deprecated // to be removed before 2.0
+    @Override public MetadataDef<BuiltInMetadata.PercentageOriginalRows> getDef() {
+      return BuiltInMetadata.PercentageOriginalRows.DEF;
+    }
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java
index 7c34639..779457f 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java
@@ -28,7 +28,6 @@
 import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -43,7 +42,7 @@
     implements MetadataHandler<BuiltInMetadata.PopulationSize> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.POPULATION_SIZE.method, new RelMdPopulationSize());
+          new RelMdPopulationSize(), BuiltInMetadata.PopulationSize.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
index 603f7a5..00aae1b 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
@@ -52,7 +52,6 @@
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.util.BitSets;
 import org.apache.calcite.util.Bug;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.Mapping;
@@ -129,7 +128,7 @@
 public class RelMdPredicates
     implements MetadataHandler<BuiltInMetadata.Predicates> {
   public static final RelMetadataProvider SOURCE = ReflectiveRelMetadataProvider
-      .reflectiveSource(BuiltInMethod.PREDICATES.method, new RelMdPredicates());
+      .reflectiveSource(new RelMdPredicates(), BuiltInMetadata.Predicates.Handler.class);
 
   private static final List<RexNode> EMPTY_LIST = ImmutableList.of();
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
index 528fa8d..6b10195 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdRowCount.java
@@ -36,7 +36,6 @@
 import org.apache.calcite.rex.RexDynamicParam;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.util.Bug;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.NumberUtil;
 import org.apache.calcite.util.Util;
@@ -51,7 +50,7 @@
     implements MetadataHandler<BuiltInMetadata.RowCount> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.ROW_COUNT.method, new RelMdRowCount());
+          new RelMdRowCount(), BuiltInMetadata.RowCount.Handler.class);
 
   //~ Methods ----------------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSelectivity.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSelectivity.java
index 4060d35..f61b26c 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSelectivity.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSelectivity.java
@@ -32,7 +32,6 @@
 import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -48,7 +47,7 @@
     implements MetadataHandler<BuiltInMetadata.Selectivity> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.SELECTIVITY.method, new RelMdSelectivity());
+          new RelMdSelectivity(), BuiltInMetadata.Selectivity.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
index e22fa53..77b2ed6 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
@@ -38,7 +38,6 @@
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableNullableList;
 import org.apache.calcite.util.NlsString;
 import org.apache.calcite.util.Pair;
@@ -64,8 +63,7 @@
    * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Size}. */
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(new RelMdSize(),
-          BuiltInMethod.AVERAGE_COLUMN_SIZES.method,
-          BuiltInMethod.AVERAGE_ROW_SIZE.method);
+          BuiltInMetadata.Size.Handler.class);
 
   /** Bytes per character (2). */
   public static final int BYTES_PER_CHARACTER = Character.SIZE / Byte.SIZE;
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdTableReferences.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdTableReferences.java
index 0d09815..e9554a6 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdTableReferences.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdTableReferences.java
@@ -31,7 +31,6 @@
 import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.Window;
 import org.apache.calcite.rex.RexTableInputRef.RelTableRef;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.HashMultimap;
@@ -64,7 +63,7 @@
     implements MetadataHandler<BuiltInMetadata.TableReferences> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.TABLE_REFERENCES.method, new RelMdTableReferences());
+          new RelMdTableReferences(), BuiltInMetadata.TableReferences.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUniqueKeys.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUniqueKeys.java
index 3d728dc..f8a845f 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUniqueKeys.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdUniqueKeys.java
@@ -35,7 +35,6 @@
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexProgram;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Util;
 
@@ -60,7 +59,7 @@
     implements MetadataHandler<BuiltInMetadata.UniqueKeys> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
-          BuiltInMethod.UNIQUE_KEYS.method, new RelMdUniqueKeys());
+          new RelMdUniqueKeys(), BuiltInMetadata.UniqueKeys.Handler.class);
 
   //~ Constructors -----------------------------------------------------------
 
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataProvider.java
index 84cff20..57c0eb9 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataProvider.java
@@ -23,6 +23,7 @@
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.lang.reflect.Method;
+import java.util.List;
 
 /**
  * RelMetadataProvider defines an interface for obtaining metadata about
@@ -70,6 +71,24 @@
   <@Nullable M extends @Nullable Metadata> @Nullable UnboundMetadata<M> apply(
       Class<? extends RelNode> relClass, Class<? extends M> metadataClass);
 
+  @Deprecated // to be removed before 2.0
   <M extends Metadata> Multimap<Method, MetadataHandler<M>> handlers(
       MetadataDef<M> def);
+
+  /**
+   * Retrieves a list of {@link MetadataHandler} for implements a particular
+   * {@link MetadataHandler}.class.  The resolution order is specificity of the relNode class,
+   * with preference given to handlers that occur earlier in the list.
+   *
+   * For instance, given a return list of {A, B, C} where A implements RelNode and Scan,
+   * B implements Scan, and C implements LogicalScan and Filter.
+   *
+   * Scan dispatches to a.method(Scan)
+   * LogicalFilter dispatches to c.method(Filter).
+   * LogicalScan dispatches to c.method(LogicalScan).
+   * Aggregate dispatches to a.method(RelNode).
+   *
+   * The behavior is undefined if the class hierarchy for dispatching is not a tree.
+   */
+  List<MetadataHandler<?>> handlers(Class<? extends MetadataHandler<?>> handlerClass);
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
index 45378f2..d05e701 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
@@ -196,7 +196,7 @@
       try {
         return nodeTypesHandler.getNodeTypes(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        nodeTypesHandler = revise(e.relClass, BuiltInMetadata.NodeTypes.DEF);
+        nodeTypesHandler = revise(BuiltInMetadata.NodeTypes.Handler.class);
       } catch (CyclicMetadataException e) {
         return null;
       }
@@ -218,7 +218,7 @@
         Double result = rowCountHandler.getRowCount(rel, this);
         return RelMdUtil.validateResult(castNonNull(result));
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        rowCountHandler = revise(e.relClass, BuiltInMetadata.RowCount.DEF);
+        rowCountHandler = revise(BuiltInMetadata.RowCount.Handler.class);
       }
     }
   }
@@ -236,8 +236,7 @@
       try {
         return maxRowCountHandler.getMaxRowCount(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        maxRowCountHandler =
-            revise(e.relClass, BuiltInMetadata.MaxRowCount.DEF);
+        maxRowCountHandler = revise(BuiltInMetadata.MaxRowCount.Handler.class);
       }
     }
   }
@@ -255,8 +254,7 @@
       try {
         return minRowCountHandler.getMinRowCount(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        minRowCountHandler =
-            revise(e.relClass, BuiltInMetadata.MinRowCount.DEF);
+        minRowCountHandler = revise(BuiltInMetadata.MinRowCount.Handler.class);
       }
     }
   }
@@ -274,8 +272,7 @@
       try {
         return cumulativeCostHandler.getCumulativeCost(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        cumulativeCostHandler =
-            revise(e.relClass, BuiltInMetadata.CumulativeCost.DEF);
+        cumulativeCostHandler = revise(BuiltInMetadata.CumulativeCost.Handler.class);
       }
     }
   }
@@ -293,8 +290,7 @@
       try {
         return nonCumulativeCostHandler.getNonCumulativeCost(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        nonCumulativeCostHandler =
-            revise(e.relClass, BuiltInMetadata.NonCumulativeCost.DEF);
+        nonCumulativeCostHandler = revise(BuiltInMetadata.NonCumulativeCost.Handler.class);
       }
     }
   }
@@ -316,7 +312,7 @@
         return RelMdUtil.validatePercentage(result);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         percentageOriginalRowsHandler =
-            revise(e.relClass, BuiltInMetadata.PercentageOriginalRows.DEF);
+            revise(BuiltInMetadata.PercentageOriginalRows.Handler.class);
       }
     }
   }
@@ -338,7 +334,7 @@
         return columnOriginHandler.getColumnOrigins(rel, this, column);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         columnOriginHandler =
-            revise(e.relClass, BuiltInMetadata.ColumnOrigin.DEF);
+            revise(BuiltInMetadata.ColumnOrigin.Handler.class);
       }
     }
   }
@@ -372,7 +368,7 @@
         return expressionLineageHandler.getExpressionLineage(rel, this, expression);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         expressionLineageHandler =
-            revise(e.relClass, BuiltInMetadata.ExpressionLineage.DEF);
+            revise(BuiltInMetadata.ExpressionLineage.Handler.class);
       }
     }
   }
@@ -386,7 +382,7 @@
         return tableReferencesHandler.getTableReferences(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         tableReferencesHandler =
-            revise(e.relClass, BuiltInMetadata.TableReferences.DEF);
+            revise(BuiltInMetadata.TableReferences.Handler.class);
       }
     }
   }
@@ -431,7 +427,7 @@
         return RelMdUtil.validatePercentage(result);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         selectivityHandler =
-            revise(e.relClass, BuiltInMetadata.Selectivity.DEF);
+            revise(BuiltInMetadata.Selectivity.Handler.class);
       }
     }
   }
@@ -468,7 +464,7 @@
         return uniqueKeysHandler.getUniqueKeys(rel, this, ignoreNulls);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         uniqueKeysHandler =
-            revise(e.relClass, BuiltInMetadata.UniqueKeys.DEF);
+            revise(BuiltInMetadata.UniqueKeys.Handler.class);
       }
     }
   }
@@ -552,7 +548,7 @@
             ignoreNulls);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         columnUniquenessHandler =
-            revise(e.relClass, BuiltInMetadata.ColumnUniqueness.DEF);
+            revise(BuiltInMetadata.ColumnUniqueness.Handler.class);
       }
     }
   }
@@ -571,7 +567,7 @@
       try {
         return collationHandler.collations(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        collationHandler = revise(e.relClass, BuiltInMetadata.Collation.DEF);
+        collationHandler = revise(BuiltInMetadata.Collation.Handler.class);
       }
     }
   }
@@ -596,7 +592,7 @@
         return distribution;
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         distributionHandler =
-            revise(e.relClass, BuiltInMetadata.Distribution.DEF);
+            revise(BuiltInMetadata.Distribution.Handler.class);
       }
     }
   }
@@ -622,7 +618,7 @@
         return RelMdUtil.validateResult(result);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         populationSizeHandler =
-            revise(e.relClass, BuiltInMetadata.PopulationSize.DEF);
+            revise(BuiltInMetadata.PopulationSize.Handler.class);
       }
     }
   }
@@ -640,7 +636,7 @@
       try {
         return sizeHandler.averageRowSize(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        sizeHandler = revise(e.relClass, BuiltInMetadata.Size.DEF);
+        sizeHandler = revise(BuiltInMetadata.Size.Handler.class);
       }
     }
   }
@@ -660,7 +656,7 @@
       try {
         return sizeHandler.averageColumnSizes(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        sizeHandler = revise(e.relClass, BuiltInMetadata.Size.DEF);
+        sizeHandler = revise(BuiltInMetadata.Size.Handler.class);
       }
     }
   }
@@ -690,7 +686,7 @@
         return parallelismHandler.isPhaseTransition(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         parallelismHandler =
-            revise(e.relClass, BuiltInMetadata.Parallelism.DEF);
+            revise(BuiltInMetadata.Parallelism.Handler.class);
       }
     }
   }
@@ -709,7 +705,7 @@
         return parallelismHandler.splitCount(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         parallelismHandler =
-            revise(e.relClass, BuiltInMetadata.Parallelism.DEF);
+            revise(BuiltInMetadata.Parallelism.Handler.class);
       }
     }
   }
@@ -729,7 +725,7 @@
       try {
         return memoryHandler.memory(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        memoryHandler = revise(e.relClass, BuiltInMetadata.Memory.DEF);
+        memoryHandler = revise(BuiltInMetadata.Memory.Handler.class);
       }
     }
   }
@@ -749,7 +745,7 @@
       try {
         return memoryHandler.cumulativeMemoryWithinPhase(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        memoryHandler = revise(e.relClass, BuiltInMetadata.Memory.DEF);
+        memoryHandler = revise(BuiltInMetadata.Memory.Handler.class);
       }
     }
   }
@@ -769,7 +765,7 @@
       try {
         return memoryHandler.cumulativeMemoryWithinPhaseSplit(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        memoryHandler = revise(e.relClass, BuiltInMetadata.Memory.DEF);
+        memoryHandler = revise(BuiltInMetadata.Memory.Handler.class);
       }
     }
   }
@@ -797,7 +793,7 @@
         return RelMdUtil.validateResult(result);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         distinctRowCountHandler =
-            revise(e.relClass, BuiltInMetadata.DistinctRowCount.DEF);
+            revise(BuiltInMetadata.DistinctRowCount.Handler.class);
       }
     }
   }
@@ -816,7 +812,7 @@
         RelOptPredicateList result = predicatesHandler.getPredicates(rel, this);
         return result != null ? result : RelOptPredicateList.EMPTY;
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        predicatesHandler = revise(e.relClass, BuiltInMetadata.Predicates.DEF);
+        predicatesHandler = revise(BuiltInMetadata.Predicates.Handler.class);
       }
     }
   }
@@ -834,7 +830,7 @@
       try {
         return allPredicatesHandler.getAllPredicates(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        allPredicatesHandler = revise(e.relClass, BuiltInMetadata.AllPredicates.DEF);
+        allPredicatesHandler = revise(BuiltInMetadata.AllPredicates.Handler.class);
       }
     }
   }
@@ -858,7 +854,7 @@
         return b == null || b;
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         explainVisibilityHandler =
-            revise(e.relClass, BuiltInMetadata.ExplainVisibility.DEF);
+            revise(BuiltInMetadata.ExplainVisibility.Handler.class);
       }
     }
   }
@@ -878,7 +874,7 @@
       try {
         return distributionHandler.distribution(rel, this);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
-        distributionHandler = revise(e.relClass, BuiltInMetadata.Distribution.DEF);
+        distributionHandler = revise(BuiltInMetadata.Distribution.Handler.class);
       }
     }
   }
@@ -892,7 +888,7 @@
         return lowerBoundCostHandler.getLowerBoundCost(rel, this, planner);
       } catch (JaninoRelMetadataProvider.NoHandler e) {
         lowerBoundCostHandler =
-            revise(e.relClass, BuiltInMetadata.LowerBoundCost.DEF);
+            revise(BuiltInMetadata.LowerBoundCost.Handler.class);
       }
     }
   }
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQueryBase.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQueryBase.java
index 851a96b..229aa42 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQueryBase.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQueryBase.java
@@ -94,10 +94,18 @@
 
   /** Re-generates the handler for a given kind of metadata, adding support for
    * {@code class_} if it is not already present. */
+  @Deprecated // to be removed before 2.0
   protected <M extends Metadata, H extends MetadataHandler<M>> H
       revise(Class<? extends RelNode> class_, MetadataDef<M> def) {
     requireNonNull(metadataProvider, "metadataProvider");
-    return metadataProvider.revise(class_, def);
+    return (H) revise(def.handlerClass);
+  }
+
+  /** Re-generates the handler for a given kind of metadata, adding support for
+   * {@code class_} if it is not already present. */
+  protected <H extends MetadataHandler<?>> H revise(Class<H> def) {
+    requireNonNull(metadataProvider, "metadataProvider");
+    return metadataProvider.revise(def);
   }
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/janino/CodeGeneratorUtil.java b/core/src/main/java/org/apache/calcite/rel/metadata/janino/CodeGeneratorUtil.java
index 0e439e7..4d902d9 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/janino/CodeGeneratorUtil.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/janino/CodeGeneratorUtil.java
@@ -16,30 +16,31 @@
  */
 package org.apache.calcite.rel.metadata.janino;
 
-import org.apache.calcite.linq4j.Ord;
-
 import java.lang.reflect.Method;
 
 /**
  * Common functions for code generation.
  */
-public class CodeGeneratorUtil {
+class CodeGeneratorUtil {
 
   private CodeGeneratorUtil() {
   }
 
-  /** Returns e.g. ",\n boolean ignoreNulls". */
+  /** Returns e.g. ",\n boolean ignoreNulls".  This ignores the first 2 arguments. */
   static StringBuilder paramList(StringBuilder buff, Method method) {
-    for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
-      buff.append(",\n      ").append(t.e.getName()).append(" a").append(t.i);
+    Class<?>[] parameterTypes = method.getParameterTypes();
+    for (int i = 2; i < parameterTypes.length; i++) {
+      Class<?> t = parameterTypes[i];
+      buff.append(",\n      ").append(t.getName()).append(" a").append(i);
     }
     return buff;
   }
 
-  /** Returns e.g. ", ignoreNulls". */
+  /** Returns e.g. ", a2, a3". This ignores the first 2 arguments. */
   static StringBuilder argList(StringBuilder buff, Method method) {
-    for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
-      buff.append(", a").append(t.i);
+    Class<?>[] argTypes = method.getParameterTypes();
+    for (int i = 2; i < argTypes.length; i++) {
+      buff.append(", a").append(i);
     }
     return buff;
   }
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/janino/DescriptiveCacheKey.java b/core/src/main/java/org/apache/calcite/rel/metadata/janino/DescriptiveCacheKey.java
new file mode 100644
index 0000000..3a9f585
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/janino/DescriptiveCacheKey.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+package org.apache.calcite.rel.metadata.janino;
+
+/**
+ * A key used in caching with descriptive to string.  Note the key uses
+ * reference equality for performance.
+ */
+public final class DescriptiveCacheKey {
+
+  private final String description;
+
+  public DescriptiveCacheKey(String description) {
+    this.description = description;
+  }
+
+  @Override public String toString() {
+    return description;
+  }
+}
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/janino/DispatchGenerator.java b/core/src/main/java/org/apache/calcite/rel/metadata/janino/DispatchGenerator.java
index 5e11cad..2c31101 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/janino/DispatchGenerator.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/janino/DispatchGenerator.java
@@ -154,7 +154,7 @@
       Method candidate) {
     if (!superMethod.getName().equals(candidate.getName())) {
       return null;
-    } else if (superMethod.getParameterCount() + 2 != candidate.getParameterCount()) {
+    } else if (superMethod.getParameterCount() != candidate.getParameterCount()) {
       return null;
     } else {
       Class<?>[] cpt = candidate.getParameterTypes();
@@ -164,8 +164,8 @@
       } else if (!RelMetadataQuery.class.equals(cpt[1])) {
         return null;
       }
-      for (int i = 0; i < smpt.length; i++) {
-        if (cpt[i + 2] != smpt[i]) {
+      for (int i = 2; i < smpt.length; i++) {
+        if (cpt[i] != smpt[i]) {
           return null;
         }
       }
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index 709b49b..d83b48c 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -107,7 +107,6 @@
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.tools.RelBuilder;
-import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.Holder;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
@@ -1025,10 +1024,16 @@
           return metadataProvider.apply(relClass, metadataClass);
         }
 
+        @Deprecated // to be removed before 2.0
         @Override public <M extends Metadata> Multimap<Method, MetadataHandler<M>> handlers(
             MetadataDef<M> def) {
           return metadataProvider.handlers(def);
         }
+
+        @Override public List<MetadataHandler<?>> handlers(
+            Class<? extends MetadataHandler<?>> handlerClass) {
+          return metadataProvider.handlers(handlerClass);
+        }
       };
       RelMetadataQuery.THREAD_PROVIDERS.set(JaninoRelMetadataProvider.of(wrappedProvider));
       final RelMetadataQuery mq = rel.getCluster().getMetadataQuery();
@@ -1546,8 +1551,9 @@
       assertThat(colType(mq, rel, 0), equalTo("DEPTNO-rel"));
       fail("expected error");
     } catch (IllegalArgumentException e) {
-      final String value = "No handler for method [public abstract java.lang.String "
-          + "org.apache.calcite.test.RelMetadataTest$ColType.getColType(int)] "
+      final String value = "No handler for method [public abstract "
+          + "java.lang.String org.apache.calcite.test.RelMetadataTest$ColType$Handler.getColType("
+          + "org.apache.calcite.rel.RelNode,org.apache.calcite.rel.metadata.RelMetadataQuery,int)] "
           + "applied to argument of type [class org.apache.calcite.rel.logical.LogicalFilter]; "
           + "we recommend you create a catch-all (RelNode) handler";
       assertThat(e.getMessage(), is(value));
@@ -1581,9 +1587,10 @@
       fail("expected error");
     } catch (IllegalArgumentException e) {
       final String value = "No handler for method [public abstract java.lang.String "
-          + "org.apache.calcite.test.RelMetadataTest$ColType.getColType(int)] "
-          + "applied to argument of type [class org.apache.calcite.rel.logical.LogicalFilter]; "
-          + "we recommend you create a catch-all (RelNode) handler";
+          + "org.apache.calcite.test.RelMetadataTest$ColType$Handler.getColType("
+          + "org.apache.calcite.rel.RelNode,org.apache.calcite.rel.metadata.RelMetadataQuery,int)]"
+          + " applied to argument of type [class org.apache.calcite.rel.logical.LogicalFilter];"
+          + " we recommend you create a catch-all (RelNode) handler";
       assertThat(e.getMessage(), is(value));
     }
   }
@@ -1887,10 +1894,8 @@
 
       // Repeat the above tests directly against the handler.
       final RelMdColumnUniqueness handler =
-          (RelMdColumnUniqueness) RelMdColumnUniqueness.SOURCE
-              .handlers(BuiltInMetadata.ColumnUniqueness.DEF)
-              .get(BuiltInMethod.COLUMN_UNIQUENESS.method)
-              .iterator().next();
+          (RelMdColumnUniqueness) Iterables.getOnlyElement(RelMdColumnUniqueness.SOURCE
+              .handlers(BuiltInMetadata.ColumnUniqueness.Handler.class));
       assertThat(handler.areColumnsUnique(values, mq, col0, false),
           is(true));
       assertThat(handler.areColumnsUnique(values, mq, col1, false),
@@ -3512,7 +3517,7 @@
    * reflection. */
   public static class ColTypeImpl extends PartialColTypeImpl {
     public static final RelMetadataProvider SOURCE =
-        ReflectiveRelMetadataProvider.reflectiveSource(ColType.METHOD, new ColTypeImpl());
+        ReflectiveRelMetadataProvider.reflectiveSource(new ColTypeImpl(), ColType.Handler.class);
 
     /** Implementation of {@link ColType#getColType(int)} for
      * {@link RelNode}, called via reflection. */
@@ -3528,8 +3533,8 @@
   /** Implementation of {@link ColType} that has no fall-back for {@link RelNode}. */
   public static class BrokenColTypeImpl extends PartialColTypeImpl {
     public static final RelMetadataProvider SOURCE =
-        ReflectiveRelMetadataProvider.reflectiveSource(ColType.METHOD,
-            new BrokenColTypeImpl());
+        ReflectiveRelMetadataProvider.reflectiveSource(
+            new BrokenColTypeImpl(), ColType.Handler.class);
   }
 
   /** Extension to {@link RelMetadataQuery} to support {@link ColType}.
@@ -3547,7 +3552,7 @@
         try {
           return colTypeHandler.getColType(rel, this, column);
         } catch (JaninoRelMetadataProvider.NoHandler e) {
-          colTypeHandler = revise(e.relClass, ColType.DEF);
+          colTypeHandler = revise(ColType.Handler.class);
         }
       }
     }