Skip to content

Commit 657769d

Browse files
committed
Feature request: type-safe role annotations with enums fix #1527
1 parent 448429a commit 657769d

File tree

11 files changed

+150
-10
lines changed

11 files changed

+150
-10
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ private void setReturnType(MethodVisitor visitor, HandlerCompiler handler)
273273

274274
List<TypeDefinition> args = returnType.getArguments();
275275

276-
ArrayWriter.write(visitor, java.lang.reflect.Type.class, args, type ->
276+
ArrayWriter.write(visitor, java.lang.reflect.Type.class.getName(), args, type ->
277277
visitor.visitLdcInsn(type.toJvmType())
278278
);
279279

@@ -295,7 +295,7 @@ private void setReturnType(MethodVisitor visitor, HandlerCompiler handler)
295295
private void setContentType(MethodVisitor visitor, String methodName, List<String> mediaTypes) {
296296
if (mediaTypes.size() > 0) {
297297
visitor.visitVarInsn(ALOAD, 2);
298-
ArrayWriter.write(visitor, MediaType.class, mediaTypes, mediaType -> {
298+
ArrayWriter.write(visitor, MediaType.class.getName(), mediaTypes, mediaType -> {
299299
visitor.visitLdcInsn(mediaType);
300300
visitor.visitMethodInsn(INVOKESTATIC, "io/jooby/MediaType", "valueOf",
301301
"(Ljava/lang/String;)Lio/jooby/MediaType;", false);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
import java.util.function.Consumer;
1313

1414
public class ArrayWriter {
15-
public static <T> void write(MethodVisitor visitor, Class componentType, List<T> items, Consumer<T> foreach) {
15+
public static <T> void write(MethodVisitor visitor, String componentType, List<T> items, Consumer<T> foreach) {
1616
visitor.visitInsn(Opcodes.ICONST_0 + items.size());
17-
visitor.visitTypeInsn(Opcodes.ANEWARRAY, componentType.getName().replace(".", "/"));
17+
visitor.visitTypeInsn(Opcodes.ANEWARRAY, componentType.replace(".", "/"));
1818
for (int i = 0; i < items.size(); i++) {
1919
visitor.visitInsn(Opcodes.DUP);
2020
visitor.visitInsn(Opcodes.ICONST_0 + i);

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

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99
import io.jooby.SneakyThrows;
1010
import io.jooby.internal.apt.Primitives;
1111
import io.jooby.internal.apt.TypeDefinition;
12-
import io.jooby.internal.apt.asm.ArrayWriter;
1312
import org.objectweb.asm.ClassWriter;
1413
import org.objectweb.asm.MethodVisitor;
1514
import org.objectweb.asm.Opcodes;
1615
import org.objectweb.asm.Type;
1716

1817
import javax.lang.model.element.AnnotationMirror;
1918
import javax.lang.model.element.AnnotationValue;
19+
import javax.lang.model.element.Element;
2020
import javax.lang.model.element.ExecutableElement;
21+
import javax.lang.model.element.Name;
22+
import javax.lang.model.element.TypeElement;
23+
import javax.lang.model.element.VariableElement;
2124
import javax.lang.model.type.TypeMirror;
2225
import javax.lang.model.util.Elements;
2326
import javax.lang.model.util.Types;
@@ -33,6 +36,7 @@
3336

3437
import static io.jooby.SneakyThrows.throwingConsumer;
3538
import static java.util.Collections.singletonList;
39+
import static java.util.Collections.sort;
3640
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
3741
import static org.objectweb.asm.Opcodes.ACC_STATIC;
3842
import static org.objectweb.asm.Opcodes.ALOAD;
@@ -59,6 +63,18 @@
5963
import static org.objectweb.asm.Opcodes.POP;
6064

6165
public class RouteAttributesWriter {
66+
67+
private static class EnumValue {
68+
private String type;
69+
70+
private String value;
71+
72+
public EnumValue(String type, String value) {
73+
this.type = type;
74+
this.value = value;
75+
}
76+
}
77+
6278
private static final Predicate<String> HTTP_ANNOTATION = it ->
6379
it.startsWith("io.jooby.annotations")
6480
|| it.startsWith("javax.ws.rs");
@@ -67,8 +83,10 @@ public class RouteAttributesWriter {
6783
|| it.endsWith("NotNull")
6884
|| it.endsWith("Nullable");
6985

86+
private static final Predicate<String> KOTLIN_ANNOTATION = it -> it.equals("kotlin.Metadata");
87+
7088
private static final Predicate<String> ATTR_FILTER = HTTP_ANNOTATION.negate()
71-
.and(NULL_ANNOTATION.negate());
89+
.and(NULL_ANNOTATION.negate()).and(KOTLIN_ANNOTATION.negate());
7290

7391
private final Elements elements;
7492

@@ -147,6 +165,13 @@ private Object annotationValue(AnnotationValue annotationValue) {
147165
if (value instanceof AnnotationMirror) {
148166
Map<String, Object> annotation = annotationMap(singletonList((AnnotationMirror) value), null);
149167
return annotation.isEmpty() ? null : annotation;
168+
} else if (value instanceof VariableElement) {
169+
// enum
170+
VariableElement vare = (VariableElement) annotationValue.getValue();
171+
TypeMirror typeMirror = vare.asType();
172+
Element element = types.asElement(typeMirror);
173+
Name binaryName = elements.getBinaryName((TypeElement) element);
174+
return new EnumValue(binaryName.toString(), value.toString());
150175
} else if (value instanceof List) {
151176
List<AnnotationValue> values = (List) value;
152177
if (values.size() > 0) {
@@ -169,8 +194,9 @@ private void annotationValue(ClassWriter writer, MethodVisitor visitor, Object v
169194
.visitMethodInsn(INVOKESTATIC, moduleInternalName, newMap, "()Ljava/util/Map;", false);
170195
} else if (value instanceof List) {
171196
List values = (List) value;
197+
String componentType = values.get(0) instanceof EnumValue? ((EnumValue) values.get(0)).type : values.get(0).getClass().getName();
172198
if (values.size() > 0) {
173-
ArrayWriter.write(visitor, values.get(0).getClass(), values, throwingConsumer(v ->
199+
ArrayWriter.write(visitor, componentType, values, throwingConsumer(v ->
174200
annotationValue(writer, visitor, v)
175201
));
176202
Method asList = Arrays.class.getDeclaredMethod("asList", Object[].class);
@@ -245,6 +271,10 @@ private void annotationSingleValue(MethodVisitor visitor, Object value)
245271
} else {
246272
visitor.visitLdcInsn(typeDef.toJvmType());
247273
}
274+
} else if (value instanceof EnumValue) {
275+
EnumValue enumValue = (EnumValue) value;
276+
Type type = Type.getObjectType(enumValue.type.replace(".", "/"));
277+
visitor.visitFieldInsn(GETSTATIC, type.getInternalName(), enumValue.value, type.getDescriptor());
248278
}
249279

250280
Method wrapper = Primitives.wrapper(value.getClass());

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void accept(ClassWriter writer, String handlerInternalName, MethodVisitor
4343
} else {
4444
visitor.visitLdcInsn(parameter.getType().toJvmType());
4545

46-
ArrayWriter.write(visitor, Type.class, parameter.getType().getArguments(), type ->
46+
ArrayWriter.write(visitor, Type.class.getName(), parameter.getType().getArguments(), type ->
4747
visitor.visitLdcInsn(type.toJvmType())
4848
);
4949
reified = Reified.class.getMethod("getParameterized", Type.class, Type[].class);

modules/jooby-apt/src/test/java/output/MvcExtension.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.jooby.Extension;
44
import io.jooby.Jooby;
55
import io.jooby.MvcFactory;
6+
import source.Controller1527;
67

78
import javax.annotation.Nonnull;
89
import javax.inject.Provider;
@@ -13,8 +14,7 @@ private static void install(Jooby application, Provider<MyController> provider)
1314
application.get("/mypath", ctx -> {
1415
MyController myController = provider.get();
1516
return myController.controllerMethod();
16-
}).setReturnType(String.class)
17-
.attribute("RoleAnnotation", "User");
17+
}).attribute("RequireRole", Controller1527.Role.USER);
1818
}
1919

2020
@Override public boolean supports(@Nonnull Class type) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package source;
2+
3+
import io.jooby.annotations.GET;
4+
import io.jooby.annotations.Path;
5+
6+
import java.lang.annotation.ElementType;
7+
import java.lang.annotation.Retention;
8+
import java.lang.annotation.RetentionPolicy;
9+
import java.lang.annotation.Target;
10+
11+
@Path("/hello")
12+
@TopAnnotation(TopEnum.FOO)
13+
public class Controller1527 {
14+
public enum Role {USER, ADMIN}
15+
16+
@Target({ElementType.METHOD, ElementType.TYPE})
17+
@Retention(RetentionPolicy.RUNTIME) @interface RequireRole {
18+
Role value();
19+
}
20+
21+
@GET @RequireRole(Role.ADMIN)
22+
public String login() {
23+
return "";
24+
}
25+
26+
@GET @TopAnnotation({TopEnum.BAR, TopEnum.FOO})
27+
public String topannotation() {
28+
return "";
29+
}
30+
31+
@GET
32+
@StringArrayAnnotation({"a", "b", "c"})
33+
public String classannotation() {
34+
return "";
35+
}
36+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package source;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target({ElementType.METHOD, ElementType.TYPE})
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface StringArrayAnnotation {
11+
String[] value();
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package source;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target({ElementType.METHOD, ElementType.TYPE})
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface TopAnnotation {
11+
TopEnum[] value();
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package source;
2+
3+
public enum TopEnum {
4+
FOO, BAR
5+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package tests;
2+
3+
import io.jooby.Route;
4+
import io.jooby.apt.MvcModuleCompilerRunner;
5+
import org.junit.jupiter.api.Test;
6+
import source.Controller1527;
7+
import source.TopEnum;
8+
9+
import java.util.Arrays;
10+
11+
import static org.junit.jupiter.api.Assertions.assertEquals;
12+
13+
public class Issue1527 {
14+
// @Test
15+
public void annotation() throws Exception {
16+
new MvcModuleCompilerRunner(new Controller1527())
17+
.module(app -> {
18+
Route route0 = app.getRoutes().get(0);
19+
assertEquals(2, route0.getAttributes().size(), route0.getAttributes().toString());
20+
assertEquals(Controller1527.Role.ADMIN, route0.attribute("requireRole"));
21+
assertEquals(Arrays.asList(TopEnum.FOO), route0.attribute("topAnnotation"));
22+
23+
Route route1 = app.getRoutes().get(1);
24+
assertEquals(1, route1.getAttributes().size(), route1.getAttributes().toString());
25+
assertEquals(Arrays.asList(TopEnum.BAR, TopEnum.FOO), route1.attribute("topAnnotation"));
26+
27+
Route route2 = app.getRoutes().get(2);
28+
assertEquals(2, route2.getAttributes().size(), route2.getAttributes().toString());
29+
assertEquals(Arrays.asList(TopEnum.FOO), route2.attribute("topAnnotation"));
30+
assertEquals(Arrays.asList("a", "b", "c"), route2.attribute("stringArrayAnnotation"));
31+
})
32+
;
33+
}
34+
}

0 commit comments

Comments
 (0)