Introduce ResultHandlerService

- Replace main use of ResultHandler with ResultHandlerService which is
  a framework type of impl for handlers found from conversion service.
  This handles types better and easier to handle with bean cycles, etc.
- Removed IterableResultHandler to think about these use cases later
  when further refactoring is done.
- TypeHierarchyResultHandler is removed and better functionality now
  via ResultHandlerService.
- Relates #336
This commit is contained in:
Janne Valkealahti
2021-12-21 17:49:06 +00:00
parent c5081acee8
commit d883e0e660
12 changed files with 531 additions and 160 deletions

View File

@@ -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<ResultHandler<?>> resultHandlers) {
GenericResultHandlerService service = new GenericResultHandlerService();
for (ResultHandler<?> resultHandler : resultHandlers) {
service.addResultHandler(resultHandler);
}
return service;
}
@Bean
public Shell shell(ResultHandlerService resultHandlerService) {
return new Shell(resultHandlerService);
}
}

View File

@@ -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);
}

View File

@@ -55,10 +55,11 @@ import org.springframework.util.ReflectionUtils;
* </p>
*
* @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);
}
}
}

View File

@@ -0,0 +1,14 @@
package org.springframework.shell.result;
import java.util.Set;
import org.springframework.core.convert.TypeDescriptor;
public interface GenericResultHandler {
Set<Class<?>> getHandlerTypes();
void handle(Object result, TypeDescriptor resultType);
boolean matches(TypeDescriptor resultType);
}

View File

@@ -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 <T> 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 <T> the type of result handler
* @param resultType the class of a result type
* @param resultHandler the result handler
*/
public <T> void addResultHandler(Class<T> 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<Object> handler;
Class<?> result;
public ResultHandlerAdapter(ResultHandler<?> handler, ResolvableType resultType) {
this.handler = (ResultHandler<Object>) handler;
this.result = resultType.toClass();
}
@Override
public Set<Class<?>> getHandlerTypes() {
return Collections.singleton(this.result);
}
@Override
public void handle(Object result, TypeDescriptor resultType) {
this.handler.handleResult(result);
}
@Override
public boolean matches(TypeDescriptor resultType) {
// always true until we create conditional handlers
return true;
}
}
/**
* Manages handlers registered with a specific {@link Class}.
*/
private static class ResultHandlersForType {
private final Deque<GenericResultHandler> handlers = new ConcurrentLinkedDeque<>();
public void add(GenericResultHandler handler) {
this.handlers.addFirst(handler);
}
@Nullable
public GenericResultHandler getHandler(TypeDescriptor resultType) {
for (GenericResultHandler handler : this.handlers) {
if (handler.matches(resultType)) {
return handler;
}
}
return null;
}
@Override
public String toString() {
return StringUtils.collectionToCommaDelimitedString(this.handlers);
}
}
private static class ResultHandlers {
private final Set<GenericResultHandler> globalHandlers = new CopyOnWriteArraySet<>();
private final Map<Class<?>, ResultHandlersForType> handlers = new ConcurrentHashMap<>(16);
public void add(GenericResultHandler handler) {
Set<Class<?>> handlerTypes = handler.getHandlerTypes();
if (handlerTypes == null) {
this.globalHandlers.add(handler);
}
else {
for (Class<?> handlerType : handlerTypes) {
getMatchableConverters(handlerType).add(handler);
}
}
}
private ResultHandlersForType getMatchableConverters(Class<?> handlerType) {
return this.handlers.computeIfAbsent(handlerType, k -> new ResultHandlersForType());
}
public GenericResultHandler find(TypeDescriptor resultType) {
List<Class<?>> resultCandidates = getClassHierarchy(resultType.getType());
for (Class<?> resultCandidate : resultCandidates) {
GenericResultHandler handler = getRegisteredHandler(resultType, resultCandidate);
if (handler != null) {
return handler;
}
}
return null;
}
@Nullable
private GenericResultHandler getRegisteredHandler(TypeDescriptor resultType, Class<?> handlerType) {
ResultHandlersForType resultHandlersForType = this.handlers.get(handlerType);
if (resultHandlersForType != null) {
GenericResultHandler handler = resultHandlersForType.getHandler(resultType);
if (handler != null) {
return handler;
}
}
for (GenericResultHandler globalHandler : this.globalHandlers) {
if (globalHandler.matches(resultType)) {
return globalHandler;
}
}
return null;
}
private List<Class<?>> getClassHierarchy(Class<?> type) {
List<Class<?>> hierarchy = new ArrayList<>(20);
Set<Class<?>> visited = new HashSet<>(20);
addToClassHierarchy(0, ClassUtils.resolvePrimitiveIfNecessary(type), false, hierarchy, visited);
boolean array = type.isArray();
int i = 0;
while (i < hierarchy.size()) {
Class<?> candidate = hierarchy.get(i);
candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));
Class<?> superclass = candidate.getSuperclass();
if (superclass != null && superclass != Object.class && superclass != Enum.class) {
addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
}
addInterfacesToClassHierarchy(candidate, array, hierarchy, visited);
i++;
}
if (Enum.class.isAssignableFrom(type)) {
addToClassHierarchy(hierarchy.size(), Enum.class, array, hierarchy, visited);
addToClassHierarchy(hierarchy.size(), Enum.class, false, hierarchy, visited);
addInterfacesToClassHierarchy(Enum.class, array, hierarchy, visited);
}
addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited);
addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited);
return hierarchy;
}
private void addInterfacesToClassHierarchy(Class<?> type, boolean asArray,
List<Class<?>> hierarchy, Set<Class<?>> visited) {
for (Class<?> implementedInterface : type.getInterfaces()) {
addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited);
}
}
private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
List<Class<?>> hierarchy, Set<Class<?>> visited) {
if (asArray) {
type = Array.newInstance(type, 0).getClass();
}
if (visited.add(type)) {
hierarchy.add(index, type);
}
}
}
private static void invokeHandler(GenericResultHandler handler, Object result, TypeDescriptor resultType) {
handler.handle(result, resultType);;
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2015 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 org.springframework.shell.ResultHandler;
/**
* A {@link ResultHandler} that deals with {@link Iterable}s and delegates to
* {@link TypeHierarchyResultHandler} for each element in turn.
*
* @author Eric Bottard
*/
public class IterableResultHandler implements ResultHandler<Iterable> {
private ResultHandler delegate;
// Setter injection to avoid circular dependency at creation time
public void setDelegate(ResultHandler delegate) {
this.delegate = delegate;
}
@Override
@SuppressWarnings("unchecked")
public void handleResult(Iterable result) {
for (Object o : result) {
delegate.handleResult(o);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-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.
@@ -16,33 +16,20 @@
package org.springframework.shell.result;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.ResultHandler;
import org.springframework.shell.TerminalSizeAware;
/**
* Used for explicit configuration of {@link org.springframework.shell.ResultHandler}s.
*
* @author Eric Bottard
* @author Janne Valkealahti
*/
@Configuration
public class ResultHandlerConfig {
@Bean
@Qualifier("main")
public ResultHandler<?> mainResultHandler() {
return new TypeHierarchyResultHandler();
}
@Bean
@Qualifier("iterableResultHandler")
public IterableResultHandler iterableResultHandler() {
return new IterableResultHandler();
}
@Bean
@ConditionalOnClass(TerminalSizeAware.class)
public TerminalSizeAwareResultHandler terminalSizeAwareResultHandler() {

View File

@@ -0,0 +1,44 @@
/*
* 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 org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
public class ResultHandlerNotFoundException extends ResultHandlingException {
@Nullable
private final TypeDescriptor resultType;
/**
* Create a new handling executor not found exception.
*
* @param resultType the result type requested to handle from
*/
public ResultHandlerNotFoundException(@Nullable TypeDescriptor resultType) {
super("No handler found capable of handling from type [" + resultType + "]");
this.resultType = resultType;
}
/**
* Return the source type that was requested to convert from.
*/
@Nullable
public TypeDescriptor getResultType() {
return this.resultType;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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 org.springframework.core.NestedRuntimeException;
public class ResultHandlingException extends NestedRuntimeException {
/**
* Construct a new result handling exception.
*
* @param message the exception message
*/
public ResultHandlingException(String message) {
super(message);
}
/**
* Construct a new result handling exception.
*
* @param message the exception message
* @param cause the cause
*/
public ResultHandlingException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,83 +0,0 @@
/*
* 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
*
* 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.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ResolvableType;
import org.springframework.shell.ResultHandler;
/**
* A delegating {@link ResultHandler} that dispatches handling based on the type of the result.
* <p>
* 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.
* </p>
*
* @author Eric Bottard
*/
public class TypeHierarchyResultHandler implements ResultHandler<Object> {
private Map<Class<?>, ResultHandler<?>> resultHandlers = new HashMap<>();
@Override
@SuppressWarnings("unchecked")
public void handleResult(Object result) {
if (result == null) { // void methods
return;
}
Class<?> clazz = result.getClass();
ResultHandler handler = getResultHandler(clazz);
handler.handleResult(result);
}
private ResultHandler getResultHandler(Class<?> clazz) {
ResultHandler handler = resultHandlers.get(clazz);
if (handler != null) {
return handler;
}
else {
for (Class type : clazz.getInterfaces()) {
handler = getResultHandler(type);
if (handler != null) {
return handler;
}
}
return clazz.getSuperclass() != null ? getResultHandler(clazz.getSuperclass()) : null;
}
}
@Autowired
public void setResultHandlers(Set<ResultHandler<?>> resultHandlers) {
for (ResultHandler<?> resultHandler : resultHandlers) {
ResolvableType type = ResolvableType.forInstance(resultHandler).as(ResultHandler.class);
registerHandler(type.resolveGeneric(0), 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));
}
}
}

View File

@@ -46,17 +46,13 @@ import static org.mockito.Mockito.when;
* @author Eric Bottard
*/
@ExtendWith(MockitoExtension.class)
// @RunWith(JUnitPlatform.class)
public class ShellTest {
// @Rule
// public MockitoRule mockitoRule = MockitoJUnit.rule();
@Mock
private InputProvider inputProvider;
@Mock
private ResultHandler resultHandler;
ResultHandlerService resultHandlerService;
@Mock
private ParameterResolver parameterResolver;
@@ -79,7 +75,7 @@ public class ShellTest {
when(inputProvider.readInput()).thenReturn(() -> "hello world how are you doing ?", null);
valueResult = new ValueResult(null, "test");
when(parameterResolver.resolve(any(), any())).thenReturn(valueResult);
doThrow(new Exit()).when(resultHandler).handleResult(any());
doThrow(new Exit()).when(resultHandlerService).handle(any());
shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
@@ -97,7 +93,7 @@ public class ShellTest {
@Test
public void commandNotFound() throws IOException {
when(inputProvider.readInput()).thenReturn(() -> "hello world how are you doing ?", null);
doThrow(new Exit()).when(resultHandler).handleResult(isA(CommandNotFound.class));
doThrow(new Exit()).when(resultHandlerService).handle(isA(CommandNotFound.class));
shell.methodTargets = Collections.singletonMap("bonjour", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
@@ -114,7 +110,7 @@ public class ShellTest {
// See https://github.com/spring-projects/spring-shell/issues/142
public void commandNotFoundPrefix() throws IOException {
when(inputProvider.readInput()).thenReturn(() -> "helloworld how are you doing ?", null);
doThrow(new Exit()).when(resultHandler).handleResult(isA(CommandNotFound.class));
doThrow(new Exit()).when(resultHandlerService).handle(isA(CommandNotFound.class));
shell.methodTargets = Collections.singletonMap("hello", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
@@ -133,7 +129,7 @@ public class ShellTest {
when(inputProvider.readInput()).thenReturn(() -> "", () -> "hello world how are you doing ?", null);
valueResult = new ValueResult(null, "test");
when(parameterResolver.resolve(any(), any())).thenReturn(valueResult);
doThrow(new Exit()).when(resultHandler).handleResult(any());
doThrow(new Exit()).when(resultHandlerService).handle(any());
shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
@@ -151,7 +147,7 @@ public class ShellTest {
@Test
public void commandThrowingAnException() throws IOException {
when(inputProvider.readInput()).thenReturn(() -> "fail", null);
doThrow(new Exit()).when(resultHandler).handleResult(isA(SomeException.class));
doThrow(new Exit()).when(resultHandlerService).handle(isA(SomeException.class));
shell.methodTargets = Collections.singletonMap("fail", MethodTarget.of("failing", this, new Command.Help("Will throw an exception")));

View File

@@ -0,0 +1,79 @@
/*
* 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.jline;
import org.junit.jupiter.api.Test;
import org.springframework.shell.ResultHandler;
import org.springframework.shell.result.GenericResultHandlerService;
import static org.assertj.core.api.Assertions.assertThat;
public class GenericResultHandlerServiceTests {
@Test
public void testSimpleHandling() {
StringResultHandler stringResultHandler = new StringResultHandler();
IntegerResultHandler integerResultHandler = new IntegerResultHandler();
GenericResultHandlerService resultHandlerService = new GenericResultHandlerService();
resultHandlerService.addResultHandler(stringResultHandler);
resultHandlerService.addResultHandler(integerResultHandler);
resultHandlerService.handle("string");
assertThat(stringResultHandler.result).isEqualTo("string");
assertThat(integerResultHandler.result).isNull();;
resultHandlerService.handle(0);
assertThat(integerResultHandler.result).isEqualTo(0);
}
@Test
public void testObjectHandling() {
ObjectResultHandler resultHandler = new ObjectResultHandler();
GenericResultHandlerService resultHandlerService = new GenericResultHandlerService();
resultHandlerService.addResultHandler(resultHandler);
resultHandlerService.handle("string");
assertThat(resultHandler.result).isEqualTo("string");
}
private static class StringResultHandler implements ResultHandler<String> {
String result;
@Override
public void handleResult(String result) {
this.result = result;
}
}
private static class IntegerResultHandler implements ResultHandler<Integer> {
Integer result;
@Override
public void handleResult(Integer result) {
this.result = result;
}
}
private static class ObjectResultHandler implements ResultHandler<Object> {
Object result;
@Override
public void handleResult(Object result) {
this.result = result;
}
}
}