diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java index d0b90bea744..94b6b2a1605 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11TypeMapping.java @@ -18,6 +18,7 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.*; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Pair; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.java.JavaTypeMapping; @@ -28,14 +29,13 @@ import javax.lang.model.type.NullType; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; @RequiredArgsConstructor @@ -684,22 +684,66 @@ private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { } } - private @Nullable List listAnnotations(Symbol symb) { + private @Nullable List listAnnotations(Symbol sym) { List annotations = null; - if (!symb.getDeclarationAttributes().isEmpty()) { - annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); - for (Attribute.Compound a : symb.getDeclarationAttributes()) { - JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); - if (annotType == null) { - continue; - } - Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); - if (retention != null && retention.value() == RetentionPolicy.SOURCE) { - continue; - } - annotations.add(annotType); + if (!sym.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(sym.getDeclarationAttributes().size()); + for (Attribute.Compound a : sym.getDeclarationAttributes()) { + JavaType.Annotation annotation = annotationType(a); + if (annotation == null) continue; + annotations.add(annotation); } } return annotations; } + + private JavaType.@Nullable Annotation annotationType(Attribute.Compound compound) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(compound.type)); + if (annotType == null) { + return null; + } + List elementValues = new ArrayList<>(); + for (Pair attr : compound.values) { + Object value = annotationElementValue(attr.snd.getValue()); + JavaType.Method element = requireNonNull(methodDeclarationType(attr.fst, annotType)); + JavaType.Annotation.ElementValue elementValue = value instanceof Object[] ? + new JavaType.Annotation.ArrayElementValue(element, ((Object[]) value)) : + new JavaType.Annotation.SingleElementValue(element, value); + elementValues.add(elementValue); + } + return new JavaType.Annotation(annotType, elementValues); + } + + private Object annotationElementValue(Object value) { + if (value instanceof Symbol.VarSymbol) { + JavaType.Variable mapped = variableType((Symbol.VarSymbol) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Type.ClassType) { + return type((Type.ClassType) value); + } else if (value instanceof Attribute.Array) { + List<@Nullable Object> list = new ArrayList<>(); + for (Attribute attribute : ((Attribute.Array) value).values) { + list.add(annotationElementValue(attribute)); + } + return list.toArray(new Object[0]); + } else if (value instanceof List) { + List<@Nullable Object> list = new ArrayList<>(); + for (Object o : ((List) value)) { + list.add(annotationElementValue(o)); + } + return list.toArray(new Object[0]); + } else if (value instanceof Attribute.Class) { + return type(((Attribute.Class) value).classType); + } else if (value instanceof Attribute.Compound) { + JavaType.Annotation mapped = annotationType((Attribute.Compound) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Attribute.Constant) { + return annotationElementValue(((Attribute.Constant) value).value); + } else if (value instanceof Attribute.Enum) { + return annotationElementValue(((Attribute.Enum) value).value); + } else if (value instanceof Attribute.Error) { + return JavaType.Unknown.getInstance(); + } + return value; + } } diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java index 7d5ad9da3f6..3c8e04a33fb 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17TypeMapping.java @@ -18,6 +18,7 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.*; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Pair; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.java.JavaTypeMapping; @@ -28,14 +29,12 @@ import javax.lang.model.type.NullType; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; @RequiredArgsConstructor @@ -47,7 +46,7 @@ class ReloadableJava17TypeMapping implements JavaTypeMapping { public JavaType type(com.sun.tools.javac.code.@Nullable Type type) { if (type == null || type instanceof Type.ErrorType || type instanceof Type.PackageType || type instanceof Type.UnknownType || - type instanceof NullType) { + type instanceof NullType) { return JavaType.Class.Unknown.getInstance(); } @@ -258,7 +257,7 @@ private JavaType.FullyQualified classType(Type.ClassType classType, String signa List interfaces = null; if (symType.interfaces_field != null) { interfaces = new ArrayList<>(symType.interfaces_field.length()); - for (com.sun.tools.javac.code.Type iParam : symType.interfaces_field) { + for (Type iParam : symType.interfaces_field) { JavaType.FullyQualified javaType = TypeUtils.asFullyQualified(type(iParam)); if (javaType != null) { interfaces.add(javaType); @@ -272,8 +271,8 @@ private JavaType.FullyQualified classType(Type.ClassType classType, String signa if (sym.members_field != null) { for (Symbol elem : sym.members_field.getSymbols()) { if (elem instanceof Symbol.VarSymbol && - (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | - Flags.GENERATEDCONSTR | Flags.ANONCONSTR)) == 0) { + (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | + Flags.GENERATEDCONSTR | Flags.ANONCONSTR)) == 0) { if (fqn.equals("java.lang.String") && elem.name.toString().equals("serialPersistentFields")) { // there is a "serialPersistentFields" member within the String class which is used in normal Java // serialization to customize how the String field is serialized. This field is tripping up Jackson @@ -286,7 +285,7 @@ private JavaType.FullyQualified classType(Type.ClassType classType, String signa } fields.add(variableType(elem, clazz)); } else if (elem instanceof Symbol.MethodSymbol && - (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | Flags.ANONCONSTR)) == 0) { + (elem.flags_field & (Flags.SYNTHETIC | Flags.BRIDGE | Flags.HYPOTHETICAL | Flags.ANONCONSTR)) == 0) { if (methods == null) { methods = new ArrayList<>(); } @@ -458,7 +457,7 @@ public JavaType.Primitive primitive(TypeTag tag) { * @param symbol The method symbol. * @return Method type attribution. */ - public JavaType.@Nullable Method methodInvocationType(com.sun.tools.javac.code.@Nullable Type selectType, @Nullable Symbol symbol) { + public JavaType.@Nullable Method methodInvocationType(@Nullable Type selectType, @Nullable Symbol symbol) { if (selectType == null || selectType instanceof Type.ErrorType || symbol == null || symbol.kind == Kinds.Kind.ERR || symbol.type instanceof Type.UnknownType) { return null; } @@ -507,7 +506,7 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!methodType.argtypes.isEmpty()) { parameterTypes = new ArrayList<>(methodType.argtypes.size()); - for (com.sun.tools.javac.code.Type argtype : methodType.argtypes) { + for (Type argtype : methodType.argtypes) { if (argtype != null) { JavaType javaType = type(argtype); parameterTypes.add(javaType); @@ -589,8 +588,8 @@ public JavaType.Primitive primitive(TypeTag tag) { .collect(Collectors.toList()); } else { try { - defaultValues = Collections.singletonList(methodSymbol.getDefaultValue().getValue().toString()); - } catch(UnsupportedOperationException e) { + defaultValues = singletonList(methodSymbol.getDefaultValue().getValue().toString()); + } catch (UnsupportedOperationException e) { // not all Attribute implementations define `getValue()` } } @@ -663,7 +662,7 @@ public JavaType.Primitive primitive(TypeTag tag) { if (!mt.argtypes.isEmpty()) { parameterTypes = new ArrayList<>(mt.argtypes.size()); - for (com.sun.tools.javac.code.Type argtype : mt.argtypes) { + for (Type argtype : mt.argtypes) { if (argtype != null) { JavaType javaType = type(argtype); parameterTypes.add(javaType); @@ -692,22 +691,66 @@ private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { } } - private @Nullable List listAnnotations(Symbol symb) { + private @Nullable List listAnnotations(Symbol sym) { List annotations = null; - if (!symb.getDeclarationAttributes().isEmpty()) { - annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); - for (Attribute.Compound a : symb.getDeclarationAttributes()) { - JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); - if (annotType == null) { - continue; - } - Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); - if (retention != null && retention.value() == RetentionPolicy.SOURCE) { - continue; - } - annotations.add(annotType); + if (!sym.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(sym.getDeclarationAttributes().size()); + for (Attribute.Compound a : sym.getDeclarationAttributes()) { + JavaType.Annotation annotation = annotationType(a); + if (annotation == null) continue; + annotations.add(annotation); } } return annotations; } + + private JavaType.@Nullable Annotation annotationType(Attribute.Compound compound) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(compound.type)); + if (annotType == null) { + return null; + } + List elementValues = new ArrayList<>(); + for (Pair attr : compound.values) { + Object value = annotationElementValue(attr.snd.getValue()); + JavaType.Method element = requireNonNull(methodDeclarationType(attr.fst, annotType)); + JavaType.Annotation.ElementValue elementValue = value instanceof Object[] ? + new JavaType.Annotation.ArrayElementValue(element, ((Object[]) value)) : + new JavaType.Annotation.SingleElementValue(element, value); + elementValues.add(elementValue); + } + return new JavaType.Annotation(annotType, elementValues); + } + + private Object annotationElementValue(Object value) { + if (value instanceof Symbol.VarSymbol) { + JavaType.Variable mapped = variableType((Symbol.VarSymbol) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Type.ClassType) { + return type((Type.ClassType) value); + } else if (value instanceof Attribute.Array) { + List<@Nullable Object> list = new ArrayList<>(); + for (Attribute attribute : ((Attribute.Array) value).values) { + list.add(annotationElementValue(attribute)); + } + return list.toArray(new Object[0]); + } else if (value instanceof List) { + List<@Nullable Object> list = new ArrayList<>(); + for (Object o : ((List) value)) { + list.add(annotationElementValue(o)); + } + return list.toArray(new Object[0]); + } else if (value instanceof Attribute.Class) { + return type(((Attribute.Class) value).classType); + } else if (value instanceof Attribute.Compound) { + JavaType.Annotation mapped = annotationType((Attribute.Compound) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Attribute.Constant) { + return annotationElementValue(((Attribute.Constant) value).value); + } else if (value instanceof Attribute.Enum) { + return annotationElementValue(((Attribute.Enum) value).value); + } else if (value instanceof Attribute.Error) { + return JavaType.Unknown.getInstance(); + } + return value; + } } diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java index 10d9b202c6f..913d7b745b5 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21TypeMapping.java @@ -18,6 +18,7 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.*; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Pair; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.java.JavaTypeMapping; @@ -28,14 +29,13 @@ import javax.lang.model.type.NullType; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.*; @RequiredArgsConstructor @@ -47,7 +47,7 @@ class ReloadableJava21TypeMapping implements JavaTypeMapping { public JavaType type(com.sun.tools.javac.code.@Nullable Type type) { if (type == null || type instanceof Type.ErrorType || type instanceof Type.PackageType || type instanceof Type.UnknownType || - type instanceof NullType) { + type instanceof NullType) { return JavaType.Class.Unknown.getInstance(); } @@ -423,7 +423,7 @@ public JavaType.Primitive primitive(TypeTag tag) { } private JavaType.@Nullable Variable variableType(@Nullable Symbol symbol, - JavaType.@Nullable FullyQualified owner) { + JavaType.@Nullable FullyQualified owner) { if (!(symbol instanceof Symbol.VarSymbol)) { return null; } @@ -593,15 +593,15 @@ public JavaType.Primitive primitive(TypeTag tag) { } } List defaultValues = null; - if(methodSymbol.getDefaultValue() != null) { - if(methodSymbol.getDefaultValue() instanceof Attribute.Array) { + if (methodSymbol.getDefaultValue() != null) { + if (methodSymbol.getDefaultValue() instanceof Attribute.Array) { defaultValues = ((Attribute.Array) methodSymbol.getDefaultValue()).getValue().stream() .map(attr -> attr.getValue().toString()) .collect(Collectors.toList()); } else { try { defaultValues = Collections.singletonList(methodSymbol.getDefaultValue().getValue().toString()); - } catch(UnsupportedOperationException e) { + } catch (UnsupportedOperationException e) { // not all Attribute implementations define `getValue()` } } @@ -703,22 +703,66 @@ private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { } } - private @Nullable List listAnnotations(Symbol symb) { + private @Nullable List listAnnotations(Symbol sym) { List annotations = null; - if (!symb.getDeclarationAttributes().isEmpty()) { - annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); - for (Attribute.Compound a : symb.getDeclarationAttributes()) { - JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); - if (annotType == null) { - continue; - } - Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); - if (retention != null && retention.value() == RetentionPolicy.SOURCE) { - continue; - } - annotations.add(annotType); + if (!sym.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(sym.getDeclarationAttributes().size()); + for (Attribute.Compound a : sym.getDeclarationAttributes()) { + JavaType.Annotation annotation = annotationType(a); + if (annotation == null) continue; + annotations.add(annotation); } } return annotations; } + + private JavaType.@Nullable Annotation annotationType(Attribute.Compound compound) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(compound.type)); + if (annotType == null) { + return null; + } + List elementValues = new ArrayList<>(); + for (Pair attr : compound.values) { + Object value = annotationElementValue(attr.snd.getValue()); + JavaType.Method element = requireNonNull(methodDeclarationType(attr.fst, annotType)); + JavaType.Annotation.ElementValue elementValue = value instanceof Object[] ? + new JavaType.Annotation.ArrayElementValue(element, ((Object[]) value)) : + new JavaType.Annotation.SingleElementValue(element, value); + elementValues.add(elementValue); + } + return new JavaType.Annotation(annotType, elementValues); + } + + private Object annotationElementValue(Object value) { + if (value instanceof Symbol.VarSymbol) { + JavaType.Variable mapped = variableType((Symbol.VarSymbol) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Type.ClassType) { + return type((Type.ClassType) value); + } else if (value instanceof Attribute.Array) { + List<@Nullable Object> list = new ArrayList<>(); + for (Attribute attribute : ((Attribute.Array) value).values) { + list.add(annotationElementValue(attribute)); + } + return list.toArray(new Object[0]); + } else if (value instanceof List) { + List<@Nullable Object> list = new ArrayList<>(); + for (Object o : ((List) value)) { + list.add(annotationElementValue(o)); + } + return list.toArray(new Object[0]); + } else if (value instanceof Attribute.Class) { + return type(((Attribute.Class) value).classType); + } else if (value instanceof Attribute.Compound) { + JavaType.Annotation mapped = annotationType((Attribute.Compound) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Attribute.Constant) { + return annotationElementValue(((Attribute.Constant) value).value); + } else if (value instanceof Attribute.Enum) { + return annotationElementValue(((Attribute.Enum) value).value); + } else if (value instanceof Attribute.Error) { + return JavaType.Unknown.getInstance(); + } + return value; + } } diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java index 988b0920206..4f71eb4fb45 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8TypeMapping.java @@ -18,6 +18,7 @@ import com.sun.source.tree.Tree; import com.sun.tools.javac.code.*; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Pair; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.java.internal.JavaTypeCache; @@ -27,14 +28,13 @@ import javax.lang.model.type.NullType; import javax.lang.model.type.TypeMirror; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.CONTRAVARIANT; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.COVARIANT; import static org.openrewrite.java.tree.JavaType.GenericTypeVariable.Variance.INVARIANT; @@ -685,22 +685,66 @@ private void completeClassSymbol(Symbol.ClassSymbol classSymbol) { } } - private @Nullable List listAnnotations(Symbol symb) { + private @Nullable List listAnnotations(Symbol sym) { List annotations = null; - if (!symb.getDeclarationAttributes().isEmpty()) { - annotations = new ArrayList<>(symb.getDeclarationAttributes().size()); - for (Attribute.Compound a : symb.getDeclarationAttributes()) { - JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(a.type)); - if (annotType == null) { - continue; - } - Retention retention = a.getAnnotationType().asElement().getAnnotation(Retention.class); - if (retention != null && retention.value() == RetentionPolicy.SOURCE) { - continue; - } - annotations.add(annotType); + if (!sym.getDeclarationAttributes().isEmpty()) { + annotations = new ArrayList<>(sym.getDeclarationAttributes().size()); + for (Attribute.Compound a : sym.getDeclarationAttributes()) { + JavaType.Annotation annotation = annotationType(a); + if (annotation == null) continue; + annotations.add(annotation); } } return annotations; } + + private JavaType.@Nullable Annotation annotationType(Attribute.Compound compound) { + JavaType.FullyQualified annotType = TypeUtils.asFullyQualified(type(compound.type)); + if (annotType == null) { + return null; + } + List elementValues = new ArrayList<>(); + for (Pair attr : compound.values) { + Object value = annotationElementValue(attr.snd.getValue()); + JavaType.Method element = requireNonNull(methodDeclarationType(attr.fst, annotType)); + JavaType.Annotation.ElementValue elementValue = value instanceof Object[] ? + new JavaType.Annotation.ArrayElementValue(element, ((Object[]) value)) : + new JavaType.Annotation.SingleElementValue(element, value); + elementValues.add(elementValue); + } + return new JavaType.Annotation(annotType, elementValues); + } + + private Object annotationElementValue(Object value) { + if (value instanceof Symbol.VarSymbol) { + JavaType.Variable mapped = variableType((Symbol.VarSymbol) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Type.ClassType) { + return type((Type.ClassType) value); + } else if (value instanceof Attribute.Array) { + List<@Nullable Object> list = new ArrayList<>(); + for (Attribute attribute : ((Attribute.Array) value).values) { + list.add(annotationElementValue(attribute)); + } + return list.toArray(new Object[0]); + } else if (value instanceof List) { + List<@Nullable Object> list = new ArrayList<>(); + for (Object o : ((List) value)) { + list.add(annotationElementValue(o)); + } + return list.toArray(new Object[0]); + } else if (value instanceof Attribute.Class) { + return type(((Attribute.Class) value).classType); + } else if (value instanceof Attribute.Compound) { + JavaType.Annotation mapped = annotationType((Attribute.Compound) value); + return mapped != null ? mapped : JavaType.Unknown.getInstance(); + } else if (value instanceof Attribute.Constant) { + return annotationElementValue(((Attribute.Constant) value).value); + } else if (value instanceof Attribute.Enum) { + return annotationElementValue(((Attribute.Enum) value).value); + } else if (value instanceof Attribute.Error) { + return JavaType.Unknown.getInstance(); + } + return value; + } } diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java index 9a89ce50b27..5dd60f9aa76 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java @@ -19,13 +19,20 @@ import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.Issue; +import org.openrewrite.SourceFile; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.MinimumJava21; import org.openrewrite.java.service.AnnotationService; import org.openrewrite.test.RewriteTest; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openrewrite.java.Assertions.java; import static org.openrewrite.test.RewriteTest.toRecipe; @@ -38,7 +45,14 @@ void annotationWithDefaultArgument() { """ @SuppressWarnings("ALL") public class A {} - """ + """, spec -> spec.afterRecipe(cu -> { + J.ClassDeclaration c = cu.getClasses().get(0); + JavaType.Class type = (JavaType.Class) c.getType(); + JavaType.Annotation a = (JavaType.Annotation) type.getAnnotations().get(0); + assertThat(a.getValues()).hasSize(1); + assertThat(a.getValues().get(0).getValue()).isEqualTo(singletonList("ALL")); + } + ) ) ); } @@ -50,7 +64,7 @@ void annotationWithArgument() { """ @SuppressWarnings(value = "ALL") public class A {} - """ + """ ) ); } @@ -62,7 +76,7 @@ void preserveOptionalEmptyParentheses() { """ @Deprecated ( ) public class A {} - """ + """ ) ); } @@ -74,7 +88,7 @@ void newArrayArgument() { """ import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; - + @Target({ FIELD, PARAMETER }) public @interface Annotation {} """ @@ -144,7 +158,7 @@ void typeParameterAnnotations() { import java.lang.annotation.*; class TypeAnnotationTest { List<@A ? extends @A String> list; - + @Target({ ElementType.FIELD, ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) private @interface A { } @@ -215,10 +229,10 @@ public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext p) """ import java.lang.annotation.*; public class TypeAnnotationTest { - + public @Deprecated @A TypeAnnotationTest() { } - + @Target({ ElementType.TYPE, ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) private @interface A { } @@ -227,10 +241,10 @@ public class TypeAnnotationTest { """ import java.lang.annotation.*; public class TypeAnnotationTest { - + public @Deprecated TypeAnnotationTest() { } - + @Target({ ElementType.TYPE, ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) private @interface A { } @@ -397,4 +411,88 @@ public J.ArrayType visitArrayType(J.ArrayType arrayType, Object o) { ) ); } + + @Test + void recursiveElementValue() { + rewriteRun( + java( + """ + import java.lang.annotation.ElementType; + import java.lang.annotation.Target; + + @Target(ElementType.TYPE) + @A + private @interface A { + A[] value() default @A; + } + + @A({@A, @A(@A)}) + class TypeAnnotationTest { + } + """, spec -> spec.afterRecipe(cu -> { + J.ClassDeclaration c = cu.getClasses().get(1); + JavaType.Class type = (JavaType.Class) c.getType(); + JavaType.Annotation a = (JavaType.Annotation) type.getAnnotations().get(0); + assertThat(a.getValues()).satisfiesExactly( + v -> { + assertThat(v.getElement()).isIn(a.getMethods()); + assertThat((List) (((JavaType.Annotation.ArrayElementValue) v).getValues())).satisfiesExactly( + a1 -> { + assertThat(a1.getType()).isSameAs(a.getType()); + assertThat(a1.getValues()).isEmpty(); + }, + a2 -> { + assertThat(a2.getType()).isSameAs(a.getType()); + assertThat(a2.getValues()).hasSize(1); + } + ); + } + ); + }) + ) + ); + } + + @Test + @MinimumJava21 // Because of `@Deprecated#forRemoval` + void annotationElementValues() { + JavaParser p = JavaParser.fromJavaVersion().build(); + /* + * Using these annotations in core library for testing this feature: + * + * @Deprecated(since="1.2", forRemoval=true) + * public final void stop() + * + * @CallerSensitive + * public ClassLoader getContextClassLoader() { + */ + List sourceFiles = p.parse( + """ + class Test { + public void test() { + Thread.currentThread().stop(); + Thread.currentThread().getContextClassLoader(); + } + } + """ + ).toList(); + J.CompilationUnit cu = (J.CompilationUnit) sourceFiles.get(0); + + J.MethodDeclaration md = (J.MethodDeclaration) cu.getClasses().get(0).getBody().getStatements().get(0); + J.MethodInvocation mi = (J.MethodInvocation) md.getBody().getStatements().get(0); + JavaType.Annotation annotation = (JavaType.Annotation) mi.getMethodType().getAnnotations().get(0); + + // Thread.currentThread().stop(); + assertEquals("java.lang.Deprecated" ,annotation.getType().getFullyQualifiedName()); + assertEquals("since", ((JavaType.Method) annotation.getValues().get(0).getElement()).getName()); + assertEquals("1.2", annotation.getValues().get(0).getValue()); + assertEquals("forRemoval", ((JavaType.Method) annotation.getValues().get(1).getElement()).getName()); + assertEquals(Boolean.TRUE, annotation.getValues().get(1).getValue()); + + // Thread.currentThread().getContextClassLoader(); + mi = (J.MethodInvocation) md.getBody().getStatements().get(1); + annotation = (JavaType.Annotation) mi.getMethodType().getAnnotations().get(0); + assertEquals("jdk.internal.reflect.CallerSensitive" ,annotation.getType().getFullyQualifiedName()); + assertTrue(annotation.getValues().isEmpty()); + } } diff --git a/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeMappingTest.java b/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeMappingTest.java index 591d897bfc3..5cc91ef765f 100644 --- a/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeMappingTest.java +++ b/rewrite-java-test/src/main/java/org/openrewrite/java/JavaTypeMappingTest.java @@ -221,14 +221,18 @@ default void enumTypeB() { } @Test - default void ignoreSourceRetentionAnnotations() { + default void includeSourceRetentionAnnotations() { JavaType.Parameterized goat = goatType(); - assertThat(goat.getAnnotations().size()).isEqualTo(1); - assertThat(goat.getAnnotations().get(0).getClassName()).isEqualTo("AnnotationWithRuntimeRetention"); + assertThat(goat.getAnnotations()).satisfiesExactlyInAnyOrder( + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithRuntimeRetention"), + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithSourceRetention") + ); JavaType.Method clazzMethod = methodType("clazz"); - assertThat(clazzMethod.getAnnotations().size()).isEqualTo(1); - assertThat(clazzMethod.getAnnotations().get(0).getClassName()).isEqualTo("AnnotationWithRuntimeRetention"); + assertThat(clazzMethod.getAnnotations()).satisfiesExactlyInAnyOrder( + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithRuntimeRetention"), + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithSourceRetention") + ); } @Issue("https://github.com/openrewrite/rewrite/issues/1367") diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java index a7fb142280a..1ce0603dc2b 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/JavaReflectionTypeMapping.java @@ -153,7 +153,7 @@ private JavaType.FullyQualified classTypeWithoutParameters(Class clazz) { annotations = new ArrayList<>(clazz.getDeclaredAnnotations().length); for (Annotation a : clazz.getDeclaredAnnotations()) { JavaType.FullyQualified type = (JavaType.FullyQualified) type(a.annotationType()); - annotations.add(type); + annotations.add(new JavaType.Annotation(type, emptyList())); } } @@ -298,7 +298,7 @@ private JavaType.Variable field(Field field) { annotations = new ArrayList<>(field.getDeclaredAnnotations().length); for (Annotation a : field.getDeclaredAnnotations()) { JavaType.FullyQualified type = (JavaType.FullyQualified) type(a.annotationType()); - annotations.add(type); + annotations.add(new JavaType.Annotation(type, emptyList())); } } @@ -359,7 +359,7 @@ private JavaType.Method method(Constructor method, JavaType.FullyQualified de annotations = new ArrayList<>(method.getDeclaredAnnotations().length); for (Annotation a : method.getDeclaredAnnotations()) { JavaType.FullyQualified fullyQualified = (JavaType.FullyQualified) type(a.annotationType()); - annotations.add(fullyQualified); + annotations.add(new JavaType.Annotation(fullyQualified, emptyList())); } } @@ -465,7 +465,7 @@ private JavaType.Method method(Method method, JavaType.FullyQualified declaringT annotations = new ArrayList<>(method.getDeclaredAnnotations().length); for (Annotation a : method.getDeclaredAnnotations()) { JavaType.FullyQualified fullyQualified = (JavaType.FullyQualified) type(a.annotationType()); - annotations.add(fullyQualified); + annotations.add(new JavaType.Annotation(fullyQualified, emptyList())); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java index 2fa596187f4..6062c9d6579 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/JavaType.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.ObjectIdGenerators; import lombok.AccessLevel; import lombok.Getter; +import lombok.Value; import lombok.With; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; @@ -48,6 +49,7 @@ public interface JavaType { Method[] EMPTY_METHOD_ARRAY = new Method[0]; String[] EMPTY_STRING_ARRAY = new String[0]; JavaType[] EMPTY_JAVA_TYPE_ARRAY = new JavaType[0]; + Annotation.ElementValue[] EMPTY_ANNOTATION_VALUE_ARRAY = new Annotation.ElementValue[0]; // TODO: To be removed with OpenRewrite 9 default @Nullable Integer getManagedReference() { @@ -323,16 +325,16 @@ public String getPackageName() { public boolean isAssignableTo(String fullyQualifiedName) { return TypeUtils.fullyQualifiedNamesAreEqual(getFullyQualifiedName(), fullyQualifiedName) || - getInterfaces().stream().anyMatch(anInterface -> anInterface.isAssignableTo(fullyQualifiedName)) || - (getSupertype() != null && getSupertype().isAssignableTo(fullyQualifiedName)); + getInterfaces().stream().anyMatch(anInterface -> anInterface.isAssignableTo(fullyQualifiedName)) || + (getSupertype() != null && getSupertype().isAssignableTo(fullyQualifiedName)); } public boolean isAssignableFrom(@Nullable JavaType type) { if (type instanceof FullyQualified) { FullyQualified clazz = (FullyQualified) type; return TypeUtils.fullyQualifiedNamesAreEqual(getFullyQualifiedName(), clazz.getFullyQualifiedName()) || - isAssignableFrom(clazz.getSupertype()) || - clazz.getInterfaces().stream().anyMatch(this::isAssignableFrom); + isAssignableFrom(clazz.getSupertype()) || + clazz.getInterfaces().stream().anyMatch(this::isAssignableFrom); } else if (type instanceof GenericTypeVariable) { GenericTypeVariable generic = (GenericTypeVariable) type; for (JavaType bound : generic.getBounds()) { @@ -577,7 +579,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; Class aClass = (Class) o; return TypeUtils.fullyQualifiedNamesAreEqual(fullyQualifiedName, aClass.fullyQualifiedName) && - (typeParameters == null && aClass.typeParameters == null || typeParameters != null && Arrays.equals(typeParameters, aClass.typeParameters)); + (typeParameters == null && aClass.typeParameters == null || typeParameters != null && Arrays.equals(typeParameters, aClass.typeParameters)); } @Override @@ -629,6 +631,124 @@ public static ShallowClass build(String fullyQualifiedName) { } } + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) + class Annotation extends FullyQualified { + + @Getter + @With + final FullyQualified type; + + final ElementValue @Nullable [] values; + + public Annotation(FullyQualified type, List values) { + this(type, arrayOrNullIfEmpty(values, EMPTY_ANNOTATION_VALUE_ARRAY)); + } + + Annotation(FullyQualified type, ElementValue @Nullable [] values) { + this.type = type; + this.values = nullIfEmpty(values); + } + + public List getValues() { + return values == null ? emptyList() : Arrays.asList(values); + } + + public Annotation withValues(@Nullable List values) { + ElementValue[] valuesArray = arrayOrNullIfEmpty(values, EMPTY_ANNOTATION_VALUE_ARRAY); + if (Arrays.equals(valuesArray, this.values)) { + return this; + } + return new Annotation(type, valuesArray); + } + + @Override + public String getFullyQualifiedName() { + return type.getFullyQualifiedName(); + } + + @Override + public FullyQualified withFullyQualifiedName(String fullyQualifiedName) { + return withType(type.withFullyQualifiedName(fullyQualifiedName)); + } + + @Override + public List getAnnotations() { + return type.getAnnotations(); + } + + @Override + public boolean hasFlags(Flag... test) { + return type.hasFlags(test); + } + + @Override + public Set getFlags() { + return type.getFlags(); + } + + @Override + public List getInterfaces() { + return type.getInterfaces(); + } + + @Override + public Kind getKind() { + return type.getKind(); + } + + @Override + public List getMembers() { + return type.getMembers(); + } + + @Override + public List getMethods() { + return type.getMethods(); + } + + @Override + public List getTypeParameters() { + return type.getTypeParameters(); + } + + @Override + public @Nullable FullyQualified getOwningClass() { + return type.getOwningClass(); + } + + @Override + public @Nullable FullyQualified getSupertype() { + return type.getSupertype(); + } + + public interface ElementValue { + JavaType getElement(); + + Object getValue(); + } + + @Value + public static class SingleElementValue implements ElementValue { + JavaType element; + Object value; + } + + @Value + public static class ArrayElementValue implements ElementValue { + JavaType element; + Object[] values; + + @Override + public Object getValue() { + return getValues(); + } + + public List getValues() { + return Arrays.asList(values); + } + } + } + @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) class Parameterized extends FullyQualified { @Getter @@ -854,7 +974,7 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; GenericTypeVariable that = (GenericTypeVariable) o; return name.equals(that.name) && variance == that.variance && - (variance == Variance.INVARIANT && bounds == null && that.bounds == null || bounds != null && Arrays.equals(bounds, that.bounds)); + (variance == Variance.INVARIANT && bounds == null && that.bounds == null || bounds != null && Arrays.equals(bounds, that.bounds)); } @Override @@ -1358,9 +1478,9 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; Method method = (Method) o; return Objects.equals(declaringType, method.declaringType) && - name.equals(method.name) && - Objects.equals(returnType, method.returnType) && - Arrays.equals(parameterTypes, method.parameterTypes); + name.equals(method.name) && + Objects.equals(returnType, method.returnType) && + Arrays.equals(parameterTypes, method.parameterTypes); } @Override diff --git a/rewrite-java/src/test/java/org/openrewrite/java/internal/JavaReflectionTypeMappingTest.java b/rewrite-java/src/test/java/org/openrewrite/java/internal/JavaReflectionTypeMappingTest.java index e858ccbceef..06d20326f58 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/internal/JavaReflectionTypeMappingTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/internal/JavaReflectionTypeMappingTest.java @@ -22,6 +22,8 @@ import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; +import static org.assertj.core.api.Assertions.assertThat; + @SuppressWarnings("ConstantConditions") class JavaReflectionTypeMappingTest implements JavaTypeMappingTest { JavaReflectionTypeMapping typeMapping = new JavaReflectionTypeMapping(new JavaTypeCache()); @@ -56,4 +58,20 @@ public void enumTypeA() { @Override public void enumTypeB() { } + + @Test + @Override + // The JavaReflectionTypeMapping cannot include source retention annotations + public void includeSourceRetentionAnnotations() { + JavaType.Parameterized goat = goatType(); + assertThat(goat.getAnnotations()).satisfiesExactlyInAnyOrder( + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithRuntimeRetention") + ); + + JavaType.Method clazzMethod = methodType("clazz"); + assertThat(clazzMethod.getAnnotations()).satisfiesExactlyInAnyOrder( + a -> assertThat(a.getClassName()).isEqualTo("AnnotationWithRuntimeRetention") + ); + } + }