Я решил написать свой собственный крошечный ArgumentParser в Java, ключевым моментом является его метод
public void addOption(String optionName, Class valueType, boolean required)
за что-то вроде
ArgumentParser parser = new ArgumentParser();
parser.addOption("--input_path", String.class, true);
Map<String, Object> options = parser.parse(args); //args is String[]
String inputPath = (String)options.get("--input_path"); //hooray!
но я не могу понять, как попытаться автоматически преобразовать String в определенный пользователем класс? Очевидно, что код ниже не работает
options.put(currentOption, registeredOptions.get(currentOption).valueOf(arg));
Есть ли простой способ сделать это, чего я не могу понять? Или, может быть, вы говорите, что это плохая и порочная идея, я ценю любой совет! Спасибо!
Уже есть несколько библиотек, например JCommander, и почти все они используют шаблон стратегии. То есть, они позволяют клиентам регистрировать объекты конвертера, которые преобразуют входную строку в объект определенного класса. Разумеется, есть несколько встроенных преобразователей для самых очевидных классов, таких как Number
, Boolean
, File
или lists.
Так что у вас будет что-то вроде этого.
public interface Converter<T> {
public T convert( String arg );
}
public class IntegerConverter implements Converter<Integer> {
public Integer convert( String arg ) {
return Integer.parseInt( arg );
}
}
И в вашем ArgumentParser
вы сохраните карту своих зарегистрированных конвертеров и вызовите соответствующий.
Конечно, вы можете уточнить это, указав иерархию преобразователей, чтобы иметь более и менее конкретные стратегии, но общая идея одинаков.
Вы в основном нацелены на создание функции, которая возвращает объект, класс которого был передан как параметр.
Я не знаю общего решения этой проблемы.
Но есть некоторые частичные решения, которые вместе, как я считаю, будут отвечать за то, что вы ищете:
A. Возвращаемый объект является числовым:
Для int и float решение очевидно:
int ival = Integer.parseInt(strParam);
float fval = Float.parseFloat(strParam);
B. Возвращенный класс объекта имеет конструктор строк, то есть конструктор, получающий один строковый параметр
То есть:
parser.addOption("--input_path", MySpecialClass.class, true);
где:
class MySpecialClass {
public MySpecialClass(String param) // <----------- string constructor
}
Здесь вы можете использовать отражение, чтобы создать экземпляр требуемого объекта, извлекая и активируя конструктор. Примечание: нашему коду не нужно знать идентификатор возвращаемого класса, но погода или нет, он имеет конструктор строк:
Constructor stringConstructor = classObject.getConstructor(new Class[]{String.class});
Object objectToReturn = stringConstructor.newInstance(stringParam);
Наконец, комбинируя приведенное выше, мы получаем функцию обработки аргументов, аналогичную этой:
Object processAnyParam(String strParam, Class returnClass, boolean mandatory) {
if (returnClass.equals(int.class)) {
return Integer.parseInt(strParam);
}
else if (returnClass.equals(float.class)) {
return Float.parseFloat(strParam);
}
else if (maybe other numeric types here...) {
}
else {
try {
Constructor stringConstructor = classObject.getConstructor(new Class[]{String.class});
} catch (NoSuchMethodException e) {
// no string constructor; bail out..
return null;
}
return stringConstructor.newInstance(strParam);
}
}
Невозможно преобразовать без передачи какого-либо метода для преобразования String в нужный тип объекта (если вы не требуете, чтобы все классы имели конструктор с параметром String
как параметр, который вы хотите использовать для синтаксического анализа String
). Одна из возможностей - передать Function<String,?>
addOption
, например:
public void addOption(String optionName, Function<String, ?> valueConverter, boolean required)
parser.addOption("--stringParameter", Function.identity(), true); // remains identical
parser.addOption("--integerParameter", Integer::parseInt, true); // use Integer.parseInt(String)
parser.addOption("--binaryIntParameter", s->Integer.parseInt(s, 2), true); // integer given in binary format
parser.addOption("--fileParameter", s -> new File(s), true); // use File(String) constructor
Ваша функция синтаксического анализа кода, преобразующая String в объект, будет содержать следующее:
Map<String, Object> result = //...
// ...
String key = //...
String stringValue = //...
Function<String, ?> converter = //... get the converter from where you stored it
result.put(key, converter.apply(stringValue));
//...
return result;
Class.forName(fullyQualifiedClassName);