All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.drill.exec.compile.ClassBuilder Maven / Gradle / Ivy

/*
 * 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.compile;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;

import org.apache.drill.common.config.DrillConfig;
import org.apache.drill.exec.compile.ClassTransformer.ClassNames;
import org.apache.drill.exec.exception.ClassTransformationException;
import org.apache.drill.exec.expr.CodeGenerator;
import org.apache.drill.exec.server.options.OptionSet;
import org.codehaus.commons.compiler.CompileException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implements the "plain Java" method of code generation and
 * compilation. Given a {@link CodeGenerator}, obtains the generated
 * source code, compiles it with the selected compiler, loads the
 * byte-codes into a class loader and provides the resulting
 * class. Compared with the {@link ClassTransformer} mechanism,
 * this one requires the code generator to have generated a complete
 * Java class that is capable of direct compilation and loading.
 * This means the generated class must be a subclass of the template
 * so that the JVM can use normal Java inheritance to associate the
 * template and generated methods.
 * 

* Here is how to use the plain Java technique to debug * generated code: *

    *
  • Set the config option drill.exec.compile.code_dir * to the location where you want to save the generated source * code.
  • *
  • Where you generate code (using a {@link CodeGenerator}), * set the "plain Java" options:
     * CodeGenerator<Foo> cg = ...
     * cg.plainJavaCapable(true); // Class supports plain Java
     * cg.preferPlainJava(true); // Actually generate plain Java
     * cg.saveCodeForDebugging(true); // Save code for debugging
     * ...
    * Note that saveCodeForDebugging automatically sets the PJ * option if the generator is capable. Call preferPlainJava * only if you want to try PJ for this particular generated class * without saving the generated code.
  • *
  • In your favorite IDE, add to the code lookup path the * code directory saved earlier. In Eclipse, for example, you do * this in the debug configuration you will use to debug Drill.
  • *
  • Set a breakpoint in template used for the generated code.
  • *
  • Run Drill. The IDE will stop at your breakpoint.
  • *
  • Step into the generated code. Examine class field and * local variables. Have fun!
  • *
*

* Most generated classes have been upgraded to support Plain Java * compilation. Once this work is complete, the calls to * plainJavaCapable can be removed as all generated classes * will be capable. *

* The setting to prefer plain Java is ignored for any remaining generated * classes not marked as plain Java capable. */ public class ClassBuilder { private static final Logger logger = LoggerFactory.getLogger(ClassBuilder.class); public static final String CODE_DIR_OPTION = CodeCompiler.COMPILE_BASE + ".code_dir"; private final DrillConfig config; private final OptionSet options; private final File codeDir; public ClassBuilder(DrillConfig config, OptionSet optionManager) { this.config = config; options = optionManager; // Code can be saved per-class to enable debugging. // Just request the code generator to persist code, // point your debugger to the directory set below, and you // can step into the code for debugging. Code is not saved // be default because doing so is expensive and unnecessary. codeDir = new File(config.getString(CODE_DIR_OPTION)); } /** * Given a code generator which has already generated plain Java * code, compile the code, create a class loader, and return the * resulting Java class. * * @param cg a plain Java capable code generator that has generated * plain Java code * @return the class that the code generator defines * @throws ClassTransformationException */ public Class getImplementationClass(CodeGenerator cg) throws ClassTransformationException { try { return compileClass(cg); } catch (CompileException | ClassNotFoundException|IOException e) { throw new ClassTransformationException(e); } } /** * Performs the actual work of compiling the code and loading the class. * * @param cg the code generator that has built the class(es) to be generated. * @return the class, after code generation and (if needed) compilation. * @throws IOException if an error occurs when optionally writing code to disk. * @throws CompileException if the generated code has compile issues. * @throws ClassNotFoundException if the generated code references unknown classes. * @throws ClassTransformationException generic "something is wrong" error from * Drill class compilation code. */ private Class compileClass(CodeGenerator cg) throws IOException, CompileException, ClassNotFoundException, ClassTransformationException { final long t1 = System.nanoTime(); // Get the plain Java code. String code = cg.getGeneratedCode(); // Get the class names (dotted, file path, etc.) String className = cg.getMaterializedClassName(); ClassTransformer.ClassNames name = new ClassTransformer.ClassNames(className); // A key advantage of this method is that the code can be // saved and debugged, if needed. if (cg.isCodeToBeSaved()) { saveCode(code, name); } Class compiledClass = getCompiledClass(code, className, config, options); logger.debug("Compiled {}: time = {} ms.", className, (System.nanoTime() - t1 + 500_000) / 1_000_000); return compiledClass; } public static Class getCompiledClass(String code, String className, DrillConfig config, OptionSet options) throws CompileException, ClassNotFoundException, ClassTransformationException, IOException { // Compile the code and load it into a class loader. CachedClassLoader classLoader = new CachedClassLoader(); ClassCompilerSelector compilerSelector = new ClassCompilerSelector(classLoader, config, options); ClassNames name = new ClassNames(className); Map results = compilerSelector.compile(name, code); classLoader.addClasses(results); // Get the class from the class loader. try { return classLoader.findClass(className); } catch (ClassNotFoundException e) { // This should never occur. throw new IllegalStateException("Code load failed", e); } } /** * Save code to a predefined location for debugging. To use the code * for debugging, make sure the save location is on your IDE's source * code search path. Code is saved in usual Java format with each * package as a directory. The provided code directory becomes a * source directory, as in Maven's "src/main/java". * * @param code the source code * @param name the class name */ private void saveCode(String code, ClassNames name) { String pathName = name.slash + ".java"; File codeFile = new File(codeDir, pathName); codeFile.getParentFile().mkdirs(); try (final FileWriter writer = new FileWriter(codeFile)) { writer.write(code); } catch (IOException e) { System.err.println("Could not save: " + codeFile.getAbsolutePath()); } } }