feat(core): add MulticastDelegateFutureCommandExecutionHandler (#363)
This PR also adds a `handler()` getter to the command builder class. This will allow for things along the line of https://github.com/Incendo/cloud/issues/189 to be achieved.
This commit is contained in:
parent
c39e0517fa
commit
d613fd0208
4 changed files with 196 additions and 0 deletions
|
|
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
- Core: Allow for setting a custom `CaptionVariableReplacementHandler` on the command manager ([#352](https://github.com/Incendo/cloud/pull/352))
|
||||
- Core: Add `DurationArgument` for parsing `java.time.Duration` ([#330](https://github.com/Incendo/cloud/pull/330))
|
||||
- Core: Add delegating command execution handlers ([#363](https://github.com/Incendo/cloud/pull/363))
|
||||
- Core: Add `builder()` getter to `Command.Builder` ([#363](https://github.com/Incendo/cloud/pull/363))
|
||||
- Annotations: Annotation string processors ([#353](https://github.com/Incendo/cloud/pull/353))
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -996,6 +996,16 @@ public class Command<C> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current command execution handler.
|
||||
*
|
||||
* @return the current handler
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public @NonNull CommandExecutionHandler<C> handler() {
|
||||
return this.commandExecutionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a required sender type
|
||||
*
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ package cloud.commandframework.execution;
|
|||
|
||||
import cloud.commandframework.Command;
|
||||
import cloud.commandframework.context.CommandContext;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
|
|
@ -38,6 +41,35 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||
@FunctionalInterface
|
||||
public interface CommandExecutionHandler<C> {
|
||||
|
||||
/**
|
||||
* Returns a {@link CommandExecutionHandler} that does nothing (no-op).
|
||||
*
|
||||
* @param <C> Command sender type
|
||||
* @return command execution handler that does nothing
|
||||
* @since 1.7.0
|
||||
*/
|
||||
static <C> @NonNull CommandExecutionHandler<C> noOpCommandExecutionHandler() {
|
||||
return new NullCommandExecutionHandler<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link CommandExecutionHandler} that delegates the given
|
||||
* {@code handlers} in sequence.
|
||||
* <p>
|
||||
* If any handler in the chain throws an exception, then no subsequent
|
||||
* handlers will be invoked.
|
||||
*
|
||||
* @param handlers The handlers to delegate to
|
||||
* @param <C> Command sender type
|
||||
* @return multicast-delegate command execution handler
|
||||
* @since 1.7.0
|
||||
*/
|
||||
static <C> @NonNull CommandExecutionHandler<C> delegatingExecutionHandler(
|
||||
final List<CommandExecutionHandler<C>> handlers
|
||||
) {
|
||||
return new MulticastDelegateFutureCommandExecutionHandler<>(handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle command execution
|
||||
*
|
||||
|
|
@ -101,4 +133,46 @@ public interface CommandExecutionHandler<C> {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to other handlers.
|
||||
*
|
||||
* @param <C> Command sender type
|
||||
* @see #delegatingExecutionHandler(List)
|
||||
* @since 1.7.0
|
||||
*/
|
||||
final class MulticastDelegateFutureCommandExecutionHandler<C> implements FutureCommandExecutionHandler<C> {
|
||||
|
||||
private final List<CommandExecutionHandler<C>> handlers;
|
||||
|
||||
private MulticastDelegateFutureCommandExecutionHandler(
|
||||
final @NonNull List<@NonNull CommandExecutionHandler<C>> handlers
|
||||
) {
|
||||
this.handlers = Collections.unmodifiableList(handlers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<@Nullable Void> executeFuture(
|
||||
@NonNull final CommandContext<C> commandContext
|
||||
) {
|
||||
@MonotonicNonNull CompletableFuture<@Nullable Void> composedHandler = null;
|
||||
|
||||
if (this.handlers.isEmpty()) {
|
||||
composedHandler = CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
for (final CommandExecutionHandler<C> handler : this.handlers) {
|
||||
if (composedHandler == null) {
|
||||
composedHandler = handler.executeFuture(commandContext);
|
||||
} else {
|
||||
composedHandler = composedHandler.thenCompose(
|
||||
ignore -> handler.executeFuture(commandContext)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return composedHandler;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2021 Alexander Söderberg & Contributors
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
package cloud.commandframework.execution;
|
||||
|
||||
import cloud.commandframework.TestCommandSender;
|
||||
import cloud.commandframework.context.CommandContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.notNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MulticastDelegateFutureCommandExecutionHandlerTest {
|
||||
|
||||
@Mock
|
||||
private CommandContext<TestCommandSender> context;
|
||||
|
||||
@Test
|
||||
void ExecuteFuture_HappyFlow_Success() {
|
||||
// Arrange
|
||||
final CommandExecutionHandler<TestCommandSender> handlerA = mock(CommandExecutionHandler.class);
|
||||
when(handlerA.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final CommandExecutionHandler<TestCommandSender> handlerB = mock(CommandExecutionHandler.class);
|
||||
when(handlerB.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final CommandExecutionHandler<TestCommandSender> handlerC = mock(CommandExecutionHandler.class);
|
||||
when(handlerC.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final CommandExecutionHandler<TestCommandSender> delegatingHandler = CommandExecutionHandler.delegatingExecutionHandler(
|
||||
Arrays.asList(handlerA, handlerB, handlerC)
|
||||
);
|
||||
|
||||
// Act
|
||||
delegatingHandler.executeFuture(this.context).join();
|
||||
|
||||
// Assert
|
||||
final InOrder inOrder = Mockito.inOrder(handlerA, handlerB, handlerC);
|
||||
inOrder.verify(handlerA).executeFuture(notNull());
|
||||
inOrder.verify(handlerB).executeFuture(notNull());
|
||||
inOrder.verify(handlerC).executeFuture(notNull());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ExecuteFuture_FailedFuture_StopsDelegating() {
|
||||
// Arrange
|
||||
final CommandExecutionHandler<TestCommandSender> handlerA = mock(CommandExecutionHandler.class);
|
||||
when(handlerA.executeFuture(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final CommandExecutionHandler<TestCommandSender> handlerB = mock(CommandExecutionHandler.class);
|
||||
final CompletableFuture<Void> futureB = new CompletableFuture<>();
|
||||
futureB.completeExceptionally(new RuntimeException());
|
||||
when(handlerB.executeFuture(any())).thenReturn(futureB);
|
||||
|
||||
final CommandExecutionHandler<TestCommandSender> handlerC = mock(CommandExecutionHandler.class);
|
||||
|
||||
final CommandExecutionHandler<TestCommandSender> delegatingHandler = CommandExecutionHandler.delegatingExecutionHandler(
|
||||
Arrays.asList(handlerA, handlerB, handlerC)
|
||||
);
|
||||
|
||||
// Act
|
||||
final CompletableFuture<Void> future = delegatingHandler.executeFuture(this.context);
|
||||
assertThrows(
|
||||
CompletionException.class,
|
||||
future::join
|
||||
);
|
||||
|
||||
// Assert
|
||||
final InOrder inOrder = Mockito.inOrder(handlerA, handlerB, handlerC);
|
||||
inOrder.verify(handlerA).executeFuture(notNull());
|
||||
inOrder.verify(handlerB).executeFuture(notNull());
|
||||
inOrder.verify(handlerC, never()).executeFuture(notNull());
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue