Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.robovm.compiler.plugin.annotation.AnnotationImplPlugin Maven / Gradle / Ivy
/*
* Copyright (C) 2014 Trillian Mobile AB
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.robovm.compiler.plugin.annotation;
import static org.objectweb.asm.Opcodes.*;
import static org.robovm.compiler.Annotations.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.robovm.compiler.ModuleBuilder;
import org.robovm.compiler.Types;
import org.robovm.compiler.clazz.Clazz;
import org.robovm.compiler.config.Config;
import org.robovm.compiler.plugin.AbstractCompilerPlugin;
import org.robovm.compiler.plugin.CompilerPlugin;
import soot.PrimType;
import soot.SootClass;
import soot.SootMethod;
import soot.tagkit.AnnotationClassElem;
import soot.tagkit.AnnotationDefaultTag;
import soot.tagkit.AnnotationDoubleElem;
import soot.tagkit.AnnotationEnumElem;
import soot.tagkit.AnnotationFloatElem;
import soot.tagkit.AnnotationIntElem;
import soot.tagkit.AnnotationLongElem;
import soot.tagkit.AnnotationStringElem;
import soot.tagkit.AnnotationTag;
/**
* {@link CompilerPlugin} which generates implementation classes for runtime
* visible annotations.
*/
public class AnnotationImplPlugin extends AbstractCompilerPlugin {
private static final int MOD_ANNOTATION = 0x00002000;
public static final String IMPL_CLASS_NAME_SUFFIX = "$Impl";
private static final String BASE_CLASS = "org/robovm/rt/annotation/Annotation";
private boolean initialized = false;
private void init() {
if (initialized) {
return;
}
initialized = true;
}
private void generateMemberFieldsAndAccessorMethods(Clazz clazz, ClassWriter cw) throws IOException {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
SootClass sootClass = clazz.getSootClass();
List methods = sootClass.getMethods();
for (SootMethod method : methods) {
String fieldName = getFieldName(method);
soot.Type type = method.getReturnType();
String typeDesc = Types.getDescriptor(type);
// Add the field. Values are always stored as Object. If there was
// an error in the annotation in the class file this will be an
// Exception which will get thrown by the accessor method.
cw.visitField(ACC_PRIVATE, fieldName, "Ljava/lang/Object;", null, null)
.visitEnd();
// Add the public accessor method
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, method.getName(),
Types.getDescriptor(method), null, null);
mv.visitCode();
// v = validate(, )
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, implName, fieldName, "Ljava/lang/Object;");
mv.visitLdcInsn(method.getName());
mv.visitMethodInsn(INVOKESPECIAL, BASE_CLASS, "validate", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;");
// return v (unboxed if needed)
int retOp = 0;
switch (typeDesc.charAt(0)) {
case 'Z':
case 'B':
case 'S':
case 'C':
case 'I':
retOp = IRETURN;
break;
case 'J':
retOp = LRETURN;
break;
case 'F':
retOp = FRETURN;
break;
case 'D':
retOp = DRETURN;
break;
default:
retOp = ARETURN;
break;
}
unboxIfNeeded(mv, type);
if (!(type instanceof PrimType)) {
// Reference type
mv.visitTypeInsn(CHECKCAST, Types.getInternalName(type));
}
mv.visitInsn(retOp);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}
private void generateSetDefaultsMethod(Clazz clazz, ClassWriter cw) {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
SootClass sootClass = clazz.getSootClass();
List methods = sootClass.getMethods();
// Generate the $setDefaults() method which is called from the constructor
// to set any default values
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE, "$setDefaults", "()V", null, null);
mv.visitCode();
for (SootMethod method : methods) {
AnnotationDefaultTag defTag = (AnnotationDefaultTag) method.getTag(AnnotationDefaultTag.class.getSimpleName());
String fieldName = getFieldName(method);
if (defTag == null) {
// No default value. Set field to super.NO_VALUE.
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETSTATIC, BASE_CLASS, "NO_VALUE", "Ljava/lang/Object;");
mv.visitFieldInsn(PUTFIELD, implName, fieldName, "Ljava/lang/Object;");
} else {
soot.Type type = method.getReturnType();
String typeDesc = Types.getDescriptor(type);
Object v = null;
if (type instanceof PrimType) {
switch (typeDesc.charAt(0)) {
case 'Z':
case 'B':
case 'S':
case 'C':
case 'I':
v = ((AnnotationIntElem) defTag.getDefaultVal()).getValue();
break;
case 'J':
v = ((AnnotationLongElem) defTag.getDefaultVal()).getValue();
break;
case 'F':
v = ((AnnotationFloatElem) defTag.getDefaultVal()).getValue();
break;
case 'D':
v = ((AnnotationDoubleElem) defTag.getDefaultVal()).getValue();
break;
}
} else if ("Ljava/lang/Class;".equals(typeDesc)) {
v = Type.getType(((AnnotationClassElem) defTag.getDefaultVal()).getDesc());
if (((Type) v).getDescriptor().length() != 1) {
// Only use a simple LDC for primitive classes (e.g. byte.class).
// Other classes may not be available at runtime. By falling back
// to Method.getDefaultValue() below we will get the proper
// exception at runtime.
v = null;
}
} else if ("Ljava/lang/String;".equals(typeDesc)) {
v = ((AnnotationStringElem) defTag.getDefaultVal()).getValue();
}
if (v != null) {
mv.visitVarInsn(ALOAD, 0);
if (v instanceof Type && ((Type) v).getDescriptor().length() == 1) {
// LDC of primitive type class such as byte.class
switch (((Type) v).getDescriptor().charAt(0)) {
case 'V':
mv.visitFieldInsn(GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
break;
case 'Z':
mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
break;
case 'B':
mv.visitFieldInsn(GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;");
break;
case 'S':
mv.visitFieldInsn(GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;");
break;
case 'C':
mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;");
break;
case 'I':
mv.visitFieldInsn(GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;");
break;
case 'J':
mv.visitFieldInsn(GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;");
break;
case 'F':
mv.visitFieldInsn(GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;");
break;
case 'D':
mv.visitFieldInsn(GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;");
break;
}
} else {
mv.visitLdcInsn(v);
}
boxIfNeeded(mv, type);
mv.visitFieldInsn(PUTFIELD, implName, fieldName, "Ljava/lang/Object;");
} else {
// Must be class, enum, array type or annotation. Fall back to super.getDefaultValue().
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitLdcInsn(method.getName());
mv.visitMethodInsn(INVOKESPECIAL, BASE_CLASS, "getDefaultValue", "(Ljava/lang/String;)Ljava/lang/Object;");
mv.visitFieldInsn(PUTFIELD, implName, fieldName, "Ljava/lang/Object;");
}
}
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private String getFieldName(SootMethod method) {
String fieldName = "m$" + method.getName();
return fieldName;
}
private void generateConstructor(Clazz clazz, ClassWriter cw) {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
// Default constructor
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn(Type.getObjectType(clazz.getInternalName()));
mv.visitMethodInsn(INVOKESPECIAL, BASE_CLASS, "", "(Ljava/lang/Class;)V");
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, implName, "$setDefaults", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generateAnnotationTypeMethod(Clazz clazz, ClassWriter cw) {
// Class extends Annotation> annotationType();
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "annotationType", "()Ljava/lang/Class;", null, null);
mv.visitCode();
mv.visitLdcInsn(Type.getObjectType(clazz.getInternalName()));
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generateMembersToStringMethod(Clazz clazz, ClassWriter cw) {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
// void membersToString(StringBuilder sb);
MethodVisitor mv = cw.visitMethod(ACC_PROTECTED, "membersToString", "(Ljava/lang/StringBuilder;)V", null, null);
mv.visitCode();
boolean first = true;
for (SootMethod method : clazz.getSootClass().getMethods()) {
String fieldName = getFieldName(method);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, implName, fieldName, "Ljava/lang/Object;");
mv.visitLdcInsn(method.getName());
mv.visitInsn(first ? ICONST_1 : ICONST_0);
mv.visitMethodInsn(INVOKESPECIAL, BASE_CLASS, "memberToString", "(Ljava/lang/StringBuilder;Ljava/lang/Object;Ljava/lang/String;Z)V");
first = false;
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generateFastEqualsMethod(Clazz clazz, ClassWriter cw) {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
// boolean fastEquals(Object that);
// that is known to of the same class as us.
MethodVisitor mv = cw.visitMethod(ACC_PROTECTED, "fastEquals", "(Ljava/lang/Object;)Z", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, implName);
mv.visitVarInsn(ASTORE, 1);
for (SootMethod method : clazz.getSootClass().getMethods()) {
String fieldName = getFieldName(method);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, implName, fieldName, "Ljava/lang/Object;");
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(GETFIELD, implName, fieldName, "Ljava/lang/Object;");
mv.visitMethodInsn(INVOKESPECIAL, BASE_CLASS, "memberEquals", "(Ljava/lang/Object;Ljava/lang/Object;)Z");
Label l1 = new Label();
mv.visitJumpInsn(IFNE, l1);
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
mv.visitLabel(l1);
}
// return true;
mv.visitInsn(ICONST_1);
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void boxIfNeeded(MethodVisitor mv, soot.Type type) {
if (type instanceof PrimType) {
String typeDesc = Types.getDescriptor(type);
String wrapperType = null;
switch (typeDesc.charAt(0)) {
case 'Z':
wrapperType = "java/lang/Boolean";
break;
case 'B':
wrapperType = "java/lang/Byte";
break;
case 'S':
wrapperType = "java/lang/Short";
break;
case 'C':
wrapperType = "java/lang/Character";
break;
case 'I':
wrapperType = "java/lang/Integer";
break;
case 'J':
wrapperType = "java/lang/Long";
break;
case 'F':
wrapperType = "java/lang/Float";
break;
case 'D':
wrapperType = "java/lang/Double";
break;
}
mv.visitMethodInsn(INVOKESTATIC, BASE_CLASS, "box", "(" + typeDesc + ")L" + wrapperType + ";");
}
}
private void unboxIfNeeded(MethodVisitor mv, soot.Type type) {
if (type instanceof PrimType) {
String typeDesc = Types.getDescriptor(type);
String wrapperType = null;
switch (typeDesc.charAt(0)) {
case 'Z':
wrapperType = "java/lang/Boolean";
break;
case 'B':
wrapperType = "java/lang/Byte";
break;
case 'S':
wrapperType = "java/lang/Short";
break;
case 'C':
wrapperType = "java/lang/Character";
break;
case 'I':
wrapperType = "java/lang/Integer";
break;
case 'J':
wrapperType = "java/lang/Long";
break;
case 'F':
wrapperType = "java/lang/Float";
break;
case 'D':
wrapperType = "java/lang/Double";
break;
}
mv.visitTypeInsn(CHECKCAST, wrapperType);
mv.visitMethodInsn(INVOKESTATIC, BASE_CLASS, "unbox", "(L" + wrapperType + ";)" + typeDesc);
}
}
private void generateSlowEqualsMethod(Clazz clazz, ClassWriter cw) {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
// boolean slowEquals(Object that);
// that is known to be an instance of the same interface but not an instance of our impl class.
MethodVisitor mv = cw.visitMethod(ACC_PROTECTED, "slowEquals", "(Ljava/lang/Object;)Z", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, clazz.getInternalName());
mv.visitVarInsn(ASTORE, 1);
Label l1 = new Label();
for (SootMethod method : clazz.getSootClass().getMethods()) {
String fieldName = getFieldName(method);
soot.Type type = method.getReturnType();
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, implName, fieldName, "Ljava/lang/Object;");
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEINTERFACE, clazz.getInternalName(), method.getName(), "()" + Types.getDescriptor(type));
boxIfNeeded(mv, type);
mv.visitMethodInsn(INVOKESPECIAL, BASE_CLASS, "memberEquals", "(Ljava/lang/Object;Ljava/lang/Object;)Z");
mv.visitJumpInsn(IFEQ, l1);
}
// return true;
mv.visitInsn(ICONST_1);
mv.visitInsn(IRETURN);
// return false
mv.visitLabel(l1);
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generateHashCodeMethod(Clazz clazz, ClassWriter cw) {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
// int hashCode();
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
mv.visitCode();
mv.visitInsn(ICONST_0);
for (SootMethod method : clazz.getSootClass().getMethods()) {
String fieldName = getFieldName(method);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(DUP);
mv.visitFieldInsn(GETFIELD, implName, fieldName, "Ljava/lang/Object;");
mv.visitLdcInsn(method.getName());
mv.visitMethodInsn(INVOKESPECIAL, BASE_CLASS, "hash", "(Ljava/lang/Object;Ljava/lang/String;)I");
mv.visitInsn(IADD);
}
mv.visitInsn(IRETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generateFactoryMethod(Clazz clazz, ClassWriter cw) throws IOException {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "$create",
"()Ljava/lang/Object;", null, null);
mv.visitCode();
if (clazz.getSootClass().getMethodCount() == 0) {
// This annotation has no members. Just call $createSingleton().
mv.visitMethodInsn(INVOKESTATIC, implName, "$createSingleton", "()Ljava/lang/Object;");
} else {
mv.visitTypeInsn(NEW, implName);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, implName, "", "()V");
}
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generateSingletonFactoryMethod(Clazz clazz, ClassWriter cw) throws IOException {
String implName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
{
FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
"$instance", "L" + implName + ";", null, null);
fv.visitEnd();
}
{
MethodVisitor mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, implName);
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, implName, "", "()V");
mv.visitFieldInsn(PUTSTATIC, implName, "$instance", "L" + implName + ";");
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
{
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "$createSingleton",
"()Ljava/lang/Object;", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, implName, "$instance", "L" + implName + ";");
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}
@Override
public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) {
init();
SootClass sootClass = clazz.getSootClass();
if ((sootClass.getModifiers() & MOD_ANNOTATION) > 0) {
try {
String implInternalName = clazz.getInternalName() + IMPL_CLASS_NAME_SUFFIX;
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_7, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC + ACC_PUBLIC,
implInternalName, null,
BASE_CLASS,
new String[] {clazz.getInternalName()});
generateConstructor(clazz, cw);
generateAnnotationTypeMethod(clazz, cw);
generateMembersToStringMethod(clazz, cw);
generateFastEqualsMethod(clazz, cw);
generateSlowEqualsMethod(clazz, cw);
generateHashCodeMethod(clazz, cw);
generateMemberFieldsAndAccessorMethods(clazz, cw);
generateSetDefaultsMethod(clazz, cw);
generateSingletonFactoryMethod(clazz, cw);
generateFactoryMethod(clazz, cw);
cw.visitEnd();
File f = clazz.getPath().getGeneratedClassFile(implInternalName);
FileUtils.writeByteArrayToFile(f, cw.toByteArray());
// The impl class is created after the interface is compiled.
// This prevents the triggering of a recompile of the interface.
f.setLastModified(clazz.lastModified());
// Add the impl class as a dependency for the annotation interface.
// Important! This must be done AFTER the class file has been written.
clazz.getClazzInfo().addDependency(implInternalName);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}