/* * Copyright 2004-2009 the original author or authors. * * 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 org.compass.core.util.reflection.asm; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.compass.core.util.reflection.ReflectionMethod; import org.compass.core.util.reflection.plain.PlainReflectionMethod; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; /** * Allows to generate an {@link org.compass.core.util.reflection.ReflectionMethod} implementation based on ASM * that does not use reflection. * * @author kimchy */ public class AsmReflectionMethodGenerator { final private static String PLAIN_PROCEDURE_INTERNAL_NAME = Type.getInternalName(PlainReflectionMethod.class); /** * Generates an {@link org.compass.core.util.reflection.ReflectionMethod} based on ASM that does not use * reflection. */ public static synchronized ReflectionMethod generateMethod(Method refMethod) throws SecurityException, NoSuchMethodException { return generateMethod(refMethod, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", ReflectionMethod.class, true, true); } private static synchronized ReflectionMethod generateMethod(Method refMethod, String name, String desc, Class interfaceClass, boolean argsParams, boolean returnValue) throws SecurityException, NoSuchMethodException { final Class declaringClass = refMethod.getDeclaringClass(); String ownerClassName = declaringClass.getName(); Method[] declaredMethods = declaringClass.getDeclaredMethods(); int methodIndex = 0; for (; methodIndex < declaredMethods.length; ++methodIndex) { if (declaredMethods[methodIndex].equals(refMethod)) break; } String className = ownerClassName + "MethodReflection" + name + methodIndex; try { Class definedClass; try { // checks if was already loaded definedClass = declaringClass.getClassLoader().loadClass(className); } catch (ClassNotFoundException e) // need to build a new class { String classInternalName = className.replace('.', '/'); // build internal name for ASM ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); cw.visit(Opcodes.V1_4, Opcodes.ACC_PUBLIC, classInternalName, null, PLAIN_PROCEDURE_INTERNAL_NAME, new String[]{Type.getInternalName(interfaceClass)}); createCtor(cw); createMethod(refMethod.getDeclaringClass(), refMethod.getName(), refMethod, cw, name, desc, argsParams, returnValue, refMethod.getParameterTypes()); cw.visitEnd(); byte[] b = cw.toByteArray(); definedClass = defineClass(declaringClass.getClassLoader(), className, b); } return (ReflectionMethod) definedClass.getConstructor(Method.class).newInstance(refMethod); } catch (Exception e) { NoSuchMethodException ex = new NoSuchMethodException("Can't create ASM method reflection helper for [" + refMethod + "]"); ex.initCause(e); throw ex; } } /** * Creates the class constructor which delegates the input to the super. */ private static void createCtor(ClassWriter cw) { MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/Method;)V", null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 1); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PlainReflectionMethod.class), "<init>", "(Ljava/lang/reflect/Method;)V"); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); } /** * Creates the method invoking wrapper method. */ private static void createMethod(Class clazz, String name, Method refMethod, ClassWriter cw, String methodName, String desc, boolean argsParams, boolean returnValue, Class... parameterTypes) { MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_VARARGS, methodName, desc, null, null); boolean isStatic = Modifier.isStatic(refMethod.getModifiers()); boolean isInteface = Modifier.isInterface(refMethod.getDeclaringClass().getModifiers()); final int invokeCode; if (isStatic) { invokeCode = Opcodes.INVOKESTATIC; } else { invokeCode = isInteface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL; mv.visitVarInsn(Opcodes.ALOAD, 1); mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(clazz)); } if (argsParams) { for (int i = 0; i < parameterTypes.length; ++i) { mv.visitVarInsn(Opcodes.ALOAD, 2); mv.visitIntInsn(Opcodes.BIPUSH, i); mv.visitInsn(Opcodes.AALOAD); prepareParameter(mv, Type.getType(parameterTypes[i])); } } else { for (int i = 0; i < parameterTypes.length; ++i) { mv.visitVarInsn(Opcodes.ALOAD, i + 2); prepareParameter(mv, Type.getType(parameterTypes[i])); } } mv.visitMethodInsn(invokeCode, Type.getInternalName(clazz), name, Type.getMethodDescriptor(refMethod)); if (returnValue) { prepareResult(mv, refMethod); mv.visitInsn(Opcodes.ARETURN); } else { mv.visitInsn(Opcodes.RETURN); } mv.visitMaxs(1, 1); // ignored since ClassWriter set as ClassWriter.COMPUTE_MAXS mv.visitEnd(); } /** * Box the result if needed. */ private static void prepareResult(MethodVisitor mv, Method refMethod) { Type type = Type.getReturnType(refMethod); switch (type.getSort()) { case Type.VOID: mv.visitInsn(Opcodes.ACONST_NULL); // nothing to return the original method returns void break; case Type.BOOLEAN: callBoxer(mv, "(Z)Ljava/lang/Object;"); break; case Type.BYTE: callBoxer(mv, "(B)Ljava/lang/Object;"); break; case Type.CHAR: callBoxer(mv, "(C)Ljava/lang/Object;"); break; case Type.SHORT: callBoxer(mv, "(S)Ljava/lang/Object;"); break; case Type.INT: callBoxer(mv, "(I)Ljava/lang/Object;"); break; case Type.LONG: callBoxer(mv, "(J)Ljava/lang/Object;"); break; case Type.FLOAT: callBoxer(mv, "(F)Ljava/lang/Object;"); break; case Type.DOUBLE: callBoxer(mv, "(D)Ljava/lang/Object;"); break; } } private static void callBoxer(MethodVisitor mv, String desc) { mv.visitMethodInsn(Opcodes.INVOKESTATIC, Boxer.INTERNAL_NAME, "box", desc); } /** * Unbox the input parameters */ private static void prepareParameter(MethodVisitor mv, Type type) { switch (type.getSort()) { case Type.BOOLEAN: mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"); break; case Type.BYTE: mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Byte"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B"); break; case Type.CHAR: mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Character"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"); break; case Type.SHORT: mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Short"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S"); break; case Type.INT: mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I"); break; case Type.LONG: mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Long"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J"); break; case Type.FLOAT: mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Float"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F"); break; case Type.DOUBLE: mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Double"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D"); break; default: mv.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName()); break; } } private static Class defineClass(ClassLoader loader, String name, byte[] b) throws Exception { Method defineMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); defineMethod.setAccessible(true); return (Class) defineMethod.invoke(loader, name, b, 0, b.length); } }