blob: 76d68e22df60f51f654a8215298c0240433b379d [file] [log] [blame]
/*
* 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.TemplateClassDefinition;
import org.apache.drill.exec.compile.sig.MappingSet;
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 org.apache.drill.exec.compile.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, OptionSet optionManager) {
this(ClassGenerator.getDefaultMapping(), definition, optionManager);
}
CodeGenerator(MappingSet mappingSet, TemplateClassDefinition<T> definition, 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(),
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 org.apache.drill.exec.compile.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) {
return get(definition, null);
}
public static <T> CodeGenerator<T> get(TemplateClassDefinition<T> definition, OptionSet optionManager) {
return new CodeGenerator<T>(definition, optionManager);
}
public static <T> ClassGenerator<T> getRoot(TemplateClassDefinition<T> definition, OptionSet optionManager) {
return get(definition, optionManager).getRoot();
}
public static <T> ClassGenerator<T> getRoot(MappingSet mappingSet, TemplateClassDefinition<T> definition, OptionSet optionManager) {
return get(mappingSet, definition, optionManager).getRoot();
}
public static <T> CodeGenerator<T> get(MappingSet mappingSet, TemplateClassDefinition<T> definition, OptionSet optionManager) {
return new CodeGenerator<T>(mappingSet, definition, 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;
}
}