Implementation of Kotlin's 'target' annotation mapping to Java's 'Target' annotation + tests

This commit is contained in:
Mikhail Glukhikh
2015-07-14 19:42:05 +03:00
parent 2a1058ed63
commit af5e7f58da
62 changed files with 547 additions and 54 deletions
@@ -24,8 +24,10 @@ import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotated;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationArgumentVisitor;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationTarget;
import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.resolve.AnnotationTargetChecker;
import org.jetbrains.kotlin.resolve.constants.*;
import org.jetbrains.kotlin.resolve.constants.StringValue;
import org.jetbrains.kotlin.types.Flexibility;
@@ -34,8 +36,10 @@ import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.TypesPackage;
import org.jetbrains.org.objectweb.asm.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.*;
import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage.getClassObjectType;
@@ -123,6 +127,7 @@ public abstract class AnnotationCodegen {
ClassDescriptor classDescriptor = (ClassDescriptor) annotated;
if (classDescriptor.getKind() == ClassKind.ANNOTATION_CLASS) {
generateRetentionAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent);
generateTargetAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent);
}
}
}
@@ -166,6 +171,47 @@ public abstract class AnnotationCodegen {
generateAnnotationIfNotPresent(annotationDescriptorsAlreadyPresent, annotationClass);
}
private static final Map<AnnotationTarget, ElementType> annotationTargetMap =
new EnumMap<AnnotationTarget, ElementType>(AnnotationTarget.class);
static {
annotationTargetMap.put(AnnotationTarget.PACKAGE, ElementType.PACKAGE);
annotationTargetMap.put(AnnotationTarget.CLASSIFIER, ElementType.TYPE);
annotationTargetMap.put(AnnotationTarget.ANNOTATION_CLASS, ElementType.ANNOTATION_TYPE);
annotationTargetMap.put(AnnotationTarget.CONSTRUCTOR, ElementType.CONSTRUCTOR);
annotationTargetMap.put(AnnotationTarget.LOCAL_VARIABLE, ElementType.LOCAL_VARIABLE);
annotationTargetMap.put(AnnotationTarget.FUNCTION, ElementType.METHOD);
annotationTargetMap.put(AnnotationTarget.PROPERTY_GETTER, ElementType.METHOD);
annotationTargetMap.put(AnnotationTarget.PROPERTY_SETTER, ElementType.METHOD);
annotationTargetMap.put(AnnotationTarget.FIELD, ElementType.FIELD);
annotationTargetMap.put(AnnotationTarget.VALUE_PARAMETER, ElementType.PARAMETER);
}
private void generateTargetAnnotation(@NotNull ClassDescriptor classDescriptor, @NotNull Set<String> annotationDescriptorsAlreadyPresent) {
String descriptor = Type.getType(Target.class).getDescriptor();
if (!annotationDescriptorsAlreadyPresent.add(descriptor)) return;
Set<AnnotationTarget> targets = AnnotationTargetChecker.INSTANCE$.possibleTargetSet(classDescriptor);
Set<ElementType> javaTargets;
if (targets == null) {
javaTargets = getJavaTargetList(classDescriptor);
if (javaTargets == null) return;
}
else {
javaTargets = EnumSet.noneOf(ElementType.class);
for (AnnotationTarget target : targets) {
if (annotationTargetMap.get(target) == null) continue;
javaTargets.add(annotationTargetMap.get(target));
}
}
AnnotationVisitor visitor = visitAnnotation(descriptor, true);
AnnotationVisitor arrayVisitor = visitor.visitArray("value");
for (ElementType javaTarget : javaTargets) {
arrayVisitor.visitEnum(null, Type.getType(ElementType.class).getDescriptor(), javaTarget.name());
}
arrayVisitor.visitEnd();
visitor.visitEnd();
}
private void generateRetentionAnnotation(@NotNull ClassDescriptor classDescriptor, @NotNull Set<String> annotationDescriptorsAlreadyPresent) {
RetentionPolicy policy = getRetentionPolicy(classDescriptor);
String descriptor = Type.getType(Retention.class).getDescriptor();
@@ -337,18 +383,46 @@ public abstract class AnnotationCodegen {
}
}
@Nullable
private Set<ElementType> getJavaTargetList(ClassDescriptor descriptor) {
AnnotationDescriptor targetAnnotation = descriptor.getAnnotations().findAnnotation(new FqName(Target.class.getName()));
if (targetAnnotation != null) {
Collection<ConstantValue<?>> valueArguments = targetAnnotation.getAllValueArguments().values();
if (!valueArguments.isEmpty()) {
ConstantValue<?> compileTimeConstant = valueArguments.iterator().next();
if (compileTimeConstant instanceof ArrayValue) {
List<? extends ConstantValue<?>> values = ((ArrayValue) compileTimeConstant).getValue();
Set<ElementType> result = EnumSet.noneOf(ElementType.class);
for (ConstantValue<?> value : values) {
if (value instanceof EnumValue) {
ClassDescriptor enumEntry = ((EnumValue) value).getValue();
JetType classObjectType = getClassObjectType(enumEntry);
if (classObjectType != null) {
if ("java/lang/annotation/ElementType".equals(typeMapper.mapType(classObjectType).getInternalName())) {
result.add(ElementType.valueOf(enumEntry.getName().asString()));
}
}
}
}
return result;
}
}
}
return null;
}
@NotNull
private RetentionPolicy getRetentionPolicy(@NotNull Annotated descriptor) {
AnnotationDescriptor kotlinAnnotation = descriptor.getAnnotations().findAnnotation(KotlinBuiltIns.FQ_NAMES.annotation);
if (kotlinAnnotation != null) {
for (Map.Entry<ValueParameterDescriptor, ConstantValue<?>> argument: kotlinAnnotation.getAllValueArguments().entrySet()) {
for (Map.Entry<ValueParameterDescriptor, ConstantValue<?>> argument : kotlinAnnotation.getAllValueArguments().entrySet()) {
if ("retention".equals(argument.getKey().getName().asString()) && argument.getValue() instanceof EnumValue) {
ClassDescriptor enumEntry = ((EnumValue) argument.getValue()).getValue();
JetType classObjectType = getClassObjectType(enumEntry);
if (classObjectType != null) {
if ("kotlin/annotation/AnnotationRetention".equals(typeMapper.mapType(classObjectType).getInternalName())) {
String entryName = enumEntry.getName().asString();
for (KotlinRetention retention: KotlinRetention.values()) {
for (KotlinRetention retention : KotlinRetention.values()) {
if (retention.name().equals(entryName)) return retention.mapped;
}
}