@@ -129,87 +129,51 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
129129 try {
130130 if (roundEnv .processingOver ()) {
131131 context .debug ("Output:" );
132- // Print all generated types for both REST and RPC
133- context
134- .getRouters ()
135- .forEach (
136- it -> {
137- if (it .hasRestRoutes ()) {
138- context .debug (" %s" , it .getRestGeneratedType ());
139- }
140- if (it .hasJsonRpcRoutes ()) {
141- context .debug (" %s" , it .getRpcGeneratedType ());
142- }
143- });
132+ context .getRouters ().forEach (it -> context .debug (" %s" , it .getGeneratedType ()));
144133 return false ;
145134 } else {
146- var routeMap = buildRouteRegistry (annotations , roundEnv );
147- verifyBeanValidationDependency (routeMap .values ());
148- for (var router : routeMap .values ()) {
135+ // 1. Discover all unique Controller classes
136+ Set <TypeElement > controllers = findControllers (annotations , roundEnv );
137+
138+ // 2. Factory Pattern: Build specific routers for each class based on method annotations
139+ List <WebRouter <?>> activeRouters = new ArrayList <>();
140+ for (TypeElement controller : controllers ) {
141+ if (controller .getModifiers ().contains (Modifier .ABSTRACT )) continue ;
142+
143+ // These factory methods will scan the class methods and return a populated router
144+ // if it finds relevant annotations (@GET for Rest, @McpTool for MCP, etc.)
145+ // We will implement these factories inside the respective Router classes.
146+
147+ RestRouter restRouter = RestRouter .parse (context , controller );
148+ if (!restRouter .isEmpty ()) activeRouters .add (restRouter );
149+
150+ JsonRpcRouter jsonRpcRouter = JsonRpcRouter .parse (context , controller );
151+ if (!jsonRpcRouter .isEmpty ()) activeRouters .add (jsonRpcRouter );
152+
153+ McpRouter mcpRouter = McpRouter .parse (context , controller );
154+ if (!mcpRouter .isEmpty ()) activeRouters .add (mcpRouter );
155+
156+ TrpcRouter trpcRouter = TrpcRouter .parse (context , controller );
157+ if (!trpcRouter .isEmpty ()) activeRouters .add (trpcRouter );
158+ }
159+
160+ verifyBeanValidationDependency (activeRouters );
161+
162+ // 3. Generate Code Iteratively!
163+ for (WebRouter <?> router : activeRouters ) {
149164 try {
150- // Track the router unconditionally so routes are available in processingOver
151- context .add (router );
152-
153- // 1. Generate Standard REST/tRPC File (e.g., MovieService_.java)
154- if (router .hasRestRoutes ()) {
155- var restSource = router .getRestSourceCode (null );
156- if (restSource != null ) {
157- var sourceLocation = router .getRestGeneratedFilename ();
158- var generatedType = router .getRestGeneratedType ();
159- onGeneratedSource (generatedType , toJavaFileObject (sourceLocation , restSource ));
160-
161- context .debug ("router %s: %s" , router .getTargetType (), generatedType );
162- router .getRoutes ().stream ()
163- .filter (it -> !it .isJsonRpc ())
164- .forEach (it -> context .debug (" %s" , it ));
165-
166- writeSource (
167- router .isKt (),
168- generatedType ,
169- sourceLocation ,
170- restSource ,
171- router .getTargetType ());
172- }
173- }
165+ context .add (router ); // Track for processingOver output
174166
175- // 2. Generate JSON-RPC File (e.g., MovieServiceRpc_.java)
176- if (router .hasJsonRpcRoutes ()) {
177- var rpcSource = router .getRpcSourceCode (null );
178- if (rpcSource != null ) {
179- var sourceLocation = router .getRpcGeneratedFilename ();
180- var generatedType = router .getRpcGeneratedType ();
181- onGeneratedSource (generatedType , toJavaFileObject (sourceLocation , rpcSource ));
182-
183- context .debug ("jsonrpc router %s: %s" , router .getTargetType (), generatedType );
184- router .getRoutes ().stream ()
185- .filter (MvcRoute ::isJsonRpc )
186- .forEach (it -> context .debug (" %s" , it ));
187-
188- writeSource (
189- router .isKt (),
190- generatedType ,
191- sourceLocation ,
192- rpcSource ,
193- router .getTargetType ());
194- }
195- }
196- // 3. Generate MCP Server File (e.g., WeatherServerMcp_.java)
197- if (router .hasMcpRoutes ()) {
198- var mcpSource = router .getMcpSourceCode (null );
199- if (mcpSource != null ) {
200- var sourceLocation = router .getMcpGeneratedFilename ();
201- var generatedType = router .getMcpGeneratedType ();
202- onGeneratedSource (generatedType , toJavaFileObject (sourceLocation , mcpSource ));
203-
204- context .debug ("mcp router %s: %s" , router .getTargetType (), generatedType );
205-
206- writeSource (
207- router .isKt (),
208- generatedType ,
209- sourceLocation ,
210- mcpSource ,
211- router .getTargetType ());
212- }
167+ String sourceCode = router .getSourceCode (null );
168+ if (sourceCode != null ) {
169+ String sourceLocation = router .getGeneratedFilename ();
170+ String generatedType = router .getGeneratedType ();
171+
172+ onGeneratedSource (generatedType , toJavaFileObject (sourceLocation , sourceCode ));
173+ context .debug ("router %s: %s" , router .getTargetType (), generatedType );
174+
175+ writeSource (
176+ router .isKt (), generatedType , sourceLocation , sourceCode , router .getTargetType ());
213177 }
214178
215179 } catch (IOException cause ) {
@@ -225,6 +189,21 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
225189 }
226190 }
227191
192+ private Set <TypeElement > findControllers (
193+ Set <? extends TypeElement > annotations , RoundEnvironment roundEnv ) {
194+ Set <TypeElement > controllers = new LinkedHashSet <>();
195+ for (var annotation : annotations ) {
196+ for (var element : roundEnv .getElementsAnnotatedWith (annotation )) {
197+ if (element instanceof TypeElement typeElement ) {
198+ controllers .add (typeElement );
199+ } else if (element instanceof ExecutableElement method ) {
200+ controllers .add ((TypeElement ) method .getEnclosingElement ());
201+ }
202+ }
203+ }
204+ return controllers ;
205+ }
206+
228207 private void writeSource (
229208 boolean isKt ,
230209 String className ,
@@ -492,8 +471,8 @@ private static <E extends Throwable> E sneakyThrow0(final Throwable x) throws E
492471 throw (E ) x ;
493472 }
494473
495- private void verifyBeanValidationDependency (Collection <MvcRouter > routers ) {
496- var hasBeanValidation = routers .stream ().anyMatch (MvcRouter ::hasBeanValidation );
474+ private void verifyBeanValidationDependency (Collection <WebRouter <?> > routers ) {
475+ var hasBeanValidation = routers .stream ().anyMatch (WebRouter ::hasBeanValidation );
497476 if (hasBeanValidation ) {
498477 var missingDependency =
499478 Stream .of (
0 commit comments