Skip to content

Commit b9c37a4

Browse files
authored
Add back configurable suggestion mapper to command manager (#694)
* Add back configurable suggestion mapper to command manager this is useful for more easily implementing a ComponentTooltipSuggestion, for example * remove final * adjust MappingSuggestionFactory#mapped * Apply suggestion mappers before suggestion processors. * SuggestionProcessor#and -> #then * Rename Suggestion.simple(String) to Suggestion.suggestion(String) (#695) The plan is to also change the static factories on (Component)TooltipSuggestion to be called suggestion. This makes static imports much cleaner when creating a variety of suggestions with and without tooltips for example.
1 parent 0c1ce36 commit b9c37a4

32 files changed

Lines changed: 226 additions & 139 deletions

File tree

cloud-annotations/src/main/java/org/incendo/cloud/annotations/assembler/ArgumentAssemblerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public ArgumentAssemblerImpl(final @NonNull AnnotationParser<C> annotationParser
153153
if (completions != null) {
154154
final List<Suggestion> suggestions = Arrays.stream(
155155
completions.value().replace(" ", "").split(",")
156-
).map(Suggestion::simple).collect(Collectors.toList());
156+
).map(Suggestion::suggestion).collect(Collectors.toList());
157157
componentBuilder.suggestionProvider(SuggestionProvider.suggesting(suggestions));
158158
} else if (descriptor.suggestions() != null) {
159159
final String suggestionProviderName = this.annotationParser.processString(descriptor.suggestions());

cloud-annotations/src/main/java/org/incendo/cloud/annotations/suggestion/MethodSuggestionProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public MethodSuggestionProvider(
138138
if (suggestion instanceof Suggestion) {
139139
return (List<Suggestion>) suggestions;
140140
} else if (suggestion instanceof String) {
141-
return suggestions.stream().map(Object::toString).map(Suggestion::simple).collect(Collectors.toList());
141+
return suggestions.stream().map(Object::toString).map(Suggestion::suggestion).collect(Collectors.toList());
142142
} else {
143143
throw new IllegalArgumentException(
144144
String.format("Cannot handle suggestions of type: %s",

cloud-annotations/src/test/java/org/incendo/cloud/annotations/AnnotationParserTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class AnnotationParserTest {
6161

6262
private static final List<Suggestion> NAMED_SUGGESTIONS = Arrays.asList("Dancing-Queen", "Gimme!-Gimme!-Gimme!",
6363
"Waterloo"
64-
).stream().map(Suggestion::simple).collect(Collectors.toList());
64+
).stream().map(Suggestion::suggestion).collect(Collectors.toList());
6565

6666
private CommandManager<TestCommandSender> manager;
6767
private AnnotationParser<TestCommandSender> annotationParser;
@@ -161,7 +161,7 @@ void testAnnotatedSuggestionProviders() {
161161

162162
assertThat(suggestionProvider).isNotNull();
163163
assertThat(suggestionProvider.suggestionsFuture(new CommandContext<>(new TestCommandSender(), manager),
164-
CommandInput.empty()).join()).contains(Suggestion.simple("Stella"));
164+
CommandInput.empty()).join()).contains(Suggestion.suggestion("Stella"));
165165
}
166166

167167
@Test
@@ -177,7 +177,7 @@ void testAnnotatedArgumentParser() {
177177
assertThat(parser.parse(context, CommandInput.empty()).parsedValue().orElse(new CustomType("")).toString())
178178
.isEqualTo("yay");
179179
assertThat(parser.suggestionProvider().suggestionsFuture(context, CommandInput.empty()).join())
180-
.contains(Suggestion.simple("Stella"));
180+
.contains(Suggestion.suggestion("Stella"));
181181
}
182182

183183
@Test

cloud-annotations/src/test/java/org/incendo/cloud/annotations/feature/MethodSuggestionProviderTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void testSuggestions(final @NonNull Object instance) {
8585
.join();
8686

8787
// Assert
88-
assertThat(suggestions).containsExactly(Suggestion.simple("foo"));
88+
assertThat(suggestions).containsExactly(Suggestion.suggestion("foo"));
8989
}
9090

9191
static @NonNull Stream<@NonNull Object> testSuggestionsSource() {
@@ -108,7 +108,7 @@ public static final class TestClassList {
108108
final @NonNull CommandContext<TestCommandSender> context,
109109
final @NonNull String input
110110
) {
111-
return Collections.singletonList(Suggestion.simple("foo"));
111+
return Collections.singletonList(Suggestion.suggestion("foo"));
112112
}
113113
}
114114

@@ -119,7 +119,7 @@ public static final class TestClassSet {
119119
final @NonNull CommandContext<TestCommandSender> context,
120120
final @NonNull String input
121121
) {
122-
return Collections.singleton(Suggestion.simple("foo"));
122+
return Collections.singleton(Suggestion.suggestion("foo"));
123123
}
124124
}
125125

@@ -130,7 +130,7 @@ public static final class TestClassStream {
130130
final @NonNull CommandContext<TestCommandSender> context,
131131
final @NonNull String input
132132
) {
133-
return Stream.of(Suggestion.simple("foo"));
133+
return Stream.of(Suggestion.suggestion("foo"));
134134
}
135135
}
136136

@@ -141,7 +141,7 @@ public static final class TestClassIterable {
141141
final @NonNull CommandContext<TestCommandSender> context,
142142
final @NonNull String input
143143
) {
144-
return Collections.singleton(Suggestion.simple("foo"));
144+
return Collections.singleton(Suggestion.suggestion("foo"));
145145
}
146146
}
147147

cloud-core/src/main/java/org/incendo/cloud/CommandManager.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,15 @@
8383
import org.incendo.cloud.suggestion.FilteringSuggestionProcessor;
8484
import org.incendo.cloud.suggestion.Suggestion;
8585
import org.incendo.cloud.suggestion.SuggestionFactory;
86+
import org.incendo.cloud.suggestion.SuggestionMapper;
8687
import org.incendo.cloud.suggestion.SuggestionProcessor;
8788
import org.incendo.cloud.syntax.CommandSyntaxFormatter;
8889
import org.incendo.cloud.syntax.StandardCommandSyntaxFormatter;
8990
import org.incendo.cloud.type.tuple.Pair;
9091
import org.incendo.cloud.type.tuple.Triplet;
9192

93+
import static java.util.Objects.requireNonNull;
94+
9295
/**
9396
* The manager is responsible for command registration, parsing delegation, etc.
9497
*
@@ -115,6 +118,7 @@ public abstract class CommandManager<C> implements Stateful<RegistrationState>,
115118
private CommandRegistrationHandler<C> commandRegistrationHandler;
116119
private CaptionRegistry<C> captionRegistry;
117120
private HelpHandlerFactory<C> helpHandlerFactory = HelpHandlerFactory.standard(this);
121+
private SuggestionMapper<? extends Suggestion> mapper = SuggestionMapper.identity();
118122
private final AtomicReference<RegistrationState> state = new AtomicReference<>(RegistrationState.BEFORE_REGISTRATION);
119123

120124
/**
@@ -137,7 +141,8 @@ protected CommandManager(
137141
this,
138142
this.commandTree,
139143
commandContextFactory,
140-
executionCoordinator
144+
executionCoordinator,
145+
suggestion -> this.mapper.map(suggestion)
141146
);
142147
this.commandExecutor = new StandardCommandExecutor<>(
143148
this,
@@ -173,22 +178,50 @@ protected CommandManager(
173178

174179
/**
175180
* Returns the suggestion factory.
176-
* <p>
177-
* Platform implementations of command manager may override this method to make it easier to work with platform
178-
* suggestion types, for example:
179-
* <pre>{@code
180-
* @Override
181-
* public @NonNull SuggestionFactory<C, YourType> suggestionFactory() {
182-
* return super.suggestionFactory().mapped(suggestion -> yourType);
183-
* }
184-
* }</pre>
181+
*
182+
* <p>Will map results using {@link #suggestionMapper()}.</p>
183+
*
185184
* @return the suggestion factory
186185
*/
187186
@API(status = API.Status.STABLE)
188187
public @NonNull SuggestionFactory<C, ? extends Suggestion> suggestionFactory() {
189188
return this.suggestionFactory;
190189
}
191190

191+
/**
192+
* Returns the suggestion mapper for {@link #suggestionFactory()}.
193+
*
194+
* <p>Platform command managers may replace the default mapper to better support
195+
* platform suggestion types. Therefore it's encouraged to chain any additional mappers using
196+
* {@link SuggestionMapper#then(SuggestionMapper)} or {@link #appendSuggestionMapper(SuggestionMapper)},
197+
* rather than replacing the mapper directly.</p>
198+
*
199+
* @return the suggestion mapper
200+
*/
201+
public @NonNull SuggestionMapper<? extends Suggestion> suggestionMapper() {
202+
return this.mapper;
203+
}
204+
205+
/**
206+
* Sets the suggestion mapper for {@link #suggestionFactory()} to the result of appending the provided mapper to the
207+
* current mapper using {@link SuggestionMapper#then(SuggestionMapper)}.
208+
*
209+
* @param mapper suggestion mapper
210+
*/
211+
public void appendSuggestionMapper(final @NonNull SuggestionMapper<? extends Suggestion> mapper) {
212+
this.suggestionMapper(this.suggestionMapper().then(mapper));
213+
}
214+
215+
/**
216+
* Sets the suggestion mapper for {@link #suggestionFactory()}.
217+
*
218+
* @param mapper suggestion mapper
219+
* @see #suggestionMapper()
220+
*/
221+
public void suggestionMapper(final @NonNull SuggestionMapper<? extends Suggestion> mapper) {
222+
this.mapper = requireNonNull(mapper, "mapper");
223+
}
224+
192225
/**
193226
* Register a new command to the command manager and insert it into the underlying command tree. The command will be
194227
* forwarded to the {@link CommandRegistrationHandler} and will, depending on the platform, be forwarded to the platform.

cloud-core/src/main/java/org/incendo/cloud/CommandTree.java

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import org.incendo.cloud.permission.PermissionResult;
6666
import org.incendo.cloud.setting.ManagerSetting;
6767
import org.incendo.cloud.suggestion.Suggestion;
68+
import org.incendo.cloud.suggestion.SuggestionMapper;
6869
import org.incendo.cloud.suggestion.Suggestions;
6970
import org.incendo.cloud.util.CompletableFutures;
7071

@@ -595,37 +596,42 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
595596
/**
596597
* Returns suggestions from the input queue
597598
*
599+
* @param <S> suggestion type
598600
* @param context Context instance
599601
* @param commandInput Input
602+
* @param mapper suggestion mapper
600603
* @param executor executor to schedule suggestion logic on
601604
* @return String suggestions. These should be filtered based on {@link String#startsWith(String)}
602605
*/
603606
@API(status = API.Status.STABLE)
604-
public @NonNull CompletableFuture<@NonNull Suggestions<C, Suggestion>> getSuggestions(
607+
public <S extends Suggestion> @NonNull CompletableFuture<@NonNull Suggestions<C, S>> getSuggestions(
605608
final @NonNull CommandContext<C> context,
606609
final @NonNull CommandInput commandInput,
610+
final @NonNull SuggestionMapper<S> mapper,
607611
final @NonNull Executor executor
608612
) {
609-
return CompletableFutures.scheduleOn(executor, () -> this.getSuggestionsDirect(context, commandInput, executor));
613+
return CompletableFutures.scheduleOn(executor, () -> this.getSuggestionsDirect(context, commandInput, mapper, executor));
610614
}
611615

612-
private @NonNull CompletableFuture<@NonNull Suggestions<C, Suggestion>> getSuggestionsDirect(
616+
private <S extends Suggestion> @NonNull CompletableFuture<@NonNull Suggestions<C, S>> getSuggestionsDirect(
613617
final @NonNull CommandContext<C> context,
614618
final @NonNull CommandInput commandInput,
619+
final @NonNull SuggestionMapper<S> mapper,
615620
final @NonNull Executor executor
616621
) {
617-
final SuggestionContext<C> suggestionContext = new SuggestionContext<>(
622+
final SuggestionContext<C, S> suggestionCtx = new SuggestionContext<>(
618623
this.commandManager.suggestionProcessor(),
619624
context,
620-
commandInput
625+
commandInput,
626+
mapper
621627
);
622-
return this.getSuggestions(suggestionContext, commandInput, this.internalTree, executor)
623-
.thenApply(s -> Suggestions.create(s.commandContext(), s.suggestions(), commandInput));
628+
return this.getSuggestions(suggestionCtx, commandInput, this.internalTree, executor)
629+
.thenApply($ -> suggestionCtx.makeSuggestions());
624630
}
625631

626632
@SuppressWarnings("MixedMutabilityReturnType")
627-
private @NonNull CompletableFuture<SuggestionContext<C>> getSuggestions(
628-
final @NonNull SuggestionContext<C> context,
633+
private @NonNull CompletableFuture<SuggestionContext<C, ?>> getSuggestions(
634+
final @NonNull SuggestionContext<C, ?> context,
629635
final @NonNull CommandInput commandInput,
630636
final @NonNull CommandNode<C> root,
631637
final @NonNull Executor executor
@@ -682,7 +688,7 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
682688
}
683689

684690
// Calculate suggestions for the literal arguments
685-
CompletableFuture<SuggestionContext<C>> suggestionFuture = CompletableFuture.completedFuture(context);
691+
CompletableFuture<SuggestionContext<C, ?>> suggestionFuture = CompletableFuture.completedFuture(context);
686692
if (commandInput.remainingTokens() <= 1) {
687693
for (final CommandNode<C> node : staticArguments) {
688694
suggestionFuture = suggestionFuture
@@ -710,8 +716,8 @@ private boolean matchesLiteral(final @NonNull List<@NonNull CommandNode<C>> chil
710716
* @param input the current input
711717
* @return future that completes with the context
712718
*/
713-
private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument(
714-
final @NonNull SuggestionContext<C> context,
719+
private CompletableFuture<SuggestionContext<C, ?>> addSuggestionsForLiteralArgument(
720+
final @NonNull SuggestionContext<C, ?> context,
715721
final @NonNull CommandNode<C> node,
716722
final @NonNull CommandInput input
717723
) {
@@ -735,8 +741,8 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument
735741
}
736742

737743
@SuppressWarnings("unchecked")
738-
private @NonNull CompletableFuture<SuggestionContext<C>> addSuggestionsForDynamicArgument(
739-
final @NonNull SuggestionContext<C> context,
744+
private @NonNull CompletableFuture<SuggestionContext<C, ?>> addSuggestionsForDynamicArgument(
745+
final @NonNull SuggestionContext<C, ?> context,
740746
final @NonNull CommandInput commandInput,
741747
final @NonNull CommandNode<C> child,
742748
final @NonNull Executor executor
@@ -776,7 +782,7 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument
776782
&& preParseResult.parsedValue().orElse(false);
777783
// END: Preprocessing
778784

779-
final CompletableFuture<SuggestionContext<C>> parsingFuture;
785+
final CompletableFuture<SuggestionContext<C, ?>> parsingFuture;
780786
if (!preParseSuccess) {
781787
parsingFuture = CompletableFuture.completedFuture(null);
782788
} else {
@@ -858,8 +864,8 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument
858864
* @param executor executor to schedule further suggestion logic to
859865
* @return the context
860866
*/
861-
private @NonNull CompletableFuture<SuggestionContext<C>> addArgumentSuggestions(
862-
final @NonNull SuggestionContext<C> context,
867+
private @NonNull CompletableFuture<SuggestionContext<C, ?>> addArgumentSuggestions(
868+
final @NonNull SuggestionContext<C, ?> context,
863869
final @NonNull CommandNode<C> node,
864870
final @NonNull CommandInput input,
865871
final @NonNull Executor executor
@@ -895,8 +901,8 @@ private CompletableFuture<SuggestionContext<C>> addSuggestionsForLiteralArgument
895901
* @param executor executor to schedule further suggestion logic to
896902
* @return future that completes with the context
897903
*/
898-
private CompletableFuture<SuggestionContext<C>> addArgumentSuggestions(
899-
final @NonNull SuggestionContext<C> context,
904+
private CompletableFuture<SuggestionContext<C, ?>> addArgumentSuggestions(
905+
final @NonNull SuggestionContext<C, ?> context,
900906
final @NonNull CommandComponent<C> component,
901907
final @NonNull CommandInput input,
902908
final @NonNull Executor executor

cloud-core/src/main/java/org/incendo/cloud/execution/ExecutionCoordinator.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.incendo.cloud.context.CommandContext;
3535
import org.incendo.cloud.context.CommandInput;
3636
import org.incendo.cloud.suggestion.Suggestion;
37+
import org.incendo.cloud.suggestion.SuggestionMapper;
3738
import org.incendo.cloud.suggestion.Suggestions;
3839

3940
/**
@@ -107,15 +108,18 @@ public interface ExecutionCoordinator<C> {
107108
/**
108109
* Coordinates the execution of a suggestions query.
109110
*
111+
* @param <S> suggestion type
110112
* @param commandTree command tree to suggest from
111113
* @param context command context
112114
* @param commandInput command input
115+
* @param mapper suggestion mapper
113116
* @return future that completes with the result
114117
*/
115-
@NonNull CompletableFuture<@NonNull Suggestions<C, Suggestion>> coordinateSuggestions(
118+
<S extends Suggestion> @NonNull CompletableFuture<@NonNull Suggestions<C, S>> coordinateSuggestions(
116119
@NonNull CommandTree<C> commandTree,
117120
@NonNull CommandContext<C> context,
118-
@NonNull CommandInput commandInput
121+
@NonNull CommandInput commandInput,
122+
@NonNull SuggestionMapper<S> mapper
119123
);
120124

121125
/**

cloud-core/src/main/java/org/incendo/cloud/execution/ExecutionCoordinatorImpl.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.incendo.cloud.exception.CommandParseException;
3838
import org.incendo.cloud.services.State;
3939
import org.incendo.cloud.suggestion.Suggestion;
40+
import org.incendo.cloud.suggestion.SuggestionMapper;
4041
import org.incendo.cloud.suggestion.Suggestions;
4142
import org.incendo.cloud.type.tuple.Pair;
4243

@@ -150,11 +151,12 @@ public void execute(final Runnable command) {
150151
}
151152

152153
@Override
153-
public @NonNull CompletableFuture<@NonNull Suggestions<C, Suggestion>> coordinateSuggestions(
154+
public <S extends Suggestion> @NonNull CompletableFuture<@NonNull Suggestions<C, S>> coordinateSuggestions(
154155
final @NonNull CommandTree<C> commandTree,
155156
final @NonNull CommandContext<C> context,
156-
final @NonNull CommandInput commandInput
157+
final @NonNull CommandInput commandInput,
158+
final @NonNull SuggestionMapper<S> mapper
157159
) {
158-
return commandTree.getSuggestions(context, commandInput, this.suggestionsExecutor);
160+
return commandTree.getSuggestions(context, commandInput, mapper, this.suggestionsExecutor);
159161
}
160162
}

0 commit comments

Comments
 (0)