Java tutorial
/* * Copyright 2012 ubiquity-copy * * 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.ubiquity.mirror.impl; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.objectweb.asm.*; import org.ubiquity.util.ClassDefinition; import org.ubiquity.util.Constants; import org.ubiquity.util.visitors.*; import java.io.IOException; import java.lang.reflect.Array; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import static org.objectweb.asm.Opcodes.*; import static org.ubiquity.util.ByteCodeStringHelper.*; /** * TODO : document.me properly !! * TODO : this class is becoming too big for a simple helper, create a good hierarchy of objects to solve the same problems * * Generate mirrors bytecode */ public final class MirrorGenerator { private static final String MIRROR_PREFIX = "org/ubiquity/mirror/Mirror$"; private static final AtomicLong SEQUENCE = new AtomicLong(); private static final String BUILD_PROPERTIES_SIGNATURE = "()Ljava/util/Map<Ljava/lang/String;Lorg/ubiquity/mirror/Property<Lorg/ubiquity/mirror/objects/ValueObject;>;>;"; private MirrorGenerator() { // I am a utility class } public static Collection<ClassDefinition> generateMirror(Class<?> aClass, Map<String, Class<?>> generics) throws IOException { ClassReader reader = new ClassReader(aClass.getName()); PropertyRetrieverVisitor visitor = new PropertyRetrieverVisitor(); reader.accept(new GenericsVisitor(visitor, generics), 0); Map<String, BytecodeProperty> properties = visitor.getProperties(); String name = generateMirrorName(aClass); String handledClassName = byteCodeName(aClass.getName()); ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); writer.visit(Constants.JAVA_VERSION, Opcodes.ACC_PUBLIC, name, "Lorg/ubiquity/mirror/impl/AbstractMirror<" + getDescription(handledClassName) + ">;", "org/ubiquity/mirror/impl/AbstractMirror", null); generateConstructor(writer); Map<String, ClassDefinition> definitions = makeClasses(writer, properties, name, handledClassName); generateBuildProperties(writer, definitions); writer.visitEnd(); List<ClassDefinition> result = Lists.newArrayList(); result.addAll(definitions.values()); result.add(new ClassDefinition(name, writer.toByteArray())); return result; } private static void generateConstructor(ClassWriter writer) { MethodVisitor visitor = writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); visitor.visitIntInsn(ALOAD, 0); visitor.visitMethodInsn(INVOKESPECIAL, "org/ubiquity/mirror/impl/AbstractMirror", "<init>", "()V"); visitor.visitInsn(RETURN); visitor.visitMaxs(1, 1); visitor.visitEnd(); } private static Map<String, ClassDefinition> makeClasses(ClassWriter writer, Map<String, BytecodeProperty> properties, String mirrorClassName, String handledClass) { Map<String, ClassDefinition> result = Maps.newHashMap(); for (Map.Entry<String, BytecodeProperty> entry : properties.entrySet()) { BytecodeProperty property = entry.getValue(); String innerClassSimpleName = property.getName(); String innerClassName = mirrorClassName + "$" + innerClassSimpleName; writer.visitInnerClass(innerClassName, mirrorClassName, innerClassSimpleName, ACC_PUBLIC); byte[] innerClass = createInnerClass(innerClassName, innerClassSimpleName, mirrorClassName, handledClass, property); result.put(property.getName(), new ClassDefinition(innerClassName, innerClass)); } return result; } private static void generateBuildProperties(ClassWriter writer, Map<String, ClassDefinition> definitions) { MethodVisitor visitor = writer.visitMethod(ACC_PROTECTED, "buildProperties", "()Ljava/util/Map;", BUILD_PROPERTIES_SIGNATURE, null); visitor.visitMethodInsn(INVOKESTATIC, "com/google/common/collect/ImmutableMap", "builder", "()Lcom/google/common/collect/ImmutableMap$Builder;"); for (Map.Entry<String, ClassDefinition> entry : definitions.entrySet()) { visitor.visitLdcInsn(entry.getKey()); String byteCodeName = entry.getValue().getClassName(); visitor.visitTypeInsn(NEW, byteCodeName); visitor.visitInsn(DUP); visitor.visitMethodInsn(INVOKESPECIAL, byteCodeName, "<init>", "()V"); visitor.visitMethodInsn(INVOKEVIRTUAL, "com/google/common/collect/ImmutableMap$Builder", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Lcom/google/common/collect/ImmutableMap$Builder;"); } visitor.visitMethodInsn(INVOKEVIRTUAL, "com/google/common/collect/ImmutableMap$Builder", "build", "()Lcom/google/common/collect/ImmutableMap;"); visitor.visitInsn(ARETURN); visitor.visitMaxs(0, 0); visitor.visitEnd(); } private static String generateMirrorName(Class<?> c) { return MIRROR_PREFIX + c.getSimpleName() + "$" + SEQUENCE.incrementAndGet(); } private static byte[] createInnerClass(String name, String innerName, String mirrorClass, String handledClass, BytecodeProperty property) { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); String resolvedType = getDescription(property.getType()); if (property.isPrimitive()) { resolvedType = Constants.SIMPLE_PROPERTIES.get(resolvedType); } writer.visit(Constants.JAVA_VERSION, ACC_PUBLIC, name, "Lorg/ubiquity/mirror/impl/AbstractProperty<" + getDescription(handledClass) + resolvedType + ">;", "org/ubiquity/mirror/impl/AbstractProperty", null); writer.visitInnerClass(name, mirrorClass, innerName, ACC_STATIC | ACC_PUBLIC); createInnerClassConstructor(writer, property); if (property.isReadable()) { createGet(writer, property, handledClass, name); createBooleanMethod(writer, "isReadable"); } if (property.isWritable()) { createSet(writer, property, handledClass, name); createBooleanMethod(writer, "isWritable"); } if (!property.getAnnotations().isEmpty()) { createGetAnnotations(writer, property); } writer.visitEnd(); return writer.toByteArray(); } private static void createGetAnnotations(ClassWriter writer, BytecodeProperty property) { MethodVisitor visitor = writer.visitMethod(ACC_PROTECTED, "buildAnnotations", "()Ljava/util/List;", "()Ljava/util/List<Lorg/ubiquity/mirror/Annotation;>;", null); visitor.visitMethodInsn(INVOKESTATIC, "com/google/common/collect/ImmutableList", "builder", "()Lcom/google/common/collect/ImmutableList$Builder;"); for (Annotation annotation : property.getAnnotations()) { mapAnnotation(visitor, annotation); visitor.visitMethodInsn(INVOKEVIRTUAL, "com/google/common/collect/ImmutableList$Builder", "add", "(Ljava/lang/Object;)Lcom/google/common/collect/ImmutableList$Builder;"); } visitor.visitMethodInsn(INVOKEVIRTUAL, "com/google/common/collect/ImmutableList$Builder", "build", "()Lcom/google/common/collect/ImmutableList;"); visitor.visitInsn(ARETURN); visitor.visitMaxs(0, 0); visitor.visitEnd(); } private static void mapAnnotation(MethodVisitor visitor, Annotation annotation) { visitor.visitTypeInsn(NEW, "org/ubiquity/mirror/Annotation"); visitor.visitInsn(DUP); visitor.visitLdcInsn(Type.getType(annotation.getClazz())); if (annotation.isVisible()) { visitor.visitInsn(ICONST_1); } else { visitor.visitInsn(ICONST_0); } visitor.visitMethodInsn(INVOKESTATIC, "com/google/common/collect/ImmutableMap", "builder", "()Lcom/google/common/collect/ImmutableMap$Builder;"); for (Map.Entry<String, AnnotationProperty> prop : annotation.getProperties().entrySet()) { visitor.visitLdcInsn(prop.getKey()); mapAnnotationProperties(visitor, prop.getValue()); visitor.visitMethodInsn(INVOKEVIRTUAL, "com/google/common/collect/ImmutableMap$Builder", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Lcom/google/common/collect/ImmutableMap$Builder;"); } visitor.visitMethodInsn(INVOKEVIRTUAL, "com/google/common/collect/ImmutableMap$Builder", "build", "()Lcom/google/common/collect/ImmutableMap;"); visitor.visitMethodInsn(INVOKESPECIAL, "org/ubiquity/mirror/Annotation", "<init>", "(Ljava/lang/Class;ZLjava/util/Map;)V"); } private static void mapAnnotationProperties(MethodVisitor visitor, AnnotationProperty property) { visitor.visitTypeInsn(NEW, "org/ubiquity/mirror/AnnotationProperty"); visitor.visitInsn(DUP); visitor.visitLdcInsn(property.getName()); mapAnnotationClassAndValue(visitor, property); visitor.visitMethodInsn(INVOKESPECIAL, "org/ubiquity/mirror/AnnotationProperty", "<init>", "(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/Object;)V"); } private static void mapAnnotationClassAndValue(MethodVisitor visitor, AnnotationProperty property) { Class valueClass = toJavaClass(byteCodeName(property.getDesc())); final Type destinationType = Type.getType(toJavaClass(byteCodeName(property.getDesc()))); if (valueClass.isArray()) { visitor.visitLdcInsn(destinationType); mapArrayValue(visitor, property); } else if (valueClass == Integer.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"); } else if (valueClass == Short.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"); } else if (valueClass == Long.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"); } else if (valueClass == Float.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"); } else if (valueClass == Double.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"); } else if (valueClass == Byte.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"); } else if (valueClass == Boolean.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"); } else if (valueClass == Character.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"); } else if (valueClass == String.class) { visitor.visitLdcInsn(destinationType); visitor.visitLdcInsn(property.getValue()); } else if (valueClass.isEnum()) { final String byteCodeName = byteCodeName(valueClass.getName()); visitor.visitLdcInsn(destinationType); visitor.visitFieldInsn(GETSTATIC, byteCodeName, property.getValue().toString(), getDescription(byteCodeName)); } else if (valueClass.isAnnotation()) { visitor.visitLdcInsn(Type.getType(org.ubiquity.mirror.Annotation.class)); mapAnnotation(visitor, (Annotation) property.getValue()); } else { throw new IllegalArgumentException("Unable to map (yet) class of type " + valueClass.getName()); } } private static void mapArrayValue(MethodVisitor visitor, AnnotationProperty property) { final String arrayType = property.getDesc().substring(1); final String arrayByteCodeName = byteCodeName(arrayType); Class concreteClass = toJavaClass(arrayByteCodeName); final String arrayBytecodeType = concreteClass.isAnnotation() ? "org/ubiquity/mirror/Annotation" : arrayByteCodeName; if (!concreteClass.isPrimitive()) { Object[] array = (Object[]) property.getValue(); visitor.visitLdcInsn(array.length); visitor.visitTypeInsn(ANEWARRAY, arrayBytecodeType); for (int i = 0; i < array.length; i++) { visitor.visitInsn(DUP); visitor.visitLdcInsn(i); addArrayValueInStack(visitor, array[i], arrayBytecodeType); visitor.visitInsn(AASTORE); } } else { final String arrayByteCodeUpperName = property.getDesc().toUpperCase(); final int length = Array.getLength(property.getValue()); visitor.visitLdcInsn(length); visitor.visitIntInsn(NEWARRAY, PRIMITIVE_ARRAY_CREATION.get(arrayByteCodeUpperName)); for (int i = 0; i < length; i++) { visitor.visitInsn(DUP); visitor.visitLdcInsn(i); visitor.visitLdcInsn(Array.get(property.getValue(), i)); visitor.visitInsn(PRIMITIVE_ARRAY_STORE.get(arrayByteCodeUpperName)); } } } private static final Map<String, Integer> PRIMITIVE_ARRAY_STORE = ImmutableMap.<String, Integer>builder() .put("[I", IASTORE).put("[B", BASTORE).put("[J", LASTORE).put("[L", LASTORE).put("[F", FASTORE) .put("[D", DASTORE).put("[S", SASTORE).put("[C", CASTORE).put("[Z", BASTORE).build(); private static final Map<String, Integer> PRIMITIVE_ARRAY_CREATION = ImmutableMap.<String, Integer>builder() .put("[I", T_INT).put("[B", T_BYTE).put("[J", T_LONG).put("[F", T_FLOAT).put("[D", T_DOUBLE) .put("[S", T_SHORT).put("[C", T_CHAR).put("[Z", T_BOOLEAN).build(); private static void addArrayValueInStack(MethodVisitor visitor, Object o, String description) { Class objectClass = toJavaClass(byteCodeName(description)); if (objectClass.isEnum()) { final String owner = byteCodeName(description); visitor.visitFieldInsn(GETSTATIC, owner, o.toString(), getDescription(owner)); } else if (o.getClass() == Annotation.class) { mapAnnotation(visitor, (Annotation) o); } else { visitor.visitLdcInsn(o); } } private static void createInnerClassConstructor(ClassWriter writer, BytecodeProperty property) { MethodVisitor constructor = writer.visitMethod(ACC_PROTECTED, "<init>", "()V", null, null); constructor.visitIntInsn(ALOAD, 0); constructor.visitLdcInsn(property.getName()); String propertyObjectName = property.getType(); if (property.isPrimitive()) { propertyObjectName = byteCodeName(Constants.SIMPLE_PROPERTIES.get(getDescription(propertyObjectName))); } constructor.visitLdcInsn(Type.getObjectType(propertyObjectName)); constructor.visitMethodInsn(INVOKESPECIAL, "org/ubiquity/mirror/impl/AbstractProperty", "<init>", "(Ljava/lang/String;Ljava/lang/Class;)V"); constructor.visitInsn(RETURN); constructor.visitMaxs(0, 0); constructor.visitEnd(); } private static void createGet(ClassWriter writer, BytecodeProperty property, String handledClassName, String innerClassName) { // Create actual get code String resolvedArgumentType = getDescription(property.getTypeGetter()); if (property.isPrimitive()) { resolvedArgumentType = Constants.SIMPLE_PROPERTIES.get(resolvedArgumentType); } String description = "(" + getDescription(handledClassName) + ")" + resolvedArgumentType; MethodVisitor visitor = writer.visitMethod(ACC_PUBLIC, "get", description, null, null); visitor.visitIntInsn(ALOAD, 1); visitor.visitMethodInsn(INVOKEVIRTUAL, handledClassName, property.getGetter(), "()" + getDescription(property.getTypeGetter())); if (property.isPrimitive()) { visitor.visitMethodInsn(INVOKESTATIC, "org/ubiquity/util/NativeConverter", "convert", "(" + property.getType() + ")" + resolvedArgumentType); } visitor.visitInsn(ARETURN); visitor.visitMaxs(0, 0); visitor.visitEnd(); // Create bridge code visitor = writer.visitMethod(ACC_PUBLIC | ACC_BRIDGE | ACC_VOLATILE | ACC_SYNTHETIC, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null); visitor.visitIntInsn(ALOAD, 0); visitor.visitIntInsn(ALOAD, 1); visitor.visitTypeInsn(CHECKCAST, byteCodeName(handledClassName)); visitor.visitMethodInsn(INVOKEVIRTUAL, innerClassName, "get", description); visitor.visitInsn(ARETURN); visitor.visitMaxs(0, 0); visitor.visitEnd(); } private static void createSet(ClassWriter writer, BytecodeProperty property, String handledClassName, String innerClassName) { // Create actual code String resolvedArgumentType = getDescription(property.getTypeSetter()); if (property.isPrimitive()) { resolvedArgumentType = Constants.SIMPLE_PROPERTIES.get(resolvedArgumentType); } String description = "(" + getDescription(handledClassName) + resolvedArgumentType + ")V"; MethodVisitor visitor = writer.visitMethod(ACC_PUBLIC, "set", description, null, null); visitor.visitIntInsn(ALOAD, 1); visitor.visitIntInsn(ALOAD, 2); if (property.isPrimitive()) { visitor.visitMethodInsn(INVOKESTATIC, "org/ubiquity/util/NativeConverter", "convert", "(" + resolvedArgumentType + ")" + property.getType()); } visitor.visitMethodInsn(INVOKEVIRTUAL, handledClassName, property.getSetter(), "(" + getDescription(property.getTypeSetter()) + ")V"); visitor.visitInsn(RETURN); visitor.visitMaxs(0, 0); visitor.visitEnd(); // create bridge visitor = writer.visitMethod(ACC_PUBLIC | ACC_BRIDGE | ACC_VOLATILE | ACC_SYNTHETIC, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V", null, null); visitor.visitIntInsn(ALOAD, 0); visitor.visitIntInsn(ALOAD, 1); visitor.visitTypeInsn(CHECKCAST, handledClassName); visitor.visitIntInsn(ALOAD, 2); visitor.visitTypeInsn(CHECKCAST, byteCodeName(resolvedArgumentType)); visitor.visitMethodInsn(INVOKEVIRTUAL, innerClassName, "set", description); visitor.visitInsn(RETURN); visitor.visitMaxs(0, 0); visitor.visitEnd(); } private static void createBooleanMethod(ClassWriter writer, String name) { MethodVisitor visitor = writer.visitMethod(ACC_PUBLIC, name, "()Z", null, null); visitor.visitInsn(ICONST_1); visitor.visitInsn(IRETURN); visitor.visitMaxs(0, 0); visitor.visitEnd(); } }