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