66import fr .traqueur .commands .api .models .Command ;
77import fr .traqueur .commands .api .models .CommandBuilder ;
88import fr .traqueur .commands .api .resolver .SenderResolver ;
9+ import fr .traqueur .commands .api .utils .Patterns ;
910
1011import java .lang .reflect .Method ;
1112import java .lang .reflect .Parameter ;
@@ -31,30 +32,47 @@ public AnnotationCommandProcessor(CommandManager<T, S> manager) {
3132 this .senderResolver = manager .getPlatform ().getSenderResolver ();
3233 }
3334
34- public void register (Object ... handlers ) {
35+ public List <Command <T , S >> register (Object ... handlers ) {
36+ List <Command <T , S >> allCommands = new ArrayList <>();
3537 for (Object handler : handlers ) {
36- processHandler (handler );
38+ allCommands . addAll ( processHandler (handler ) );
3739 }
40+ return allCommands ;
3841 }
3942
40- private void processHandler (Object handler ) {
43+ private List < Command < T , S >> processHandler (Object handler ) {
4144 Class <?> clazz = handler .getClass ();
45+ validateCommandContainer (clazz );
4246
47+ collectTabCompleters (handler , clazz );
48+
49+ List <CommandMethodInfo > commandMethods = collectCommandMethods (handler , clazz );
50+ Set <String > allPaths = extractAllPaths (commandMethods );
51+
52+ Map <String , Command <T , S >> builtCommands = buildAllCommands (commandMethods , allPaths );
53+ Set <String > rootCommands = organizeHierarchy (commandMethods , allPaths , builtCommands );
54+
55+ return registerRootCommands (rootCommands , builtCommands );
56+ }
57+
58+ private void validateCommandContainer (Class <?> clazz ) {
4359 if (!clazz .isAnnotationPresent (CommandContainer .class )) {
4460 throw new IllegalArgumentException (
4561 "Class must be annotated with @CommandContainer: " + clazz .getName ()
4662 );
4763 }
64+ }
4865
49- // First pass: collect all @TabComplete methods
66+ private void collectTabCompleters ( Object handler , Class <?> clazz ) {
5067 tabCompleters .clear ();
5168 for (Method method : clazz .getDeclaredMethods ()) {
5269 if (method .isAnnotationPresent (TabComplete .class )) {
5370 processTabCompleter (handler , method );
5471 }
5572 }
73+ }
5674
57- // Second pass: collect all @Command methods and sort by depth
75+ private List < CommandMethodInfo > collectCommandMethods ( Object handler , Class <?> clazz ) {
5876 List <CommandMethodInfo > commandMethods = new ArrayList <>();
5977 for (Method method : clazz .getDeclaredMethods ()) {
6078 if (method .isAnnotationPresent (fr .traqueur .commands .annotations .Command .class )) {
@@ -63,46 +81,51 @@ private void processHandler(Object handler) {
6381 commandMethods .add (new CommandMethodInfo (handler , method , annotation .name ()));
6482 }
6583 }
84+ commandMethods .sort (Comparator .comparingInt (info -> Patterns .DOT .split (info .name ).length ));
85+ return commandMethods ;
86+ }
6687
67- // Sort by depth (parents first)
68- commandMethods .sort (Comparator .comparingInt (info -> info .name .split ("\\ ." ).length ));
69-
70- // Collect all command paths to determine which have parents defined
88+ private Set <String > extractAllPaths (List <CommandMethodInfo > commandMethods ) {
7189 Set <String > allPaths = new HashSet <>();
7290 for (CommandMethodInfo info : commandMethods ) {
7391 allPaths .add (info .name );
7492 }
93+ return allPaths ;
94+ }
7595
76- // Third pass: build ALL commands first
96+ private Map < String , Command < T , S >> buildAllCommands ( List < CommandMethodInfo > commandMethods , Set < String > allPaths ) {
7797 Map <String , Command <T , S >> builtCommands = new LinkedHashMap <>();
78- Set <String > rootCommands = new LinkedHashSet <>();
79-
8098 for (CommandMethodInfo info : commandMethods ) {
8199 String parentPath = getParentPath (info .name );
82100 boolean hasParentInBatch = parentPath != null && allPaths .contains (parentPath );
83-
84101 Command <T , S > command = buildCommand (info .handler , info .method , info .name , hasParentInBatch );
85102 builtCommands .put (info .name , command );
86103 }
104+ return builtCommands ;
105+ }
87106
88- // Fourth pass: organize hierarchy (add subcommands to parents)
107+ private Set <String > organizeHierarchy (List <CommandMethodInfo > commandMethods , Set <String > allPaths ,
108+ Map <String , Command <T , S >> builtCommands ) {
109+ Set <String > rootCommands = new LinkedHashSet <>();
89110 for (CommandMethodInfo info : commandMethods ) {
90111 String parentPath = getParentPath (info .name );
91-
92112 if (parentPath != null && allPaths .contains (parentPath )) {
93- Command <T , S > parent = builtCommands .get (parentPath );
94- Command <T , S > child = builtCommands .get (info .name );
95- parent .addSubCommand (child );
113+ builtCommands .get (parentPath ).addSubCommand (builtCommands .get (info .name ));
96114 } else {
97115 rootCommands .add (info .name );
98116 }
99117 }
118+ return rootCommands ;
119+ }
100120
101- // Fifth pass: register only root commands
121+ private List <Command <T , S >> registerRootCommands (Set <String > rootCommands , Map <String , Command <T , S >> builtCommands ) {
122+ List <Command <T , S >> registeredCommands = new ArrayList <>();
102123 for (String rootPath : rootCommands ) {
103- Command <T , S > rootCommand = builtCommands .get (rootPath );
104- manager .registerCommand (rootCommand );
124+ Command <T , S > command = builtCommands .get (rootPath );
125+ manager .registerCommand (command );
126+ registeredCommands .add (command );
105127 }
128+ return registeredCommands ;
106129 }
107130
108131 private String getParentPath (String path ) {
@@ -167,67 +190,48 @@ private void processTabCompleter(Object handler, Method method) {
167190
168191 private void processParameters (CommandBuilder <T , S > builder , Method method , String commandPath ) {
169192 Parameter [] params = method .getParameters ();
170- Type [] genericTypes = method .getGenericParameterTypes ();
171193
172194 for (int i = 0 ; i < params .length ; i ++) {
173195 Parameter param = params [i ];
174- Class <?> paramType = param .getType ();
175-
176- // First parameter is sender (skip it for args)
177- if (i == 0 ) {
178- Class <?> senderType = paramType ;
179- if (paramType == Optional .class ) {
180- senderType = extractOptionalType (param );
181- }
182- if (senderResolver .canResolve (senderType )) {
183- continue ;
184- }
185- }
186196
187- // Must have @Arg annotation
188- Arg argAnnotation = param .getAnnotation (Arg .class );
189- if (argAnnotation == null ) {
190- throw new IllegalArgumentException (
191- "Parameter '" + param .getName () + "' in method '" + method .getName () +
192- "' must be annotated with @Arg or be the sender type"
193- );
197+ if (i == 0 && isSenderParameter (param )) {
198+ continue ;
194199 }
195200
196- String argName = argAnnotation . value ( );
197- boolean isOptional = paramType == Optional . class ;
198- boolean isInfinite = param . isAnnotationPresent ( Infinite . class );
201+ registerArgument ( builder , param , commandPath );
202+ }
203+ }
199204
200- // Determine the actual argument type
201- Class <?> argType ;
202- if (isOptional ) {
203- argType = extractOptionalType (param );
204- } else {
205- argType = paramType ;
206- }
205+ private boolean isSenderParameter (Parameter param ) {
206+ Class <?> paramType = param .getType ();
207+ Class <?> senderType = (paramType == Optional .class ) ? extractOptionalType (param ) : paramType ;
208+ return senderResolver .canResolve (senderType );
209+ }
207210
208- // If @Infinite, use Infinite.class as the type
209- if (isInfinite ) {
210- argType = fr .traqueur .commands .api .arguments .Infinite .class ;
211- }
211+ private void registerArgument (CommandBuilder <T , S > builder , Parameter param , String commandPath ) {
212+ String argName = getArgumentName (param );
213+ Class <?> argType = resolveArgumentType (param );
214+ boolean isOptional = param .getType () == Optional .class ;
215+ TabCompleter <S > completer = getTabCompleter (commandPath , argName );
212216
213- // Get tab completer if exists
214- TabCompleter <S > completer = getTabCompleter (commandPath , argName );
217+ if (isOptional ) {
218+ builder .optionalArg (argName , argType , completer );
219+ } else {
220+ builder .arg (argName , argType , completer );
221+ }
222+ }
215223
216- // Add argument to builder
217- if (isOptional ) {
218- if (completer != null ) {
219- builder .optionalArg (argName , argType , completer );
220- } else {
221- builder .optionalArg (argName , argType );
222- }
223- } else {
224- if (completer != null ) {
225- builder .arg (argName , argType , completer );
226- } else {
227- builder .arg (argName , argType );
228- }
229- }
224+ private String getArgumentName (Parameter param ) {
225+ Arg argAnnotation = param .getAnnotation (Arg .class );
226+ return (argAnnotation != null ) ? argAnnotation .value () : param .getName ();
227+ }
228+
229+ private Class <?> resolveArgumentType (Parameter param ) {
230+ if (param .isAnnotationPresent (Infinite .class )) {
231+ return fr .traqueur .commands .api .arguments .Infinite .class ;
230232 }
233+ Class <?> paramType = param .getType ();
234+ return (paramType == Optional .class ) ? extractOptionalType (param ) : paramType ;
231235 }
232236
233237 /**
@@ -257,67 +261,71 @@ private TabCompleter<S> getTabCompleter(String commandPath, String argName) {
257261
258262 return (sender , args ) -> {
259263 try {
260- Object result ;
261- Parameter [] params = tcMethod .method .getParameters ();
262-
263- if (params .length == 0 ) {
264- result = tcMethod .method .invoke (tcMethod .handler );
265- } else if (params .length == 1 ) {
266- Object resolvedSender = senderResolver .resolve (sender , params [0 ].getType ());
267- result = tcMethod .method .invoke (tcMethod .handler , resolvedSender );
268- } else {
269- Object resolvedSender = senderResolver .resolve (sender , params [0 ].getType ());
270- String current = !args .isEmpty () ? args .getLast () : "" ;
271- result = tcMethod .method .invoke (tcMethod .handler , resolvedSender , current );
272- }
273-
264+ Object result = invokeTabCompleter (tcMethod , sender , args );
274265 return (List <String >) result ;
275266 } catch (Exception e ) {
276- throw new RuntimeException ("Failed to invoke tab completer" , e );
267+ throw new RuntimeException (
268+ "Failed to invoke tab completer for command '" + commandPath +
269+ "', argument '" + argName + "', method '" + tcMethod .method .getName () + "'" , e );
277270 }
278271 };
279272 }
280273
274+ private Object invokeTabCompleter (TabCompleterMethod tcMethod , S sender , List <String > args ) throws Exception {
275+ Parameter [] params = tcMethod .method .getParameters ();
276+
277+ if (params .length == 0 ) {
278+ return tcMethod .method .invoke (tcMethod .handler );
279+ }
280+
281+ Object resolvedSender = senderResolver .resolve (sender , params [0 ].getType ());
282+ if (params .length == 1 ) {
283+ return tcMethod .method .invoke (tcMethod .handler , resolvedSender );
284+ }
285+
286+ String current = !args .isEmpty () ? args .getLast () : "" ;
287+ return tcMethod .method .invoke (tcMethod .handler , resolvedSender , current );
288+ }
289+
281290 private void invokeMethod (Object handler , Method method , S sender , Arguments args ) {
282291 try {
283292 Parameter [] params = method .getParameters ();
284- Object [] invokeArgs = new Object [params .length ];
285-
286- for (int i = 0 ; i < params .length ; i ++) {
287- Parameter param = params [i ];
288- Class <?> paramType = param .getType ();
289- boolean isOptional = paramType == Optional .class ;
290-
291- // First param: sender
292- if (i == 0 ) {
293- Class <?> senderType = isOptional ? extractOptionalType (param ) : paramType ;
294- if (senderResolver .canResolve (senderType )) {
295- Object resolved = senderResolver .resolve (sender , senderType );
296- invokeArgs [i ] = isOptional ? Optional .ofNullable (resolved ) : resolved ;
297- continue ;
298- }
299- }
300-
301- // Other params: @Arg
302- Arg argAnnotation = param .getAnnotation (Arg .class );
303- if (argAnnotation != null ) {
304- String argName = argAnnotation .value ();
305-
306- if (isOptional ) {
307- invokeArgs [i ] = args .getOptional (argName );
308- } else {
309- invokeArgs [i ] = args .get (argName );
310- }
311- }
312- }
313-
293+ Object [] invokeArgs = buildInvokeArgs (params , sender , args );
314294 method .invoke (handler , invokeArgs );
315-
316295 } catch (Exception e ) {
317296 throw new RuntimeException ("Failed to invoke command method: " + method .getName (), e );
318297 }
319298 }
320299
300+ private Object [] buildInvokeArgs (Parameter [] params , S sender , Arguments args ) {
301+ Object [] invokeArgs = new Object [params .length ];
302+
303+ for (int i = 0 ; i < params .length ; i ++) {
304+ Parameter param = params [i ];
305+
306+ if (i == 0 && isSenderParameter (param )) {
307+ invokeArgs [i ] = resolveSender (param , sender );
308+ } else {
309+ invokeArgs [i ] = resolveArgument (param , args );
310+ }
311+ }
312+ return invokeArgs ;
313+ }
314+
315+ private Object resolveSender (Parameter param , S sender ) {
316+ Class <?> paramType = param .getType ();
317+ boolean isOptional = paramType == Optional .class ;
318+ Class <?> senderType = isOptional ? extractOptionalType (param ) : paramType ;
319+ Object resolved = senderResolver .resolve (sender , senderType );
320+ return isOptional ? Optional .ofNullable (resolved ) : resolved ;
321+ }
322+
323+ private Object resolveArgument (Parameter param , Arguments args ) {
324+ String argName = getArgumentName (param );
325+ boolean isOptional = param .getType () == Optional .class ;
326+ return isOptional ? args .getOptional (argName ) : args .get (argName );
327+ }
328+
321329 private record CommandMethodInfo (Object handler , Method method , String name ) {}
322330 private record TabCompleterMethod (Object handler , Method method ) {}
323331}
0 commit comments