package org.concord.otrunk.asm;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.concord.framework.otrunk.otcore.OTClass;
import org.concord.framework.otrunk.otcore.OTClassProperty;
import org.concord.otrunk.AbstractOTObject;
import org.concord.otrunk.OTInvocationHandler;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
public class ImplementOTClassAdapter extends ClassAdapter
implements Opcodes
{
private static final int UNKNOWN = 0;
private static final int GETTER = 1;
private static final int SETTER = 2;
private String className;
private Class<?> abstractClass;
private HashMap<String, String> internalMethodMap;
private String parentName;
private OTClass otClass;
public ImplementOTClassAdapter(ClassVisitor cv, Class<?> abstractClass, OTClass otClass)
{
super(cv);
this.abstractClass = abstractClass;
this.otClass = otClass;
Method[] internalMethods = AbstractOTObject.class.getMethods();
internalMethodMap = new HashMap<String, String>();
for (Method method : internalMethods) {
internalMethodMap.put(method.getName(), "");
}
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces)
{
className = name + "_Generated";
String [] updatedInterfaces = null;
if((access & ACC_INTERFACE) == 0){
// this should be an abstract class
parentName = name;
} else {
// this is an interface
parentName = "org/concord/otrunk/AbstractOTObject";
updatedInterfaces = new String[] { name };
}
cv.visit(V1_5, ACC_PUBLIC + ACC_SUPER, className, null,
parentName, updatedInterfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions)
{
return null;
}
public void processMethod(Method method)
{
if(!Modifier.isAbstract(method.getModifiers())){
// skip this method
// System.out.println("skipping non abstract method: " + className + "." + method.toString());
return;
}
// check if OTOjectInternal implements this method
if(internalMethodMap.get(method.getName()) != null){
// skip this method
// System.out.println("skipping already implemented method: " + className + "." + method.toString());
return;
}
String name = method.getName();
String desc = Type.getMethodDescriptor(method);
Type returnType = Type.getReturnType(desc);
Type[] argumentTypes = Type.getArgumentTypes(desc);
MethodVisitor mv;
int type = UNKNOWN;
String propertyName = "";
if(name.startsWith("get")){
type = GETTER;
propertyName = OTInvocationHandler.getResourceName(3, name);
} else if(name.startsWith("is")){
type = GETTER;
propertyName = OTInvocationHandler.getResourceName(2, name);
} else if(name.startsWith("_get")){
type = GETTER;
propertyName = OTInvocationHandler.getResourceName(4, name);
} else if(name.startsWith("_is")){
type = GETTER;
propertyName = OTInvocationHandler.getResourceName(3, name);
} else if(name.startsWith("set")){
type = SETTER;
propertyName = OTInvocationHandler.getResourceName(3, name);
} else if(name.startsWith("_set")){
type = SETTER;
propertyName = OTInvocationHandler.getResourceName(4, name);
}
if(type == SETTER){
if(returnType != Type.VOID_TYPE){
System.err.println("setter has a non void return type: " + method.toString());
// don't process this method, which should create a semi valid class that has
// an abtract method in it.
return;
}
if(argumentTypes.length != 1){
System.err.println("setter wrong number of arguments: " + method.toString());
// don't process this method, which should create a semi valid class that has
// an abtract method in it.
return;
}
}
if(type == UNKNOWN){
System.err.println("unknown abstract method: " + method.toString() );
return;
}
// verify this property is a valid class property
OTClassProperty classProperty = otClass.getProperty(propertyName);
if(classProperty == null){
System.err.println("unknown classProperty: " + propertyName +
" method: " + method.toString() );
return;
}
// figure out the field name with this class prop
String staticFieldName = getStaticFieldName(propertyName);
int modifiers = ACC_PUBLIC;
// allow protected methods if the name starts with _
if(Modifier.isProtected(method.getModifiers())){
if(name.startsWith("_")){
modifiers = ACC_PROTECTED;
} else {
// error
System.err.println("OTrunk property method cannot be protected unless it starts " +
" with '_'. method: " + method.toString() );
return;
}
}
mv = cv.visitMethod(modifiers, name, desc, null, null);
if(type == GETTER){
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETSTATIC, className, staticFieldName, "Lorg/concord/framework/otrunk/otcore/OTClassProperty;");
Class<?> boxedType = boxedType(returnType);
if(boxedType == null){
// asm handles putting .classS on the stack for non primitives
mv.visitLdcInsn(returnType);
} else {
// for primitives the static .TYPE field of the boxedType has to be used instead
mv.visitFieldInsn(GETSTATIC, Type.getInternalName(boxedType), "TYPE", "Ljava/lang/Class;");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "org/concord/otrunk/AbstractOTObject", "getResourceChecked", "(Lorg/concord/framework/otrunk/otcore/OTClassProperty;Ljava/lang/Class;)Ljava/lang/Object;");
// Only do the following if the return type is a primitive
if(boxedType != null){
String asmTypeString = Type.getInternalName(boxedType);
mv.visitTypeInsn(CHECKCAST, asmTypeString);
mv.visitMethodInsn(INVOKEVIRTUAL, asmTypeString,
returnType.getClassName() + "Value", "()" + returnType.getDescriptor());
} else {
mv.visitTypeInsn(CHECKCAST, returnType.getInternalName());
}
mv.visitInsn(returnType.getOpcode(IRETURN));
mv.visitMaxs(3, 1);
mv.visitEnd();
}
if(type == SETTER){
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn(propertyName);
Type argType = argumentTypes[0];
mv.visitVarInsn(argType.getOpcode(ILOAD), 1);
Class <?> boxedType = boxedType(argType);
if(boxedType != null){
String asmTypeString = Type.getInternalName(boxedType);
mv.visitMethodInsn(INVOKESTATIC, asmTypeString, "valueOf",
"(" + argType.getDescriptor() + ")L" + asmTypeString + ";");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "org/concord/otrunk/AbstractOTObject", "setResource", "(Ljava/lang/String;Ljava/lang/Object;)Z");
mv.visitInsn(POP);
mv.visitInsn(RETURN);
// the max size of the stack is 1 for the "this" plus 1 for the property name
// plus the size of the argument
// the max size of the locals is 1 for "this" plus the size of the single argument
// which is normal 1 except for doubles and longs
mv.visitMaxs(2 + argType.getSize(), 1 + argType.getSize());
mv.visitEnd();
}
}
public static String getStaticFieldName(String propertyName)
{
return "OT_PROP_" + propertyName.toUpperCase();
}
@Override
public void visitEnd()
{
// create static fields for all of the properties of this class
FieldVisitor fv;
ArrayList<OTClassProperty> allClassProperties = otClass.getOTAllClassProperties();
for (OTClassProperty classProperty : allClassProperties) {
String name = getStaticFieldName(classProperty.getName());
fv = cv.visitField(ACC_STATIC | ACC_PUBLIC, name, "Lorg/concord/framework/otrunk/otcore/OTClassProperty;", null, null);
fv.visitEnd();
}
MethodVisitor mv;
// Add the null constructor
{
mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, parentName, "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
abstractClass.getSuperclass();
abstractClass.getInterfaces();
ArrayList<Method> methods = getAbstractMethods(abstractClass);
for (Method method : methods) {
processMethod(method);
}
super.visitEnd();
}
/**
* Find all the abstract methods, of any of the classes in this class tree.
* However, this should not return methods that are implemented within this class tree.
*
* @param klass
* @return
*/
public static ArrayList<Method> getAbstractMethods(Class<?> klass){
ArrayList<Method> abstractMethods = new ArrayList<Method>();
ArrayList<Method> definedMethods = new ArrayList<Method>();
ArrayList<Class<?>> classesToVisit = new ArrayList<Class<?>>();
classesToVisit.add(klass);
for(int i=0; i < classesToVisit.size(); i++){
Class<?> currentClass = classesToVisit.get(i);
Method[] declaredMethods = currentClass.getDeclaredMethods();
for (Method method : declaredMethods) {
if(Modifier.isAbstract(method.getModifiers())){
boolean skip = false;
for (Method definedMethod : definedMethods) {
if(method.getName().equals(definedMethod.getName()) &&
Arrays.equals(method.getParameterTypes(),definedMethod.getParameterTypes())){
// this abstract method is defined within this class tree so skip it
skip = true;
break;
}
}
for (Method abstractMethod : abstractMethods) {
if(method.getName().equals(abstractMethod.getName()) &&
Arrays.equals(method.getParameterTypes(),abstractMethod.getParameterTypes())){
// this abstract method is already defined elsewhere so don't add it twice
skip = true;
break;
}
}
if(skip){
continue;
}
abstractMethods.add(method);
} else {
definedMethods.add(method);
}
}
Class<?> superclass = currentClass.getSuperclass();
if(superclass != null && !classesToVisit.contains(superclass)){
classesToVisit.add(superclass);
}
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> _interface : interfaces) {
if(!classesToVisit.contains(_interface)){
classesToVisit.add(_interface);
}
}
}
return abstractMethods;
}
public static Class<?> boxedType(Type type) {
switch(type.getSort()){
case Type.VOID:
return null;
case Type.BOOLEAN:
return Boolean.class;
case Type.CHAR:
return Character.class;
case Type.BYTE:
return Byte.class;
case Type.SHORT:
return Short.class;
case Type.INT:
return Integer.class;
case Type.FLOAT:
return Float.class;
case Type.LONG:
return Long.class;
case Type.DOUBLE:
return Double.class;
default:
return null;
}
}
}