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.asterix.runtime.evaluators.staticcodegen; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.tuple.Pair; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; /** * A utility class that generates byte code for scalar function descriptors. */ public class CodeGenUtil { public final static String DEFAULT_SUFFIX_FOR_GENERATED_CLASS = "Gen"; private final static String OBJECT_CLASS_NAME = "java/lang/Object"; private final static String DESCRIPTOR_SUPER_CLASS_NAME = "org/apache/asterix/runtime/" + "evaluators/base/AbstractScalarFunctionDynamicDescriptor"; private final static String EVALUATOR_FACTORY = "EvaluatorFactory"; private final static String EVALUATOR = "Evaluator"; private final static String INNER = "Inner"; private final static String DOLLAR = "$"; private final static String NESTED_CLASSNAME_PREFIX = "_"; /** * The callback interface for a caller to determine what it needs to do for * the generated class bytes. */ public static interface ClassByteCodeAction { /** * Run a user-defined action for the generated class definition bytes. * * @param targetClassName, * the name for the generated class. * @param classDefinitionBytes, * the class definition bytes. * @throws IOException */ public void runAction(String targetClassName, byte[] classDefinitionBytes) throws IOException; }; /** * Generates the byte code for a scalar function descriptor. * * @param packagePrefix, * the prefix of evaluators for code generation. * @param originalFuncDescriptorClassName, * the original class name of the function descriptor. * @param suffixForGeneratedClass, * the suffix for the generated class. * @param action, * the customized action for the generated class definition bytes. * @throws IOException * @throws ClassNotFoundException */ public static List<Pair<String, String>> generateScalarFunctionDescriptorBinary(String packagePrefix, String originalFuncDescriptorClassName, String suffixForGeneratedClass, ClassLoader classLoader, ClassByteCodeAction action) throws IOException, ClassNotFoundException { originalFuncDescriptorClassName = toInternalClassName(originalFuncDescriptorClassName); if (originalFuncDescriptorClassName.equals(DESCRIPTOR_SUPER_CLASS_NAME)) { return Collections.emptyList(); } String targetFuncDescriptorClassName = getGeneratedFunctionDescriptorInternalClassName( originalFuncDescriptorClassName, suffixForGeneratedClass); // Adds the mapping of the old/new names of the function descriptor. List<Pair<String, String>> nameMappings = new ArrayList<>(); // Generates code for super classes except java.lang.Object. Class<?> evaluatorClass = CodeGenUtil.class.getClassLoader() .loadClass(toJdkStandardName(originalFuncDescriptorClassName)); nameMappings.addAll(generateScalarFunctionDescriptorBinary(packagePrefix, evaluatorClass.getSuperclass().getName(), suffixForGeneratedClass, classLoader, action)); nameMappings.add(Pair.of(originalFuncDescriptorClassName, targetFuncDescriptorClassName)); nameMappings.add(Pair.of(toJdkStandardName(originalFuncDescriptorClassName), toJdkStandardName(targetFuncDescriptorClassName))); // Gathers evaluator factory classes that are created in the function descriptor. ClassReader reader = new ClassReader(getResourceStream(originalFuncDescriptorClassName, classLoader)); GatherEvaluatorFactoryCreationVisitor evalFactoryCreationVisitor = new GatherEvaluatorFactoryCreationVisitor( toInternalClassName(packagePrefix)); reader.accept(evalFactoryCreationVisitor, 0); Set<String> evaluatorFactoryClassNames = evalFactoryCreationVisitor.getCreatedEvaluatorFactoryClassNames(); // Generates inner classes other than evaluator factories. generateNonEvalInnerClasses(reader, evaluatorFactoryClassNames, nameMappings, suffixForGeneratedClass, classLoader, action); // Generates evaluator factories that are created in the function descriptor. int evalFactoryCounter = 0; for (String evaluateFactoryClassName : evaluatorFactoryClassNames) { generateEvaluatorFactoryClassBinary(packagePrefix, evaluateFactoryClassName, suffixForGeneratedClass, evalFactoryCounter++, nameMappings, classLoader, action); } // Transforms the function descriptor class and outputs the generated class binary. ClassWriter writer = new ClassWriter(reader, 0); RenameClassVisitor renamingVisitor = new RenameClassVisitor(writer, nameMappings); reader.accept(renamingVisitor, 0); action.runAction(targetFuncDescriptorClassName, writer.toByteArray()); return nameMappings; } public static String getGeneratedFunctionDescriptorClassName(String originalFuncDescriptorClassName, String suffixForGeneratedClass) { return toJdkStandardName(getGeneratedFunctionDescriptorInternalClassName(originalFuncDescriptorClassName, suffixForGeneratedClass)); } private static String getGeneratedFunctionDescriptorInternalClassName(String originalFuncDescriptorClassName, String suffixForGeneratedClass) { String originalFuncDescriptorClassInternalName = toInternalClassName(originalFuncDescriptorClassName); String targetFuncDescriptorClassName = getGeneratedClassName(originalFuncDescriptorClassInternalName, suffixForGeneratedClass, 0); return targetFuncDescriptorClassName; } /** * Apply mappings for a class name. * * @param nameMappings, * the mappings from existing class names to that of their generated counterparts. * @param inputStr, * the name of a class. * @return the name of the generated counterpart class. */ static String applyMapping(List<Pair<String, String>> nameMappings, String inputStr) { if (inputStr == null) { return null; } String result = inputStr; // Applies name mappings in the reverse order, i.e., // mapping recent added old/new name pairs first. int index = nameMappings.size() - 1; for (; index >= 0; --index) { Pair<String, String> entry = nameMappings.get(index); if (result.contains(entry.getLeft())) { return result.replace(entry.getLeft(), entry.getRight()); } } return result; } /** * Generates the byte code for an evaluator factory class. * * @param packagePrefix, * the prefix of evaluators for code generation. * @param originalEvaluatorFactoryClassName, * the original evaluator factory class name. * @param suffixForGeneratedClass, * the suffix for the generated class. * @param factoryCounter, * the counter for the generated class. * @param nameMappings, * class names that needs to be rewritten in the generated byte code. * @param classLoader, * a class loader that has the original evaluator factory class in its resource paths. * @param action, * a user definition action for the generated byte code. * @throws IOException * @throws ClassNotFoundException */ private static void generateEvaluatorFactoryClassBinary(String packagePrefix, String originalEvaluatorFactoryClassName, String suffixForGeneratedClass, int factoryCounter, List<Pair<String, String>> nameMappings, ClassLoader classLoader, ClassByteCodeAction action) throws IOException, ClassNotFoundException { originalEvaluatorFactoryClassName = toInternalClassName(originalEvaluatorFactoryClassName); String targetEvaluatorFactoryClassName = getGeneratedClassName(originalEvaluatorFactoryClassName, EVALUATOR_FACTORY + suffixForGeneratedClass, factoryCounter); // Adds the old/new names of the evaluator factory into the mapping. nameMappings.add(Pair.of(originalEvaluatorFactoryClassName, targetEvaluatorFactoryClassName)); nameMappings.add(Pair.of(toJdkStandardName(originalEvaluatorFactoryClassName), toJdkStandardName(targetEvaluatorFactoryClassName))); // Gathers the class names of the evaluators that are created in the evaluator factory. ClassReader reader = new ClassReader(getResourceStream(originalEvaluatorFactoryClassName, classLoader)); GatherEvaluatorCreationVisitor evalCreationVisitor = new GatherEvaluatorCreationVisitor( toInternalClassName(packagePrefix)); reader.accept(evalCreationVisitor, 0); Set<String> evaluatorClassNames = evalCreationVisitor.getCreatedEvaluatorClassNames(); // Generates inner classes other than evaluators. generateNonEvalInnerClasses(reader, evaluatorClassNames, nameMappings, suffixForGeneratedClass, classLoader, action); // Generates code for all evaluators. int evalCounter = 0; for (String evaluateClassName : evaluatorClassNames) { generateEvaluatorClassBinary(evaluateClassName, suffixForGeneratedClass, evalCounter++, nameMappings, classLoader, action); } // Transforms the evaluator factory class and outputs the generated class binary. ClassWriter writer = new ClassWriter(reader, 0); RenameClassVisitor renamingVisitor = new RenameClassVisitor(writer, nameMappings); reader.accept(renamingVisitor, 0); action.runAction(targetEvaluatorFactoryClassName, writer.toByteArray()); } /** * Generates the byte code for an evaluator class. * * @param originalEvaluatorClassName, * the name of the original evaluator class. * @param suffixForGeneratedClass, * the suffix for the generated class. * @param evalCounter, * the counter for the generated class. * @param nameMappings, * class names that needs to be rewritten in the generated byte code. * @param classLoader, * a class loader that has the original evaluator factory class in its resource paths. * @param action, * a user definition action for the generated byte code. * @throws IOException * @throws ClassNotFoundException */ private static void generateEvaluatorClassBinary(String originalEvaluatorClassName, String suffixForGeneratedClass, int evalCounter, List<Pair<String, String>> nameMappings, ClassLoader classLoader, ClassByteCodeAction action) throws IOException, ClassNotFoundException { // Convert class names. originalEvaluatorClassName = toInternalClassName(originalEvaluatorClassName); if (originalEvaluatorClassName.equals(OBJECT_CLASS_NAME)) { return; } String targetEvaluatorClassName = getGeneratedClassName(originalEvaluatorClassName, EVALUATOR + suffixForGeneratedClass, evalCounter); // Generates code for super classes except java.lang.Object. Class<?> evaluatorClass = CodeGenUtil.class.getClassLoader() .loadClass(toJdkStandardName(originalEvaluatorClassName)); generateEvaluatorClassBinary(evaluatorClass.getSuperclass().getName(), suffixForGeneratedClass, evalCounter, nameMappings, classLoader, action); // Adds name mapping. nameMappings.add(Pair.of(originalEvaluatorClassName, targetEvaluatorClassName)); nameMappings.add(Pair.of(toJdkStandardName(originalEvaluatorClassName), toJdkStandardName(targetEvaluatorClassName))); ClassReader firstPassReader = new ClassReader(getResourceStream(originalEvaluatorClassName, classLoader)); // Generates inner classes other than the evaluator. Set<String> excludedNames = new HashSet<>(); for (Pair<String, String> entry : nameMappings) { excludedNames.add(entry.getKey()); } generateNonEvalInnerClasses(firstPassReader, excludedNames, nameMappings, suffixForGeneratedClass, classLoader, action); // Injects missing-handling byte code. ClassWriter firstPassWriter = new ClassWriter(firstPassReader, 0); EvaluatorMissingCheckVisitor missingHandlingVisitor = new EvaluatorMissingCheckVisitor(firstPassWriter); firstPassReader.accept(missingHandlingVisitor, 0); ClassReader secondPassReader = new ClassReader(firstPassWriter.toByteArray()); // Injects null-handling byte code and output the class binary. // Since we're going to add jump instructions, we have to let the ClassWriter to // automatically generate frames for JVM to verify the class. ClassWriter secondPassWriter = new ClassWriter(secondPassReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); RenameClassVisitor renamingVisitor = new RenameClassVisitor(secondPassWriter, nameMappings); EvaluatorNullCheckVisitor nullHandlingVisitor = new EvaluatorNullCheckVisitor(renamingVisitor, missingHandlingVisitor.getLastAddedLabel()); secondPassReader.accept(nullHandlingVisitor, 0); action.runAction(targetEvaluatorClassName, secondPassWriter.toByteArray()); } /** * Generates non-evaluator(-factory) inner classes defined in either a function descriptor * or an evaluator factory. * * @param reader, * the reader of the outer class. * @param evalClassNames, * the names of evaluator/evaluator-factory classes that shouldn't be generated in this * method. * @param nameMappings, * class names that needs to be rewritten in the generated byte code. * @param classLoader, * a class loader that has the original evaluator factory class in its resource paths. * @param action, * a user definition action for the generated byte code. * @throws IOException */ private static void generateNonEvalInnerClasses(ClassReader reader, Set<String> evalClassNames, List<Pair<String, String>> nameMappings, String suffixForGeneratedClass, ClassLoader classLoader, ClassByteCodeAction action) throws IOException { // Gathers inner classes of the function descriptor. GatherInnerClassVisitor innerClassVisitor = new GatherInnerClassVisitor(); reader.accept(innerClassVisitor, 0); Set<String> innerClassNames = innerClassVisitor.getInnerClassNames(); innerClassNames.removeAll(evalClassNames); // Rewrites inner classes. int counter = 0; String suffix = INNER + suffixForGeneratedClass; for (String innerClassName : innerClassNames) { // adds name mapping. String targetInnerClassName = getGeneratedClassName(innerClassName, suffix, counter++); nameMappings.add(Pair.of(innerClassName, targetInnerClassName)); nameMappings.add(Pair.of(toJdkStandardName(innerClassName), toJdkStandardName(targetInnerClassName))); // Renaming appearances of original class names. ClassReader innerClassReader = new ClassReader(getResourceStream(innerClassName, classLoader)); ClassWriter writer = new ClassWriter(innerClassReader, 0); RenameClassVisitor renamingVisitor = new RenameClassVisitor(writer, nameMappings); innerClassReader.accept(renamingVisitor, 0); action.runAction(targetInnerClassName, writer.toByteArray()); } } /** * Converts a JDK class name to the class naming format of ASM. * * @param name, * a class name following the JDK convention. * @return a "/"-separated class name assumed by ASM. */ private static String toInternalClassName(String name) { return name.replace(".", "/"); } /** * Converts an ASM class name to the JDK class naming format. * * @param name, * a class name following the ASM convention. * @return a "."-separated class name for JDK. */ private static String toJdkStandardName(String name) { return name.replace("/", "."); } /** * Gets the name of a generated class. * * @param originalClassName, * the original class, i.e., the source of the generated class. * @param suffix, * the suffix for the generated class. * @param counter, * a counter that appearing at the end of the name of the generated class. * @return the name of the generated class. */ private static String getGeneratedClassName(String originalClassName, String suffix, int counter) { StringBuilder sb = new StringBuilder(); int end = originalClassName.indexOf(DOLLAR); if (end < 0) { end = originalClassName.length(); } String name = originalClassName.substring(0, end); sb.append(name); sb.append(DOLLAR); sb.append(NESTED_CLASSNAME_PREFIX); sb.append(suffix); if (counter > 0) { sb.append(counter); } return sb.toString(); } /** * Gets the input stream from a class file. * * @param className, * the name of a class. * @param classLoader, * the corresponding class loader. * @return the input stream. */ private static InputStream getResourceStream(String className, ClassLoader classLoader) { return classLoader.getResourceAsStream(className.replace('.', '/') + ".class"); } }