Java tutorial
/* * 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; import org.apache.calcite.adapter.enumerable.EnumerableAggregate; import org.apache.calcite.adapter.enumerable.EnumerableFilter; import org.apache.calcite.adapter.enumerable.EnumerableJoin; import org.apache.calcite.adapter.enumerable.EnumerableProject; import org.apache.calcite.adapter.enumerable.EnumerableTableScan; import org.apache.calcite.interpreter.JaninoRexCompiler; import org.apache.calcite.linq4j.Ord; import org.apache.calcite.linq4j.tree.ClassDeclaration; import org.apache.calcite.linq4j.tree.MemberDeclaration; import org.apache.calcite.linq4j.tree.Primitive; import org.apache.calcite.plan.hep.HepRelVertex; import org.apache.calcite.plan.volcano.AbstractConverter; import org.apache.calcite.plan.volcano.RelSubset; import org.apache.calcite.prepare.CalcitePrepareImpl; import org.apache.calcite.rel.AbstractRelNode; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.convert.ConverterImpl; import org.apache.calcite.rel.logical.LogicalAggregate; import org.apache.calcite.rel.logical.LogicalCalc; import org.apache.calcite.rel.logical.LogicalCorrelate; import org.apache.calcite.rel.logical.LogicalExchange; import org.apache.calcite.rel.logical.LogicalFilter; import org.apache.calcite.rel.logical.LogicalIntersect; import org.apache.calcite.rel.logical.LogicalJoin; import org.apache.calcite.rel.logical.LogicalMinus; import org.apache.calcite.rel.logical.LogicalProject; import org.apache.calcite.rel.logical.LogicalSort; import org.apache.calcite.rel.logical.LogicalTableFunctionScan; import org.apache.calcite.rel.logical.LogicalTableModify; import org.apache.calcite.rel.logical.LogicalTableScan; import org.apache.calcite.rel.logical.LogicalUnion; import org.apache.calcite.rel.logical.LogicalValues; import org.apache.calcite.rel.logical.LogicalWindow; import org.apache.calcite.rel.stream.LogicalChi; import org.apache.calcite.rel.stream.LogicalDelta; import org.apache.calcite.rex.RexNode; import org.apache.calcite.util.ControlFlowException; import org.apache.calcite.util.Pair; import org.apache.calcite.util.Util; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.UncheckedExecutionException; import org.codehaus.commons.compiler.CompileException; import org.codehaus.commons.compiler.CompilerFactoryFactory; import org.codehaus.commons.compiler.IClassBodyEvaluator; import org.codehaus.commons.compiler.ICompilerFactory; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutionException; import javax.annotation.Nonnull; /** * Implementation of the {@link RelMetadataProvider} interface that generates * a class that dispatches to the underlying providers. */ public class JaninoRelMetadataProvider implements RelMetadataProvider { private final RelMetadataProvider provider; // Constants and static fields public static final JaninoRelMetadataProvider DEFAULT = JaninoRelMetadataProvider .of(DefaultRelMetadataProvider.INSTANCE); private static final Set<Class<? extends RelNode>> ALL_RELS = new CopyOnWriteArraySet<>(); /** Cache of pre-generated handlers by provider and kind of metadata. * For the cache to be effective, providers should implement identity * correctly. */ private static final LoadingCache<Key, MetadataHandler> HANDLERS = CacheBuilder.newBuilder() .build(new CacheLoader<Key, MetadataHandler>() { public MetadataHandler load(@Nonnull Key key) { //noinspection unchecked return load3(key.def, key.provider.handlers(key.def), key.relClasses); } }); // Pre-register the most common relational operators, to reduce the number of // times we re-generate. static { DEFAULT.register(Arrays.asList(RelNode.class, AbstractRelNode.class, RelSubset.class, HepRelVertex.class, ConverterImpl.class, AbstractConverter.class, LogicalAggregate.class, LogicalCalc.class, LogicalCorrelate.class, LogicalExchange.class, LogicalFilter.class, LogicalIntersect.class, LogicalJoin.class, LogicalMinus.class, LogicalProject.class, LogicalSort.class, LogicalTableFunctionScan.class, LogicalTableModify.class, LogicalTableScan.class, LogicalUnion.class, LogicalValues.class, LogicalWindow.class, LogicalChi.class, LogicalDelta.class, EnumerableAggregate.class, EnumerableFilter.class, EnumerableProject.class, EnumerableJoin.class, EnumerableTableScan.class)); } /** Private constructor; use {@link #of}. */ private JaninoRelMetadataProvider(RelMetadataProvider provider) { this.provider = provider; } /** Creates a JaninoRelMetadataProvider. * * @param provider Underlying provider */ public static JaninoRelMetadataProvider of(RelMetadataProvider provider) { if (provider instanceof JaninoRelMetadataProvider) { return (JaninoRelMetadataProvider) provider; } return new JaninoRelMetadataProvider(provider); } @Override public boolean equals(Object obj) { return obj == this || obj instanceof JaninoRelMetadataProvider && ((JaninoRelMetadataProvider) obj).provider.equals(provider); } @Override public int hashCode() { return 109 + provider.hashCode(); } public <M extends Metadata> UnboundMetadata<M> apply(Class<? extends RelNode> relClass, Class<? extends M> metadataClass) { throw new UnsupportedOperationException(); } 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, MetadataHandler<M>> map, ImmutableList<Class<? extends RelNode>> relClasses) { final StringBuilder buff = new StringBuilder(); final String name = "GeneratedMetadataHandler_" + def.metadataClass.getSimpleName(); final Set<MetadataHandler> providerSet = new HashSet<>(); final List<Pair<String, MetadataHandler>> providerList = new ArrayList<>(); //noinspection unchecked final ReflectiveRelMetadataProvider.Space space = new ReflectiveRelMetadataProvider.Space((Multimap) map); for (MetadataHandler provider : space.providerMap.values()) { if (providerSet.add(provider)) { providerList.add(Pair.of("provider" + (providerSet.size() - 1), provider)); } } buff.append(" private final java.util.List relClasses;\n"); for (Pair<String, MetadataHandler> pair : providerList) { buff.append(" public final ").append(pair.right.getClass().getName()).append(' ').append(pair.left) .append(";\n"); } buff.append(" public ").append(name).append("(java.util.List relClasses"); for (Pair<String, MetadataHandler> pair : providerList) { buff.append(",\n").append(" ").append(pair.right.getClass().getName()).append(' ') .append(pair.left); } buff.append(") {\n").append(" this.relClasses = relClasses;\n"); for (Pair<String, MetadataHandler> pair : providerList) { buff.append(" this.").append(pair.left).append(" = ").append(pair.left).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"); for (Ord<Method> method : Ord.zip(def.methods)) { buff.append(" public ").append(method.e.getReturnType().getName()).append(" ") .append(method.e.getName()).append("(\n").append(" ").append(RelNode.class.getName()) .append(" r,\n").append(" ").append(RelMetadataQuery.class.getName()).append(" mq"); paramList(buff, method.e).append(") {\n"); buff.append(" final java.util.List key = ") .append((method.e.getParameterTypes().length < 4 ? org.apache.calcite.runtime.FlatLists.class : ImmutableList.class).getName()) .append(".of(").append(def.metadataClass.getName()); if (method.i == 0) { buff.append(".DEF"); } else { buff.append(".DEF.methods.get(").append(method.i).append(")"); } buff.append(", r"); safeArgList(buff, method.e).append(");\n").append(" final Object v = mq.map.get(key);\n") .append(" if (v != null) {\n").append(" if (v == ").append(NullSentinel.class.getName()) .append(".ACTIVE) {\n").append(" throw ").append(CyclicMetadataException.class.getName()) .append(".INSTANCE;\n").append(" }\n").append(" return (") .append(method.e.getReturnType().getName()).append(") v;\n").append(" }\n") .append(" mq.map.put(key,").append(NullSentinel.class.getName()).append(".ACTIVE);\n") .append(" try {\n").append(" final ").append(method.e.getReturnType().getName()) .append(" x = ").append(method.e.getName()).append("_(r, mq"); argList(buff, method.e).append(");\n").append(" mq.map.put(key, x);\n").append(" return x;\n") .append(" } catch (").append(NoHandler.class.getName()).append(" e) {\n") .append(" mq.map.remove(key);\n").append(" throw e;\n").append(" }\n") .append(" }\n").append("\n").append(" private ").append(method.e.getReturnType().getName()) .append(" ").append(method.e.getName()).append("_(\n").append(" ") .append(RelNode.class.getName()).append(" r,\n").append(" ") .append(RelMetadataQuery.class.getName()).append(" mq"); paramList(buff, method.e).append(") {\n"); buff.append(" switch (relClasses.indexOf(r.getClass())) {\n"); // Build a list of clauses, grouping clauses that have the same action. final Multimap<String, Integer> clauses = LinkedHashMultimap.create(); final StringBuilder buf2 = new StringBuilder(); for (Ord<Class<? extends RelNode>> relClass : Ord.zip(relClasses)) { if (relClass.e == HepRelVertex.class) { buf2.append(" return ").append(method.e.getName()).append("(((") .append(relClass.e.getName()).append(") r).getCurrentRel(), mq"); argList(buf2, method.e).append(");\n"); } else { final Method handler = space.find(relClass.e, method.e); final String v = findProvider(providerList, handler.getDeclaringClass()); buf2.append(" return ").append(v).append(".").append(method.e.getName()).append("((") .append(handler.getParameterTypes()[0].getName()).append(") r, mq"); argList(buf2, method.e).append(");\n"); } clauses.put(buf2.toString(), relClass.i); buf2.setLength(0); } buf2.append(" throw new ").append(NoHandler.class.getName()).append("(r.getClass());\n") .append(" }\n").append(" }\n"); clauses.put(buf2.toString(), -1); for (Map.Entry<String, Collection<Integer>> pair : clauses.asMap().entrySet()) { if (pair.getValue().contains(relClasses.indexOf(RelNode.class))) { buff.append(" default:\n"); } else { for (Integer integer : pair.getValue()) { buff.append(" case ").append(integer).append(":\n"); } } buff.append(pair.getKey()); } } ClassDeclaration decl = new ClassDeclaration(0, name, Object.class, ImmutableList.<Type>of(), ImmutableList.<MemberDeclaration>of()); final List<Object> argList = new ArrayList<Object>(Pair.right(providerList)); argList.add(0, ImmutableList.copyOf(relClasses)); try { return compile(decl, buff.toString(), def, argList); } catch (CompileException | IOException e) { throw new RuntimeException("Error compiling:\n" + buff, e); } } private static String findProvider(List<Pair<String, MetadataHandler>> providerList, Class<?> declaringClass) { for (Pair<String, MetadataHandler> pair : providerList) { if (declaringClass.isInstance(pair.right)) { return pair.left; } } throw new AssertionError("not found: " + declaringClass); } /** 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); } 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)) { buff.append(", a").append(t.i); } else if (RexNode.class.isAssignableFrom(t.e)) { // For RexNode, convert to string, because equals does not look deep. // a1 == null ? "" : a1.toString() buff.append(", a").append(t.i).append(" == null ? \"\" : a").append(t.i).append(".toString()"); } else { buff.append(", ").append(NullSentinel.class.getName()).append(".mask(a").append(t.i).append(")"); } } return buff; } /** 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); } return buff; } static <M extends Metadata> MetadataHandler<M> compile(ClassDeclaration expr, String s, MetadataDef<M> def, List<Object> argList) throws CompileException, IOException { final ICompilerFactory compilerFactory; try { compilerFactory = CompilerFactoryFactory.getDefaultCompilerFactory(); } catch (Exception e) { throw new IllegalStateException("Unable to instantiate java compiler", e); } final IClassBodyEvaluator cbe = compilerFactory.newClassBodyEvaluator(); cbe.setClassName(expr.name); cbe.setImplementedInterfaces(new Class[] { def.handlerClass }); cbe.setParentClassLoader(JaninoRexCompiler.class.getClassLoader()); if (CalcitePrepareImpl.DEBUG) { // Add line numbers to the generated janino class cbe.setDebuggingInformation(true, true, true); System.out.println(s); } cbe.cook(new StringReader(s)); final Constructor constructor = cbe.getClazz().getDeclaredConstructors()[0]; final Object o; try { o = constructor.newInstance(argList.toArray()); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } return def.handlerClass.cast(o); } synchronized <M extends Metadata, H extends MetadataHandler<M>> H create(MetadataDef<M> def) { try { final Key key = new Key((MetadataDef) def, provider, ImmutableList.copyOf(ALL_RELS)); //noinspection unchecked return (H) HANDLERS.get(key); } catch (UncheckedExecutionException | ExecutionException e) { Util.throwIfUnchecked(e.getCause()); throw new RuntimeException(e.getCause()); } } synchronized <M extends Metadata, H extends MetadataHandler<M>> H revise(Class<? extends RelNode> rClass, MetadataDef<M> def) { if (ALL_RELS.add(rClass)) { HANDLERS.invalidateAll(); } //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. */ public void register(Iterable<Class<? extends RelNode>> classes) { // Register the classes and their base classes up to RelNode. Don't bother // to remove duplicates; addAll will do that. final List<Class<? extends RelNode>> list = Lists.newArrayList(classes); for (int i = 0; i < list.size(); i++) { final Class<? extends RelNode> c = list.get(i); final Class s = c.getSuperclass(); if (s != null && RelNode.class.isAssignableFrom(s)) { //noinspection unchecked list.add(s); } } synchronized (this) { if (ALL_RELS.addAll(list)) { HANDLERS.invalidateAll(); } } } /** Exception that indicates there there should be a handler for * this class but there is not. The action is probably to * re-generate the handler class. */ public static class NoHandler extends ControlFlowException { public final Class<? extends RelNode> relClass; public NoHandler(Class<? extends RelNode> relClass) { this.relClass = relClass; } } /** Key for the cache. */ private static class Key { public final MetadataDef def; public final RelMetadataProvider provider; public final ImmutableList<Class<? extends RelNode>> relClasses; private Key(MetadataDef def, RelMetadataProvider provider, ImmutableList<Class<? extends RelNode>> relClassList) { this.def = def; this.provider = provider; this.relClasses = relClassList; } @Override public int hashCode() { return (def.hashCode() * 37 + provider.hashCode()) * 37 + relClasses.hashCode(); } @Override public boolean equals(Object obj) { return this == obj || obj instanceof Key && ((Key) obj).def.equals(def) && ((Key) obj).provider.equals(provider) && ((Key) obj).relClasses.equals(relClasses); } } } // End JaninoRelMetadataProvider.java