2

I'm converting an existing application to use picocli. One of the existing options looks like this:

-t, --threads           [1, n] for fixed thread pool, 'cpus' for number of cpus, 'cached' for cached

This allows the option to be a positive integer or one of a couple special strings. The existing code treats it as a string, and if it's not one of the special strings, passes it to Integer.parseInt.

I can do the same thing with picocli, of course, but I was wondering if there was a better way to handle this. E.g., something that allows multiple fields to be used for the same option, with the appropriate one being filled in based on what was passed? This might also allow me to use a enum for the possible string options.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
marinier
  • 387
  • 3
  • 13

1 Answers1

2

One idea is to create a class for this, like, maybe ThreadPoolSize, that encapsulates either a fixed numeric value or an enum for dynamic values. You would need to create a custom converter for this data type.

Then you can define the option as follows:

@Option(names = { "-t", "--threads" }, converter = ThreadPoolSizeConverter.class,
  description = "[1, n] for fixed thread pool, 'cpus' for number of cpus, 'cached' for cached")
ThreadPoolSize threadPoolSize;

The custom data type for thread pool size and the converter can look something like this:

class ThreadPoolSize {
    enum Dynamic { cpus, cached }

    int fixed = -1;  // if -1, then use the dynamic value
    Dynamic dynamic; // if null, then use the fixed value
}

class ThreadPoolSizeConverter implements CommandLine.ITypeConverter<ThreadPoolSize> {

    @Override
    public ThreadPoolSize convert(String value) throws Exception {
        ThreadPoolSize result = new ThreadPoolSize();
        try {
            result.fixed = Integer.parseInt(value);
            if (result.fixed < 1) {
                throw new CommandLine.TypeConversionException("Invalid value " +
                        value + ": must be 1 or more.");
            }
        } catch (NumberFormatException nan) {
            try {
                result.dynamic = ThreadPoolSize.Dynamic.valueOf(
                        value.toLowerCase(Locale.ENGLISH));
            } catch (IllegalArgumentException ex) {
                throw new CommandLine.TypeConversionException("Invalid value " +
                        value + ": must be one of " + 
                        Arrays.toString(ThreadPoolSize.Dynamic.values()));
            }
        }
        return result;
    }
}

Remko Popma
  • 35,130
  • 11
  • 92
  • 114
  • Thank you -- this is better than just using a String! I wonder if there might be room in picocli for a generic composite type -- e.g., you make a class that has a field for each possible type, and the class gets something like `@Option(composite = true)`. It then automatically tries to convert the arg to each field type, in the order they are defined, stopping when one succeeds. The internal fields can also be annotated if they require custom conversions, etc. Maybe this case is too rare to be worth directly supporting, though. – marinier Dec 23 '20 at 21:13
  • My intuition is that doing this in a custom type converter, like we did here, is cleaner than trying to push this into the library. – Remko Popma Dec 24 '20 at 12:34