Skip to content

Commit 4d5ea35

Browse files
committed
jooby apt: generate source code
- Add new annotation processor - Simplify annotation processor a lot - More clean and readable version - Name generated router like `Controller` + `_` - Use JavaPoet to generate code - For now it generates binary and source code
1 parent 3056c3e commit 4d5ea35

38 files changed

Lines changed: 1649 additions & 119 deletions

jooby/src/main/java/io/jooby/Jooby.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,16 @@ public Jooby mount(@NonNull Router router) {
526526
return mount("/", router);
527527
}
528528

529+
@NonNull @Override
530+
public Jooby mvc(@NonNull MvcExtension router) {
531+
try {
532+
router.install(this);
533+
return this;
534+
} catch (Exception cause) {
535+
throw SneakyThrows.propagate(cause);
536+
}
537+
}
538+
529539
@NonNull @Override
530540
public Jooby mvc(@NonNull Object router) {
531541
Provider provider = () -> router;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby;
7+
8+
/**
9+
* Marker interface for generated MVC router.
10+
*
11+
* @author edgar
12+
* @since 3.2.0
13+
*/
14+
public interface MvcExtension extends Extension {}

jooby/src/main/java/io/jooby/Route.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -687,11 +687,11 @@ public boolean isNonBlockingSet() {
687687
* @return This route.
688688
*/
689689
public @NonNull Route setProduces(@NonNull Collection<MediaType> produces) {
690-
if (produces.size() > 0) {
690+
if (!produces.isEmpty()) {
691691
if (this.produces == EMPTY_LIST) {
692692
this.produces = new ArrayList<>();
693693
}
694-
produces.forEach(this.produces::add);
694+
this.produces.addAll(produces);
695695
}
696696
return this;
697697
}
@@ -724,11 +724,11 @@ public boolean isNonBlockingSet() {
724724
* @return This route.
725725
*/
726726
public @NonNull Route setConsumes(@NonNull Collection<MediaType> consumes) {
727-
if (consumes.size() > 0) {
727+
if (!consumes.isEmpty()) {
728728
if (this.consumes == EMPTY_LIST) {
729729
this.consumes = new ArrayList<>();
730730
}
731-
consumes.forEach(this.consumes::add);
731+
this.consumes.addAll(consumes);
732732
}
733733
return this;
734734
}

jooby/src/main/java/io/jooby/Router.java

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
package io.jooby;
77

8-
import static java.util.Arrays.asList;
98
import static java.util.Collections.unmodifiableList;
109
import static java.util.Objects.requireNonNull;
1110

@@ -109,8 +108,7 @@ default Object execute(@NonNull Context context) {
109108
String TRACE = "TRACE";
110109

111110
/** HTTP Methods. */
112-
List<String> METHODS =
113-
unmodifiableList(asList(GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE));
111+
List<String> METHODS = List.of(GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE);
114112

115113
/** Web socket. */
116114
String WS = "WS";
@@ -410,13 +408,23 @@ default Object execute(@NonNull Context context) {
410408
* ***********************************************************************************************
411409
*/
412410

411+
/**
412+
* Import all route method from the given controller class.
413+
*
414+
* @param router Router extension.
415+
* @return This router.
416+
*/
417+
@NonNull Router mvc(@NonNull MvcExtension router);
418+
413419
/**
414420
* Import all route method from the given controller class. At runtime the controller instance is
415421
* resolved by calling {@link Jooby#require(Class)}.
416422
*
417423
* @param router Controller class.
418424
* @return This router.
425+
* @deprecated See {{@link #mvc(MvcExtension)}}
419426
*/
427+
@Deprecated
420428
@NonNull Router mvc(@NonNull Class router);
421429

422430
/**
@@ -426,15 +434,19 @@ default Object execute(@NonNull Context context) {
426434
* @param provider Controller provider.
427435
* @param <T> Controller type.
428436
* @return This router.
437+
* @deprecated See {{@link #mvc(MvcExtension)}}
429438
*/
439+
@Deprecated
430440
@NonNull <T> Router mvc(@NonNull Class<T> router, @NonNull Provider<T> provider);
431441

432442
/**
433443
* Import all route methods from given controller instance.
434444
*
435445
* @param router Controller instance.
436446
* @return This routes.
447+
* @deprecated See {{@link #mvc(MvcExtension)}}
437448
*/
449+
@Deprecated
438450
@NonNull Router mvc(@NonNull Object router);
439451

440452
/**
@@ -1105,14 +1117,11 @@ default Object execute(@NonNull Context context) {
11051117
i = len;
11061118
}
11071119
}
1108-
switch (result.size()) {
1109-
case 0:
1110-
return Collections.emptyList();
1111-
case 1:
1112-
return Collections.singletonList(result.get(0));
1113-
default:
1114-
return unmodifiableList(result);
1115-
}
1120+
return switch (result.size()) {
1121+
case 0 -> Collections.emptyList();
1122+
case 1 -> Collections.singletonList(result.get(0));
1123+
default -> unmodifiableList(result);
1124+
};
11161125
}
11171126

11181127
/**
@@ -1207,7 +1216,7 @@ default Object execute(@NonNull Context context) {
12071216
if (paths.isEmpty()) {
12081217
return Collections.singletonList(pattern);
12091218
}
1210-
if (segment.length() > 0) {
1219+
if (!segment.isEmpty()) {
12111220
pathAppender.accept(key.get(), segment);
12121221
if (isLastOptional) {
12131222
paths.put(key.incrementAndGet(), segment);
@@ -1273,7 +1282,7 @@ default Object execute(@NonNull Context context) {
12731282
i = len;
12741283
}
12751284
}
1276-
if (path.length() == 0) {
1285+
if (path.isEmpty()) {
12771286
return pattern;
12781287
}
12791288
if (start > 0) {

jooby/src/main/java/io/jooby/internal/RouterImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,11 @@ public Router mount(@NonNull Router router) {
333333
return mount("/", router);
334334
}
335335

336+
@NonNull @Override
337+
public Router mvc(@NonNull MvcExtension router) {
338+
throw new UnsupportedOperationException();
339+
}
340+
336341
@NonNull @Override
337342
public Router mvc(@NonNull Object router) {
338343
throw new UnsupportedOperationException();

modules/jooby-apt/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919

2020
<dependencies>
2121

22+
<!-- https://mvnrepository.com/artifact/com.squareup/javapoet -->
23+
<dependency>
24+
<groupId>com.squareup</groupId>
25+
<artifactId>javapoet</artifactId>
26+
<version>1.13.0</version>
27+
</dependency>
28+
2229
<!-- ASM -->
2330
<dependency>
2431
<groupId>org.ow2.asm</groupId>

modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public class JoobyProcessor extends AbstractProcessor {
6969
private int round;
7070
private Map<TypeElement, String> modules = new LinkedHashMap<>();
7171
private Set<String> alreadyProcessed = new HashSet<>();
72+
private MvcSourceCodeProcessor sourceCodeProcessor = new MvcSourceCodeProcessor();
7273

7374
@Override
7475
public Set<String> getSupportedOptions() {
@@ -102,6 +103,7 @@ public SourceVersion getSupportedSourceVersion() {
102103

103104
@Override
104105
public synchronized void init(ProcessingEnvironment processingEnvironment) {
106+
sourceCodeProcessor.init(processingEnvironment);
105107
this.processingEnv = processingEnvironment;
106108

107109
debug = Opts.boolOpt(processingEnv, Opts.OPT_DEBUG, false);
@@ -118,6 +120,7 @@ public synchronized void init(ProcessingEnvironment processingEnvironment) {
118120
@Override
119121
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
120122
try {
123+
sourceCodeProcessor.process(annotations, roundEnv);
121124
debug("Round #%s", round++);
122125
if (roundEnv.processingOver()) {
123126
if (services) {
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.apt;
7+
8+
import java.util.*;
9+
import java.util.stream.Stream;
10+
11+
import javax.annotation.processing.Messager;
12+
import javax.annotation.processing.ProcessingEnvironment;
13+
import javax.lang.model.element.*;
14+
import javax.tools.Diagnostic;
15+
16+
import io.jooby.internal.apt.Annotations;
17+
import io.jooby.internal.apt.Opts;
18+
19+
public class MvcContext {
20+
private final ProcessingEnvironment processingEnvironment;
21+
private final boolean debug;
22+
private final boolean incremental;
23+
private final boolean services;
24+
private int round;
25+
private final Messager messager;
26+
private final Map<Object, Object> attributes = new HashMap<>();
27+
28+
public MvcContext(ProcessingEnvironment processingEnvironment, Messager messager) {
29+
this.processingEnvironment = processingEnvironment;
30+
this.messager = messager;
31+
this.debug = Opts.boolOpt(processingEnvironment, Opts.OPT_DEBUG, false);
32+
this.incremental = Opts.boolOpt(processingEnvironment, Opts.OPT_INCREMENTAL, true);
33+
this.services = Opts.boolOpt(processingEnvironment, Opts.OPT_SERVICES, true);
34+
35+
debug("Incremental annotation processing is turned %s.", incremental ? "ON" : "OFF");
36+
debug("Generation of service provider configuration is turned %s.", services ? "ON" : "OFF");
37+
}
38+
39+
public ProcessingEnvironment getProcessingEnvironment() {
40+
return processingEnvironment;
41+
}
42+
43+
public Map<Object, Object> getAttributes() {
44+
return attributes;
45+
}
46+
47+
public boolean isHttpMethod(TypeElement annotated) {
48+
if (annotated == null) {
49+
return false;
50+
}
51+
return Annotations.HTTP_METHODS.contains(annotated.toString())
52+
|| Annotations.HTTP_METHODS.contains(annotated.asType().toString());
53+
}
54+
55+
public List<String> path(TypeElement owner, ExecutableElement exec, TypeElement annotation) {
56+
var prefix = Collections.<String>emptyList();
57+
// Look at parent @path annotation
58+
var superTypes = superTypes(owner);
59+
var i = 0;
60+
while (prefix.isEmpty() && i < superTypes.size()) {
61+
prefix = path(superTypes.get(i++));
62+
}
63+
64+
// Favor GET("/path") over Path("/path") at method level
65+
var path = path(annotation.getQualifiedName().toString(), annotation.getAnnotationMirrors());
66+
if (path.isEmpty()) {
67+
path = path(annotation.getQualifiedName().toString(), exec.getAnnotationMirrors());
68+
}
69+
var methodPath = path;
70+
if (prefix.isEmpty()) {
71+
return path.isEmpty() ? Collections.singletonList("/") : path;
72+
}
73+
if (path.isEmpty()) {
74+
return prefix;
75+
}
76+
return prefix.stream()
77+
.flatMap(root -> methodPath.stream().map(p -> root.equals("/") ? p : root + p))
78+
.distinct()
79+
.toList();
80+
}
81+
82+
private List<String> path(Element element) {
83+
return path(null, element.getAnnotationMirrors());
84+
}
85+
86+
private List<String> path(String method, List<? extends AnnotationMirror> annotations) {
87+
return annotations.stream()
88+
.map(AnnotationMirror.class::cast)
89+
.flatMap(
90+
mirror -> {
91+
String type = mirror.getAnnotationType().toString();
92+
if (Annotations.PATH.contains(type) || type.equals(method)) {
93+
return Stream.concat(
94+
Annotations.attribute(mirror, "path").stream(),
95+
Annotations.attribute(mirror, "value").stream());
96+
}
97+
return Stream.empty();
98+
})
99+
.distinct()
100+
.toList();
101+
}
102+
103+
public List<TypeElement> superTypes(Element owner) {
104+
var typeUtils = processingEnvironment.getTypeUtils();
105+
var supertypes = typeUtils.directSupertypes(owner.asType());
106+
List<TypeElement> result = new ArrayList<>();
107+
result.add((TypeElement) owner);
108+
if (supertypes == null || supertypes.isEmpty()) {
109+
return result;
110+
}
111+
var supertype = supertypes.get(0);
112+
var supertypeName = typeUtils.erasure(supertype).toString();
113+
var supertypeElement = typeUtils.asElement(supertype);
114+
if (!Object.class.getName().equals(supertypeName)
115+
&& supertypeElement.getKind() == ElementKind.CLASS) {
116+
result.addAll(superTypes(supertypeElement));
117+
}
118+
return result;
119+
}
120+
121+
public boolean generateServices() {
122+
return services;
123+
}
124+
125+
public boolean isIncremental() {
126+
return incremental;
127+
}
128+
129+
public boolean isServices() {
130+
return services;
131+
}
132+
133+
public int nextRound() {
134+
return ++round;
135+
}
136+
137+
public void debug(String message, Object... args) {
138+
if (debug) {
139+
print(Diagnostic.Kind.NOTE, message, args);
140+
}
141+
}
142+
143+
public void error(String message, Object... args) {
144+
print(Diagnostic.Kind.ERROR, message, args);
145+
}
146+
147+
public void warning(String message, Object... args) {
148+
print(Diagnostic.Kind.WARNING, message, args);
149+
}
150+
151+
public void info(String message, Object... args) {
152+
print(Diagnostic.Kind.NOTE, message, args);
153+
}
154+
155+
private void print(Diagnostic.Kind kind, String message, Object... args) {
156+
var msg = message.formatted(args);
157+
Element element =
158+
args.length > 0
159+
? (args[args.length - 1] instanceof Element) ? (Element) args[args.length - 1] : null
160+
: null;
161+
messager.printMessage(kind, msg, element);
162+
}
163+
}

0 commit comments

Comments
 (0)