Refactor dependencies to avoid package tangles

Fixes #37
This commit is contained in:
Eric Bottard
2017-05-18 12:16:51 +02:00
parent 9ca98cdd7d
commit 452c7dcb9f
13 changed files with 94 additions and 32 deletions

View File

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

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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;
/**

View File

@@ -16,6 +16,7 @@
package org.springframework.shell2.result;
import org.springframework.shell2.ResultHandler;
import org.springframework.stereotype.Component;
/**

View File

@@ -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;
/**

View File

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

View File

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

View File

@@ -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;
/**

View File

@@ -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.
* <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
*/
@Component
public class ResultHandlers {
public class TypeHierarchyResultHandler implements ResultHandler<Object> {
private Map<Class<?>, ResultHandler<?>> resultHandlers = new HashMap<>();
@@ -67,7 +70,14 @@ public class ResultHandlers {
public void setResultHandlers(Set<ResultHandler<?>> 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));
}
}

View File

@@ -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

View File

@@ -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<CacheKey, Map<Parameter, ParameterRawValue>> parameterCache = new ConcurrentReferenceHashMap<>();
@Autowired
public StandardParameterResolver(ConversionService conversionService) {
this.conversionService = conversionService;
}

View File

@@ -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.<String>any())).thenThrow(
new UserInterruptException("some input"),
new UserInterruptException("")