net.wpm.codegen.AsmBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of records Show documentation
Show all versions of records Show documentation
C-Struct like features for Java 6+ to improve performance.
The newest version!
/*
* Copyright (C) 2015 SoftIndex LLC.
*
* Licensed 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 net.wpm.codegen;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.wpm.codegen.AsmBuilder;
import net.wpm.codegen.ClassScope;
import net.wpm.codegen.Context;
import net.wpm.codegen.Expression;
import net.wpm.codegen.utils.DefiningClassLoader;
import net.wpm.codegen.utils.DefiningClassWriter;
import net.wpm.codegen.utils.Preconditions;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Arrays.asList;
import static net.wpm.codegen.Utils.loadAndCast;
import static org.objectweb.asm.Opcodes.*;
import static org.objectweb.asm.Type.getInternalName;
import static org.objectweb.asm.Type.getType;
import static org.objectweb.asm.commons.Method.getMethod;
/**
* Intends for dynamic description of the behaviour of the object in runtime
*
* @param type of item
*/
@SuppressWarnings("unchecked")
public class AsmBuilder {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public static final String DEFAULT_CLASS_NAME = AsmBuilder.class.getPackage().getName() + ".Class";
private static final AtomicInteger COUNTER = new AtomicInteger();
private final DefiningClassLoader classLoader;
private Path bytecodeSaveDir;
private final ClassScope scope;
private final Map> fields = new LinkedHashMap<>();
private final Map> staticFields = new LinkedHashMap<>();
private final Map expressionMap = new LinkedHashMap<>();
private final Map expressionStaticMap = new LinkedHashMap<>();
public AsmBuilder setBytecodeSaveDir(Path bytecodeSaveDir) {
this.bytecodeSaveDir = bytecodeSaveDir;
return this;
}
public static class AsmClassKey {
private final Set> parentClasses;
private final Map> fields;
private final Map> staticFields;
private final Map expressionMap;
private final Map expressionStaticMap;
public AsmClassKey(Set> parentClasses, Map> fields, Map> staticFields,
Map expressionMap, Map expressionStaticMap) {
this.parentClasses = parentClasses;
this.fields = fields;
this.staticFields = staticFields;
this.expressionMap = expressionMap;
this.expressionStaticMap = expressionStaticMap;
}
public Set> getParentClasses() {
return parentClasses;
}
@Override
public String toString() {
return "AsmClassKey{" +
"parentClasses=" + parentClasses +
", fields=" + fields +
", staticFields=" + staticFields +
", expressionMap=" + expressionMap +
", expressionStaticMap=" + expressionStaticMap +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AsmClassKey> that = (AsmClassKey>) o;
return Objects.equals(parentClasses, that.parentClasses) &&
Objects.equals(fields, that.fields) &&
Objects.equals(staticFields, that.staticFields) &&
Objects.equals(expressionMap, that.expressionMap) &&
Objects.equals(expressionStaticMap, that.expressionStaticMap);
}
@Override
public int hashCode() {
return Objects.hash(parentClasses, fields, expressionMap, expressionStaticMap);
}
}
/**
* Creates a new instance of AsmFunctionFactory
*
* @param classLoader class loader
* @param type type of dynamic class
*/
public AsmBuilder(DefiningClassLoader classLoader, Class type) {
this(classLoader, type, Collections.EMPTY_LIST);
}
public AsmBuilder(DefiningClassLoader classLoader, Class mainType, List> types) {
this.classLoader = classLoader;
this.scope = new ClassScope<>(mainType, types);
}
/**
* Creates a new field for a dynamic class
*
* @param field name of field
* @param fieldClass type of field
* @return changed AsmFunctionFactory
*/
public AsmBuilder field(String field, Class> fieldClass) {
fields.put(field, fieldClass);
scope.addField(field, fieldClass);
return this;
}
/**
* Creates a new static field for a dynamic class
*
* @param field
* @param fieldClass
* @return changed AsmBuilder
*/
public AsmBuilder staticField(String field, Class> fieldClass) {
staticFields.put(field, fieldClass);
scope.addStaticField(field, fieldClass);
return this;
}
/**
* Creates a new method for a dynamic class
*
* @param method new method for class
* @param expression function which will be processed
* @return changed AsmFunctionFactory
*/
public AsmBuilder method(Method method, Expression expression) {
expressionMap.put(method, expression);
scope.addMethod(method);
return this;
}
public AsmBuilder staticMethod(Method method, Expression expression) {
expressionStaticMap.put(method, expression);
scope.addStaticMethod(method);
return this;
}
/**
* Creates a new method for a dynamic class
*
* @param methodName name of method
* @param returnClass type which returns this method
* @param argumentTypes list of types of arguments
* @param expression function which will be processed
* @return changed AsmFunctionFactory
*/
public AsmBuilder method(String methodName, Class> returnClass, List extends Class>> argumentTypes, Expression expression) {
Type[] types = new Type[argumentTypes.size()];
for (int i = 0; i < argumentTypes.size(); i++) {
types[i] = getType(argumentTypes.get(i));
}
return method(new Method(methodName, getType(returnClass), types), expression);
}
/**
* Create a new static method for a dynamic class
*
* @param methodName name of method
* @param returnClass type which returns this method
* @param argumentTypes list of types of arguments
* @param expression function which will be processed
* @return changed AsmFunctionFactory
*/
public AsmBuilder staticMethod(String methodName, Class> returnClass, List extends Class>> argumentTypes, Expression expression) {
Type[] types = new Type[argumentTypes.size()];
for (int i = 0; i < argumentTypes.size(); i++) {
types[i] = getType(argumentTypes.get(i));
}
return staticMethod(new Method(methodName, getType(returnClass), types), expression);
}
/**
* Create a new static initialization block for a dynamic class. Overwrites the existing one.
*
* @param expression function which will be processed
* @return changed AsmFunctionFactory
*/
public AsmBuilder staticInitializationBlock(Expression expression) {
return staticMethod("", void.class, Collections.EMPTY_LIST, expression);
}
/**
* Creates a new method for a dynamic class. The method must be part of the provided interfaces or abstract class.
*
* @param methodName name of method
* @param expression function which will be processed
* @return changed AsmFunctionFactory
*/
public AsmBuilder method(String methodName, Expression expression) {
if (methodName.contains("(")) {
Method method = Method.getMethod(methodName);
return method(method, expression);
}
Method foundMethod = null;
List> listOfMethods = new ArrayList<>();
listOfMethods.add(asList(Object.class.getMethods()));
for (Class> type : scope.getParentClasses()) {
listOfMethods.add(asList(type.getMethods()));
listOfMethods.add(asList(type.getDeclaredMethods()));
}
for (List list : listOfMethods) {
for (java.lang.reflect.Method m : list) {
if (m.getName().equals(methodName)) {
Method method = getMethod(m);
if (foundMethod != null && !method.equals(foundMethod))
throw new IllegalArgumentException("Method " + method + " collides with " + foundMethod);
foundMethod = method;
}
}
}
Preconditions.check(foundMethod != null, "Could not find method '" + methodName + "'");
return method(foundMethod, expression);
}
/**
* Returns a new class which is created in a dynamic way
*
* @return completed class
*/
public Class defineClass() {
return defineClass(null);
}
public Class defineClass(String className) {
synchronized (classLoader) {
AsmClassKey key = new AsmClassKey<>(scope.getParentClasses(), fields, staticFields, expressionMap, expressionStaticMap);
Class> cachedClass = classLoader.getClassByKey(key);
if (cachedClass != null) {
logger.trace("Fetching {} for key {} from cache", cachedClass, key);
return (Class) cachedClass;
}
return defineNewClass(key, className);
}
}
/**
* Returns a new class which is created in a dynamic way
*
* @param key key
* @return completed class
*/
private Class defineNewClass(AsmClassKey key, String newClassName) {
DefiningClassWriter cw = new DefiningClassWriter(classLoader);
String className;
if (newClassName == null) {
className = DEFAULT_CLASS_NAME + COUNTER.incrementAndGet();
} else {
className = newClassName;
}
Type classType = getType('L' + className.replace('.', '/') + ';');
// contains all classes (abstract and interfaces)
final Set> parentClasses = scope.getParentClasses();
final String[] internalNames = new String[parentClasses.size()];
int pos = 0;
for (Class> clazz : parentClasses)
internalNames[pos++] = getInternalName(clazz);
if (scope.getMainType().isInterface()) {
cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER,
classType.getInternalName(),
null,
"java/lang/Object",
internalNames);
} else {
cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER,
classType.getInternalName(),
null,
internalNames[0],
Arrays.copyOfRange(internalNames, 1, internalNames.length));
}
{
Method m = getMethod("void ()");
GeneratorAdapter g = new GeneratorAdapter(ACC_PUBLIC, m, null, null, cw);
g.loadThis();
if (scope.getMainType().isInterface()) {
g.invokeConstructor(getType(Object.class), m);
} else {
g.invokeConstructor(getType(scope.getMainType()), m);
}
g.returnValue();
g.endMethod();
}
for (String field : fields.keySet()) {
Class> fieldClass = fields.get(field);
cw.visitField(ACC_PUBLIC, field, getType(fieldClass).getDescriptor(), null, null);
}
for (String field : staticFields.keySet()) {
Class> fieldClass = staticFields.get(field);
cw.visitField(ACC_PUBLIC + ACC_STATIC, field, getType(fieldClass).getDescriptor(), null, null);
}
for (Method m : expressionStaticMap.keySet()) {
try {
GeneratorAdapter g = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, m, null, null, cw);
Context ctx = new Context(classLoader, g, classType, scope.getParentClasses(), Collections.EMPTY_MAP, scope.getStaticFields(), m.getArgumentTypes(), m, scope.getMethods(), scope.getStaticMethods());
Expression expression = expressionStaticMap.get(m);
loadAndCast(ctx, expression, m.getReturnType());
g.returnValue();
g.endMethod();
} catch (Exception e) {
throw new RuntimeException("Unable to implement "+m.getName()+m.getDescriptor(),e);
}
}
for (Method m : expressionMap.keySet()) {
try {
GeneratorAdapter g = new GeneratorAdapter(ACC_PUBLIC + ACC_FINAL, m, null, null, cw);
Context ctx = new Context(classLoader, g, classType, scope.getParentClasses(), scope.getFields(), scope.getStaticFields(), m.getArgumentTypes(), m, scope.getMethods(), scope.getStaticMethods());
Expression expression = expressionMap.get(m);
loadAndCast(ctx, expression, m.getReturnType());
g.returnValue();
g.endMethod();
} catch (Exception e) {
throw new RuntimeException("Unable to implement "+m.getName()+m.getDescriptor(),e);
}
}
if (bytecodeSaveDir != null) {
try (FileOutputStream fos = new FileOutputStream(bytecodeSaveDir.resolve(className + ".class").toFile())) {
fos.write(cw.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
cw.visitEnd();
Class> definedClass = classLoader.defineClass(className, key, cw.toByteArray());
logger.trace("Defined new {} for key {}", definedClass, key);
return (Class) definedClass;
}
/**
* Returns a new instance of a dynamic class
*
* @return new instance of the class which was created before in a dynamic way
*/
public T newInstance() {
try {
return defineClass().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}