/* * 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.drill.exec.expr; import java.io.IOException; import org.apache.drill.exec.compile.ClassBuilder; import org.apache.drill.exec.compile.TemplateClassDefinition; import org.apache.drill.exec.compile.sig.MappingSet; import org.apache.drill.exec.expr.fn.FunctionImplementationRegistry; import org.apache.drill.exec.server.options.OptionSet; import com.google.common.base.Preconditions; import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JDefinedClass; /** * A code generator is responsible for generating the Java source code required * to complete the implementation of an abstract template. * A code generator can contain one or more ClassGenerators that implement * outer and inner classes associated with a particular runtime generated instance. * <p> * Drill supports two ways to generate and compile the code from a code * generator: via byte-code manipulations or as "plain Java." * <p> * When using byte-code transformations, the code generator is used with a * class transformer to merge precompiled template code with runtime generated and * compiled query specific code to create a runtime instance. * <p> * The code generator can optionally be marked as "plain Java" capable. * This means that the generated code can be compiled directly as a Java * class without the normal byte-code manipulations. Plain Java allows * the option to persist, and debug, the generated code when building new * generated classes or otherwise working with generated code. To turn * on debugging, see the explanation in {@link ClassBuilder}. * * @param <T> * The interface that results from compiling and merging the runtime * code that is generated. */ public class CodeGenerator<T> { private static final String PACKAGE_NAME = "org.apache.drill.exec.test.generated"; private final TemplateClassDefinition<T> definition; private final String className; private final String fqcn; private final JCodeModel model; private final ClassGenerator<T> rootGenerator; /** * True if the code generated for this class is suitable for compilation * as a plain Java class. */ private boolean plainJavaCapable; /** * True if the code generated for this class should actually be compiled * via the plain Java mechanism. Considered only if the class is * capable of this technique. */ private boolean usePlainJava; /** * Whether to write code to disk to aid in debugging. Should only be set * during development, never in production. */ private boolean saveDebugCode; private String generatedCode; private String generifiedCode; CodeGenerator(TemplateClassDefinition<T> definition, FunctionImplementationRegistry funcRegistry, OptionSet optionManager) { this(ClassGenerator.getDefaultMapping(), definition, funcRegistry, optionManager); } CodeGenerator(MappingSet mappingSet, TemplateClassDefinition<T> definition, FunctionImplementationRegistry funcRegistry, OptionSet optionManager) { Preconditions.checkNotNull(definition.getSignature(), "The signature for defintion %s was incorrectly initialized.", definition); this.definition = definition; this.className = definition.getExternalInterface().getSimpleName() + "Gen" + definition.getNextClassNumber(); this.fqcn = PACKAGE_NAME + "." + className; try { this.model = new JCodeModel(); JDefinedClass clazz = model._package(PACKAGE_NAME)._class(className); rootGenerator = new ClassGenerator<>(this, mappingSet, definition.getSignature(), new EvaluationVisitor( funcRegistry), clazz, model, optionManager); } catch (JClassAlreadyExistsException e) { throw new IllegalStateException(e); } } /** * Indicates that the code for this class can be generated using the * "Plain Java" mechanism based on inheritance. The byte-code * method is more lenient, so some code is missing some features such * as proper exception labeling, etc. Set this option to true once * the generation mechanism for a class has been cleaned up to work * via the plain Java mechanism. * * @param flag true if the code generated from this instance is * ready to be compiled as a plain Java class */ public void plainJavaCapable(boolean flag) { plainJavaCapable = flag; } /** * Identifies that this generated class should be generated via the * plain Java mechanism. This flag only has meaning if the * generated class is capable of plain Java generation. * * @param flag true if the class should be generated and compiled * as a plain Java class (rather than via byte-code manipulations) */ public void preferPlainJava(boolean flag) { usePlainJava = flag; } public boolean supportsPlainJava() { return plainJavaCapable; } public boolean isPlainJava() { return plainJavaCapable && usePlainJava; } /** * Debug-time option to persist the code for the generated class to permit debugging. * Has effect only when code is generated using the plain Java option. Code * is written to the code directory specified in {@link ClassBuilder}. * To debug code, set this option, then point your IDE to the code directory * when the IDE prompts you for the source code location. * * @param persist true to write the code to disk, false (the default) to keep * code only in memory. */ public void saveCodeForDebugging(boolean persist) { if (supportsPlainJava()) { saveDebugCode = persist; usePlainJava = true; } } public boolean isCodeToBeSaved() { return saveDebugCode; } public ClassGenerator<T> getRoot() { return rootGenerator; } public void generate() { // If this generated class uses the "plain Java" technique // (no byte code manipulation), then the class must extend the // template so it plays by normal Java rules for finding the // template methods via inheritance rather than via code injection. if (isPlainJava()) { rootGenerator.preparePlainJava( ); } rootGenerator.flushCode(); SingleClassStringWriter w = new SingleClassStringWriter(); try { model.build(w); } catch (IOException e) { // No I/O errors should occur during model building // unless something is terribly wrong. throw new IllegalStateException(e); } generatedCode = w.getCode().toString(); generifiedCode = generatedCode.replaceAll(className, "GenericGenerated"); } public String generateAndGet() throws IOException { generate(); return generatedCode; } public String getGeneratedCode() { return generatedCode; } public TemplateClassDefinition<T> getDefinition() { return definition; } public String getMaterializedClassName() { return fqcn; } public String getClassName() { return className; } public static <T> CodeGenerator<T> get(TemplateClassDefinition<T> definition, FunctionImplementationRegistry funcRegistry) { return get(definition, funcRegistry, null); } public static <T> CodeGenerator<T> get(TemplateClassDefinition<T> definition, FunctionImplementationRegistry funcRegistry, OptionSet optionManager) { return new CodeGenerator<T>(definition, funcRegistry, optionManager); } public static <T> ClassGenerator<T> getRoot(TemplateClassDefinition<T> definition, FunctionImplementationRegistry funcRegistry, OptionSet optionManager) { return get(definition, funcRegistry, optionManager).getRoot(); } public static <T> ClassGenerator<T> getRoot(MappingSet mappingSet, TemplateClassDefinition<T> definition, FunctionImplementationRegistry funcRegistry, OptionSet optionManager) { return get(mappingSet, definition, funcRegistry, optionManager).getRoot(); } public static <T> CodeGenerator<T> get(MappingSet mappingSet, TemplateClassDefinition<T> definition, FunctionImplementationRegistry funcRegistry, OptionSet optionManager) { return new CodeGenerator<T>(mappingSet, definition, funcRegistry, optionManager); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((definition == null) ? 0 : definition.hashCode()); result = prime * result + ((generifiedCode == null) ? 0 : generifiedCode.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj){ return true; } if (obj == null){ return false; } if (getClass() != obj.getClass()){ return false; } CodeGenerator<?> other = (CodeGenerator<?>) obj; if (definition == null) { if (other.definition != null){ return false; } } else if (!definition.equals(other.definition)) { return false; } if (generifiedCode == null) { if (other.generifiedCode != null){ return false; } } else if (!generifiedCode.equals(other.generifiedCode)) { return false; } return true; } }