SHL-173: Add converter for array of convertible
This commit is contained in:
committed by
Mark Pollack
parent
511a7e230b
commit
4c18cfb8f4
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ target/
|
||||
/*.ipr
|
||||
/*.iws
|
||||
out
|
||||
.gradletasknamecache
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
version=1.2.0.BUILD-SNAPSHOT
|
||||
|
||||
slf4jVersion=1.7.7
|
||||
guavaVersion=17.0
|
||||
jlineVersion=2.11
|
||||
jlineVersion=2.12
|
||||
junitVersion=4.11
|
||||
springVersion=4.0.6.RELEASE
|
||||
commonsioVersion=2.4
|
||||
hamcrestVersion=1.3
|
||||
version=1.2.0.BUILD-SNAPSHOT
|
||||
mockitoVersion=1.9.5
|
||||
hamcrestDateVersion=0.9.3
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.springframework.shell.converters;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.shell.core.Completion;
|
||||
import org.springframework.shell.core.Converter;
|
||||
import org.springframework.shell.core.MethodTarget;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A converter that knows how to use other converters to create arrays of supported types.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@Component
|
||||
public class ArrayConverter implements Converter<Object[]>{
|
||||
|
||||
private Set<Converter<?>> converters;
|
||||
|
||||
@Autowired
|
||||
public void setConverters(Set<Converter<?>> converters) {
|
||||
this.converters = converters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> type, String optionContext) {
|
||||
return findComponentConverter(type, optionContext) != null && !optionContext.contains("disable-array-converter");
|
||||
|
||||
}
|
||||
|
||||
private Converter<?> findComponentConverter(Class<?> targetType, String optionContext) {
|
||||
if (!targetType.isArray()) {
|
||||
return null;
|
||||
}
|
||||
Class<?> componentType = targetType.getComponentType();
|
||||
for (Converter<?> converter : converters) {
|
||||
if (converter.supports(componentType, optionContext)) {
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] convertFromText(String value, Class<?> targetType, String optionContext) {
|
||||
Class<?> componentType = targetType.getComponentType();
|
||||
|
||||
String splittingRegex = inferSplittingRegex(targetType, optionContext);
|
||||
String[] splits = value.split(splittingRegex);
|
||||
Object[] result = (Object[]) Array.newInstance(componentType, splits.length);
|
||||
Converter<?> converter = findComponentConverter(targetType, optionContext);
|
||||
|
||||
for (int i = 0; i < splits.length; i++) {
|
||||
result[i] = converter.convertFromText(splits[i], componentType, optionContext);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a regex used to split the string representation of items.
|
||||
* <p>The default delimiter is a comma, unless we're dealing with Files, in which case
|
||||
* {@link java.io.File.pathSeparator} is used.</p>
|
||||
* <p>Delimiters can be protected by an escape character, which is '\' by default.</p>
|
||||
* <p>Command methods may override bot the delimiter and the escape through the {@code splittingRegex} option context
|
||||
* string.</p>
|
||||
*/
|
||||
private String inferSplittingRegex(Class<?> targetType, String optionContext) {
|
||||
String regex = extract(optionContext, "splittingRegex");
|
||||
if (regex == null) {
|
||||
// Default for files is to use system separator with no way to escape
|
||||
if (File[].class.isAssignableFrom(targetType)) {
|
||||
regex = File.pathSeparator;
|
||||
} else {
|
||||
String delimiter = ",";
|
||||
String escape = "\\";
|
||||
regex = String.format("(?<!\\Q%s\\E)\\Q%s\\E", escape, delimiter);
|
||||
}
|
||||
}
|
||||
return regex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAllPossibleValues(List<Completion> completions, Class<?> targetType, String existingData, String optionContext, MethodTarget target) {
|
||||
Class<?> componentType = targetType.getComponentType();
|
||||
|
||||
String splittingRegex = inferSplittingRegex(targetType, optionContext);
|
||||
String[] splits = existingData.split(splittingRegex);
|
||||
Converter<?> converter = findComponentConverter(targetType, optionContext);
|
||||
|
||||
// Search for completions with the last part only, prefixing the results by everything that was
|
||||
// before the delimiter
|
||||
String last = splits[splits.length - 1];
|
||||
int end = existingData.lastIndexOf(last);
|
||||
String prefix = existingData.substring(0, end);
|
||||
List<Completion> ours = new ArrayList<Completion>();
|
||||
|
||||
// Passing our method target below, as we can't do better. Obviously, method sig will be wrong
|
||||
boolean result = converter.getAllPossibleValues(ours, componentType, last, optionContext, target);
|
||||
for (Completion completion : ours) {
|
||||
completions.add(new Completion(prefix + completion.getValue(), completion.getValue(), null, 0));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String extract(String optionContext, String key) {
|
||||
String[] splits = optionContext.split(" ");
|
||||
String prefix = key + "=";
|
||||
for (String split : splits) {
|
||||
if (split.startsWith(prefix)) {
|
||||
return split.substring(prefix.length());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.springframework.shell;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.junit.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.shell.core.JLineShellComponent;
|
||||
@@ -19,7 +19,7 @@ public class BootstrapTest {
|
||||
|
||||
//This is a brittle assertion - as additiona 'test' commands are added to the suite, this number will increase.
|
||||
Assert.assertEquals("Number of CommandMarkers is incorrect", 10, shell.getSimpleParser().getCommandMarkers().size());
|
||||
Assert.assertEquals("Number of Converters is incorrect", 16, shell.getSimpleParser().getConverters().size());
|
||||
Assert.assertEquals("Number of Converters is incorrect", 17, shell.getSimpleParser().getConverters().size());
|
||||
} catch (RuntimeException t) {
|
||||
throw t;
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package org.springframework.shell.converters;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.DiagnosingMatcher;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.shell.core.Completion;
|
||||
import org.springframework.shell.core.Converter;
|
||||
|
||||
/**
|
||||
* Tests for ArrayConverter.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class ArrayConverterTest {
|
||||
|
||||
private ArrayConverter arrayConverter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
FileConverter fileConverter = new FileConverter() {
|
||||
|
||||
@Override
|
||||
protected File getWorkingDirectory() {
|
||||
return new File(".");
|
||||
}
|
||||
};
|
||||
arrayConverter = new ArrayConverter();
|
||||
Set<Converter<?>> allConverters = new HashSet<Converter<?>>();
|
||||
allConverters.add(fileConverter);
|
||||
allConverters.add(arrayConverter);
|
||||
allConverters.add(new IntegerConverter());
|
||||
arrayConverter.setConverters(allConverters);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInferredFileSeparator() throws IOException {
|
||||
|
||||
String raw = "src/main" + File.pathSeparator + "src/main/java";
|
||||
File main = new File("src/main").getCanonicalFile();
|
||||
File src = new File("src/main/java").getCanonicalFile();
|
||||
|
||||
|
||||
assertThat(arrayConverter.supports(File[].class, ""), equalTo(true));
|
||||
File[] result = (File[]) arrayConverter.convertFromText(raw, File[].class, "");
|
||||
assertThat(result, arrayContaining(main, src));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultDelimiter() {
|
||||
assertThat(arrayConverter.supports(Integer[].class, ""), equalTo(true));
|
||||
Integer[] result = (Integer[]) arrayConverter.convertFromText("1,2,3,4,5", Integer[].class, "");
|
||||
assertThat(result, arrayContaining(1, 2, 3, 4, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverriddenDelimiter() {
|
||||
assertThat(arrayConverter.supports(Integer[].class, "splittingRegex=;"), equalTo(true));
|
||||
Integer[] result = (Integer[]) arrayConverter.convertFromText("1;2;3;4;5", Integer[].class, "splittingRegex=;");
|
||||
assertThat(result, arrayContaining(1, 2, 3, 4, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsupportedType() {
|
||||
assertThat(arrayConverter.supports(Integer.class, ""), equalTo(false));
|
||||
assertThat(arrayConverter.supports(Float[].class, ""), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptOut() {
|
||||
assertThat(arrayConverter.supports(Integer[].class, "disable-array-converter"), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testCompletions() throws IOException {
|
||||
String raw = "src/test/java/" + File.pathSeparator + "src/main/";
|
||||
|
||||
|
||||
List<Completion> completions = new ArrayList<Completion>();
|
||||
arrayConverter.getAllPossibleValues(completions, File[].class, raw, "", null);
|
||||
assertThat(completions, containsInAnyOrder(
|
||||
completionWhoseValue(equalTo("src/test/java/" + File.pathSeparator + "src/main/java/")),
|
||||
completionWhoseValue(equalTo("src/test/java/" + File.pathSeparator +" src/main/resources/"))));
|
||||
|
||||
}
|
||||
|
||||
private Matcher<Completion> completionWhoseValue(final Matcher<String> matcher) {
|
||||
return new DiagnosingMatcher<Completion>() {
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("a completion that ").appendDescriptionOf(matcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matches(Object item, Description mismatchDescription) {
|
||||
Completion completion = (Completion) item;
|
||||
boolean match = matcher.matches(completion.getValue());
|
||||
matcher.describeMismatch(completion.getValue(), mismatchDescription);
|
||||
return match;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user