diff --git a/src/main/java/spoon/reflect/factory/AnnotationFactory.java b/src/main/java/spoon/reflect/factory/AnnotationFactory.java index b1678822074..4b2a5a9ce39 100644 --- a/src/main/java/spoon/reflect/factory/AnnotationFactory.java +++ b/src/main/java/spoon/reflect/factory/AnnotationFactory.java @@ -16,6 +16,7 @@ */ package spoon.reflect.factory; +import spoon.SpoonException; import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtAnnotationType; import spoon.reflect.declaration.CtElement; @@ -26,8 +27,8 @@ import spoon.reflect.reference.CtTypeReference; import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; import java.lang.reflect.Method; -import java.util.Collection; /** * The {@link CtAnnotationType} sub-factory. @@ -112,6 +113,7 @@ public CtAnnotation annotate(CtElement element, CtType boolean isArray; // try with CT reflection CtAnnotationType ctAnnotationType = ((CtAnnotationType) annotation.getAnnotationType().getDeclaration()); + boolean newValue = annotation.getValue(annotationElementName) == null; if (ctAnnotationType != null) { CtMethod e = ctAnnotationType.getMethod(annotationElementName); isArray = (e.getType() instanceof CtArrayTypeReference); @@ -125,12 +127,10 @@ public CtAnnotation annotate(CtElement element, CtType } isArray = m.getReturnType().isArray(); } - if (isArray == (value instanceof Collection || value.getClass().isArray())) { - annotation.addValue(annotationElementName, value); - } else if (isArray) { + if (isArray || newValue) { annotation.addValue(annotationElementName, value); } else { - throw new RuntimeException("cannot assign an array to a non-array annotation element"); + throw new SpoonException("cannot assign an array to a non-array annotation element"); } return annotation; } @@ -158,8 +158,13 @@ public CtAnnotation annotate(CtElement element, Class< * @return the concerned annotation */ public CtAnnotation annotate(CtElement element, CtTypeReference annotationType) { + CtAnnotationType ctAnnotationType = ((CtAnnotationType) annotationType.getDeclaration()); + boolean isRepeatable = false; + if (ctAnnotationType != null) { + isRepeatable = (ctAnnotationType.getAnnotation(factory.Type().createReference(Repeatable.class)) != null); + } CtAnnotation annotation = element.getAnnotation(annotationType); - if (annotation == null) { + if (annotation == null || isRepeatable) { annotation = factory.Core().createAnnotation(); annotation.setAnnotationType(factory.Core().clone(annotationType)); element.addAnnotation(annotation); diff --git a/src/test/java/spoon/test/annotation/AnnotationTest.java b/src/test/java/spoon/test/annotation/AnnotationTest.java index 3b9d09b22e9..63ebfe2c6d9 100644 --- a/src/test/java/spoon/test/annotation/AnnotationTest.java +++ b/src/test/java/spoon/test/annotation/AnnotationTest.java @@ -60,6 +60,11 @@ import spoon.test.annotation.testclasses.SuperAnnotation; import spoon.test.annotation.testclasses.TestInterface; import spoon.test.annotation.testclasses.TypeAnnotation; +import spoon.test.annotation.testclasses.notrepeatable.StringAnnot; +import spoon.test.annotation.testclasses.repeatable.Repeated; +import spoon.test.annotation.testclasses.repeatable.Tag; +import spoon.test.annotation.testclasses.repeatandarrays.RepeatedArrays; +import spoon.test.annotation.testclasses.repeatandarrays.TagArrays; import spoon.test.annotation.testclasses.spring.AliasFor; import java.io.File; @@ -1100,4 +1105,133 @@ public void testSpoonManageRecursivelyDefinedAnnotation() { CtType type = spoon.getFactory().Type().get(AliasFor.class); assertEquals(3, type.getFields().size()); } + + @Test + public void testRepeatableAnnotationAreManaged() { + // contract: when two identical repeatable annotation are used, they should be displayed in two different annotations and not factorized + Launcher spoon = new Launcher(); + spoon.addInputResource("./src/test/java/spoon/test/annotation/testclasses/repeatable"); + spoon.buildModel(); + + CtType type = spoon.getFactory().Type().get(Repeated.class); + CtMethod firstMethod = (CtMethod)type.getMethodsByName("method").get(0); + List> annotations = firstMethod.getAnnotations(); + + assertEquals(2, annotations.size()); + + for (CtAnnotation a : annotations) { + assertEquals("Tag", a.getAnnotationType().getSimpleName()); + } + + String classContent = type.toString(); + assertTrue("Content of the file: "+classContent, classContent.contains("@spoon.test.annotation.testclasses.repeatable.Tag(\"machin\")")); + assertTrue("Content of the file: "+classContent, classContent.contains("@spoon.test.annotation.testclasses.repeatable.Tag(\"truc\")")); + } + + @Test + public void testCreateRepeatableAnnotation() { + // contract: when creating two repeatable annotations, two annotations should be created + + Launcher spoon = new Launcher(); + spoon.addInputResource("./src/test/java/spoon/test/annotation/testclasses/repeatable"); + spoon.buildModel(); + + CtType type = spoon.getFactory().Type().get(Repeated.class); + CtMethod firstMethod = (CtMethod)type.getMethodsByName("withoutAnnotation").get(0); + List> annotations = firstMethod.getAnnotations(); + + assertTrue(annotations.isEmpty()); + + spoon.getFactory().Annotation().annotate(firstMethod, Tag.class,"value", "foo"); + assertEquals(1, firstMethod.getAnnotations().size()); + + spoon.getFactory().Annotation().annotate(firstMethod, Tag.class,"value", "bar"); + + annotations = firstMethod.getAnnotations(); + assertEquals(2, annotations.size()); + + for (CtAnnotation a : annotations) { + assertEquals("Tag", a.getAnnotationType().getSimpleName()); + } + + String classContent = type.toString(); + assertTrue("Content of the file: "+classContent, classContent.contains("@spoon.test.annotation.testclasses.repeatable.Tag(\"foo\")")); + assertTrue("Content of the file: "+classContent, classContent.contains("@spoon.test.annotation.testclasses.repeatable.Tag(\"bar\")")); + } + + @Test + public void testRepeatableAnnotationAreManagedWithArrays() { + // contract: when two identical repeatable annotation with arrays are used, they should be displayed in two different annotations and not factorized + Launcher spoon = new Launcher(); + spoon.addInputResource("./src/test/java/spoon/test/annotation/testclasses/repeatandarrays"); + spoon.buildModel(); + + CtType type = spoon.getFactory().Type().get(RepeatedArrays.class); + CtMethod firstMethod = (CtMethod)type.getMethodsByName("method").get(0); + List> annotations = firstMethod.getAnnotations(); + + assertEquals(2, annotations.size()); + + for (CtAnnotation a : annotations) { + assertEquals("TagArrays", a.getAnnotationType().getSimpleName()); + } + + String classContent = type.toString(); + assertTrue("Content of the file: "+classContent, classContent.contains("@spoon.test.annotation.testclasses.repeatandarrays.TagArrays({ \"machin\", \"truc\" })")); + assertTrue("Content of the file: "+classContent, classContent.contains("@spoon.test.annotation.testclasses.repeatandarrays.TagArrays({ \"truc\", \"bidule\" })")); + } + + @Test + public void testCreateRepeatableAnnotationWithArrays() { + // contract: when using annotate with a repeatable annotation, it will create a new annotation, even if an annotation with an array already exists + Launcher spoon = new Launcher(); + spoon.addInputResource("./src/test/java/spoon/test/annotation/testclasses/repeatandarrays"); + spoon.buildModel(); + + CtType type = spoon.getFactory().Type().get(Repeated.class); + CtMethod firstMethod = (CtMethod)type.getMethodsByName("withoutAnnotation").get(0); + List> annotations = firstMethod.getAnnotations(); + + assertTrue(annotations.isEmpty()); + + spoon.getFactory().Annotation().annotate(firstMethod, TagArrays.class,"value", "foo"); + assertEquals(1, firstMethod.getAnnotations().size()); + + spoon.getFactory().Annotation().annotate(firstMethod, TagArrays.class,"value", "bar"); + annotations = firstMethod.getAnnotations(); + assertEquals(2, annotations.size()); + + for (CtAnnotation a : annotations) { + assertEquals("TagArrays", a.getAnnotationType().getSimpleName()); + } + + String classContent = type.toString(); + assertTrue("Content of the file: "+classContent, classContent.contains("@spoon.test.annotation.testclasses.repeatandarrays.TagArrays(\"foo\")")); + assertTrue("Content of the file: "+classContent, classContent.contains("@spoon.test.annotation.testclasses.repeatandarrays.TagArrays(\"bar\")")); + } + + @Test + public void testAnnotationNotRepeatableNotArrayAnnotation() { + // contract: when trying to annotate multiple times with same not repeatable, not array annotation, it should throw an exception + Launcher spoon = new Launcher(); + spoon.addInputResource("./src/test/java/spoon/test/annotation/testclasses/notrepeatable/StringAnnot.java"); + spoon.buildModel(); + + CtMethod aMethod = spoon.getFactory().createMethod().setSimpleName(("mamethod")); + + spoon.getFactory().Annotation().annotate(aMethod, StringAnnot.class, "value", "foo"); + assertEquals(1, aMethod.getAnnotations().size()); + + String methodContent = aMethod.toString(); + assertTrue("Content: "+methodContent, methodContent.contains("@spoon.test.annotation.testclasses.notrepeatable.StringAnnot(\"foo\")")); + + try { + spoon.getFactory().Annotation().annotate(aMethod, StringAnnot.class, "value", "bar"); + methodContent = aMethod.toString(); + fail("You should not be able to add two values to StringAnnot annotation: "+methodContent); + } catch (SpoonException e) { + assertEquals("cannot assign an array to a non-array annotation element", e.getMessage()); + } + + } } diff --git a/src/test/java/spoon/test/annotation/testclasses/notrepeatable/StringAnnot.java b/src/test/java/spoon/test/annotation/testclasses/notrepeatable/StringAnnot.java new file mode 100644 index 00000000000..12768e618b2 --- /dev/null +++ b/src/test/java/spoon/test/annotation/testclasses/notrepeatable/StringAnnot.java @@ -0,0 +1,5 @@ +package spoon.test.annotation.testclasses.notrepeatable; + +public @interface StringAnnot { + String value() default ""; +} diff --git a/src/test/java/spoon/test/annotation/testclasses/repeatable/Repeated.java b/src/test/java/spoon/test/annotation/testclasses/repeatable/Repeated.java new file mode 100644 index 00000000000..e1861eb5d2f --- /dev/null +++ b/src/test/java/spoon/test/annotation/testclasses/repeatable/Repeated.java @@ -0,0 +1,11 @@ +package spoon.test.annotation.testclasses.repeatable; + +public class Repeated { + @Tag("machin") + @Tag("truc") + public void method() {} + + public void withoutAnnotation() { + + } +} diff --git a/src/test/java/spoon/test/annotation/testclasses/repeatable/Tag.java b/src/test/java/spoon/test/annotation/testclasses/repeatable/Tag.java new file mode 100644 index 00000000000..2f0affc45b0 --- /dev/null +++ b/src/test/java/spoon/test/annotation/testclasses/repeatable/Tag.java @@ -0,0 +1,8 @@ +package spoon.test.annotation.testclasses.repeatable; + +import java.lang.annotation.Repeatable; + +@Repeatable(Tags.class) +public @interface Tag { + String value() default ""; +} diff --git a/src/test/java/spoon/test/annotation/testclasses/repeatable/Tags.java b/src/test/java/spoon/test/annotation/testclasses/repeatable/Tags.java new file mode 100644 index 00000000000..d9b2c0f9dbe --- /dev/null +++ b/src/test/java/spoon/test/annotation/testclasses/repeatable/Tags.java @@ -0,0 +1,5 @@ +package spoon.test.annotation.testclasses.repeatable; + +public @interface Tags { + Tag[] value(); +} diff --git a/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/RepeatedArrays.java b/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/RepeatedArrays.java new file mode 100644 index 00000000000..c91151a4ccc --- /dev/null +++ b/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/RepeatedArrays.java @@ -0,0 +1,11 @@ +package spoon.test.annotation.testclasses.repeatandarrays; + +public class RepeatedArrays { + @TagArrays({"machin", "truc"}) + @TagArrays({"truc", "bidule"}) + public void method() {} + + public void withoutAnnotation() { + + } +} diff --git a/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/TagArrays.java b/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/TagArrays.java new file mode 100644 index 00000000000..d4c611974f1 --- /dev/null +++ b/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/TagArrays.java @@ -0,0 +1,8 @@ +package spoon.test.annotation.testclasses.repeatandarrays; + +import java.lang.annotation.Repeatable; + +@Repeatable(TagsArrays.class) +public @interface TagArrays { + String[] value(); +} diff --git a/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/TagsArrays.java b/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/TagsArrays.java new file mode 100644 index 00000000000..98c0782e71b --- /dev/null +++ b/src/test/java/spoon/test/annotation/testclasses/repeatandarrays/TagsArrays.java @@ -0,0 +1,5 @@ +package spoon.test.annotation.testclasses.repeatandarrays; + +public @interface TagsArrays { + TagArrays[] value(); +}