diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/Bootstrap.java b/spring-shell2-core/src/main/java/org/springframework/shell2/Bootstrap.java index 744bf30b..50109b16 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/Bootstrap.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/Bootstrap.java @@ -40,7 +40,6 @@ import org.springframework.shell2.standard.StandardParameterResolver; * @author Camilo Gonzalez */ @SpringBootApplication -@ComponentScan(basePackageClasses = Bootstrap.class) public class Bootstrap { public static void main(String[] args) throws Exception { @@ -53,18 +52,9 @@ public class Bootstrap { return new DefaultConversionService(); } - @Bean - public ParameterResolver parameterResolver(ConversionService conversionService) { - return new StandardParameterResolver(conversionService); - } - @Bean public Terminal terminal() throws IOException { return TerminalBuilder.builder().build(); } - @Bean - public EnumValueProvider enumValueProvider() { - return new EnumValueProvider(); - } } diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/JLineShell.java b/spring-shell2-core/src/main/java/org/springframework/shell2/JLineShell.java index 1a104be7..c1e710f0 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/JLineShell.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/JLineShell.java @@ -46,8 +46,10 @@ import org.jline.utils.AttributedStringBuilder; import org.jline.utils.AttributedStyle; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.core.MethodParameter; +import org.springframework.shell2.result.TypeHierarchyResultHandler; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; @@ -64,7 +66,8 @@ import org.springframework.util.ReflectionUtils; public class JLineShell implements Shell { @Autowired - ResultHandlers resultHandlers = new ResultHandlers(); + @Qualifier("main") + ResultHandler resultHandler; @Autowired private ApplicationContext applicationContext; @@ -133,7 +136,7 @@ public class JLineShell implements Shell { } catch (UserInterruptException e) { if (e.getPartialLine().isEmpty()) { - resultHandlers.handleResult(new ExitRequest(1)); + resultHandler.handleResult(new ExitRequest(1)); } else { continue; } @@ -171,7 +174,7 @@ public class JLineShell implements Shell { result = e; } - resultHandlers.handleResult(result); + resultHandler.handleResult(result); } else { diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/ResultHandler.java similarity index 90% rename from spring-shell2-core/src/main/java/org/springframework/shell2/result/ResultHandler.java rename to spring-shell2-core/src/main/java/org/springframework/shell2/ResultHandler.java index 357c4584..23366bf2 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ResultHandler.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/ResultHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2017 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.shell2.result; +package org.springframework.shell2; /** * Implementations know how to deal with results of method invocations, whether normal results or exceptions thrown. diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/AttributedCharSequenceResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/AttributedCharSequenceResultHandler.java index 550fd1d3..14afa8fe 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/result/AttributedCharSequenceResultHandler.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/AttributedCharSequenceResultHandler.java @@ -20,6 +20,7 @@ import org.jline.terminal.Terminal; import org.jline.utils.AttributedCharSequence; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.shell2.ResultHandler; import org.springframework.stereotype.Component; /** diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/DefaultResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/DefaultResultHandler.java index 799bd849..394fa8b6 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/result/DefaultResultHandler.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/DefaultResultHandler.java @@ -16,6 +16,7 @@ package org.springframework.shell2.result; +import org.springframework.shell2.ResultHandler; import org.springframework.stereotype.Component; /** diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ExitRequestResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/ExitRequestResultHandler.java index 7a7814d8..e41f499b 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ExitRequestResultHandler.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/ExitRequestResultHandler.java @@ -22,6 +22,7 @@ import java.io.IOException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.shell2.ExitRequest; +import org.springframework.shell2.ResultHandler; import org.springframework.stereotype.Component; /** diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/IterableResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/IterableResultHandler.java index 5c21156c..9ca3767c 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/result/IterableResultHandler.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/IterableResultHandler.java @@ -17,29 +17,29 @@ package org.springframework.shell2.result; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.shell2.ResultHandlers; +import org.springframework.shell2.ResultHandler; import org.springframework.stereotype.Component; /** * A {@link ResultHandler} that deals with {@link Iterable}s and delegates to - * {@link ResultHandlers} for each element in turn. + * {@link TypeHierarchyResultHandler} for each element in turn. * * @author Eric Bottard */ @Component public class IterableResultHandler implements ResultHandler { - private ResultHandlers resultHandlers; + private ResultHandler delegate; - @Autowired - public void setResultHandlers(ResultHandlers resultHandlers) { - this.resultHandlers = resultHandlers; + // Setter injection to avoid circular dependency at creation time + public void setDelegate(ResultHandler delegate) { + this.delegate = delegate; } @Override public void handleResult(Iterable result) { for (Object o : result) { - resultHandlers.handleResult(o); + delegate.handleResult(o); } } } diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ResultHandlerConfig.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/ResultHandlerConfig.java new file mode 100644 index 00000000..7abf6d53 --- /dev/null +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/ResultHandlerConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.shell2.result; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Bean; +import org.springframework.shell2.ResultHandler; + +/** + * Used for explicit configuration of {@link org.springframework.shell2.ResultHandler}s. + * + * @author Eric Bottard + */ +@Configuration +public class ResultHandlerConfig { + + @Bean + @Qualifier("main") + public ResultHandler mainResultHandler() { + return new TypeHierarchyResultHandler(); + } + + @Bean + public IterableResultHandler iterableResultHandler() { + return new IterableResultHandler(); + } + + @PostConstruct + public void wireIterableResultHandler() { + iterableResultHandler().setDelegate(mainResultHandler()); + } + +} diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ThrowableResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/ThrowableResultHandler.java index 8ebaa86d..33799c25 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ThrowableResultHandler.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/ThrowableResultHandler.java @@ -19,6 +19,7 @@ package org.springframework.shell2.result; import org.jline.utils.AttributedString; import org.jline.utils.AttributedStyle; +import org.springframework.shell2.ResultHandler; import org.springframework.stereotype.Component; /** diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/ResultHandlers.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/TypeHierarchyResultHandler.java similarity index 67% rename from spring-shell2-core/src/main/java/org/springframework/shell2/ResultHandlers.java rename to spring-shell2-core/src/main/java/org/springframework/shell2/result/TypeHierarchyResultHandler.java index a853450f..c71536c9 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/ResultHandlers.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/TypeHierarchyResultHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.shell2; +package org.springframework.shell2.result; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -23,17 +23,20 @@ import java.util.Map; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.shell2.result.ResultHandler; +import org.springframework.shell2.ResultHandler; import org.springframework.stereotype.Component; /** - * A unique entry point delegating to the most appropriate {@link ResultHandler}, - * according to the type of result to handle. + * A delegating {@link ResultHandler} that dispatches handling based on the type of the result. + *

+ * If no direct match is found, the type hierarchy of the result is considered, including implemented interfaces. + * Auto-populates the handler map based on Generics type declaration of each discovered {@link ResultHandler} in the + * ApplicationContext. + *

* * @author Eric Bottard */ -@Component -public class ResultHandlers { +public class TypeHierarchyResultHandler implements ResultHandler { private Map, ResultHandler> resultHandlers = new HashMap<>(); @@ -67,7 +70,14 @@ public class ResultHandlers { public void setResultHandlers(Set> resultHandlers) { for (ResultHandler resultHandler : resultHandlers) { Type type = ((ParameterizedType) resultHandler.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0]; - this.resultHandlers.put((Class) type, resultHandler); + registerHandler((Class) type, resultHandler); + } + } + + private void registerHandler(Class type, ResultHandler resultHandler) { + ResultHandler previous = this.resultHandlers.put(type, resultHandler); + if (previous != null) { + throw new IllegalArgumentException(String.format("Multiple ResultHandlers configured for %s: both %s and %s", type, previous, resultHandler)); } } diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/standard/EnumValueProvider.java b/spring-shell2-core/src/main/java/org/springframework/shell2/standard/EnumValueProvider.java index 1bf45c24..a8ab96ca 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/standard/EnumValueProvider.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/standard/EnumValueProvider.java @@ -22,11 +22,13 @@ import java.util.List; import org.springframework.core.MethodParameter; import org.springframework.shell2.CompletionContext; import org.springframework.shell2.CompletionProposal; +import org.springframework.stereotype.Component; /** * A {@link ValueProvider} that knows how to complete values for {@link Enum} typed parameters. * @author Eric Bottard */ +@Component public class EnumValueProvider implements ValueProvider { @Override diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java b/spring-shell2-core/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java index bbd1c296..b849e062 100644 --- a/spring-shell2-core/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java +++ b/spring-shell2-core/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java @@ -45,6 +45,7 @@ import org.springframework.shell2.ParameterMissingResolutionException; import org.springframework.shell2.ParameterResolver; import org.springframework.shell2.UnfinishedParameterResolutionException; import org.springframework.shell2.Utils; +import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; @@ -71,6 +72,7 @@ import org.springframework.util.ConcurrentReferenceHashMap; * @author Eric Bottard * @author Florent Biville */ +@Component public class StandardParameterResolver implements ParameterResolver { private final ConversionService conversionService; @@ -84,6 +86,7 @@ public class StandardParameterResolver implements ParameterResolver { */ private final Map> parameterCache = new ConcurrentReferenceHashMap<>(); + @Autowired public StandardParameterResolver(ConversionService conversionService) { this.conversionService = conversionService; } diff --git a/spring-shell2-core/src/test/java/org/springframework/shell2/JLineShellTest.java b/spring-shell2-core/src/test/java/org/springframework/shell2/JLineShellTest.java index e45ba8da..74025c59 100644 --- a/spring-shell2-core/src/test/java/org/springframework/shell2/JLineShellTest.java +++ b/spring-shell2-core/src/test/java/org/springframework/shell2/JLineShellTest.java @@ -23,13 +23,13 @@ import org.jline.reader.UserInterruptException; import org.junit.Test; import org.mockito.Mockito; -import org.springframework.shell2.result.ResultHandler; - import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.springframework.shell2.result.TypeHierarchyResultHandler; + /** * Unit tests for {@link JLineShell}. * @@ -43,7 +43,7 @@ public class JLineShellTest { public void testCtrlCInterception() throws Exception { shell.lineReader = mock(LineReader.class); AssertingExitRequestResultHandler resultHandler = new AssertingExitRequestResultHandler(); - shell.resultHandlers.setResultHandlers(Collections.singleton(resultHandler)); + shell.resultHandler = resultHandler; when(shell.lineReader.readLine(Mockito.any())).thenThrow( new UserInterruptException("some input"), new UserInterruptException("")