at.chrl.callbacks.enhancer.ObjectCallbackEnhancer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of chrl-callbacks Show documentation
Show all versions of chrl-callbacks Show documentation
Callback Framework to hook up on method calls with annotations
/**
* This file is part of aion-lightning .
*
* aion-lightning is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* aion-lightning is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* aion-lightning. If not, see .
*/
package at.chrl.callbacks.enhancer;
import java.io.ByteArrayInputStream;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import at.chrl.callbacks.Callback;
import at.chrl.callbacks.CallbackResult;
import at.chrl.callbacks.EnhancedObject;
import at.chrl.callbacks.metadata.ObjectCallback;
import at.chrl.callbacks.util.CallbacksUtil;
import at.chrl.callbacks.util.ObjectCallbackHelper;
public class ObjectCallbackEnhancer extends CallbackClassFileTransformer {
private static final Logger log = LoggerFactory.getLogger(ObjectCallbackEnhancer.class);
/**
* Field name for callbacks map
*/
public static final String FIELD_NAME_CALLBACKS = "$$$callbacks";
/**
* Field name for synchronizer
*/
public static final String FIELD_NAME_CALLBACKS_LOCK = "$$$callbackLock";
/**
* Does actual transformation
*
* @param loader
* class loader
* @param clazzBytes
* class bytecode
* @return transformed class bytecode
* @throws Exception
* is something went wrong
*/
protected byte[] transformClass(ClassLoader loader, byte[] clazzBytes) throws Exception {
ClassPool cp = new ClassPool();
cp.appendClassPath(new LoaderClassPath(loader));
CtClass clazz = cp.makeClass(new ByteArrayInputStream(clazzBytes));
Set methdosToEnhance = new HashSet();
for (CtMethod method : clazz.getDeclaredMethods()) {
if (!isEnhanceable(method)) {
continue;
}
methdosToEnhance.add(method);
}
if (!methdosToEnhance.isEmpty()) {
CtClass eo = cp.get(EnhancedObject.class.getName());
for (CtClass i : clazz.getInterfaces()) {
if (i.equals(eo)) {
throw new RuntimeException("Class already implements EnhancedObject interface, WTF???");
}
}
log.debug("Enhancing class: " + clazz.getName());
writeEnhancedObjectImpl(clazz);
for (CtMethod method : methdosToEnhance) {
log.debug("Enhancing method: " + method.getLongName());
enhanceMethod(method);
}
return clazz.toBytecode();
} else {
log.trace("Class " + clazz.getName() + " was not enhanced");
return null;
}
}
/**
* Responsible for method enhancing, writing service calls to method.
*
* @param method
* Method that has to be edited
* @throws javassist.CannotCompileException
* if something went wrong
* @throws javassist.NotFoundException
* if something went wrong
* @throws ClassNotFoundException
* if something went wrong
*/
protected void enhanceMethod(CtMethod method) throws CannotCompileException, NotFoundException, ClassNotFoundException {
ClassPool cp = method.getDeclaringClass().getClassPool();
method.addLocalVariable("___cbr", cp.get(CallbackResult.class.getName()));
CtClass listenerClazz = cp.get(((ObjectCallback) method.getAnnotation(ObjectCallback.class)).value().getName());
String listenerFieldName = "$$$" + listenerClazz.getSimpleName();
CtClass clazz = method.getDeclaringClass();
try {
clazz.getField(listenerFieldName);
} catch (NotFoundException e) {
clazz.addField(CtField.make("Class " + listenerFieldName + " = Class.forName(\"" + listenerClazz.getName() + "\");", clazz));
}
int paramLength = method.getParameterTypes().length;
method.insertBefore(writeBeforeMethod(method, paramLength, listenerFieldName));
method.insertAfter(writeAfterMethod(method, paramLength, listenerFieldName));
}
/**
* Code that is added in the begining of the method
*
* @param method
* method that should be edited
* @param paramLength
* Lenght of methods parameters
* @param listenerFieldName
* Listener class that is used for method
* @return code that will be inserted before method
* @throws NotFoundException
* if something went wrong
* @throws CannotCompileException
*/
protected String writeBeforeMethod(CtMethod method, int paramLength, String listenerFieldName) throws NotFoundException, CannotCompileException {
StringBuilder sb = new StringBuilder();
sb.append('{');
sb.append(" ___cbr = ");
sb.append(ObjectCallbackHelper.class.getName()).append(".beforeCall((");
sb.append(EnhancedObject.class.getName());
sb.append(")this, " + listenerFieldName + ", ");
if (paramLength > 0) {
sb.append("new Object[]{");
for (int i = 1; i <= paramLength; i++) {
sb.append("($w)$").append(i);
if (i < paramLength) {
sb.append(',');
}
}
sb.append("}");
} else {
sb.append("null");
}
sb.append(");");
sb.append("if(___cbr.isBlockingCaller()){");
// Fake return due to javassist bug
// $r is not available in "insertBefore"
CtClass returnType = method.getReturnType();
if (returnType.equals(CtClass.voidType)) {
sb.append("return");
} else if (returnType.equals(CtClass.booleanType)) {
sb.append("return false");
} else if (returnType.equals(CtClass.charType)) {
sb.append("return 'a'");
} else if (returnType.equals(CtClass.byteType) || returnType.equals(CtClass.shortType) || returnType.equals(CtClass.intType) || returnType.equals(CtClass.floatType) || returnType.equals(CtClass.longType) || returnType.equals(CtClass.longType)) {
sb.append("return 0");
}
sb.append(";}}");
return sb.toString();
}
/**
* Writes code that will be inserted after method
*
* @param method
* method to edit
* @param paramLength
* lenght of method paramenters
* @param listenerFieldName
* method listener
* @return actual code that should be inserted
* @throws NotFoundException
* if something went wrong
*/
protected String writeAfterMethod(CtMethod method, int paramLength, String listenerFieldName) throws NotFoundException {
StringBuilder sb = new StringBuilder();
sb.append('{');
// workaround for javassist bug, $r is not available in "insertBefore"
if (!method.getReturnType().equals(CtClass.voidType)) {
sb.append("if(___cbr.isBlockingCaller()){");
sb.append("$_ = ($r)($w)___cbr.getResult();");
sb.append("}");
}
sb.append("___cbr = ").append(ObjectCallbackHelper.class.getName()).append(".afterCall((");
sb.append(EnhancedObject.class.getName()).append(")this, " + listenerFieldName + ", ");
if (paramLength > 0) {
sb.append("new Object[]{");
for (int i = 1; i <= paramLength; i++) {
sb.append("($w)$").append(i);
if (i < paramLength) {
sb.append(',');
}
}
sb.append("}");
} else {
sb.append("null");
}
sb.append(", ($w)$_);");
sb.append("if(___cbr.isBlockingCaller()){");
if (method.getReturnType().equals(CtClass.voidType)) {
sb.append("return;");
} else {
sb.append("return ($r)($w)___cbr.getResult();");
}
sb.append("}");
sb.append("else {return $_;}");
sb.append("}");
return sb.toString();
}
/**
* Implements {@link EnhancedObject on class}
*
* @param clazz
* class to edit
* @throws NotFoundException
* if something went wrong
* @throws CannotCompileException
* if something went wrong
*/
protected void writeEnhancedObjectImpl(CtClass clazz) throws NotFoundException, CannotCompileException {
ClassPool cp = clazz.getClassPool();
clazz.addInterface(cp.get(EnhancedObject.class.getName()));
writeEnhancedOBjectFields(clazz);
writeEnhancedObjectMethods(clazz);
}
/**
* Implements {@link EnhancedObject} fields
*
* @param clazz
* Class to add fields
* @throws CannotCompileException
* if something went wrong
* @throws NotFoundException
* if something went wrong
*/
private void writeEnhancedOBjectFields(CtClass clazz) throws CannotCompileException, NotFoundException {
ClassPool cp = clazz.getClassPool();
// add map that holds callbacks
CtField cbField = new CtField(cp.get(Map.class.getName()), FIELD_NAME_CALLBACKS, clazz);
cbField.setModifiers(java.lang.reflect.Modifier.PRIVATE);
clazz.addField(cbField, CtField.Initializer.byExpr("null;"));
// add reetrantReadWriteLock
CtField cblField = new CtField(cp.get(ReentrantReadWriteLock.class.getName()), FIELD_NAME_CALLBACKS_LOCK, clazz);
cblField.setModifiers(java.lang.reflect.Modifier.PRIVATE);
clazz.addField(cblField, CtField.Initializer.byExpr("new " + ReentrantReadWriteLock.class.getName() + "();"));
}
/**
* Implements {@link EnhancedObject methods}
*
* @param clazz
* Class to add methods
* @throws NotFoundException
* if something went wrong
* @throws CannotCompileException
* if something went wrong
*/
private void writeEnhancedObjectMethods(CtClass clazz) throws NotFoundException, CannotCompileException {
ClassPool cp = clazz.getClassPool();
CtClass callbackClass = cp.get(Callback.class.getName());
CtClass mapClass = cp.get(Map.class.getName());
CtClass reentrantReadWriteLockClass = cp.get(ReentrantReadWriteLock.class.getName());
CtMethod method = new CtMethod(CtClass.voidType, "addCallback", new CtClass[] { callbackClass }, clazz);
method.setModifiers(java.lang.reflect.Modifier.PUBLIC);
method.setBody("at.chrl.callbacks.util.ObjectCallbackHelper.addCallback($1, this);");
clazz.addMethod(method);
method = new CtMethod(CtClass.voidType, "removeCallback", new CtClass[] { callbackClass }, clazz);
method.setModifiers(java.lang.reflect.Modifier.PUBLIC);
method.setBody("at.chrl.callbacks.util.ObjectCallbackHelper.removeCallback($1, this);");
clazz.addMethod(method);
method = new CtMethod(mapClass, "getCallbacks", new CtClass[] {}, clazz);
method.setModifiers(java.lang.reflect.Modifier.PUBLIC);
method.setBody("return " + FIELD_NAME_CALLBACKS + ";");
clazz.addMethod(method);
method = new CtMethod(CtClass.voidType, "setCallbacks", new CtClass[] { mapClass }, clazz);
method.setModifiers(java.lang.reflect.Modifier.PUBLIC);
method.setBody("this." + FIELD_NAME_CALLBACKS + " = $1;");
clazz.addMethod(method);
method = new CtMethod(reentrantReadWriteLockClass, "getCallbackLock", new CtClass[] {}, clazz);
method.setModifiers(java.lang.reflect.Modifier.PUBLIC);
method.setBody("return " + FIELD_NAME_CALLBACKS_LOCK + ";");
clazz.addMethod(method);
}
/**
* Checks if method is enhanceable. It should be marked with
* {@link at.chrl.callbacks.metadata.ObjectCallback} annotation, be not
* native and not abstract
*
* @param method
* method to check
* @return check result
*/
protected boolean isEnhanceable(CtMethod method) {
int modifiers = method.getModifiers();
return !(Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers) || Modifier.isStatic(modifiers)) && CallbacksUtil.isAnnotationPresent(method, ObjectCallback.class);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy