Warning: the post is a bit lengthy, but it contains an error
descrioption (in JC implementation) and a fix for it, and also
outlines some limitations of the current "varArity" implementation.
This is what I've come up with so far. I have one parameter of type
List which can be specified in chunks of length 1 to 2. If less than 2
values are specified in a chunk, the chunk should be filled up to the
length 2. See my findings below (I've found an error and a problem in
the arity implementation).
---- ArityMinMax.java ----
package priv.jc.arity;
import static java.lang.annotation.ElementType.FIELD;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ FIELD })
public @interface ArityMinMax {
int min() default -1;
int max() default -1;
boolean fillToMaxArity() default false;
String fillingValue() default "X";
}
---- Main.java ----
package priv.jc.arity;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import com.beust.jcommander.IVariableArity;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
public class Main implements IVariableArity {
private static final String FIELD_NAME = "param";
@Parameter(names = "-param", variableArity = true)
@ArityMinMax(min = 1, max = 2, fillToMaxArity = true)
public List<String> param = new ArrayList<String>();
public static void main(String[] args) {
Main t = new Main();
JCommander jc = new JCommander(t);
jc.parse(new String[]{"-param", "AAA", "-param", "BBB", "-param",
"CCC", "DDD"});
System.out.println("Param: " + t.param);
}
@Override
public int processVariableArity(String optionName, String[] options)
{
System.out.println("Variable arity parser called");
ArityMinMax amm = this.getArityDescription(FIELD_NAME);
int min = amm.min();
int max = amm.max();
int i = 0;
while (i < options.length && !options[i].startsWith("-")) {
System.out.println("Got value: '" + options[i] + "'");
i++;
}
if (max > 0 && i > max) {
throw new ParameterException("Max " + max
+ " values are allowed (offending value: "
+ options[i - 1] + ")");
}
if (min > 0 && i < min) {
throw new ParameterException("At least " + min + " values must be
specified");
}
if (max >= 0 && amm.fillToMaxArity()) {
List<String> targetList = getTargetList(FIELD_NAME);
int nToAdd = max - i;
System.out.println("Values to add: " + nToAdd);
for (int k = 0; k < nToAdd; k++) {
targetList.add(amm.fillingValue());
}
}
return i;
}
private List<String> getTargetList(String fieldName) {
try {
return (List<String>)
this.getClass().getDeclaredField(fieldName).get(this);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
private ArityMinMax getArityDescription(String fieldName) {
Field f;
try {
f = this.getClass().getDeclaredField(fieldName);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
return (ArityMinMax) f.getAnnotation(ArityMinMax.class);
}
}
-------------------------------
---- Program output ----
Variable arity parser called
Got value: 'AAA'
Values to add: 1
Variable arity parser called
Got value: 'BBB'
Values to add: 1
Variable arity parser called
Got value: 'CCC'
Got value: 'DDD'
Values to add: 0
Param: [AAA, X, BBB, CCC, DDD]
--------
The result of the call is: Param: [AAA, X, BBB], i.e. not the value
I'd expected.
First, there is an error in the implementation in your master branch.
It stems from the fact that "processVariableArity" and
"processFixedArity" have different semantics. The former returns the
number of options processed, and the latter returns the resulting
index. Since varArity calls fixedArity, the semantics of varArity is
"spoiled". If a parameter with var arity is specified multiple times
(like in my case), the index is no more correct.
To fix this, I changed the semantics of "processFixedArity" to be the
same as in "processVariableArity". In code:
JCommander, private int processFixedArity(String[] args ... (line
749):
add the line "int originalIndex = index;" as the first line in the
method, and replace the last line ("return index") with "return index
- originalIndex".
In the main loop, replace "i = processFixedArity(args, i, pd,
fieldType)" with "i += processFixedArity(args, i, pd,
fieldType);" (change "=" to "+=").
That was the error. Now the problem which still is resent after the
fix.
The problem is that in processVariableArity I'm only supposed to find
out the real number of values to eat. If I assign values (which I want
to do to fill up the chunk), the values
a) Get assigned not at the end but in the beginning of the chunk
(because the "real" value assignment is done in JCommander *after*
processVariableArity)
b) If it's the first chunk, the assigned values are lost because the
collection is created anew (see ParameterDescription, line "if (l ==
null || fieldIsSetForTheFirstTime(isDefault))" and then "l =
newCollection(type)").
A remedy could be to delegate more responsibility to
"processVariableArity", i.e. not only to find out the number of the
options but also to really assign the values. The name already
suggests this.
But then you'd lose e.g. the type conversion infrastructure etc.
I don't know how to solve this best. The best solution would of course
be to just add the proposed attributes to the core JCommander
annotation! :-)