/*
* 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.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.compass.core.util.reflection.ReflectionField;
import org.compass.core.util.reflection.plain.PlainReflectionField;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* Allows to generate {@link org.compass.core.util.reflection.ReflectionField} implementation based on
* ASM that does not use reflection.
*
* @author kimchy
*/
public class AsmReflectionFieldGenerator {
final private static String PLAIN_FIELD_INTERNAL_NAME = Type.getInternalName(PlainReflectionField.class);
/**
* Allows to generate {@link org.compass.core.util.reflection.ReflectionField} implementation based on
* ASM that does not use reflection.
*/
public static synchronized ReflectionField generateField(Field refField) throws NoSuchFieldException {
Class declaringClass = refField.getDeclaringClass();
String ownerClassName = declaringClass.getName();
Field[] declaredFields = declaringClass.getDeclaredFields();
int methodIndex = 0;
for (; methodIndex < declaredFields.length; ++methodIndex) {
if (declaredFields[methodIndex].equals(refField))
break;
}
String className = ownerClassName + "FieldReflection" + 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 + Opcodes.ACC_SUPER, classInternalName,
null, PLAIN_FIELD_INTERNAL_NAME, null);
createConstructor(cw);
createGetMethod(cw, declaringClass, refField);
createSetMethod(cw, declaringClass, refField);
cw.visitEnd();
byte[] b = cw.toByteArray();
definedClass = defineClass(refField.getDeclaringClass().getClassLoader(), className, b);
}
Constructor ctor = definedClass.getConstructor(Field.class);
return (ReflectionField) ctor.newInstance(refField);
}
catch (Exception e) {
NoSuchFieldException err = new NoSuchFieldException("Can't create ASM field reflection helper for [" + refField + "]");
err.initCause(e);
throw err;
}
}
private static void createConstructor(ClassVisitor cw) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/Field;)V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PlainReflectionField.class), "<init>",
"(Ljava/lang/reflect/Field;)V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
private static void createGetMethod(ClassVisitor cw, Class entryClass, Field field) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", null,
new String[]{"java/lang/IllegalArgumentException", "java/lang/IllegalAccessException"});
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(entryClass));
mv.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(entryClass), field.getName(), Type.getDescriptor(field.getType()));
boxIfNeeded(mv, field);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(1, 2);
mv.visitEnd();
}
private static void createSetMethod(ClassVisitor cw, Class entryClass, Field field) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V",
null, new String[]{"java/lang/IllegalArgumentException", "java/lang/IllegalAccessException"});
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(entryClass));
mv.visitVarInsn(Opcodes.ALOAD, 2);
castAndUnboxIfNeeded(mv, field);
mv.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(entryClass), field.getName(), Type.getDescriptor(field.getType()));
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 3);
mv.visitEnd();
}
private static void castAndUnboxIfNeeded(MethodVisitor mv, Field field) {
if (!field.getType().isPrimitive()) {
mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(field.getType()));
return;
}
Type type = Type.getType(field.getType());
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.INT:
mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Integer");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
break;
case Type.SHORT:
mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Short");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
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;
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;
}
}
private static void boxIfNeeded(MethodVisitor mv, Field field) {
if (!field.getType().isPrimitive())
return;
Type type = Type.getType(field.getType());
switch (type.getSort()) {
case Type.BOOLEAN:
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
break;
case Type.INT:
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
break;
case Type.SHORT:
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
break;
case Type.LONG:
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
break;
case Type.FLOAT:
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
break;
case Type.DOUBLE:
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
break;
case Type.BYTE:
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
break;
case Type.CHAR:
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
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);
}
}