Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

review fix: fix bug with repeatable annotations #1724

Merged
merged 6 commits into from
Nov 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions src/main/java/spoon/reflect/factory/AnnotationFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -112,6 +113,7 @@ public <A extends Annotation> CtAnnotation<A> annotate(CtElement element, CtType
boolean isArray;
// try with CT reflection
CtAnnotationType<A> ctAnnotationType = ((CtAnnotationType<A>) annotation.getAnnotationType().getDeclaration());
boolean newValue = annotation.getValue(annotationElementName) == null;
if (ctAnnotationType != null) {
CtMethod<?> e = ctAnnotationType.getMethod(annotationElementName);
isArray = (e.getType() instanceof CtArrayTypeReference);
Expand All @@ -125,12 +127,10 @@ public <A extends Annotation> CtAnnotation<A> 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;
}
Expand Down Expand Up @@ -158,8 +158,13 @@ public <A extends Annotation> CtAnnotation<A> annotate(CtElement element, Class<
* @return the concerned annotation
*/
public <A extends Annotation> CtAnnotation<A> annotate(CtElement element, CtTypeReference<A> annotationType) {
CtAnnotationType<A> ctAnnotationType = ((CtAnnotationType<A>) annotationType.getDeclaration());
boolean isRepeatable = false;
if (ctAnnotationType != null) {
isRepeatable = (ctAnnotationType.getAnnotation(factory.Type().createReference(Repeatable.class)) != null);
}
CtAnnotation<A> annotation = element.getAnnotation(annotationType);
if (annotation == null) {
if (annotation == null || isRepeatable) {
annotation = factory.Core().createAnnotation();
annotation.setAnnotationType(factory.Core().clone(annotationType));
element.addAnnotation(annotation);
Expand Down
134 changes: 134 additions & 0 deletions src/test/java/spoon/test/annotation/AnnotationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<CtAnnotation<?>> 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<CtAnnotation<?>> 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<CtAnnotation<?>> 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<CtAnnotation<?>> 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());
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package spoon.test.annotation.testclasses.notrepeatable;

public @interface StringAnnot {
String value() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package spoon.test.annotation.testclasses.repeatable;

public class Repeated {
@Tag("machin")
@Tag("truc")
public void method() {}

public void withoutAnnotation() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package spoon.test.annotation.testclasses.repeatable;

import java.lang.annotation.Repeatable;

@Repeatable(Tags.class)
public @interface Tag {
String value() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package spoon.test.annotation.testclasses.repeatable;

public @interface Tags {
Tag[] value();
}
Original file line number Diff line number Diff line change
@@ -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() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package spoon.test.annotation.testclasses.repeatandarrays;

import java.lang.annotation.Repeatable;

@Repeatable(TagsArrays.class)
public @interface TagArrays {
String[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package spoon.test.annotation.testclasses.repeatandarrays;

public @interface TagsArrays {
TagArrays[] value();
}