Fix alias handling with @Command annotation

- Revisit how alias commands are added using
  @Command annotation when using if/or on class
  and/or method level.
- With this change alias handling is more logical
  and there's better tests and docs.
- Backport #945
- Fixes #973
This commit is contained in:
Janne Valkealahti
2024-01-13 12:19:12 +00:00
parent be6a25b221
commit 134af2dbd2
8 changed files with 409 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -69,8 +69,8 @@ public @interface Command {
* {@code alias1 sub1} it can be defined as:
*
* <pre class="code">
* command = { "alias1", "sub1" }
* command = "alias1 sub1"
* alias = { "alias1", "sub1" }
* alias = "alias1 sub1"
* </pre>
*
* Values are split and trimmed meaning spaces doesn't matter.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -159,10 +159,11 @@ class CommandAnnotationUtils {
.collect(Collectors.toList());
return Stream.of(right.getStringArray(field))
.flatMap(command -> Stream.of(command.split(" ")))
.filter(command -> StringUtils.hasText(command))
.map(command -> command.strip())
.map(command -> Stream.concat(prefix.stream(), Stream.of(command)).collect(Collectors.toList()))
.map(command -> Stream.concat(
prefix.stream(),
Stream.of(command).filter(c -> StringUtils.hasText(c)))
.collect(Collectors.toList()))
.map(arr -> arr.toArray(String[]::new))
.toArray(String[][]::new);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -108,6 +108,10 @@ class CommandAnnotationUtilsTests {
.get(Command.class);
private static MergedAnnotation<Command> aliasValues4 = MergedAnnotations.from(AliasValues4.class)
.get(Command.class);
private static MergedAnnotation<Command> aliasValues5 = MergedAnnotations.from(AliasValues5.class)
.get(Command.class);
private static MergedAnnotation<Command> aliasValues6 = MergedAnnotations.from(AliasValues6.class)
.get(Command.class);
@Command
private static class AliasDefault {
@@ -129,6 +133,14 @@ class CommandAnnotationUtilsTests {
private static class AliasValues4 {
}
@Command(alias = { "one" })
private static class AliasValues5 {
}
@Command(alias = { "" })
private static class AliasValues6 {
}
@Test
void testAlias() {
assertThat(CommandAnnotationUtils.deduceAlias(aliasDefault, aliasDefault)).isEmpty();
@@ -139,7 +151,9 @@ class CommandAnnotationUtilsTests {
assertThat(CommandAnnotationUtils.deduceAlias(aliasDefault, aliasValues3))
.isEqualTo(new String[][] { { "five" }, { "six" }, { "seven" } });
assertThat(CommandAnnotationUtils.deduceAlias(aliasDefault, aliasValues4))
.isEqualTo(new String[][] { { "eight" }, { "nine" } });
.isEqualTo(new String[][] { { "eight nine" } });
assertThat(CommandAnnotationUtils.deduceAlias(aliasValues5, aliasValues6))
.isEqualTo(new String[][] { { "one" } });
}
private static MergedAnnotation<Command> groupValue1 = MergedAnnotations.from(GroupValues1.class)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.springframework.shell.command.annotation.support;
import java.util.Collections;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -375,4 +376,186 @@ class CommandRegistrationFactoryBeanTests {
bd.getPropertyValues().add(CommandRegistrationFactoryBean.COMMAND_METHOD_PARAMETERS, parameters);
});
}
@Nested
class Aliases {
@Test
void aliasOnlyOnMethod() {
configCommon(AliasOnlyOnMethod.class, new AliasOnlyOnMethod(), "command1", new Class[] { })
.run((context) -> {
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
CommandRegistrationFactoryBean.class);
assertThat(fb).isNotNull();
CommandRegistration registration = fb.getObject();
assertThat(registration).isNotNull();
assertThat(registration.getCommand()).isEqualTo("one two");
assertThat(registration.getAliases()).hasSize(1);
assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four");
});
}
@Command(command = "one")
private static class AliasOnlyOnMethod {
@Command(command = "two", alias = "four")
void command1(){
}
}
@Test
void aliasOnlyOnClass() {
configCommon(AliasOnlyOnClass.class, new AliasOnlyOnClass(), "command1", new Class[] { })
.run((context) -> {
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
CommandRegistrationFactoryBean.class);
assertThat(fb).isNotNull();
CommandRegistration registration = fb.getObject();
assertThat(registration).isNotNull();
assertThat(registration.getCommand()).isEqualTo("one two");
assertThat(registration.getAliases()).hasSize(0);
});
}
@Command(command = "one", alias = "three")
private static class AliasOnlyOnClass {
@Command(command = "two")
void command1(){
}
}
@Test
void aliasOnlyOnMethodMultiCommandString() {
configCommon(AliasOnlyOnMethodMultiCommandString.class, new AliasOnlyOnMethodMultiCommandString(), "command1", new Class[] { })
.run((context) -> {
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
CommandRegistrationFactoryBean.class);
assertThat(fb).isNotNull();
CommandRegistration registration = fb.getObject();
assertThat(registration).isNotNull();
assertThat(registration.getCommand()).isEqualTo("one two");
assertThat(registration.getAliases()).hasSize(1);
assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four five");
});
}
@Command(command = "one")
private static class AliasOnlyOnMethodMultiCommandString {
@Command(command = "two", alias = "four five")
void command1(){
}
}
@Test
void aliasOnlyOnMethodMultiCommandArray() {
configCommon(AliasOnlyOnMethodMultiCommandArray.class, new AliasOnlyOnMethodMultiCommandArray(), "command1", new Class[] { })
.run((context) -> {
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
CommandRegistrationFactoryBean.class);
assertThat(fb).isNotNull();
CommandRegistration registration = fb.getObject();
assertThat(registration).isNotNull();
assertThat(registration.getCommand()).isEqualTo("one two");
assertThat(registration.getAliases()).hasSize(2);
assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("four");
assertThat(registration.getAliases().get(1).getCommand()).isEqualTo("five");
});
}
@Command(command = "one")
private static class AliasOnlyOnMethodMultiCommandArray {
@Command(command = "two", alias = {"four", "five"})
void command1(){
}
}
@Test
void aliasOnBothMethodStringEmpty() {
configCommon(AliasOnBothMethodStringEmpty.class, new AliasOnBothMethodStringEmpty(), "command1", new Class[] { })
.run((context) -> {
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
CommandRegistrationFactoryBean.class);
assertThat(fb).isNotNull();
CommandRegistration registration = fb.getObject();
assertThat(registration).isNotNull();
assertThat(registration.getCommand()).isEqualTo("one two");
assertThat(registration.getAliases()).hasSize(1);
assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three");
});
}
@Command(command = "one", alias = "three")
private static class AliasOnBothMethodStringEmpty {
@Command(command = "two", alias = "")
void command1(){
}
}
@Test
void aliasOnBoth() {
configCommon(AliasOnBoth.class, new AliasOnBoth(), "command1", new Class[] { })
.run((context) -> {
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
CommandRegistrationFactoryBean.class);
assertThat(fb).isNotNull();
CommandRegistration registration = fb.getObject();
assertThat(registration).isNotNull();
assertThat(registration.getCommand()).isEqualTo("one two");
assertThat(registration.getAliases()).hasSize(1);
assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("three four");
});
}
@Command(command = "one", alias = "three")
private static class AliasOnBoth {
@Command(command = "two", alias = "four")
void command1(){
}
}
@Test
void aliasWithCommandOnBothMethodStringEmpty() {
configCommon(AliasWithCommandOnBothMethodStringEmpty.class, new AliasWithCommandOnBothMethodStringEmpty(), "command1", new Class[] { })
.run((context) -> {
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
CommandRegistrationFactoryBean.class);
assertThat(fb).isNotNull();
CommandRegistration registration = fb.getObject();
assertThat(registration).isNotNull();
assertThat(registration.getCommand()).isEqualTo("one");
assertThat(registration.getAliases()).hasSize(1);
assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("ten");
});
configCommon(AliasWithCommandOnBothMethodStringEmpty.class, new AliasWithCommandOnBothMethodStringEmpty(), "command2", new Class[] { })
.run((context) -> {
CommandRegistrationFactoryBean fb = context.getBean(FACTORYBEANREF,
CommandRegistrationFactoryBean.class);
assertThat(fb).isNotNull();
CommandRegistration registration = fb.getObject();
assertThat(registration).isNotNull();
assertThat(registration.getCommand()).isEqualTo("one two");
assertThat(registration.getAliases()).hasSize(1);
assertThat(registration.getAliases().get(0).getCommand()).isEqualTo("ten twelve");
});
}
@Command(command = "one", alias = "ten")
private static class AliasWithCommandOnBothMethodStringEmpty {
@Command(command = "", alias = "")
void command1(){
}
@Command(command = "two", alias = "twelve")
void command2(){
}
}
}
}