blob: 87a9a84fb84be97ddd93d48bf27f47dd174d9203 [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.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");
}
}