cbeust / jcommander

Command line parsing framework for Java
Apache License 2.0
1.95k stars 332 forks source link

Jcommander support Enum #526

Open jja725 opened 2 years ago

jja725 commented 2 years ago

I try to create an abstract class with some common parameters so I can inherit from this class and create several similar parameters. But I'm struggling with the Jcommander with Enum types. The following code giving me the error. I'm wondering if no converter is specified for an enum type, does EnumConverter gets called?

public abstract class GeneralParameters<T extends Enum<T>> extends Parameters {
    @Parameter(names = {"--operation"}, description =
        "the operation to perform", required = true)
    public T mOperation;
}
com.beust.jcommander.ParameterException: Could not invoke null
    Reason: Can not set java.lang.Enum field alluxio.stress.GeneralParameters.mOperation to java.lang.String
    at com.beust.jcommander.Parameterized.set(Parameterized.java:273)
    at com.beust.jcommander.WrappedParameter.addValue(WrappedParameter.java:86)
    at com.beust.jcommander.WrappedParameter.addValue(WrappedParameter.java:74)
    at com.beust.jcommander.ParameterDescription.addValue(ParameterDescription.java:277)
    at com.beust.jcommander.JCommander.processFixedArity(JCommander.java:913)
    at com.beust.jcommander.JCommander.processFixedArity(JCommander.java:894)
    at com.beust.jcommander.JCommander.parseValues(JCommander.java:724)
    at com.beust.jcommander.JCommander.parse(JCommander.java:356)
    at com.beust.jcommander.JCommander.parse(JCommander.java:335)

I search online and try to add converter but doesn't know how to do so. (Need an interface for Enum with common fromString method)

public abstract class GeneralParameters<T extends Operation> extends Parameters {
    @Parameter(names = {"--operation"}, description =
        "the operation to perform for the stress bench", converter = OperationConverter.class, required = true)
    public T mOperation;

    public static class OperationConverter<S extends Operation> implements IStringConverter<S> {
        private final Class<S> clazz;

        protected OperationConverter(Class<S> clazz) {
            this.clazz = clazz;
        }

        @Override
        public S convert(String value) {

            return S.fromString(value, Class<T>);
        }
    }

Does anyone have any idea how to make it work?

thnaeff commented 2 years ago

If I see this correctly, because of type erasure it does not actually know the type of the enum (or the fact that it is an enum) once the code is compiled. Meaning what it actually is after compilation is public Object mOperation;

What you could try in your OperationConverter

if (clazz.isEnum()) // probably good to check to ensure it is an enum and throw an exception if not. You should get some safety with your initial approach with `T extends Enum<T>` though.

for (S element : clazz.getEnumConstants()) {
  if (value.equals(element.name()) 
    ...
}

but I am not sure what you would get provided to this constructor protected OperationConverter(Class<S> clazz), I would think that JCommander requires an empty constructor to create an instance. Again, JCommander would not know what type the enum is supposed to be due to type erasure.