/**
* EasyBeans
* Copyright (C) 2012 Bull S.A.S.
* Contact: easybeans@ow2.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* --------------------------------------------------------------------------
* $Id$
* --------------------------------------------------------------------------
*/
package org.ow2.easybeans.enhancer.bean;
import java.lang.reflect.InvocationHandler;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.ow2.easybeans.api.bean.proxy.EasyBeansNoInterfaceProxyBean;
import org.ow2.easybeans.asm.Label;
import org.ow2.easybeans.asm.MethodVisitor;
import org.ow2.easybeans.asm.Type;
import org.ow2.easybeans.deployment.metadata.ejbjar.EasyBeansEjbJarClassMetadata;
import org.ow2.easybeans.deployment.metadata.ejbjar.EasyBeansEjbJarMethodMetadata;
import org.ow2.easybeans.enhancer.CommonClassGenerator;
import org.ow2.easybeans.enhancer.EasyBeansClassWriter;
import org.ow2.easybeans.enhancer.lib.ProxyClassEncoder;
import org.ow2.util.scan.api.metadata.structures.IMethod;
import org.ow2.util.scan.impl.metadata.JMethod;
/**
* Adapter used to generate a new class based on a super class.
* @author Florent Benoit
*/
public class NoInterfaceViewClassGenerator extends CommonClassGenerator {
/**
* JMethod object for toString().
*/
public static final IMethod TO_STRING_JMETHOD = new JMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
/**
* JMethod object for equals(Other object).
*/
public static final IMethod EQUALS_JMETHOD = new JMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
/**
* JMethod object for hashCode().
*/
public static final IMethod HASHCODE_JMETHOD = new JMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
/**
* Object jmethods.
*/
public static final List<String> METHODS_TO_IGNORE = Arrays.asList(new String[] {"<init>", "<clinit>",
TO_STRING_JMETHOD.getName(), EQUALS_JMETHOD.getName(), HASHCODE_JMETHOD.getName()});
/**
* InvocationHelper class.
*/
private static final String INVOCATION_HELPER_CLASSNAME = Type.getInternalName(InvocationHelper.class);
/**
* The classmetadata.
*/
private EasyBeansEjbJarClassMetadata classMetadata = null;
/**
* Name of the class to generate.
*/
private String generatedClassName = null;
/**
* Default constructor for the given classmetadata.
* @param classMetadata the given classmetadata
* @param readLoader the classloader used to load classes.
*/
public NoInterfaceViewClassGenerator(final EasyBeansEjbJarClassMetadata classMetadata, final ClassLoader readLoader) {
super(new EasyBeansClassWriter(readLoader));
this.classMetadata = classMetadata;
this.generatedClassName = ProxyClassEncoder.getProxyClassName(classMetadata.getClassName());
}
/**
* Generates the class. It call sub methods for being more clear for read
* the code
*/
public void generate() {
addClassDeclaration();
addAttributes();
addConstructor();
addMethods();
endClass();
}
/**
* Add attributes of the class in two steps.
* <ul>
* <li>InvocationHandler interface</li>
* </ul>
*/
private void addAttributes() {
addInvocationHandlerAttributes();
}
/**
* Add attribute InvocationHandler + getter/setter.
*/
private void addInvocationHandlerAttributes() {
CommonClassGenerator.addFieldGettersSetters(getCW(), this.generatedClassName, "invocationHandler",
InvocationHandler.class);
}
/**
* Creates the declaration of the class with the given interfaces.
*/
private void addClassDeclaration() {
// Add an interface
String interfaceName = Type.getInternalName(EasyBeansNoInterfaceProxyBean.class);
// create class
getCW().visit(V1_5, ACC_PUBLIC + ACC_SUPER, this.generatedClassName, null, this.classMetadata.getClassName(),
new String[] {interfaceName});
}
/**
* Creates the constructor which should look like.
*
* <pre>
* public XXXBean() {
* super();
* }
* </pre>
*/
private void addConstructor() {
// Generate constructor
MethodVisitor mv = getCW().visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
// Call super constructor
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, this.classMetadata.getClassName(), "<init>", "()V");
// need to add return instruction
mv.visitInsn(RETURN);
// visit max compute automatically
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Generate two kinds of methods : Call for public methods Call for not
* public methods.
*/
private void addMethods() {
Collection<EasyBeansEjbJarMethodMetadata> methodsMetadata = this.classMetadata.getMethodMetadataCollection();
if (methodsMetadata != null) {
for (EasyBeansEjbJarMethodMetadata methodMetadata : methodsMetadata) {
IMethod jMethod = methodMetadata.getJMethod();
// Skip special methods
if (METHODS_TO_IGNORE.contains(jMethod.getName())) {
continue;
}
// Method is not public and not private
if ((jMethod.getAccess() & ACC_PUBLIC) != ACC_PUBLIC && (jMethod.getAccess() & ACC_PRIVATE) != ACC_PRIVATE) {
addThrowsExceptionMethod(jMethod);
} else if ((jMethod.getAccess() & ACC_PUBLIC) == ACC_PUBLIC) {
// Method is public, generate a method which calls the invocation handler
// the same method name but with a different content
addTransformedMethod(methodMetadata);
}
}
}
// Now add Object methods
addTransformedMethod(new EasyBeansEjbJarMethodMetadata(TO_STRING_JMETHOD, this.classMetadata));
addTransformedMethod(new EasyBeansEjbJarMethodMetadata(EQUALS_JMETHOD, this.classMetadata));
addTransformedMethod(new EasyBeansEjbJarMethodMetadata(HASHCODE_JMETHOD, this.classMetadata));
}
/**
* Each public method should call the invocation handler.
* <pre>
* public void methodWithException() throws MyException {
* Method m = Helper.getMethod(superClass.class, methodName, new Class[] {});
* try {
* Helper.invoke(this, m, handler, new Object[] {});
* } catch (Throwable originalThrowable) {
* if (originalThrowable instanceof MyException) {
* throw (MyException) originalThrowable;
* } else {
* if (originalThrowable instanceof RuntimeException) {
throw (RuntimeException) originalThrowable;
}
* if (originalThrowable instanceof Exception) {
* throw new EJBException("illegal", (Exception) originalThrowable);
* }
* throw new EJBException("error", new RuntimeException(originalThrowable));
* }
* }
* }
* </pre>
* @param methodMetadata the metadata used to generate the call
*/
private void addTransformedMethod(final EasyBeansEjbJarMethodMetadata methodMetadata) {
IMethod jMethod = methodMetadata.getJMethod();
MethodVisitor mv = getCW().visitMethod(jMethod.getAccess(), jMethod.getName(), jMethod.getDescriptor(),
jMethod.getSignature(), jMethod.getExceptions());
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Throwable");
// Get method of the super class
mv.visitLdcInsn(Type.getType("L".concat(methodMetadata.getClassMetadata().getClassName()).concat(";")));
mv.visitLdcInsn(jMethod.getName());
// Arguments of the method
// parameters = new Class[] {arg0, arg1, arg...};
// put size of the array
Type[] args = Type.getArgumentTypes(jMethod.getDescriptor());
int methodArg = 1;
putConstNumber(args.length, mv);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
// for each argument of the methods, load the class parameter
int argCount = 0;
for (Type type : args) {
mv.visitInsn(DUP);
putConstNumber(argCount, mv);
visitClassType(type, mv);
mv.visitInsn(AASTORE);
int opCode = CommonClassGenerator.putFieldLoadOpCode(type.getSort());
// Double and Long are special parameters
if (opCode == LLOAD || opCode == DLOAD) {
methodArg++;
}
methodArg++;
argCount++;
}
mv.visitMethodInsn(INVOKESTATIC, INVOCATION_HELPER_CLASSNAME, "getMethod",
"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
mv.visitVarInsn(ASTORE, methodArg);
// Begin try
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, methodArg);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, this.generatedClassName, "getInvocationHandler",
"()Ljava/lang/reflect/InvocationHandler;");
// Give args for the call of the invoke method
putConstNumber(args.length, mv);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
// for each argument of the methods :
argCount = 0;
int localArg = 1;
for (Type type : args) {
mv.visitInsn(DUP);
putConstNumber(argCount, mv);
int opCode = CommonClassGenerator.putFieldLoadOpCode(type.getSort());
mv.visitVarInsn(opCode, localArg);
// Double and Long are special parameters
if (opCode == LLOAD || opCode == DLOAD) {
localArg++;
}
localArg++;
// if type is not object type, need to convert it
// for example : Integer.valueOf(i);
CommonClassGenerator.transformPrimitiveIntoObject(type, mv);
mv.visitInsn(AASTORE);
argCount++;
}
// Invoke
mv
.visitMethodInsn(INVOKESTATIC, INVOCATION_HELPER_CLASSNAME, "invoke",
"(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/InvocationHandler;[Ljava/lang/Object;)Ljava/lang/Object;");
Type returnType = Type.getReturnType(jMethod.getDescriptor());
// Cast and return value
CommonClassGenerator.transformObjectIntoPrimitive(returnType, mv);
mv.visitLabel(l1);
CommonClassGenerator.addReturnType(returnType, mv);
mv.visitLabel(l2);
methodArg++;
mv.visitVarInsn(ASTORE, methodArg);
// Exceptions to catch/rethrow ?
String[] methodExceptions = jMethod.getExceptions();
if (methodExceptions != null) {
// ifLabels = exceptions thrown by method + 1
Label[] ifLabels = new Label[methodExceptions.length + 1];
// init labels
for (int i = 0; i < ifLabels.length; i++) {
ifLabels[i] = new Label();
}
for (int ifBlock = 0; ifBlock < methodExceptions.length; ifBlock++) {
mv.visitLabel(ifLabels[ifBlock]);
mv.visitVarInsn(ALOAD, methodArg);
mv.visitTypeInsn(INSTANCEOF, methodExceptions[ifBlock]);
mv.visitJumpInsn(IFEQ, ifLabels[ifBlock + 1]);
mv.visitVarInsn(ALOAD, methodArg);
mv.visitTypeInsn(CHECKCAST, methodExceptions[ifBlock]);
mv.visitInsn(ATHROW);
}
mv.visitLabel(ifLabels[methodExceptions.length]);
}
// Check if throwable is exception or not
mv.visitVarInsn(ALOAD, methodArg);
mv.visitTypeInsn(INSTANCEOF, "java/lang/RuntimeException");
Label notRuntimeException = new Label();
mv.visitJumpInsn(IFEQ, notRuntimeException);
mv.visitVarInsn(ALOAD, methodArg);
mv.visitTypeInsn(CHECKCAST, "java/lang/RuntimeException");
mv.visitInsn(ATHROW);
mv.visitLabel(notRuntimeException);
mv.visitVarInsn(ALOAD, methodArg);
mv.visitTypeInsn(INSTANCEOF, "java/lang/Exception");
Label notException = new Label();
mv.visitJumpInsn(IFEQ, notException);
mv.visitTypeInsn(NEW, "javax/ejb/EJBException");
mv.visitInsn(DUP);
mv.visitLdcInsn("Unable to invoke method");
mv.visitVarInsn(ALOAD, methodArg);
mv.visitTypeInsn(CHECKCAST, "java/lang/Exception");
mv.visitMethodInsn(INVOKESPECIAL, "javax/ejb/EJBException", "<init>", "(Ljava/lang/String;Ljava/lang/Exception;)V");
mv.visitInsn(ATHROW);
mv.visitLabel(notException);
mv.visitTypeInsn(NEW, "javax/ejb/EJBException");
mv.visitInsn(DUP);
mv.visitLdcInsn("Unable to invoke method");
mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, methodArg);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/Throwable;)V");
mv.visitMethodInsn(INVOKESPECIAL, "javax/ejb/EJBException", "<init>", "(Ljava/lang/String;Ljava/lang/Exception;)V");
mv.visitInsn(ATHROW);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Each method which is not public (and private) should throw a
* javax.ejb.EJBException.
*
* <pre>
* public void methodName() {
* throw new EJBException("This method is not a public method. Access is not allowed");
* }
* </pre>
* @param jMethod the method used to add the throws
*/
private void addThrowsExceptionMethod(final IMethod jMethod) {
// Create the method which throw the exception with the same
// signature
MethodVisitor mv = getCW().visitMethod(jMethod.getAccess(), jMethod.getName(), jMethod.getDescriptor(),
jMethod.getSignature(), jMethod.getExceptions());
mv.visitCode();
mv.visitTypeInsn(NEW, "javax/ejb/EJBException");
mv.visitInsn(DUP);
mv.visitLdcInsn("This method is not a public method. Access is not allowed");
mv.visitMethodInsn(INVOKESPECIAL, "javax/ejb/EJBException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Called when the generated class is done.
*/
private void endClass() {
getCW().visitEnd();
}
/**
* @return the bytecode of the generated class.
*/
public byte[] getBytes() {
return getCW().toByteArray();
}
/**
* @return the name of the generated class name (with package name)
*/
public String getGeneratedClassName() {
return this.generatedClassName;
}
}