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();
+}