diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaDescriptorResolver.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaDescriptorResolver.java index d565819dc5f..747dbfc9530 100644 --- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaDescriptorResolver.java +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaDescriptorResolver.java @@ -396,7 +396,6 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes .getResolverBinaryClassData(); classDescriptorCache.put(fqName, classData); classData.classDescriptor.setName(name); - classData.classDescriptor.setAnnotations(resolveAnnotations(psiClass, taskList)); List supertypes = new ArrayList(); @@ -433,6 +432,8 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes classData.classDescriptor.getBuilder().setClassObjectDescriptor(classObject); } + classData.classDescriptor.setAnnotations(resolveAnnotations(psiClass, taskList)); + trace.record(BindingContext.CLASS, psiClass, classData.classDescriptor); return classData; @@ -1580,57 +1581,219 @@ public class JavaDescriptorResolver implements DependencyClassByQualifiedNameRes @Nullable private AnnotationDescriptor resolveAnnotation(PsiAnnotation psiAnnotation, @NotNull List taskList) { final AnnotationDescriptor annotation = new AnnotationDescriptor(); - String qname = psiAnnotation.getQualifiedName(); - if (qname.startsWith("java.lang.annotation.") || qname.startsWith("jet.runtime.typeinfo.") || qname.equals(JvmAbi.JETBRAINS_NOT_NULL_ANNOTATION.getFqName().getFqName())) { - // TODO + if (qname == null) { return null; } - final ClassDescriptor clazz = resolveClass(new FqName(psiAnnotation.getQualifiedName()), DescriptorSearchRule.INCLUDE_KOTLIN, taskList); + // Don't process internal jet annotations and jetbrains NotNull annotations + if (qname.startsWith("jet.runtime.typeinfo.") || qname.equals(JvmAbi.JETBRAINS_NOT_NULL_ANNOTATION.getFqName().getFqName())) { + return null; + } + + FqName annotationFqName = new FqName(qname); + final ClassDescriptor clazz = resolveClass(annotationFqName, DescriptorSearchRule.INCLUDE_KOTLIN, taskList); if (clazz == null) { return null; } + taskList.add(new Runnable() { @Override public void run() { annotation.setAnnotationType(clazz.getDefaultType()); } }); - ArrayList> valueArguments = new ArrayList>(); + PsiAnnotationParameterList parameterList = psiAnnotation.getParameterList(); for (PsiNameValuePair psiNameValuePair : parameterList.getAttributes()) { PsiAnnotationMemberValue value = psiNameValuePair.getValue(); - if (!(value instanceof PsiLiteralExpression)) { - // todo - continue; + String name = psiNameValuePair.getName(); + if (name == null) name = "value"; + Name identifier = Name.identifier(name); + + CompileTimeConstant compileTimeConst = getCompileTimeConstFromExpression(annotationFqName, identifier, value, taskList); + if (compileTimeConst != null) { + ValueParameterDescriptor valueParameterDescriptor = getValueParameterDescriptorForAnnotationParameter(identifier, clazz); + if (valueParameterDescriptor != null) { + annotation.setValueArgument(valueParameterDescriptor, compileTimeConst); + } } - Object literalValue = ((PsiLiteralExpression) value).getValue(); - if (literalValue instanceof String) - valueArguments.add(new StringValue((String) literalValue)); - else if (literalValue instanceof Byte) - valueArguments.add(new ByteValue((Byte) literalValue)); - else if (literalValue instanceof Short) - valueArguments.add(new ShortValue((Short) literalValue)); - else if (literalValue instanceof Character) - valueArguments.add(new CharValue((Character) literalValue)); - else if (literalValue instanceof Integer) - valueArguments.add(new IntValue((Integer) literalValue)); - else if (literalValue instanceof Long) - valueArguments.add(new LongValue((Long) literalValue)); - else if (literalValue instanceof Float) - valueArguments.add(new FloatValue((Float) literalValue)); - else if (literalValue instanceof Double) - valueArguments.add(new DoubleValue((Double) literalValue)); - else if (literalValue == null) - valueArguments.add(NullValue.NULL); } - annotation.setValueArguments(valueArguments); // TODO return annotation; } + @Nullable + public static ValueParameterDescriptor getValueParameterDescriptorForAnnotationParameter( + Name argumentName, + ClassDescriptor classDescriptor + ) { + Collection constructors = classDescriptor.getConstructors(); + assert constructors.size() == 1 : "Annotation class descriptor must have only one constructor"; + List valueParameters = constructors.iterator().next().getValueParameters(); + + for (ValueParameterDescriptor parameter : valueParameters) { + Name parameterName = parameter.getName(); + if (parameterName.equals(argumentName)) { + return parameter; + } + } + return null; + } + + @Nullable + private CompileTimeConstant getCompileTimeConstFromExpression( + FqName annotationFqName, Name parameterName, + PsiAnnotationMemberValue value, List taskList + ) { + if (value instanceof PsiLiteralExpression) { + return getCompileTimeConstFromLiteralExpression((PsiLiteralExpression) value); + } + // Enum + else if (value instanceof PsiReferenceExpression) { + return getCompileTimeConstFromReferenceExpression((PsiReferenceExpression) value, taskList); + } + // Array + else if (value instanceof PsiArrayInitializerMemberValue) { + return getCompileTimeConstFromArrayExpression(annotationFqName, parameterName, (PsiArrayInitializerMemberValue) value, taskList); + } + // Annotation + else if (value instanceof PsiAnnotation) { + return getCompileTimeConstFromAnnotation((PsiAnnotation) value, taskList); + } + return null; + } + + @Nullable + private CompileTimeConstant getCompileTimeConstFromAnnotation(PsiAnnotation value, List taskList) { + AnnotationDescriptor annotationDescriptor = resolveAnnotation(value, taskList); + if (annotationDescriptor != null) { + return new AnnotationValue(annotationDescriptor); + } + return null; + } + + @Nullable + private CompileTimeConstant getCompileTimeConstFromArrayExpression( + FqName annotationFqName, + Name valueName, PsiArrayInitializerMemberValue value, + List taskList + ) { + PsiAnnotationMemberValue[] initializers = value.getInitializers(); + List> values = getCompileTimeConstantForArrayValues(annotationFqName, valueName, taskList, initializers); + + ClassDescriptor classDescriptor = resolveClass(annotationFqName, DescriptorSearchRule.INCLUDE_KOTLIN, taskList); + + JetType expectedArrayType = getExpectedArrayType(classDescriptor, valueName); + if (expectedArrayType == null) { + return null; + } + return new ArrayValue(values, expectedArrayType); + } + + private List> getCompileTimeConstantForArrayValues( + FqName annotationQualifiedName, + Name valueName, + List taskList, + PsiAnnotationMemberValue[] initializers + ) { + List> values = new ArrayList>(); + for (PsiAnnotationMemberValue initializer : initializers) { + CompileTimeConstant compileTimeConstant = + getCompileTimeConstFromExpression(annotationQualifiedName, valueName, initializer, taskList); + if (compileTimeConstant == null) { + compileTimeConstant = NullValue.NULL; + } + values.add(compileTimeConstant); + } + return values; + } + + @Nullable + private JetType getExpectedArrayType(ClassDescriptor clazz, Name argumentName) { + Collection constructors = clazz.getConstructors(); + assert constructors.size() == 1 : "Annotation class descriptor must have only one constructor"; + List valueParameters = constructors.iterator().next().getValueParameters(); + + for (ValueParameterDescriptor parameter : valueParameters) { + Name parameterName = parameter.getName(); + if (parameterName.equals(argumentName)) { + return parameter.getType(); + } + } + return null; + } + + @Nullable + private CompileTimeConstant getCompileTimeConstFromReferenceExpression(PsiReferenceExpression value, List taskList) { + PsiElement resolveElement = value.resolve(); + if (resolveElement instanceof PsiEnumConstant) { + PsiElement psiElement = resolveElement.getParent(); + if (psiElement instanceof PsiClass) { + PsiClass psiClass = (PsiClass) psiElement; + String fqName = psiClass.getQualifiedName(); + if (fqName == null) { + return null; + } + + JetScope scope; + ClassDescriptor classDescriptor = resolveClass(new FqName(fqName), DescriptorSearchRule.INCLUDE_KOTLIN, taskList); + if (classDescriptor == null) { + return null; + } + ClassDescriptor classObjectDescriptor = classDescriptor.getClassObjectDescriptor(); + if (classObjectDescriptor == null) { + return null; + } + scope = classObjectDescriptor.getMemberScope(Lists.newArrayList()); + + Name identifier = Name.identifier(((PsiEnumConstant) resolveElement).getName()); + Collection properties = scope.getProperties(identifier); + for (VariableDescriptor variableDescriptor : properties) { + if (!variableDescriptor.getReceiverParameter().exists()) { + return new EnumValue((PropertyDescriptor) variableDescriptor); + } + } + return null; + } + } + return null; + } + + @Nullable + private CompileTimeConstant getCompileTimeConstFromLiteralExpression(PsiLiteralExpression value) { + Object literalValue = value.getValue(); + if (literalValue instanceof String) { + return new StringValue((String) literalValue); + } + else if (literalValue instanceof Byte) { + return new ByteValue((Byte) literalValue); + } + else if (literalValue instanceof Short) { + return new ShortValue((Short) literalValue); + } + else if (literalValue instanceof Character) { + return new CharValue((Character) literalValue); + } + else if (literalValue instanceof Integer) { + return new IntValue((Integer) literalValue); + } + else if (literalValue instanceof Long) { + return new LongValue((Long) literalValue); + } + else if (literalValue instanceof Float) { + return new FloatValue((Float) literalValue); + } + else if (literalValue instanceof Double) { + return new DoubleValue((Double) literalValue); + } + else if (literalValue == null) { + return NullValue.NULL; + } + return null; + } + public List resolveMethods(@NotNull ResolverScopeData scopeData) { getResolverScopeData(scopeData); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/annotations/AnnotationArgumentVisitor.java b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/annotations/AnnotationArgumentVisitor.java index fbfcef05595..780e15717d0 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/annotations/AnnotationArgumentVisitor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/annotations/AnnotationArgumentVisitor.java @@ -45,4 +45,10 @@ public interface AnnotationArgumentVisitor { R visitStringValue(StringValue value, D data); R visitNullValue(NullValue value, D data); + + R visitEnumValue(EnumValue value, D data); + + R visitArrayValue(ArrayValue value, D data); + + R visitAnnotationValue(AnnotationValue value, D data); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/AnnotationValue.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/AnnotationValue.java new file mode 100644 index 00000000000..075971ba4ee --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/AnnotationValue.java @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * 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.jetbrains.jet.lang.resolve.constants; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.annotations.AnnotationArgumentVisitor; +import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; + +/** + * @author Natalia Ukhorskaya + */ +public class AnnotationValue implements CompileTimeConstant { + + private final AnnotationDescriptor value; + + public AnnotationValue(@NotNull AnnotationDescriptor value) { + this.value = value; + } + + @Override + @NotNull + public AnnotationDescriptor getValue() { + return value; + } + + @Override + @NotNull + public JetType getType(@NotNull JetStandardLibrary standardLibrary) { + return value.getType(); + } + + @Override + public R accept(AnnotationArgumentVisitor visitor, D data) { + return visitor.visitAnnotationValue(this, data); + } + + @Override + public String toString() { + return value.toString(); + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/ArrayValue.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/ArrayValue.java new file mode 100644 index 00000000000..53a8afea1e6 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/ArrayValue.java @@ -0,0 +1,93 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * 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.jetbrains.jet.lang.resolve.constants; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.annotations.AnnotationArgumentVisitor; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; + +import java.util.List; + +/** + * @author Natalia.Ukhorskaya + */ +public class ArrayValue implements CompileTimeConstant>> { + + private final List> value; + private final JetType type; + + public ArrayValue(@NotNull List> value, @NotNull JetType type) { + this.value = value; + this.type = type; + } + + @Override + @NotNull + public List> getValue() { + return value; + } + + @NotNull + @Override + public JetType getType(@NotNull JetStandardLibrary standardLibrary) { + return type; + } + + @Override + public R accept(AnnotationArgumentVisitor visitor, D data) { + return visitor.visitArrayValue(this, data); + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ArrayValue that = (ArrayValue) o; + + if (value == null) { + return that.value == null; + } + + int i = 0; + for (Object thisObject : value) { + if (!thisObject.equals(that.value.get(i))) { + return false; + } + i++; + } + + return true; + } + + @Override + public int hashCode() { + int hashCode = 0; + if (value == null) return hashCode; + for (Object o : value) { + hashCode += o.hashCode(); + } + return hashCode; + } +} + diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/EnumValue.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/EnumValue.java new file mode 100644 index 00000000000..d5f4cfc7cc0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/constants/EnumValue.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * 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.jetbrains.jet.lang.resolve.constants; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.lang.descriptors.PropertyDescriptor; +import org.jetbrains.jet.lang.descriptors.annotations.AnnotationArgumentVisitor; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; + +/** + * @author Natalia.Ukhorskaya + */ +public class EnumValue implements CompileTimeConstant { + + private final PropertyDescriptor value; + + public EnumValue(@NotNull PropertyDescriptor value) { + this.value = value; + } + + @Override + @NotNull + public PropertyDescriptor getValue() { + return value; + } + + @NotNull + @Override + public JetType getType(@NotNull JetStandardLibrary standardLibrary) { + return value.getType(); + } + + @Override + public R accept(AnnotationArgumentVisitor visitor, D data) { + return visitor.visitEnumValue(this, data); + } + + @Override + public String toString() { + return value.getType() + "." + value.getName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EnumValue enumValue = (EnumValue) o; + + return value.equals(enumValue.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} + diff --git a/compiler/testData/javaDescriptorResolver/annotations/annotationWithAnnotationInParam.java b/compiler/testData/javaDescriptorResolver/annotations/annotationWithAnnotationInParam.java new file mode 100644 index 00000000000..9d5bb086682 --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/annotationWithAnnotationInParam.java @@ -0,0 +1,37 @@ +package annotations; + +import java.lang.String; + +@interface MyAnnotationWithParam { + MyAnnotation value(); +} + +@interface MyAnnotation { + String value(); +} + +@MyAnnotationWithParam(@MyAnnotation("test")) +class A {} + +@interface MyAnnotation2 { + String[] value(); +} + +@interface MyAnnotationWithParam2 { + MyAnnotation2 value(); +} + +@MyAnnotationWithParam2(@MyAnnotation2({"test", "test2"})) +class B {} + +@interface MyAnnotation3 { + String first(); + String second(); +} + +@interface MyAnnotationWithParam3 { + MyAnnotation3 value(); +} + +@MyAnnotationWithParam3(@MyAnnotation3(first = "f", second = "s")) +class C {} diff --git a/compiler/testData/javaDescriptorResolver/annotations/annotationWithArrayOfEnumInParam.java b/compiler/testData/javaDescriptorResolver/annotations/annotationWithArrayOfEnumInParam.java new file mode 100644 index 00000000000..4794e5922f3 --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/annotationWithArrayOfEnumInParam.java @@ -0,0 +1,9 @@ +package annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.CONSTRUCTOR}) +@interface targetAnnotation { + String value(); +} diff --git a/compiler/testData/javaDescriptorResolver/annotations/annotationWithArrayOfStringInParam.java b/compiler/testData/javaDescriptorResolver/annotations/annotationWithArrayOfStringInParam.java new file mode 100644 index 00000000000..1790ac526fe --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/annotationWithArrayOfStringInParam.java @@ -0,0 +1,14 @@ +package annotations; + +import java.lang.String; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@interface MyAnnotation { + String[] value(); +} + +@MyAnnotation({"a", "b", "c"}) +class A { + +} diff --git a/compiler/testData/javaDescriptorResolver/annotations/annotationWithEmptyArrayInParam.java b/compiler/testData/javaDescriptorResolver/annotations/annotationWithEmptyArrayInParam.java new file mode 100644 index 00000000000..2198570f900 --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/annotationWithEmptyArrayInParam.java @@ -0,0 +1,14 @@ +package annotations; + +import java.lang.String; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@interface MyAnnotation { + String[] value(); +} + +@MyAnnotation({}) +class A { + +} diff --git a/compiler/testData/javaDescriptorResolver/annotations/annotationWithEnumInParam.java b/compiler/testData/javaDescriptorResolver/annotations/annotationWithEnumInParam.java new file mode 100644 index 00000000000..783dd98b55c --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/annotationWithEnumInParam.java @@ -0,0 +1,9 @@ +package annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@interface retentionAnnotation { + String value(); +} diff --git a/compiler/testData/javaDescriptorResolver/annotations/customAnnotation.java b/compiler/testData/javaDescriptorResolver/annotations/customAnnotation.java new file mode 100644 index 00000000000..e7bdff6d2e7 --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/customAnnotation.java @@ -0,0 +1,12 @@ +package annotations; + +@MyAnnotation(MyEnum.ONE) +class MyTest {} + +@interface MyAnnotation { + MyEnum value(); +} + +enum MyEnum { + ONE +} diff --git a/compiler/testData/javaDescriptorResolver/annotations/customAnnotationWithDefaultParameter.java b/compiler/testData/javaDescriptorResolver/annotations/customAnnotationWithDefaultParameter.java new file mode 100644 index 00000000000..e3e2df2c1f4 --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/customAnnotationWithDefaultParameter.java @@ -0,0 +1,10 @@ +package annotations; + +@MyAnnotation(first = "f", second = "s") +class MyTest {} + +@interface MyAnnotation { + String first(); + String second() default("s"); +} + diff --git a/compiler/testData/javaDescriptorResolver/annotations/customAnnotationWithKotlinEnum.java b/compiler/testData/javaDescriptorResolver/annotations/customAnnotationWithKotlinEnum.java new file mode 100644 index 00000000000..4d629b4d79b --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/customAnnotationWithKotlinEnum.java @@ -0,0 +1,10 @@ +package annotations; + +import annotations.MyEnum; + +@interface MyAnnotation { + MyEnum value(); +} + +@MyAnnotation(MyEnum.ONE) +class testClass {} diff --git a/compiler/testData/javaDescriptorResolver/annotations/kotlinEnum.kt b/compiler/testData/javaDescriptorResolver/annotations/kotlinEnum.kt new file mode 100644 index 00000000000..41f39eda38f --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/kotlinEnum.kt @@ -0,0 +1,6 @@ +package annotations + +public enum class MyEnum { + ONE +} + diff --git a/compiler/testData/javaDescriptorResolver/annotations/recursiveAnnotation.java b/compiler/testData/javaDescriptorResolver/annotations/recursiveAnnotation.java new file mode 100644 index 00000000000..0c389274cbc --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/recursiveAnnotation.java @@ -0,0 +1,11 @@ +package annotations; + +@B(@A("test")) +@interface A { + String value(); +} + +@B(@A("test")) +@interface B { + A value(); +} diff --git a/compiler/testData/javaDescriptorResolver/annotations/recursiveAnnotation2.java b/compiler/testData/javaDescriptorResolver/annotations/recursiveAnnotation2.java new file mode 100644 index 00000000000..c65aac41586 --- /dev/null +++ b/compiler/testData/javaDescriptorResolver/annotations/recursiveAnnotation2.java @@ -0,0 +1,10 @@ +package annotations; + +@interface A { + B value(); +} + +@A(@B("test")) +@interface B { + String value(); +} diff --git a/compiler/testData/loadJava/annotation/AnnotatedAnnotation.java b/compiler/testData/loadJava/annotation/AnnotatedAnnotation.java index 96917bc5fc6..974035705b2 100644 --- a/compiler/testData/loadJava/annotation/AnnotatedAnnotation.java +++ b/compiler/testData/loadJava/annotation/AnnotatedAnnotation.java @@ -1,6 +1,6 @@ package test; -@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) +//@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @AnnotatedAnnotation public @interface AnnotatedAnnotation { } diff --git a/compiler/tests/org/jetbrains/jet/jvm/compiler/AnnotationJavaDescriptorResolverTest.java b/compiler/tests/org/jetbrains/jet/jvm/compiler/AnnotationJavaDescriptorResolverTest.java new file mode 100644 index 00000000000..40fe51ee167 --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/jvm/compiler/AnnotationJavaDescriptorResolverTest.java @@ -0,0 +1,319 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * 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.jetbrains.jet.jvm.compiler; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.CompileCompilerDependenciesTest; +import org.jetbrains.jet.ConfigurationKind; +import org.jetbrains.jet.JetTestUtils; +import org.jetbrains.jet.TestJdkKind; +import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment; +import org.jetbrains.jet.di.InjectorForJavaSemanticServices; +import org.jetbrains.jet.lang.BuiltinsScopeExtensionMode; +import org.jetbrains.jet.lang.descriptors.ClassDescriptor; +import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor; +import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor; +import org.jetbrains.jet.lang.resolve.constants.AnnotationValue; +import org.jetbrains.jet.lang.resolve.constants.ArrayValue; +import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant; +import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver; +import org.jetbrains.jet.lang.resolve.name.FqName; +import org.jetbrains.jet.lang.resolve.name.Name; +import org.jetbrains.jet.lang.types.JetType; +import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; +import org.jetbrains.jet.resolve.DescriptorRenderer; +import org.jetbrains.jet.test.TestCaseWithTmpdir; +import org.jetbrains.jet.utils.PathUtil; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver.getValueParameterDescriptorForAnnotationParameter; + +/** + * @author Natalia Ukhorskaya + */ +public class AnnotationJavaDescriptorResolverTest extends TestCaseWithTmpdir { + + private static final String PATH = "compiler/testData/javaDescriptorResolver/annotations/"; + private static final String DEFAULT_PACKAGE = "annotations"; + + private JavaDescriptorResolver javaDescriptorResolver; + + public void testCustomAnnotationWithKotlinEnum() throws IOException { + File testFile = new File(PATH + "kotlinEnum.kt"); + + LoadDescriptorUtil.compileKotlinToDirAndGetAnalyzeExhaust(testFile, tmpdir, myTestRootDisposable); + + StringBuilder builder = new StringBuilder(tmpdir.getAbsolutePath()); + builder.append(File.pathSeparator); + File runtimePath = PathUtil.getDefaultRuntimePath(); + if (runtimePath != null) { + builder.append(runtimePath.getAbsolutePath()); + builder.append(File.pathSeparator); + } + + File annotationsPath = PathUtil.getJdkAnnotationsPath(); + if (annotationsPath != null) { + builder.append(annotationsPath.getAbsolutePath()); + } + + setUpJavaDescriptorResolver(); + compileJavaFile("customAnnotationWithKotlinEnum.java", builder.toString()); + + String annotationTypeName = DEFAULT_PACKAGE + ".MyAnnotation"; + AnnotationDescriptor annotation = getAnnotationInClassByType("testClass", annotationTypeName); + + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + checkSimpleCompileTimeConstant(actualCompileTimeConstant, DEFAULT_PACKAGE + ".MyEnum", "MyEnum.ONE"); + } + + public void testCustomAnnotation() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("customAnnotation.java", null); + String annotationTypeName = DEFAULT_PACKAGE + ".MyAnnotation"; + AnnotationDescriptor annotation = getAnnotationInClassByType("MyTest", annotationTypeName); + + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + checkSimpleCompileTimeConstant(actualCompileTimeConstant, DEFAULT_PACKAGE + ".MyEnum", "MyEnum.ONE"); + } + + public void testCustomAnnotationWithDefaultParameter() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("customAnnotationWithDefaultParameter.java", null); + String annotationTypeName = DEFAULT_PACKAGE + ".MyAnnotation"; + AnnotationDescriptor annotation = getAnnotationInClassByType("MyTest", annotationTypeName); + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "first"); + checkSimpleCompileTimeConstant(actualCompileTimeConstant, "jet.String", "\"f\""); + + actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "second"); + checkSimpleCompileTimeConstant(actualCompileTimeConstant, "jet.String", "\"s\""); + } + + public void testAnnotationWithAnnotationInParam() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("annotationWithAnnotationInParam.java", null); + String annotationTypeName = DEFAULT_PACKAGE + ".MyAnnotationWithParam"; + AnnotationDescriptor annotation = getAnnotationInClassByType("A", + annotationTypeName); + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + assert actualCompileTimeConstant instanceof AnnotationValue; + checkAnnotationCompileTimeConstant((AnnotationValue) actualCompileTimeConstant, "value", + DEFAULT_PACKAGE + ".MyAnnotation", "jet.String", "\"test\""); + + String annotationTypeName2 = DEFAULT_PACKAGE + ".MyAnnotationWithParam2"; + AnnotationDescriptor annotation2 = getAnnotationInClassByType("B", annotationTypeName2); + actualCompileTimeConstant = getCompileTimeConstant(annotation2, annotationTypeName2, "value"); + assert actualCompileTimeConstant instanceof AnnotationValue; + checkAnnotationCompileTimeConstant((AnnotationValue) actualCompileTimeConstant, "value", + DEFAULT_PACKAGE + ".MyAnnotation2", "jet.Array?", "[\"test\", \"test2\"]"); + + String annotationTypeName3 = DEFAULT_PACKAGE + ".MyAnnotationWithParam3"; + AnnotationDescriptor annotation3 = getAnnotationInClassByType("C", annotationTypeName3); + + actualCompileTimeConstant = getCompileTimeConstant(annotation3, annotationTypeName3, "value"); + assert actualCompileTimeConstant instanceof AnnotationValue; + checkAnnotationCompileTimeConstant((AnnotationValue) actualCompileTimeConstant, "first", + DEFAULT_PACKAGE + ".MyAnnotation3", "jet.String", "\"f\""); + checkAnnotationCompileTimeConstant((AnnotationValue) actualCompileTimeConstant, "second", + DEFAULT_PACKAGE + ".MyAnnotation3", "jet.String", "\"s\""); + } + + public void testRecursiveAnnotation() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("recursiveAnnotation.java", null); + String annotationTypeName = DEFAULT_PACKAGE + ".B"; + AnnotationDescriptor annotation = getAnnotationInClassByType("A", annotationTypeName); + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + assert actualCompileTimeConstant instanceof AnnotationValue; + checkAnnotationCompileTimeConstant((AnnotationValue) actualCompileTimeConstant, "value", + DEFAULT_PACKAGE + ".A", "jet.String", "\"test\""); + + AnnotationDescriptor annotation2 = getAnnotationInClassByType("B", annotationTypeName); + actualCompileTimeConstant = getCompileTimeConstant(annotation2, annotationTypeName, "value"); + assert actualCompileTimeConstant instanceof AnnotationValue; + checkAnnotationCompileTimeConstant((AnnotationValue) actualCompileTimeConstant, "value", + DEFAULT_PACKAGE + ".A", "jet.String", "\"test\""); + } + + public void testRecursiveAnnotation2() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("recursiveAnnotation2.java", null); + String annotationTypeName = DEFAULT_PACKAGE + ".A"; + AnnotationDescriptor annotation = getAnnotationInClassByType("B", annotationTypeName); + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + assert actualCompileTimeConstant instanceof AnnotationValue; + checkAnnotationCompileTimeConstant((AnnotationValue) actualCompileTimeConstant, "value", + DEFAULT_PACKAGE + ".B", "jet.String", "\"test\""); + } + + public void testAnnotationWithEnumInParam() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("annotationWithEnumInParam.java", null); + String annotationTypeName = "java.lang.annotation.Retention"; + AnnotationDescriptor annotation = getAnnotationInClassByType("retentionAnnotation", annotationTypeName); + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + checkSimpleCompileTimeConstant(actualCompileTimeConstant, "java.lang.annotation.RetentionPolicy", "RetentionPolicy.RUNTIME"); + } + + public void testAnnotationWithArrayOfEnumInParam() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("annotationWithArrayOfEnumInParam.java", null); + String annotationTypeName = "java.lang.annotation.Target"; + AnnotationDescriptor annotation = getAnnotationInClassByType("targetAnnotation", annotationTypeName); + assertEquals("Number of arguments is incorrect", 1, annotation.getAllValueArguments().size()); + String[] values = new String[] {"ElementType.FIELD", "ElementType.CONSTRUCTOR"}; + + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + assert actualCompileTimeConstant instanceof ArrayValue; + checkArrayCompileTimeConstant((ArrayValue) actualCompileTimeConstant, "jet.Array?", + "java.lang.annotation.ElementType", values); + } + + public void testAnnotationWithArrayOfStringInParam() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("annotationWithArrayOfStringInParam.java", null); + String annotationTypeName = DEFAULT_PACKAGE + ".MyAnnotation"; + AnnotationDescriptor annotation = getAnnotationInClassByType("A", annotationTypeName); + assertEquals("Number of arguments is incorrect", 1, annotation.getAllValueArguments().size()); + String[] values = new String[] {"\"a\"", "\"b\"", "\"c\""}; + + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + assert actualCompileTimeConstant instanceof ArrayValue; + checkArrayCompileTimeConstant((ArrayValue) actualCompileTimeConstant, "jet.Array?", "jet.String", values); + } + + public void testAnnotationWithEmptyArrayInParam() throws IOException { + setUpJavaDescriptorResolver(); + compileJavaFile("annotationWithEmptyArrayInParam.java", null); + String annotationTypeName = DEFAULT_PACKAGE + ".MyAnnotation"; + AnnotationDescriptor annotation = getAnnotationInClassByType("A", annotationTypeName); + + assertEquals("Number of arguments is incorrect", 1, annotation.getAllValueArguments().size()); + String[] values = new String[] {}; + + CompileTimeConstant actualCompileTimeConstant = getCompileTimeConstant(annotation, annotationTypeName, "value"); + assert actualCompileTimeConstant instanceof ArrayValue; + checkArrayCompileTimeConstant((ArrayValue) actualCompileTimeConstant, "jet.Array?", "jet.String", values); + } + + private void compileJavaFile(@NotNull String fileRelativePath, @Nullable String classPath) + throws IOException { + File javaFile = new File(PATH + fileRelativePath); + assertNotNull(javaFile); + List options = new ArrayList(); + options.add("-d"); + options.add(tmpdir.getPath()); + if (classPath != null) { + options.add("-cp"); + options.add(classPath); + } + JetTestUtils.compileJavaFiles(Collections.singleton(javaFile), options); + } + + private void setUpJavaDescriptorResolver() { + JetCoreEnvironment jetCoreEnvironment = + new JetCoreEnvironment(myTestRootDisposable, CompileCompilerDependenciesTest.compilerConfigurationForTests( + ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK, JetTestUtils.getAnnotationsJar(), tmpdir)); + + InjectorForJavaSemanticServices injector = new InjectorForJavaSemanticServices(BuiltinsScopeExtensionMode.ALL, + jetCoreEnvironment.getProject()); + javaDescriptorResolver = injector.getJavaDescriptorResolver(); + } + + private static void compareJetTypeWithClass(@NotNull JetType actualType, @NotNull String expectedType) { + assertEquals(expectedType, DescriptorRenderer.TEXT.renderType(actualType)); + } + + @NotNull + private CompileTimeConstant getCompileTimeConstant( + @NotNull AnnotationDescriptor annotationDescriptor, + @NotNull String annotationType, + @NotNull String parameterName + ) { + ValueParameterDescriptor valueParameterDescriptor = getValueParameterDescriptor(annotationType, parameterName); + CompileTimeConstant actualCompileTimeValue = annotationDescriptor.getValueArgument(valueParameterDescriptor); + assertNotNull(actualCompileTimeValue); + return actualCompileTimeValue; + } + + @NotNull + private ValueParameterDescriptor getValueParameterDescriptor(@NotNull String annotationTypeName, @NotNull String parameterName) { + ClassDescriptor clazz = javaDescriptorResolver.resolveClass(new FqName(annotationTypeName)); + assertNotNull("Cannot resolve class with name " + annotationTypeName, clazz); + ValueParameterDescriptor valueParameterDescriptor = + getValueParameterDescriptorForAnnotationParameter(Name.identifier(parameterName), clazz); + assertNotNull("Cannot resolve value parameter for " + parameterName, valueParameterDescriptor); + return valueParameterDescriptor; + } + + @NotNull + private AnnotationDescriptor getAnnotationInClassByType(@NotNull String className, @NotNull String type) throws IOException { + ClassDescriptor classDescriptor = javaDescriptorResolver.resolveClass(new FqName(DEFAULT_PACKAGE + "." + className)); + assertNotNull("Cannot resolve class with name " + className, classDescriptor); + List annotations = classDescriptor.getAnnotations(); + assertEquals(annotations.size(), 1); + for (AnnotationDescriptor annotation : annotations) { + if (type.endsWith(annotation.getType().toString())) { + compareJetTypeWithClass(annotation.getType(), type); + return annotation; + } + } + fail("Cannot find annotation for class " + className + ", type " + type); + return null; + } + + private static void checkSimpleCompileTimeConstant(@NotNull CompileTimeConstant actual, @NotNull String expectedType, @NotNull String expectedValue) { + assertEquals(expectedValue, actual.toString()); + compareJetTypeWithClass(actual.getType(JetStandardLibrary.getInstance()), expectedType); + } + + private void checkAnnotationCompileTimeConstant( + @NotNull AnnotationValue actual, + @NotNull String parameterName, + @NotNull String expectedType, + @NotNull String expectedParameterType, + @NotNull String expectedParameterValue + ) { + compareJetTypeWithClass(actual.getType(JetStandardLibrary.getInstance()), expectedType); + CompileTimeConstant innerAnnotation = getCompileTimeConstant(actual.getValue(), expectedType, parameterName); + checkSimpleCompileTimeConstant(innerAnnotation, expectedParameterType, expectedParameterValue); + } + + private static void checkArrayCompileTimeConstant( + @NotNull ArrayValue actual, + @NotNull String expectedType, + @NotNull String expectedArgumentType, + @NotNull String[] expectedValues + ) { + JetType actualType = actual.getType(JetStandardLibrary.getInstance()); + compareJetTypeWithClass(actualType, expectedType); + + List> arrayValuesCompileTimeConst = actual.getValue(); + + assertEquals("Number of arguments is incorrect", expectedValues.length, arrayValuesCompileTimeConst.size()); + int i = 0; + for (CompileTimeConstant constant : arrayValuesCompileTimeConst) { + checkSimpleCompileTimeConstant(constant, expectedArgumentType, expectedValues[i]); + i++; + } + } +}