diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellAutoConfiguration.java
index 711638f1..5b3ee8cb 100644
--- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellAutoConfiguration.java
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellAutoConfiguration.java
@@ -17,6 +17,7 @@
package org.springframework.shell.boot;
import java.util.Collection;
+import java.util.Set;
import javax.validation.Validation;
import javax.validation.Validator;
@@ -33,8 +34,9 @@ import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.shell.ResultHandler;
+import org.springframework.shell.ResultHandlerService;
import org.springframework.shell.Shell;
-import org.springframework.shell.result.IterableResultHandler;
+import org.springframework.shell.result.GenericResultHandlerService;
import org.springframework.shell.result.ResultHandlerConfig;
/**
@@ -71,8 +73,16 @@ public class SpringShellAutoConfiguration {
}
@Bean
- public Shell shell(@Qualifier("main") ResultHandler resultHandler, @Qualifier("iterableResultHandler") IterableResultHandler iterableResultHandler) {
- iterableResultHandler.setDelegate(resultHandler);
- return new Shell(resultHandler);
+ public ResultHandlerService resultHandlerService(Set> resultHandlers) {
+ GenericResultHandlerService service = new GenericResultHandlerService();
+ for (ResultHandler> resultHandler : resultHandlers) {
+ service.addResultHandler(resultHandler);
+ }
+ return service;
+ }
+
+ @Bean
+ public Shell shell(ResultHandlerService resultHandlerService) {
+ return new Shell(resultHandlerService);
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ResultHandlerService.java b/spring-shell-core/src/main/java/org/springframework/shell/ResultHandlerService.java
new file mode 100644
index 00000000..733559b3
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/ResultHandlerService.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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.shell;
+
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+
+/**
+ * A service interface for result handling.
+ *
+ * @author Janne Valkealahti
+ */
+public interface ResultHandlerService {
+
+ /**
+ * Handle result.
+ *
+ * @param result the result
+ */
+ void handle(@Nullable Object result);
+
+ /**
+ * Handle result to the specified {@ resultType}.
+ *
+ * @param result the result
+ * @param resultType the result type
+ */
+ void handle(@Nullable Object result, @Nullable TypeDescriptor resultType);
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
index 50505c11..fe4b94ad 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
@@ -55,10 +55,11 @@ import org.springframework.util.ReflectionUtils;
*
*
* @author Eric Bottard
+ * @author Janne Valkealahti
*/
public class Shell {
- private final ResultHandler resultHandler;
+ private final ResultHandlerService resultHandlerService;
/**
* Marker object returned to signify that there was no input to turn into a command
@@ -84,8 +85,8 @@ public class Shell {
*/
protected static final Object UNRESOLVED = new Object();
- public Shell(ResultHandler resultHandler) {
- this.resultHandler = resultHandler;
+ public Shell(ResultHandlerService resultHandlerService) {
+ this.resultHandlerService = resultHandlerService;
}
@Autowired(required = false)
@@ -125,7 +126,7 @@ public class Shell {
if (e instanceof ExitRequest) { // Handles ExitRequest thrown from hitting CTRL-C
break;
}
- resultHandler.handleResult(e);
+ resultHandlerService.handle(e);
continue;
}
if (input == null) {
@@ -134,7 +135,7 @@ public class Shell {
result = evaluate(input);
if (result != NO_INPUT && !(result instanceof ExitRequest)) {
- resultHandler.handleResult(result);
+ resultHandlerService.handle(result);
}
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandler.java
new file mode 100644
index 00000000..c679f4b4
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandler.java
@@ -0,0 +1,14 @@
+package org.springframework.shell.result;
+
+import java.util.Set;
+
+import org.springframework.core.convert.TypeDescriptor;
+
+public interface GenericResultHandler {
+
+ Set> getHandlerTypes();
+
+ void handle(Object result, TypeDescriptor resultType);
+
+ boolean matches(TypeDescriptor resultType);
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandlerService.java b/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandlerService.java
new file mode 100644
index 00000000..783a776c
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandlerService.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2021 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
+ *
+ * https://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.shell.result;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.springframework.core.ResolvableType;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.lang.Nullable;
+import org.springframework.shell.ResultHandler;
+import org.springframework.shell.ResultHandlerService;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Base {@ResultHandlerService} implementation suitable for use in most
+ * environments.
+ *
+ * @author Janne Valkealahti
+ */
+public class GenericResultHandlerService implements ResultHandlerService {
+
+ private final ResultHandlers resultHandlers = new ResultHandlers();
+
+ @Override
+ public void handle(Object source) {
+ handle(source, TypeDescriptor.forObject(source));
+ }
+
+ @Override
+ public void handle(Object result, TypeDescriptor resultType) {
+ if (result == null) {
+ return;
+ }
+ GenericResultHandler handler = getResultHandler(resultType);
+ if (handler != null) {
+ invokeHandler(handler, result, resultType);
+ return;
+ }
+ handleResultHandlerNotFound(result, resultType);
+ }
+
+ /**
+ * Add a plain result handler to this registry.
+ *
+ * @param resultHandler the result handler
+ */
+ public void addResultHandler(ResultHandler> resultHandler) {
+ ResolvableType[] typeInfo = getRequiredTypeInfo(resultHandler.getClass(), ResultHandler.class);
+ if (typeInfo == null) {
+ throw new IllegalArgumentException("Unable to determine result type for your " +
+ "ResultHandler [" + resultHandler.getClass().getName() + "]; does the class parameterize those types?");
+ }
+ addResultHandler(new ResultHandlerAdapter(resultHandler, typeInfo[0]));
+ }
+
+ /**
+ * Add a plain result handler to this registry.
+ *
+ * @param the type of result handler
+ * @param resultType the class of a result type
+ * @param resultHandler the result handler
+ */
+ public void addResultHandler(Class resultType, ResultHandler super T> resultHandler) {
+ addResultHandler(new ResultHandlerAdapter(resultHandler, ResolvableType.forClass(resultType)));
+ }
+
+ /**
+ * Add a generic result handler this this registry.
+ *
+ * @param handler the generic result handler
+ */
+ public void addResultHandler(GenericResultHandler handler) {
+ this.resultHandlers.add(handler);
+ }
+
+ private GenericResultHandler getResultHandler(TypeDescriptor resultType) {
+ return this.resultHandlers.find(resultType);
+ }
+
+ @Nullable
+ private Object handleResultHandlerNotFound(
+ @Nullable Object source, @Nullable TypeDescriptor sourceType) {
+ if (source == null) {
+ return null;
+ }
+ if (sourceType == null) {
+ return source;
+ }
+ throw new ResultHandlerNotFoundException(sourceType);
+ }
+
+ @Nullable
+ private ResolvableType[] getRequiredTypeInfo(Class> handlerClass, Class> genericIfc) {
+ ResolvableType resolvableType = ResolvableType.forClass(handlerClass).as(genericIfc);
+ ResolvableType[] generics = resolvableType.getGenerics();
+ if (generics.length < 1) {
+ return null;
+ }
+ Class> resultType = generics[0].resolve();
+ if (resultType == null) {
+ return null;
+ }
+ return generics;
+ }
+
+ @SuppressWarnings("unchecked")
+ private final static class ResultHandlerAdapter implements GenericResultHandler {
+
+ ResultHandler