Skip to content

Commit b5dca0f

Browse files
committed
- build: code clean up
1 parent d8e2047 commit b5dca0f

14 files changed

Lines changed: 237 additions & 3689 deletions

File tree

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

Lines changed: 37 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@
1616
import java.nio.file.Paths;
1717
import java.util.*;
1818
import java.util.function.BiConsumer;
19-
import java.util.stream.Collectors;
2019
import java.util.stream.Stream;
2120

2221
import javax.annotation.processing.*;
2322
import javax.lang.model.SourceVersion;
2423
import javax.lang.model.element.*;
25-
import javax.lang.model.type.DeclaredType;
2624
import javax.tools.Diagnostic;
2725
import javax.tools.JavaFileObject;
2826
import javax.tools.SimpleJavaFileObject;
@@ -132,50 +130,55 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
132130
context.getRouters().forEach(it -> context.debug(" %s", it.getGeneratedType()));
133131
return false;
134132
} else {
135-
// 1. Discover all unique Controller classes
136-
Set<TypeElement> controllers = findControllers(annotations, roundEnv);
133+
// Discover all unique Controller classes
134+
var controllers = findControllers(annotations, roundEnv);
137135

138-
// 2. Factory Pattern: Build specific routers for each class based on method annotations
136+
// Factory Pattern: Build specific routers for each class based on method annotations
139137
List<WebRouter<?>> activeRouters = new ArrayList<>();
140-
for (TypeElement controller : controllers) {
138+
for (var controller : controllers) {
141139
if (controller.getModifiers().contains(Modifier.ABSTRACT)) continue;
142140

143141
// These factory methods will scan the class methods and return a populated router
144142
// if it finds relevant annotations (@GET for Rest, @McpTool for MCP, etc.)
145143
// We will implement these factories inside the respective Router classes.
146144

147-
RestRouter restRouter = RestRouter.parse(context, controller);
148-
if (!restRouter.isEmpty()) activeRouters.add(restRouter);
145+
var restRouter = RestRouter.parse(context, controller);
146+
if (!restRouter.isEmpty()) {
147+
activeRouters.add(restRouter);
148+
}
149149

150-
JsonRpcRouter jsonRpcRouter = JsonRpcRouter.parse(context, controller);
151-
if (!jsonRpcRouter.isEmpty()) activeRouters.add(jsonRpcRouter);
150+
var jsonRpcRouter = JsonRpcRouter.parse(context, controller);
151+
if (!jsonRpcRouter.isEmpty()) {
152+
activeRouters.add(jsonRpcRouter);
153+
}
152154

153-
McpRouter mcpRouter = McpRouter.parse(context, controller);
154-
if (!mcpRouter.isEmpty()) activeRouters.add(mcpRouter);
155+
var mcpRouter = McpRouter.parse(context, controller);
156+
if (!mcpRouter.isEmpty()) {
157+
activeRouters.add(mcpRouter);
158+
}
155159

156-
TrpcRouter trpcRouter = TrpcRouter.parse(context, controller);
157-
if (!trpcRouter.isEmpty()) activeRouters.add(trpcRouter);
160+
var trpcRouter = TrpcRouter.parse(context, controller);
161+
if (!trpcRouter.isEmpty()) {
162+
activeRouters.add(trpcRouter);
163+
}
158164
}
159165

160166
verifyBeanValidationDependency(activeRouters);
161167

162-
// 3. Generate Code Iteratively!
163-
for (WebRouter<?> router : activeRouters) {
168+
// Generate Code Iteratively!
169+
for (var router : activeRouters) {
164170
try {
165-
context.add(router); // Track for processingOver output
166-
167-
String sourceCode = router.getSourceCode(null);
168-
if (sourceCode != null) {
169-
String sourceLocation = router.getGeneratedFilename();
170-
String generatedType = router.getGeneratedType();
171+
context.add(router);
171172

172-
onGeneratedSource(generatedType, toJavaFileObject(sourceLocation, sourceCode));
173-
context.debug("router %s: %s", router.getTargetType(), generatedType);
173+
var sourceCode = router.toSourceCode(null);
174+
var sourceLocation = router.getGeneratedFilename();
175+
var generatedType = router.getGeneratedType();
174176

175-
writeSource(
176-
router.isKt(), generatedType, sourceLocation, sourceCode, router.getTargetType());
177-
}
177+
onGeneratedSource(generatedType, toJavaFileObject(sourceLocation, sourceCode));
178+
context.debug("router %s: %s", router.getTargetType(), generatedType);
178179

180+
writeSource(
181+
router.isKt(), generatedType, sourceLocation, sourceCode, router.getTargetType());
179182
} catch (IOException cause) {
180183
throw new RuntimeException("Unable to generate: " + router.getTargetType(), cause);
181184
}
@@ -194,7 +197,8 @@ private Set<TypeElement> findControllers(
194197
Set<TypeElement> controllers = new LinkedHashSet<>();
195198
for (var annotation : annotations) {
196199
for (var element : roundEnv.getElementsAnnotatedWith(annotation)) {
197-
if (element instanceof TypeElement typeElement) {
200+
if (element instanceof TypeElement typeElement
201+
&& !typeElement.getModifiers().contains(Modifier.ABSTRACT)) {
198202
controllers.add(typeElement);
199203
} else if (element instanceof ExecutableElement method) {
200204
controllers.add((TypeElement) method.getEnclosingElement());
@@ -262,142 +266,16 @@ public String toString() {
262266

263267
protected void onGeneratedSource(String className, JavaFileObject source) {}
264268

265-
private Map<TypeElement, MvcRouter> buildRouteRegistry(
266-
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
267-
Map<TypeElement, MvcRouter> registry = new LinkedHashMap<>();
268-
269-
for (var annotation : annotations) {
270-
context.debug("found annotation: %s", annotation);
271-
var elements = roundEnv.getElementsAnnotatedWith(annotation);
272-
// Element could be Class or Method, bc @Path can be applied to both of them
273-
// Also we need to expand lookup to external jars see #2486
274-
for (var element : elements) {
275-
context.debug(" %s", element);
276-
if (element instanceof TypeElement typeElement) {
277-
// FORCE INIT: Ensures MvcRouter constructor executes our JsonRpc class-level rules
278-
registry.computeIfAbsent(typeElement, type -> new MvcRouter(context, type));
279-
buildRouteRegistry(registry, typeElement);
280-
} else if (element instanceof ExecutableElement method) {
281-
TypeElement typeElement = (TypeElement) method.getEnclosingElement();
282-
// FORCE INIT
283-
registry.computeIfAbsent(typeElement, type -> new MvcRouter(context, type));
284-
buildRouteRegistry(registry, typeElement);
285-
}
286-
}
287-
}
288-
289-
// Remove all abstract router
290-
var abstractTypes =
291-
registry.entrySet().stream()
292-
.filter(it -> it.getValue().isAbstract())
293-
.map(Map.Entry::getKey)
294-
.collect(Collectors.toSet());
295-
abstractTypes.forEach(registry::remove);
296-
297-
// Generate unique method name by router
298-
for (var router : registry.values()) {
299-
// Split routes by their target generated classes to avoid false collisions
300-
var restAndTrpcRoutes = router.getRoutes().stream().filter(r -> !r.isJsonRpc()).toList();
301-
302-
var rpcRoutes = router.getRoutes().stream().filter(MvcRoute::isJsonRpc).toList();
303-
304-
resolveGeneratedNames(restAndTrpcRoutes);
305-
resolveGeneratedNames(rpcRoutes);
306-
}
307-
return registry;
308-
}
309-
310-
private void resolveGeneratedNames(List<MvcRoute> routes) {
311-
// Group by the actual target method name in the generated class
312-
var grouped =
313-
routes.stream()
314-
.collect(
315-
Collectors.groupingBy(
316-
route -> {
317-
String baseName = route.getMethodName();
318-
return route.isTrpc()
319-
? "trpc"
320-
+ Character.toUpperCase(baseName.charAt(0))
321-
+ baseName.substring(1)
322-
: baseName;
323-
}));
324-
325-
for (var overloads : grouped.values()) {
326-
if (overloads.size() == 1) {
327-
// No conflict in this specific output file, use the clean original name
328-
overloads.get(0).setGeneratedName(overloads.get(0).getMethodName());
329-
} else {
330-
// Conflict detected: generate names based on parameter types
331-
for (var route : overloads) {
332-
var paramsString =
333-
route.getRawParameterTypes(true).stream()
334-
.map(it -> it.substring(Math.max(0, it.lastIndexOf(".") + 1)))
335-
.map(it -> Character.toUpperCase(it.charAt(0)) + it.substring(1))
336-
.collect(Collectors.joining());
337-
338-
// A 0-arg method gets exactly the base name.
339-
// Methods with args get the base name + their parameter types.
340-
route.setGeneratedName(route.getMethodName() + paramsString);
341-
}
342-
}
343-
}
344-
}
345-
346-
/**
347-
* Scan routes from basType and any super class of it. It saves all route method found in current
348-
* type or super (inherited). Routes method from super types are also saved.
349-
*
350-
* <p>Abstract route method are ignored.
351-
*
352-
* @param registry Route registry.
353-
* @param currentType Base type.
354-
*/
355-
private void buildRouteRegistry(Map<TypeElement, MvcRouter> registry, TypeElement currentType) {
356-
for (TypeElement superType : context.superTypes(currentType)) {
357-
// collect all declared methods
358-
superType.getEnclosedElements().stream()
359-
.filter(ExecutableElement.class::isInstance)
360-
.map(ExecutableElement.class::cast)
361-
.forEach(
362-
method -> {
363-
if (method.getModifiers().contains(Modifier.ABSTRACT)) {
364-
context.debug("ignoring abstract method: %s %s", superType, method);
365-
} else {
366-
method.getAnnotationMirrors().stream()
367-
.map(AnnotationMirror::getAnnotationType)
368-
.map(DeclaredType::asElement)
369-
.filter(TypeElement.class::isInstance)
370-
.map(TypeElement.class::cast)
371-
.filter(HttpMethod::hasAnnotation)
372-
.forEach(
373-
annotation -> {
374-
Stream.of(currentType, superType)
375-
.distinct()
376-
.forEach(
377-
routerClass ->
378-
registry
379-
.computeIfAbsent(
380-
routerClass, type -> new MvcRouter(context, type))
381-
.put(annotation, method));
382-
});
383-
}
384-
});
385-
if (!currentType.equals(superType)) {
386-
// edge-case #1: when a controller has no method and extends another class which has.
387-
// edge-case #2: some odd usage a controller could be empty.
388-
// See https://github.com/jooby-project/jooby/issues/3656
389-
if (registry.containsKey(superType)) {
390-
registry.computeIfAbsent(currentType, key -> new MvcRouter(key, registry.get(superType)));
391-
}
392-
}
393-
}
394-
}
395-
396269
@Override
397270
public Set<String> getSupportedAnnotationTypes() {
398271
var supportedTypes = new HashSet<String>();
399272
supportedTypes.addAll(HttpPath.PATH.getAnnotations());
400273
supportedTypes.addAll(HttpMethod.annotations());
274+
// Add Rcp annotations
275+
supportedTypes.add("io.jooby.annotation.Trpc");
276+
supportedTypes.add("io.jooby.annotation.Trpc.Mutation");
277+
supportedTypes.add("io.jooby.annotation.Trpc.Query");
278+
supportedTypes.add("io.jooby.annotation.JsonRpc");
401279
// Add MCP Annotations
402280
supportedTypes.add("io.jooby.annotation.McpTool");
403281
supportedTypes.add("io.jooby.annotation.McpPrompt");
@@ -428,37 +306,6 @@ public Set<String> getSupportedOptions() {
428306
return options;
429307
}
430308

431-
/**
432-
* Throws any throwable 'sneakily' - you don't need to catch it, nor declare that you throw it
433-
* onwards. The exception is still thrown - javac will just stop whining about it.
434-
*
435-
* <p>Example usage:
436-
*
437-
* <pre>public void run() {
438-
* throw sneakyThrow(new IOException("You don't need to catch me!"));
439-
* }</pre>
440-
*
441-
* <p>NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does
442-
* not know or care about the concept of a 'checked exception'. All this method does is hide the
443-
* act of throwing a checked exception from the java compiler.
444-
*
445-
* <p>Note that this method has a return type of {@code RuntimeException}; it is advised you
446-
* always call this method as argument to the {@code throw} statement to avoid compiler errors
447-
* regarding no return statement and similar problems. This method won't of course return an
448-
* actual {@code RuntimeException} - it never returns, it always throws the provided exception.
449-
*
450-
* @param x The throwable to throw without requiring you to catch its type.
451-
* @return A dummy RuntimeException; this method never returns normally, it <em>always</em> throws
452-
* an exception!
453-
*/
454-
public static RuntimeException propagate(final Throwable x) {
455-
if (x == null) {
456-
throw new NullPointerException("x");
457-
}
458-
459-
return sneakyThrow0(x);
460-
}
461-
462309
/**
463310
* Make a checked exception un-checked and rethrow it.
464311
*

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

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,7 @@ public enum HttpMethod implements AnnotationSupport {
2727
OPTIONS,
2828
PATCH,
2929
POST,
30-
PUT,
31-
// Special
32-
tRPC(
33-
List.of(
34-
"io.jooby.annotation.Trpc",
35-
"io.jooby.annotation.Trpc.Mutation",
36-
"io.jooby.annotation.Trpc.Query")),
37-
JSON_RPC(List.of("io.jooby.annotation.JsonRpc")),
38-
MCP(
39-
List.of(
40-
"io.jooby.annotation.McpTool",
41-
"io.jooby.annotation.McpCompletion",
42-
"io.jooby.annotation.McpPrompt",
43-
"io.jooby.annotation.McpResource",
44-
"io.jooby.annotation.McpServer"));
30+
PUT;
4531

4632
private final List<String> annotations;
4733

0 commit comments

Comments
 (0)