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
|
/*.ipr
|
||||||
/*.iws
|
/*.iws
|
||||||
out
|
out
|
||||||
|
.gradletasknamecache
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
version=1.2.0.BUILD-SNAPSHOT
|
||||||
|
|
||||||
slf4jVersion=1.7.7
|
slf4jVersion=1.7.7
|
||||||
guavaVersion=17.0
|
guavaVersion=17.0
|
||||||
jlineVersion=2.11
|
jlineVersion=2.12
|
||||||
junitVersion=4.11
|
junitVersion=4.11
|
||||||
springVersion=4.0.6.RELEASE
|
springVersion=4.0.6.RELEASE
|
||||||
commonsioVersion=2.4
|
commonsioVersion=2.4
|
||||||
hamcrestVersion=1.3
|
hamcrestVersion=1.3
|
||||||
version=1.2.0.BUILD-SNAPSHOT
|
|
||||||
mockitoVersion=1.9.5
|
mockitoVersion=1.9.5
|
||||||
hamcrestDateVersion=0.9.3
|
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.io.IOException;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.shell.core.JLineShellComponent;
|
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.
|
//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 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) {
|
} catch (RuntimeException t) {
|
||||||
throw t;
|
throw t;
|
||||||
} finally {
|
} 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