Skip to content

Commit f446258

Browse files
committed
Introduce command lookup
Signed-off-by: Piotr Olaszewski <piotr.olaszewski@thulium.pl>
1 parent 7199ea4 commit f446258

File tree

20 files changed

+322
-336
lines changed

20 files changed

+322
-336
lines changed

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
root = true
2+
3+
[*.{java,xml,gradle}]
4+
indent_style = tab
5+
indent_size = 4

spring-shell-core/src/main/java/org/springframework/shell/core/EnumValueProvider.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2022 the original author or authors.
2+
* Copyright 2016-present the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
* parameters.
3030
*
3131
* @author Eric Bottard
32+
* @author Piotr Olaszewski
3233
*/
3334
public class EnumValueProvider implements ValueProvider {
3435

@@ -37,7 +38,7 @@ public List<CompletionProposal> complete(CompletionContext completionContext) {
3738
List<CompletionProposal> result = new ArrayList<>();
3839
CommandOption commandOption = completionContext.getCommandOption();
3940
if (commandOption != null) {
40-
ResolvableType type = commandOption.getType();
41+
ResolvableType type = commandOption.type();
4142
if (type != null) {
4243
Class<?> clazz = type.getRawClass();
4344
if (clazz != null) {

spring-shell-core/src/main/java/org/springframework/shell/core/Input.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 the original author or authors.
2+
* Copyright 2017-present the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
* Represents the input buffer to the shell.
2525
*
2626
* @author Eric Bottard
27+
* @author Piotr Olaszewski
2728
*/
2829
public interface Input {
2930

@@ -42,7 +43,7 @@ public interface Input {
4243
* single "word")
4344
*/
4445
default List<String> words() {
45-
return "".equals(rawText()) ? Collections.emptyList() : Arrays.asList(rawText().split(" "));
46+
return rawText().isEmpty() ? Collections.emptyList() : Arrays.asList(rawText().split(" "));
4647
}
4748

4849
}
Lines changed: 5 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022-2023 the original author or authors.
2+
* Copyright 2022-present the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,202 +20,13 @@
2020
import org.springframework.shell.core.completion.CompletionResolver;
2121

2222
/**
23-
* Interface representing an option in a command.
23+
* Represents an option of a command.
2424
*
2525
* @author Janne Valkealahti
2626
* @author Piotr Olaszewski
2727
*/
28-
// TODO this is better defined as a record.
29-
public interface CommandOption {
30-
31-
/**
32-
* Gets a long name of an option.
33-
* @return long name of an option
34-
*/
35-
String getLongName();
36-
37-
/**
38-
* Gets a modified long names of an option. Set within a command registration if
39-
* option name modifier were used to have an info about original names.
40-
* @return modified long names of an option
41-
*/
42-
String getLongNameModified();
43-
44-
/**
45-
* Gets a short names of an option.
46-
* @return short names of an option
47-
*/
48-
Character getShortName();
49-
50-
/**
51-
* Gets a description of an option.
52-
* @return description of an option
53-
*/
54-
@Nullable String getDescription();
55-
56-
/**
57-
* Gets a {@link ResolvableType} of an option.
58-
* @return type of an option
59-
*/
60-
@Nullable ResolvableType getType();
61-
62-
/**
63-
* Gets a flag if option is required.
64-
* @return the required flag
65-
*/
66-
boolean isRequired();
67-
68-
/**
69-
* Gets a default value of an option.
70-
* @return the default value
71-
*/
72-
@Nullable String getDefaultValue();
73-
74-
/**
75-
* Gets a positional value.
76-
* @return the positional value
77-
*/
78-
int getPosition();
79-
80-
/**
81-
* Gets a minimum arity.
82-
* @return the minimum arity
83-
*/
84-
int getArityMin();
85-
86-
/**
87-
* Gets a maximum arity.
88-
* @return the maximum arity
89-
*/
90-
int getArityMax();
91-
92-
/**
93-
* Gets a completion function.
94-
* @return the completion function
95-
*/
96-
@Nullable CompletionResolver getCompletion();
97-
98-
/**
99-
* Gets an instance of a default {@link CommandOption}.
100-
* @param longName the long name
101-
* @param longNameModified the modified long name
102-
* @param shortName the short name
103-
* @param description the description
104-
* @param type the type
105-
* @param required the required flag
106-
* @param defaultValue the default value
107-
* @param position the position value
108-
* @param arityMin the min arity
109-
* @param arityMax the max arity
110-
* @param label the label
111-
* @param completion the completion
112-
* @return default command option
113-
*/
114-
static CommandOption of(String longName, String longNameModified, Character shortName, String description,
115-
ResolvableType type, boolean required, String defaultValue, Integer position, Integer arityMin,
116-
Integer arityMax, String label, CompletionResolver completion) {
117-
return new DefaultCommandOption(longName, longNameModified, shortName, description, type, required,
118-
defaultValue, position, arityMin, arityMax, label, completion);
119-
}
120-
121-
/**
122-
* Default implementation of {@link CommandOption}.
123-
*/
124-
class DefaultCommandOption implements CommandOption {
125-
126-
private String longName;
127-
128-
private String longNameModified;
129-
130-
private Character shortName;
131-
132-
private String description;
133-
134-
private ResolvableType type;
135-
136-
private boolean required;
137-
138-
private String defaultValue;
139-
140-
private int position;
141-
142-
private int arityMin;
143-
144-
private int arityMax;
145-
146-
private CompletionResolver completion;
147-
148-
public DefaultCommandOption(String longName, String longNameModified, Character shortName, String description,
149-
ResolvableType type, boolean required, String defaultValue, Integer position, Integer arityMin,
150-
Integer arityMax, String label, CompletionResolver completion) {
151-
this.longName = longName;
152-
this.longNameModified = longNameModified;
153-
this.shortName = shortName;
154-
this.description = description;
155-
this.type = type;
156-
this.required = required;
157-
this.defaultValue = defaultValue;
158-
this.position = position != null && position > -1 ? position : -1;
159-
this.arityMin = arityMin != null ? arityMin : -1;
160-
this.arityMax = arityMax != null ? arityMax : -1;
161-
this.completion = completion;
162-
}
163-
164-
@Override
165-
public String getLongName() {
166-
return longName;
167-
}
168-
169-
@Override
170-
public String getLongNameModified() {
171-
return longNameModified;
172-
}
173-
174-
@Override
175-
public Character getShortName() {
176-
return shortName;
177-
}
178-
179-
@Override
180-
public String getDescription() {
181-
return description;
182-
}
183-
184-
@Override
185-
public ResolvableType getType() {
186-
return type;
187-
}
188-
189-
@Override
190-
public boolean isRequired() {
191-
return required;
192-
}
193-
194-
@Override
195-
public String getDefaultValue() {
196-
return defaultValue;
197-
}
198-
199-
@Override
200-
public int getPosition() {
201-
return position;
202-
}
203-
204-
@Override
205-
public int getArityMin() {
206-
return arityMin;
207-
}
208-
209-
@Override
210-
public int getArityMax() {
211-
return arityMax;
212-
}
213-
214-
@Override
215-
public CompletionResolver getCompletion() {
216-
return completion;
217-
}
218-
219-
}
28+
public record CommandOption(String longName, String longNameModified, Character shortName, @Nullable String description,
29+
@Nullable ResolvableType type, boolean required, @Nullable String defaultValue, int position, int arityMin,
30+
int arityMax, @Nullable String label, @Nullable CompletionResolver completion) {
22031

22132
}

spring-shell-core/src/main/java/org/springframework/shell/core/command/CommandRegistry.java

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
package org.springframework.shell.core.command;
1717

1818
import java.util.Collection;
19+
import java.util.HashMap;
1920
import java.util.HashSet;
21+
import java.util.List;
22+
import java.util.Map;
2023
import java.util.Set;
2124

2225
import org.jspecify.annotations.Nullable;
@@ -38,6 +41,8 @@ public class CommandRegistry implements SmartInitializingSingleton, ApplicationC
3841

3942
private final Set<Command> commands;
4043

44+
private final CommandTree.Node root = new CommandTree.Node();
45+
4146
@SuppressWarnings("NullAway.Init")
4247
private ApplicationContext applicationContext;
4348

@@ -53,31 +58,76 @@ public Set<Command> getCommands() {
5358
return Set.copyOf(commands);
5459
}
5560

56-
@Nullable public Command getCommandByName(String name) {
61+
public @Nullable Command getCommandByName(String name) {
5762
return commands.stream().filter(command -> command.getName().equals(name)).findFirst().orElse(null);
5863
}
5964

60-
public void registerCommand(Command command) {
61-
commands.add(command);
62-
}
65+
public @Nullable Command lookupCommand(List<String> args) {
66+
Command command = null;
6367

64-
public void unregisterCommand(Command command) {
65-
commands.remove(command);
66-
}
68+
CommandTree.Node current = root;
69+
for (String arg : args) {
70+
CommandTree.Node next = current.getChild().get(arg);
71+
if (next == null) {
72+
return command;
73+
}
74+
current = next;
75+
76+
Command cmd = current.getCommand();
77+
if (cmd != null) {
78+
command = cmd;
79+
}
80+
}
6781

68-
public void clearCommands() {
69-
commands.clear();
82+
return command;
7083
}
7184

7285
@Override
7386
public void afterSingletonsInstantiated() {
74-
Collection<Command> commandCollection = this.applicationContext.getBeansOfType(Command.class).values();
87+
Collection<Command> commandCollection = applicationContext.getBeansOfType(Command.class).values();
7588
commands.addAll(commandCollection);
89+
90+
for (Command command : commandCollection) {
91+
CommandTree.Node current = root;
92+
String[] names = command.getName().split(" ");
93+
for (String name : names) {
94+
current = current.child(name);
95+
}
96+
current.setCommand(command);
97+
}
7698
}
7799

78100
@Override
79101
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
80102
this.applicationContext = applicationContext;
81103
}
82104

105+
private static class CommandTree {
106+
107+
private static class Node {
108+
109+
private final Map<String, Node> child = new HashMap<>();
110+
111+
private @Nullable Command command;
112+
113+
private Map<String, Node> getChild() {
114+
return child;
115+
}
116+
117+
private Node child(String name) {
118+
return child.computeIfAbsent(name, n -> new Node());
119+
}
120+
121+
private @Nullable Command getCommand() {
122+
return command;
123+
}
124+
125+
private void setCommand(Command command) {
126+
this.command = command;
127+
}
128+
129+
}
130+
131+
}
132+
83133
}

0 commit comments

Comments
 (0)