Skip to content

Commit de6e7fe

Browse files
committed
[MVC] TYPE_USE annotation on MVC method parameters break the annotation processor
- Support ElementType.TYPE_USE - Support generic Nonnull and Nullable annotation types - Fixes jooby-project#2408
1 parent 0c8e887 commit de6e7fe

File tree

7 files changed

+159
-7
lines changed

7 files changed

+159
-7
lines changed

modules/jooby-apt/src/main/java/io/jooby/internal/apt/ParamDefinition.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
2020
import io.jooby.internal.apt.asm.ParamWriter;
2121

2222
import javax.annotation.processing.ProcessingEnvironment;
23+
import javax.lang.model.AnnotatedConstruct;
2324
import javax.lang.model.element.AnnotationMirror;
2425
import javax.lang.model.element.VariableElement;
26+
import javax.lang.model.type.TypeMirror;
2527
import javax.lang.model.util.Types;
28+
29+
import java.lang.reflect.AnnotatedType;
2630
import java.lang.reflect.Method;
2731
import java.lang.reflect.Type;
2832
import java.net.URI;
@@ -31,6 +35,7 @@
3135
import java.time.Duration;
3236
import java.time.Period;
3337
import java.time.ZoneId;
38+
import java.util.LinkedHashSet;
3439
import java.util.List;
3540
import java.util.Optional;
3641
import java.util.Set;
@@ -111,14 +116,32 @@ public boolean isNullable() {
111116
}
112117

113118
private boolean hasAnnotation(String type) {
114-
for (AnnotationMirror annotation : parameter.getAnnotationMirrors()) {
115-
if (annotation.getAnnotationType().toString().endsWith(type)) {
119+
Set<String> annotations = annotations(parameter);
120+
for (String annotation : annotations) {
121+
if (annotation.endsWith(type)) {
116122
return true;
117123
}
118124
}
119125
return false;
120126
}
121127

128+
private Set<String> annotations(VariableElement parameter) {
129+
Set<String> annotations = new LinkedHashSet<>();
130+
annotations.addAll(annotationsFrom(parameter));
131+
annotations.addAll(annotationsFrom(parameter.asType()));
132+
return annotations;
133+
}
134+
135+
private Set<String> annotationsFrom(AnnotatedConstruct annotated) {
136+
Set<String> annotations = new LinkedHashSet<>();
137+
for (AnnotationMirror annotation : annotated.getAnnotationMirrors()) {
138+
TypeMirror typeMirror = annotation.getAnnotationType();
139+
annotations.add(typeMirror.toString());
140+
}
141+
return annotations;
142+
}
143+
144+
122145
public Method getObjectValue() throws NoSuchMethodException {
123146
return getKind().valueObject(this);
124147
}

modules/jooby-apt/src/main/java/io/jooby/internal/apt/TypeDefinition.java

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ public TypeDefinition(Types types, TypeMirror type) {
4040
this.type = type;
4141
}
4242

43+
/**
44+
* Check for declared type and get the underlying type. This is required for annotated type.
45+
* Example:
46+
*
47+
* <pre>{@code
48+
* @Nullable @QueryParam String name
49+
* }</pre>
50+
*
51+
* @param type
52+
* @return
53+
*/
54+
private TypeMirror unwrapType(TypeMirror type) {
55+
if (type instanceof DeclaredType) {
56+
return ((DeclaredType) type).asElement().asType();
57+
} else {
58+
return type;
59+
}
60+
}
61+
4362
public String getName() {
4463
return getRawType().toString();
4564
}
@@ -49,15 +68,15 @@ public TypeMirror getType() {
4968
}
5069

5170
public boolean isPrimitive() {
52-
return getType().getKind().isPrimitive();
71+
return unwrapType(getType()).getKind().isPrimitive();
5372
}
5473

5574
public boolean isVoid() {
56-
return type.getKind() == TypeKind.VOID;
75+
return unwrapType(getType()).getKind() == TypeKind.VOID;
5776
}
5877

5978
public TypeMirror getRawType() {
60-
return typeUtils.erasure(type);
79+
return typeUtils.erasure(unwrapType(getType()));
6180
}
6281

6382
public boolean is(Class type, Class... arguments) {
@@ -84,10 +103,12 @@ private boolean is(String type, String... arguments) {
84103
}
85104

86105
private boolean equalType(TypeMirror type, String typeName) {
87-
if (!typeUtils.erasure(type).toString().equals(typeName)) {
106+
TypeMirror realType = unwrapType(type);
107+
TypeMirror erasure = typeUtils.erasure(realType);
108+
if (!erasure.toString().equals(typeName)) {
88109
// check for enum subclasses:
89110
if (Enum.class.getName().equals(typeName)) {
90-
return typeUtils.asElement(type).getKind() == ElementKind.ENUM;
111+
return typeUtils.asElement(realType).getKind() == ElementKind.ENUM;
91112
} else {
92113
return false;
93114
}

modules/jooby-apt/src/main/java/io/jooby/internal/apt/asm/NameGenerator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
16
package io.jooby.internal.apt.asm;
27

38
import java.util.HashMap;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tests.i2408;
2+
3+
import org.checkerframework.checker.nullness.qual.NonNull;
4+
import org.checkerframework.checker.nullness.qual.Nullable;
5+
6+
import io.jooby.annotations.GET;
7+
import io.jooby.annotations.QueryParam;
8+
9+
public class C2408 {
10+
@GET("/2408/nonnull")
11+
public String nonnull(@NonNull @QueryParam String name) {
12+
return name;
13+
}
14+
15+
@GET("/2408/nullable")
16+
public String nullable(@Nullable @QueryParam String name, @QueryParam String blah) {
17+
if (name == null) {
18+
return "nothing";
19+
}
20+
return name;
21+
}
22+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package tests.i2408;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
import io.jooby.MockContext;
9+
import io.jooby.MockRouter;
10+
import io.jooby.apt.MvcModuleCompilerRunner;
11+
import io.jooby.exception.MissingValueException;
12+
13+
public class Issue2408 {
14+
@Test
15+
public void shouldNotIgnoreAnnotationOnParam() throws Exception {
16+
new MvcModuleCompilerRunner(new C2408())
17+
.module(app -> {
18+
MockRouter router = new MockRouter(app);
19+
assertEquals("nothing", router.get("/2408/nullable", new MockContext()
20+
.setQueryString("?blah=stuff")).value());
21+
22+
assertThrows(MissingValueException.class, () -> router.get("/2408/nonnull", new MockContext()).value());
23+
24+
assertEquals("cool", router.get("/2408/nonnull", new MockContext()
25+
.setQueryString("?name=cool")).value());
26+
});
27+
}
28+
}
29+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.jooby.i2408;
2+
3+
import org.checkerframework.checker.nullness.qual.NonNull;
4+
import org.checkerframework.checker.nullness.qual.Nullable;
5+
6+
import io.jooby.annotations.GET;
7+
import io.jooby.annotations.QueryParam;
8+
9+
public class C2408 {
10+
@GET("/2408/nonnull")
11+
public String nonnull(@NonNull @QueryParam String name) {
12+
return name;
13+
}
14+
15+
@GET("/2408/nullable")
16+
public String nullable(@Nullable @QueryParam String name, @QueryParam String blah) {
17+
if (name == null) {
18+
return "nothing";
19+
}
20+
return name;
21+
}
22+
23+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.jooby.i2408;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import io.jooby.WebClient;
6+
import io.jooby.junit.ServerTest;
7+
import io.jooby.junit.ServerTestRunner;
8+
9+
public class Issue2408 {
10+
@ServerTest
11+
public void shouldNotIgnoreAnnotationOnParam(ServerTestRunner runner) {
12+
runner.define(app -> {
13+
app.mvc(new C2408());
14+
app.error((ctx, cause, code) -> {
15+
ctx.send(cause.getMessage());
16+
});
17+
}).ready((WebClient http) -> {
18+
19+
http.get("/2408/nullable?blah=stuff", rsp -> {
20+
assertEquals("nothing", rsp.body().string());
21+
});
22+
23+
http.get("/2408/nonnull", rsp -> {
24+
assertEquals("Missing value: 'name'", rsp.body().string());
25+
});
26+
27+
});
28+
}
29+
}

0 commit comments

Comments
 (0)