• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2010 the original author or authors.
3  * See the notice.md file distributed with this work for additional
4  * information regarding copyright ownership.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package com.beust.jcommander;
20 
21 import com.beust.jcommander.FuzzyMap.IKey;
22 import com.beust.jcommander.converters.*;
23 import com.beust.jcommander.internal.*;
24 
25 import java.io.BufferedReader;
26 import java.io.IOException;
27 import java.lang.reflect.*;
28 import java.nio.charset.Charset;
29 import java.nio.file.Files;
30 import java.nio.file.Paths;
31 import java.util.*;
32 import java.util.ResourceBundle;
33 import java.util.concurrent.CopyOnWriteArrayList;
34 
35 /**
36  * The main class for JCommander. It's responsible for parsing the object that contains
37  * all the annotated fields, parse the command line and assign the fields with the correct
38  * values and a few other helper methods, such as usage().
39  *
40  * The object(s) you pass in the constructor are expected to have one or more
41  * \@Parameter annotations on them. You can pass either a single object, an array of objects
42  * or an instance of Iterable. In the case of an array or Iterable, JCommander will collect
43  * the \@Parameter annotations from all the objects passed in parameter.
44  *
45  * @author Cedric Beust <cedric@beust.com>
46  */
47 public class JCommander {
48     public static final String DEBUG_PROPERTY = "jcommander.debug";
49 
50     /**
51      * A map to look up parameter description per option name.
52      */
53     private Map<IKey, ParameterDescription> descriptions;
54 
55     /**
56      * The objects that contain fields annotated with @Parameter.
57      */
58     private List<Object> objects = Lists.newArrayList();
59 
60     private boolean firstTimeMainParameter = true;
61 
62     /**
63      * This field/method will contain whatever command line parameter is not an option.
64      * It is expected to be a List<String>.
65      */
66     private Parameterized mainParameter = null;
67 
68     /**
69      * The object on which we found the main parameter field.
70      */
71     private Object mainParameterObject;
72 
73     /**
74      * The annotation found on the main parameter field.
75      */
76     private Parameter mainParameterAnnotation;
77 
78     private ParameterDescription mainParameterDescription;
79 
80     /**
81      * A set of all the parameterizeds that are required. During the reflection phase,
82      * this field receives all the fields that are annotated with required=true
83      * and during the parsing phase, all the fields that are assigned a value
84      * are removed from it. At the end of the parsing phase, if it's not empty,
85      * then some required fields did not receive a value and an exception is
86      * thrown.
87      */
88     private Map<Parameterized, ParameterDescription> requiredFields = Maps.newHashMap();
89 
90     /**
91      * A map of all the parameterized fields/methods.
92      */
93     private Map<Parameterized, ParameterDescription> fields = Maps.newHashMap();
94 
95     /**
96      * List of commands and their instance.
97      */
98     private Map<ProgramName, JCommander> commands = Maps.newLinkedHashMap();
99 
100     /**
101      * Alias database for reverse lookup
102      */
103     private Map<IKey, ProgramName> aliasMap = Maps.newLinkedHashMap();
104 
105     /**
106      * The name of the command after the parsing has run.
107      */
108     private String parsedCommand;
109 
110     /**
111      * The name of command or alias as it was passed to the
112      * command line
113      */
114     private String parsedAlias;
115 
116     private ProgramName programName;
117 
118     private boolean helpWasSpecified;
119 
120     private List<String> unknownArgs = Lists.newArrayList();
121 
122     private static Console console;
123 
124     private final Options options;
125 
126     /**
127      * Options shared with sub commands
128      */
129     private static class Options {
130 
131         private ResourceBundle bundle;
132 
133         /**
134          * A default provider returns default values for the parameters.
135          */
136         private IDefaultProvider defaultProvider;
137 
138         private Comparator<? super ParameterDescription> parameterDescriptionComparator
139                 = new Comparator<ParameterDescription>() {
140             @Override
141             public int compare(ParameterDescription p0, ParameterDescription p1) {
142                 Parameter a0 = p0.getParameterAnnotation();
143                 Parameter a1 = p1.getParameterAnnotation();
144                 if (a0 != null && a0.order() != -1 && a1 != null && a1.order() != -1) {
145                     return Integer.compare(a0.order(), a1.order());
146                 } else if (a0 != null && a0.order() != -1) {
147                     return -1;
148                 } else if (a1 != null && a1.order() != -1) {
149                     return 1;
150                 } else {
151                     return p0.getLongestName().compareTo(p1.getLongestName());
152                 }
153             }
154         };
155         private int columnSize = 79;
156         private boolean acceptUnknownOptions = false;
157         private boolean allowParameterOverwriting = false;
158         private boolean expandAtSign = true;
159         private int verbose = 0;
160         private boolean caseSensitiveOptions = true;
161         private boolean allowAbbreviatedOptions = false;
162         /**
163          * The factories used to look up string converters.
164          */
165         private final List<IStringConverterInstanceFactory> converterInstanceFactories = new CopyOnWriteArrayList<>();
166         private Charset atFileCharset = Charset.defaultCharset();
167     }
168 
JCommander(Options options)169     private JCommander(Options options) {
170         if (options == null) {
171             throw new NullPointerException("options");
172         }
173         this.options = options;
174         addConverterFactory(new DefaultConverterFactory());
175     }
176 
177     /**
178      * Creates a new un-configured JCommander object.
179      */
JCommander()180     public JCommander() {
181         this(new Options());
182     }
183 
184     /**
185      * @param object The arg object expected to contain {@link Parameter} annotations.
186      */
JCommander(Object object)187     public JCommander(Object object) {
188         this(object, (ResourceBundle) null);
189     }
190 
191     /**
192      * @param object The arg object expected to contain {@link Parameter} annotations.
193      * @param bundle The bundle to use for the descriptions. Can be null.
194      */
JCommander(Object object, @Nullable ResourceBundle bundle)195     public JCommander(Object object, @Nullable ResourceBundle bundle) {
196         this(object, bundle, (String[]) null);
197     }
198 
199     /**
200      * @param object The arg object expected to contain {@link Parameter} annotations.
201      * @param bundle The bundle to use for the descriptions. Can be null.
202      * @param args The arguments to parse (optional).
203      */
JCommander(Object object, @Nullable ResourceBundle bundle, String... args)204     public JCommander(Object object, @Nullable  ResourceBundle bundle, String... args) {
205         this();
206         addObject(object);
207         if (bundle != null) {
208             setDescriptionsBundle(bundle);
209         }
210         createDescriptions();
211         if (args != null) {
212             parse(args);
213         }
214     }
215 
216     /**
217      * @param object The arg object expected to contain {@link Parameter} annotations.
218      * @param args The arguments to parse (optional).
219      *
220      * @deprecated Construct a JCommander instance first and then call parse() on it.
221      */
222     @Deprecated()
JCommander(Object object, String... args)223     public JCommander(Object object, String... args) {
224         this(object);
225         parse(args);
226     }
227 
228     /**
229      * Disables expanding {@code @file}.
230      *
231      * JCommander supports the {@code @file} syntax, which allows you to put all your options
232      * into a file and pass this file as parameter @param expandAtSign whether to expand {@code @file}.
233      */
setExpandAtSign(boolean expandAtSign)234     public void setExpandAtSign(boolean expandAtSign) {
235         options.expandAtSign = expandAtSign;
236     }
237 
getConsole()238     public static Console getConsole() {
239         if (console == null) {
240             try {
241                 Method consoleMethod = System.class.getDeclaredMethod("console");
242                 Object console = consoleMethod.invoke(null);
243                 JCommander.console = new JDK6Console(console);
244             } catch (Throwable t) {
245                 console = new DefaultConsole();
246             }
247         }
248         return console;
249     }
250 
251     /**
252      * Adds the provided arg object to the set of objects that this commander
253      * will parse arguments into.
254      *
255      * @param object The arg object expected to contain {@link Parameter}
256      * annotations. If <code>object</code> is an array or is {@link Iterable},
257      * the child objects will be added instead.
258      */
259     // declared final since this is invoked from constructors
addObject(Object object)260     public final void addObject(Object object) {
261         if (object instanceof Iterable) {
262             // Iterable
263             for (Object o : (Iterable<?>) object) {
264                 objects.add(o);
265             }
266         } else if (object.getClass().isArray()) {
267             // Array
268             for (Object o : (Object[]) object) {
269                 objects.add(o);
270             }
271         } else {
272             // Single object
273             objects.add(object);
274         }
275     }
276 
277     /**
278      * Sets the {@link ResourceBundle} to use for looking up descriptions.
279      * Set this to <code>null</code> to use description text directly.
280      */
281     // declared final since this is invoked from constructors
setDescriptionsBundle(ResourceBundle bundle)282     public final void setDescriptionsBundle(ResourceBundle bundle) {
283         options.bundle = bundle;
284     }
285 
286     /**
287      * Parse and validate the command line parameters.
288      */
parse(String... args)289     public void parse(String... args) {
290         try {
291             parse(true /* validate */, args);
292         } catch(ParameterException ex) {
293             ex.setJCommander(this);
294             throw ex;
295         }
296     }
297 
298     /**
299      * Parse the command line parameters without validating them.
300      */
parseWithoutValidation(String... args)301     public void parseWithoutValidation(String... args) {
302         parse(false /* no validation */, args);
303     }
304 
parse(boolean validate, String... args)305     private void parse(boolean validate, String... args) {
306         StringBuilder sb = new StringBuilder("Parsing \"");
307         sb.append(join(args).append("\"\n  with:").append(join(objects.toArray())));
308         p(sb.toString());
309 
310         if (descriptions == null) createDescriptions();
311         initializeDefaultValues();
312         parseValues(expandArgs(args), validate);
313         if (validate) validateOptions();
314     }
315 
join(Object[] args)316     private StringBuilder join(Object[] args) {
317         StringBuilder result = new StringBuilder();
318         for (int i = 0; i < args.length; i++) {
319             if (i > 0) result.append(" ");
320             result.append(args[i]);
321         }
322         return result;
323     }
324 
initializeDefaultValues()325     private void initializeDefaultValues() {
326         if (options.defaultProvider != null) {
327             for (ParameterDescription pd : descriptions.values()) {
328                 initializeDefaultValue(pd);
329             }
330 
331             for (Map.Entry<ProgramName, JCommander> entry : commands.entrySet()) {
332                 entry.getValue().initializeDefaultValues();
333             }
334         }
335     }
336 
337     /**
338      * Make sure that all the required parameters have received a value.
339      */
validateOptions()340     private void validateOptions() {
341         // No validation if we found a help parameter
342         if (helpWasSpecified) {
343             return;
344         }
345 
346         if (!requiredFields.isEmpty()) {
347             List<String> missingFields = new ArrayList<>();
348             for (ParameterDescription pd : requiredFields.values()) {
349                 missingFields.add("[" + String.join(" | ", pd.getParameter().names()) + "]");
350             }
351             String message = String.join(", ", missingFields);
352             throw new ParameterException("The following "
353                     + pluralize(requiredFields.size(), "option is required: ", "options are required: ")
354                     + message);
355         }
356 
357         if (mainParameterDescription != null) {
358             if (mainParameterDescription.getParameter().required() &&
359                     !mainParameterDescription.isAssigned()) {
360                 throw new ParameterException("Main parameters are required (\""
361                         + mainParameterDescription.getDescription() + "\")");
362             }
363         }
364     }
365 
pluralize(int quantity, String singular, String plural)366     private static String pluralize(int quantity, String singular, String plural) {
367         return quantity == 1 ? singular : plural;
368     }
369 
370     /**
371      * Expand the command line parameters to take @ parameters into account.
372      * When @ is encountered, the content of the file that follows is inserted
373      * in the command line.
374      *
375      * @param originalArgv the original command line parameters
376      * @return the new and enriched command line parameters
377      */
expandArgs(String[] originalArgv)378     private String[] expandArgs(String[] originalArgv) {
379         List<String> vResult1 = Lists.newArrayList();
380 
381         //
382         // Expand @
383         //
384         for (String arg : originalArgv) {
385 
386             if (arg.startsWith("@") && options.expandAtSign) {
387                 String fileName = arg.substring(1);
388                 vResult1.addAll(readFile(fileName));
389             } else {
390                 List<String> expanded = expandDynamicArg(arg);
391                 vResult1.addAll(expanded);
392             }
393         }
394 
395         // Expand separators
396         //
397         List<String> vResult2 = Lists.newArrayList();
398         for (String arg : vResult1) {
399             if (isOption(arg)) {
400                 String sep = getSeparatorFor(arg);
401                 if (!" ".equals(sep)) {
402                     String[] sp = arg.split("[" + sep + "]", 2);
403                     for (String ssp : sp) {
404                         vResult2.add(ssp);
405                     }
406                 } else {
407                     vResult2.add(arg);
408                 }
409             } else {
410                 vResult2.add(arg);
411             }
412         }
413 
414         return vResult2.toArray(new String[vResult2.size()]);
415     }
416 
expandDynamicArg(String arg)417     private List<String> expandDynamicArg(String arg) {
418         for (ParameterDescription pd : descriptions.values()) {
419             if (pd.isDynamicParameter()) {
420                 for (String name : pd.getParameter().names()) {
421                     if (arg.startsWith(name) && !arg.equals(name)) {
422                         return Arrays.asList(name, arg.substring(name.length()));
423                     }
424                 }
425             }
426         }
427 
428         return Arrays.asList(arg);
429     }
430 
matchArg(String arg, IKey key)431     private boolean matchArg(String arg, IKey key) {
432         String kn = options.caseSensitiveOptions
433                 ? key.getName()
434                 : key.getName().toLowerCase();
435         if (options.allowAbbreviatedOptions) {
436             if (kn.startsWith(arg)) return true;
437         } else {
438             ParameterDescription pd = descriptions.get(key);
439             if (pd != null) {
440                 // It's an option. If the option has a separator (e.g. -author==foo) then
441                 // we only do a beginsWith match
442                 String separator = getSeparatorFor(arg);
443                 if (! " ".equals(separator)) {
444                     if (arg.startsWith(kn)) return true;
445                 } else {
446                     if (kn.equals(arg)) return true;
447                 }
448             } else {
449                 // It's a command do a strict equality check
450                 if (kn.equals(arg)) return true;
451             }
452         }
453         return false;
454     }
455 
isOption(String passedArg)456     private boolean isOption(String passedArg) {
457         if (options.acceptUnknownOptions) return true;
458 
459         String arg = options.caseSensitiveOptions ? passedArg : passedArg.toLowerCase();
460 
461         for (IKey key : descriptions.keySet()) {
462             if (matchArg(arg, key)) return true;
463         }
464         for (IKey key : commands.keySet()) {
465             if (matchArg(arg, key)) return true;
466         }
467 
468         return false;
469     }
470 
getPrefixDescriptionFor(String arg)471     private ParameterDescription getPrefixDescriptionFor(String arg) {
472         for (Map.Entry<IKey, ParameterDescription> es : descriptions.entrySet()) {
473             if (arg.startsWith(es.getKey().getName())) return es.getValue();
474         }
475 
476         return null;
477     }
478 
479     /**
480      * If arg is an option, we can look it up directly, but if it's a value,
481      * we need to find the description for the option that precedes it.
482      */
getDescriptionFor(String arg)483     private ParameterDescription getDescriptionFor(String arg) {
484         return getPrefixDescriptionFor(arg);
485     }
486 
getSeparatorFor(String arg)487     private String getSeparatorFor(String arg) {
488         ParameterDescription pd = getDescriptionFor(arg);
489 
490         // Could be null if only main parameters were passed
491         if (pd != null) {
492             Parameters p = pd.getObject().getClass().getAnnotation(Parameters.class);
493             if (p != null) return p.separators();
494         }
495 
496         return " ";
497     }
498 
499     /**
500      * Reads the file specified by filename and returns the file content as a string.
501      * End of lines are replaced by a space.
502      *
503      * @param fileName the command line filename
504      * @return the file content as a string.
505      */
readFile(String fileName)506     private List<String> readFile(String fileName) {
507         List<String> result = Lists.newArrayList();
508 
509         try (BufferedReader bufRead = Files.newBufferedReader(Paths.get(fileName), options.atFileCharset)) {
510             String line;
511             // Read through file one line at time. Print line # and line
512             while ((line = bufRead.readLine()) != null) {
513                 // Allow empty lines and # comments in these at files
514                 if (line.length() > 0 && !line.trim().startsWith("#")) {
515                     result.add(line);
516                 }
517             }
518         } catch (IOException e) {
519             throw new ParameterException("Could not read file " + fileName + ": " + e);
520         }
521 
522         return result;
523     }
524 
525     /**
526      * Remove spaces at both ends and handle double quotes.
527      */
trim(String string)528     private static String trim(String string) {
529         String result = string.trim();
530         if (result.startsWith("\"") && result.endsWith("\"") && result.length() > 1) {
531             result = result.substring(1, result.length() - 1);
532         }
533         return result;
534     }
535 
536     /**
537      * Create the ParameterDescriptions for all the \@Parameter found.
538      */
createDescriptions()539     private void createDescriptions() {
540         descriptions = Maps.newHashMap();
541 
542         for (Object object : objects) {
543             addDescription(object);
544         }
545     }
546 
addDescription(Object object)547     private void addDescription(Object object) {
548         Class<?> cls = object.getClass();
549 
550         List<Parameterized> parameterizeds = Parameterized.parseArg(object);
551         for (Parameterized parameterized : parameterizeds) {
552             WrappedParameter wp = parameterized.getWrappedParameter();
553             if (wp != null && wp.getParameter() != null) {
554                 Parameter annotation = wp.getParameter();
555                 //
556                 // @Parameter
557                 //
558                 Parameter p = annotation;
559                 if (p.names().length == 0) {
560                     p("Found main parameter:" + parameterized);
561                     if (mainParameter != null) {
562                         throw new ParameterException("Only one @Parameter with no names attribute is"
563                                 + " allowed, found:" + mainParameter + " and " + parameterized);
564                     }
565                     mainParameter = parameterized;
566                     mainParameterObject = object;
567                     mainParameterAnnotation = p;
568                     mainParameterDescription =
569                             new ParameterDescription(object, p, parameterized, options.bundle, this);
570                 } else {
571                     ParameterDescription pd =
572                             new ParameterDescription(object, p, parameterized, options.bundle, this);
573                     for (String name : p.names()) {
574                         if (descriptions.containsKey(new StringKey(name))) {
575                             throw new ParameterException("Found the option " + name + " multiple times");
576                         }
577                         p("Adding description for " + name);
578                         fields.put(parameterized, pd);
579                         descriptions.put(new StringKey(name), pd);
580 
581                         if (p.required()) requiredFields.put(parameterized, pd);
582                     }
583                 }
584             } else if (parameterized.getDelegateAnnotation() != null) {
585                 //
586                 // @ParametersDelegate
587                 //
588                 Object delegateObject = parameterized.get(object);
589                 if (delegateObject == null) {
590                     throw new ParameterException("Delegate field '" + parameterized.getName()
591                             + "' cannot be null.");
592                 }
593                 addDescription(delegateObject);
594             } else if (wp != null && wp.getDynamicParameter() != null) {
595                 //
596                 // @DynamicParameter
597                 //
598                 DynamicParameter dp = wp.getDynamicParameter();
599                 for (String name : dp.names()) {
600                     if (descriptions.containsKey(name)) {
601                         throw new ParameterException("Found the option " + name + " multiple times");
602                     }
603                     p("Adding description for " + name);
604                     ParameterDescription pd =
605                             new ParameterDescription(object, dp, parameterized, options.bundle, this);
606                     fields.put(parameterized, pd);
607                     descriptions.put(new StringKey(name), pd);
608 
609                     if (dp.required()) requiredFields.put(parameterized, pd);
610                 }
611             }
612         }
613     }
614 
initializeDefaultValue(ParameterDescription pd)615     private void initializeDefaultValue(ParameterDescription pd) {
616         for (String optionName : pd.getParameter().names()) {
617             String def = options.defaultProvider.getDefaultValueFor(optionName);
618             if (def != null) {
619                 p("Initializing " + optionName + " with default value:" + def);
620                 pd.addValue(def, true /* default */);
621                 // remove the parameter from the list of fields to be required
622                 requiredFields.remove(pd.getParameterized());
623                 return;
624             }
625         }
626     }
627 
628     /**
629      * Main method that parses the values and initializes the fields accordingly.
630      */
parseValues(String[] args, boolean validate)631     private void parseValues(String[] args, boolean validate) {
632         // This boolean becomes true if we encounter a command, which indicates we need
633         // to stop parsing (the parsing of the command will be done in a sub JCommander
634         // object)
635         boolean commandParsed = false;
636         int i = 0;
637         boolean isDashDash = false; // once we encounter --, everything goes into the main parameter
638         while (i < args.length && !commandParsed) {
639             String arg = args[i];
640             String a = trim(arg);
641             args[i] = a;
642             p("Parsing arg: " + a);
643 
644             JCommander jc = findCommandByAlias(arg);
645             int increment = 1;
646             if (!isDashDash && !"--".equals(a) && isOption(a) && jc == null) {
647                 //
648                 // Option
649                 //
650                 ParameterDescription pd = findParameterDescription(a);
651 
652                 if (pd != null) {
653                     if (pd.getParameter().password()) {
654                         increment = processPassword(args, i, pd, validate);
655                     } else {
656                         if (pd.getParameter().variableArity()) {
657                             //
658                             // Variable arity?
659                             //
660                             increment = processVariableArity(args, i, pd, validate);
661                         } else {
662                             //
663                             // Regular option
664                             //
665                             Class<?> fieldType = pd.getParameterized().getType();
666 
667                             // Boolean, set to true as soon as we see it, unless it specified
668                             // an arity of 1, in which case we need to read the next value
669                             if ((fieldType == boolean.class || fieldType == Boolean.class)
670                                     && pd.getParameter().arity() == -1) {
671                                 // Flip the value this boolean was initialized with
672                                 Boolean value = (Boolean) pd.getParameterized().get(pd.getObject());
673                                 pd.addValue(value ? "false" : "true");
674                                 requiredFields.remove(pd.getParameterized());
675                             } else {
676                                 increment = processFixedArity(args, i, pd, validate, fieldType);
677                             }
678                             // If it's a help option, remember for later
679                             if (pd.isHelp()) {
680                                 helpWasSpecified = true;
681                             }
682                         }
683                     }
684                 } else {
685                     if (options.acceptUnknownOptions) {
686                         unknownArgs.add(arg);
687                         i++;
688                         while (i < args.length && !isOption(args[i])) {
689                             unknownArgs.add(args[i++]);
690                         }
691                         increment = 0;
692                     } else {
693                         throw new ParameterException("Unknown option: " + arg);
694                     }
695                 }
696             } else {
697                 //
698                 // Main parameter
699                 //
700                 if ("--".equals(arg) && !isDashDash) {
701                     isDashDash = true;
702                 }
703                 else if (commands.isEmpty()) {
704                     //
705                     // Regular (non-command) parsing
706                     //
707                     List mp = getMainParameter(arg);
708                     String value = a; // If there's a non-quoted version, prefer that one
709                     Object convertedValue = value;
710 
711                     if (mainParameter.getGenericType() instanceof ParameterizedType) {
712                         ParameterizedType p = (ParameterizedType) mainParameter.getGenericType();
713                         Type cls = p.getActualTypeArguments()[0];
714                         if (cls instanceof Class) {
715                             convertedValue = convertValue(mainParameter, (Class) cls, null, value);
716                         }
717                     }
718 
719                     for(final Class<? extends IParameterValidator> validator : mainParameterAnnotation.validateWith() ) {
720                         ParameterDescription.validateParameter(mainParameterDescription,
721                         	validator,
722                             "Default", value);
723                     }
724 
725                     mainParameterDescription.setAssigned(true);
726                     mp.add(convertedValue);
727                 } else {
728                     //
729                     // Command parsing
730                     //
731                     if (jc == null && validate) {
732                         throw new MissingCommandException("Expected a command, got " + arg, arg);
733                     } else if (jc != null) {
734                         parsedCommand = jc.programName.name;
735                         parsedAlias = arg; //preserve the original form
736 
737                         // Found a valid command, ask it to parse the remainder of the arguments.
738                         // Setting the boolean commandParsed to true will force the current
739                         // loop to end.
740                         jc.parse(validate, subArray(args, i + 1));
741                         commandParsed = true;
742                     }
743                 }
744             }
745             i += increment;
746         }
747 
748         // Mark the parameter descriptions held in fields as assigned
749         for (ParameterDescription parameterDescription : descriptions.values()) {
750             if (parameterDescription.isAssigned()) {
751                 fields.get(parameterDescription.getParameterized()).setAssigned(true);
752             }
753         }
754 
755     }
756 
757     private class DefaultVariableArity implements IVariableArity {
758 
759         @Override
processVariableArity(String optionName, String[] options)760         public int processVariableArity(String optionName, String[] options) {
761             int i = 0;
762             while (i < options.length && !isOption(options[i])) {
763                 i++;
764             }
765             return i;
766         }
767     }
768 
769     private final IVariableArity DEFAULT_VARIABLE_ARITY = new DefaultVariableArity();
770 
determineArity(String[] args, int index, ParameterDescription pd, IVariableArity va)771     private final int determineArity(String[] args, int index, ParameterDescription pd, IVariableArity va) {
772         List<String> currentArgs = Lists.newArrayList();
773         for (int j = index + 1; j < args.length; j++) {
774             currentArgs.add(args[j]);
775         }
776         return va.processVariableArity(pd.getParameter().names()[0],
777                 currentArgs.toArray(new String[0]));
778     }
779 
780     /**
781      * @return the number of options that were processed.
782      */
processPassword(String[] args, int index, ParameterDescription pd, boolean validate)783     private int processPassword(String[] args, int index, ParameterDescription pd, boolean validate) {
784         final int passwordArity = determineArity(args, index, pd, DEFAULT_VARIABLE_ARITY);
785         if (passwordArity == 0) {
786             // password option with password not specified, use the Console to retrieve the password
787             char[] password = readPassword(pd.getDescription(), pd.getParameter().echoInput());
788             pd.addValue(new String(password));
789             requiredFields.remove(pd.getParameterized());
790             return 1;
791         } else if (passwordArity == 1) {
792             // password option with password specified
793             return processFixedArity(args, index, pd, validate, List.class, 1);
794         } else {
795             throw new ParameterException("Password parameter must have at most 1 argument.");
796         }
797     }
798 
799     /**
800      * @return the number of options that were processed.
801      */
processVariableArity(String[] args, int index, ParameterDescription pd, boolean validate)802     private int processVariableArity(String[] args, int index, ParameterDescription pd, boolean validate) {
803         Object arg = pd.getObject();
804         IVariableArity va;
805         if (!(arg instanceof IVariableArity)) {
806             va = DEFAULT_VARIABLE_ARITY;
807         } else {
808             va = (IVariableArity) arg;
809         }
810 
811         int arity = determineArity(args, index, pd, va);
812         int result = processFixedArity(args, index, pd, validate, List.class, arity);
813         return result;
814     }
815 
processFixedArity(String[] args, int index, ParameterDescription pd, boolean validate, Class<?> fieldType)816     private int processFixedArity(String[] args, int index, ParameterDescription pd, boolean validate,
817                                   Class<?> fieldType) {
818         // Regular parameter, use the arity to tell use how many values
819         // we need to consume
820         int arity = pd.getParameter().arity();
821         int n = (arity != -1 ? arity : 1);
822 
823         return processFixedArity(args, index, pd, validate, fieldType, n);
824     }
825 
processFixedArity(String[] args, int originalIndex, ParameterDescription pd, boolean validate, Class<?> fieldType, int arity)826     private int processFixedArity(String[] args, int originalIndex, ParameterDescription pd, boolean validate,
827                                   Class<?> fieldType, int arity) {
828         int index = originalIndex;
829         String arg = args[index];
830         // Special case for boolean parameters of arity 0
831         if (arity == 0 &&
832                 (Boolean.class.isAssignableFrom(fieldType)
833                         || boolean.class.isAssignableFrom(fieldType))) {
834             // Flip the value this boolean was initialized with
835             Boolean value = (Boolean) pd.getParameterized().get(pd.getObject());
836             pd.addValue(value ? "false" : "true");
837             requiredFields.remove(pd.getParameterized());
838         } else if (arity == 0) {
839             throw new ParameterException("Expected a value after parameter " + arg);
840 
841         } else if (index < args.length - 1) {
842             int offset = "--".equals(args[index + 1]) ? 1 : 0;
843 
844             Object finalValue = null;
845             if (index + arity < args.length) {
846                 for (int j = 1; j <= arity; j++) {
847                     String value = trim(args[index + j + offset]);
848                     finalValue = pd.addValue(arg, value, false, validate, j - 1);
849                     requiredFields.remove(pd.getParameterized());
850                 }
851 
852                 if (finalValue != null && validate) {
853                   pd.validateValueParameter(arg, finalValue);
854                 }
855                 index += arity + offset;
856             } else {
857                 throw new ParameterException("Expected " + arity + " values after " + arg);
858             }
859         } else {
860             throw new ParameterException("Expected a value after parameter " + arg);
861         }
862 
863         return arity + 1;
864     }
865 
866     /**
867      * Invoke Console.readPassword through reflection to avoid depending
868      * on Java 6.
869      */
readPassword(String description, boolean echoInput)870     private char[] readPassword(String description, boolean echoInput) {
871         getConsole().print(description + ": ");
872         return getConsole().readPassword(echoInput);
873     }
874 
subArray(String[] args, int index)875     private String[] subArray(String[] args, int index) {
876         int l = args.length - index;
877         String[] result = new String[l];
878         System.arraycopy(args, index, result, 0, l);
879 
880         return result;
881     }
882 
883     /**
884      * @return the field that's meant to receive all the parameters that are not options.
885      *
886      * @param arg the arg that we're about to add (only passed here to output a meaningful
887      * error message).
888      */
getMainParameter(String arg)889     private List<?> getMainParameter(String arg) {
890         if (mainParameter == null) {
891             throw new ParameterException(
892                     "Was passed main parameter '" + arg + "' but no main parameter was defined in your arg class");
893         }
894 
895         List<?> result = (List<?>) mainParameter.get(mainParameterObject);
896         if (result == null) {
897             result = Lists.newArrayList();
898             if (!List.class.isAssignableFrom(mainParameter.getType())) {
899                 throw new ParameterException("Main parameter field " + mainParameter
900                         + " needs to be of type List, not " + mainParameter.getType());
901             }
902             mainParameter.set(mainParameterObject, result);
903         }
904         if (firstTimeMainParameter) {
905             result.clear();
906             firstTimeMainParameter = false;
907         }
908         return result;
909     }
910 
getMainParameterDescription()911     public String getMainParameterDescription() {
912         if (descriptions == null) createDescriptions();
913         return mainParameterAnnotation != null ? mainParameterAnnotation.description()
914                 : null;
915     }
916 
917     /**
918      * Set the program name (used only in the usage).
919      */
setProgramName(String name)920     public void setProgramName(String name) {
921         setProgramName(name, new String[0]);
922     }
923 
924     /**
925      * Get the program name (used only in the usage).
926      */
getProgramName()927     public String getProgramName(){
928         return programName == null ? null : programName.getName();
929     }
930 
931     /**
932      * Set the program name
933      *
934      * @param name    program name
935      * @param aliases aliases to the program name
936      */
setProgramName(String name, String... aliases)937     public void setProgramName(String name, String... aliases) {
938         programName = new ProgramName(name, Arrays.asList(aliases));
939     }
940 
941     /**
942      * Display the usage for this command.
943      */
usage(String commandName)944     public void usage(String commandName) {
945         StringBuilder sb = new StringBuilder();
946         usage(commandName, sb);
947         getConsole().println(sb.toString());
948     }
949 
950     /**
951      * Store the help for the command in the passed string builder.
952      */
usage(String commandName, StringBuilder out)953     public void usage(String commandName, StringBuilder out) {
954         usage(commandName, out, "");
955     }
956 
957     /**
958      * Store the help for the command in the passed string builder, indenting
959      * every line with "indent".
960      */
usage(String commandName, StringBuilder out, String indent)961     public void usage(String commandName, StringBuilder out, String indent) {
962         String description = getCommandDescription(commandName);
963         JCommander jc = findCommandByAlias(commandName);
964         if (description != null) {
965             out.append(indent).append(description);
966             out.append("\n");
967         }
968         jc.usage(out, indent);
969     }
970 
971     /**
972      * @return the description of the command.
973      */
getCommandDescription(String commandName)974     public String getCommandDescription(String commandName) {
975         JCommander jc = findCommandByAlias(commandName);
976         if (jc == null) {
977             throw new ParameterException("Asking description for unknown command: " + commandName);
978         }
979 
980         Object arg = jc.getObjects().get(0);
981         Parameters p = arg.getClass().getAnnotation(Parameters.class);
982         ResourceBundle bundle = null;
983         String result = null;
984         if (p != null) {
985             result = p.commandDescription();
986             String bundleName = p.resourceBundle();
987             if (!"".equals(bundleName)) {
988                 bundle = ResourceBundle.getBundle(bundleName, Locale.getDefault());
989             } else {
990                 bundle = options.bundle;
991             }
992 
993             if (bundle != null) {
994                 String descriptionKey = p.commandDescriptionKey();
995                 if (!"".equals(descriptionKey)) {
996                     result = getI18nString(bundle, descriptionKey, p.commandDescription());
997                 }
998             }
999         }
1000 
1001         return result;
1002     }
1003 
1004     /**
1005      * @return The internationalized version of the string if available, otherwise
1006      * return def.
1007      */
getI18nString(ResourceBundle bundle, String key, String def)1008     private String getI18nString(ResourceBundle bundle, String key, String def) {
1009         String s = bundle != null ? bundle.getString(key) : null;
1010         return s != null ? s : def;
1011     }
1012 
1013     /**
1014      * Display the help on System.out.
1015      */
usage()1016     public void usage() {
1017         StringBuilder sb = new StringBuilder();
1018         usage(sb);
1019         getConsole().println(sb.toString());
1020     }
1021 
newBuilder()1022     public static Builder newBuilder() {
1023         return new Builder();
1024     }
1025 
1026     public static class Builder {
1027         private JCommander jCommander = new JCommander();
1028         private String[] args = null;
1029 
Builder()1030         public Builder() {
1031         }
1032 
1033         /**
1034          * Adds the provided arg object to the set of objects that this commander
1035          * will parse arguments into.
1036          *
1037          * @param o The arg object expected to contain {@link Parameter}
1038          * annotations. If <code>object</code> is an array or is {@link Iterable},
1039          * the child objects will be added instead.
1040          */
addObject(Object o)1041         public Builder addObject(Object o) {
1042             jCommander.addObject(o);
1043             return this;
1044         }
1045 
1046         /**
1047          * Sets the {@link ResourceBundle} to use for looking up descriptions.
1048          * Set this to <code>null</code> to use description text directly.
1049          */
resourceBundle(ResourceBundle bundle)1050         public Builder resourceBundle(ResourceBundle bundle) {
1051             jCommander.setDescriptionsBundle(bundle);
1052             return this;
1053         }
1054 
args(String[] args)1055         public Builder args(String[] args) {
1056             this.args = args;
1057             return this;
1058         }
1059 
1060         /**
1061          * Disables expanding {@code @file}.
1062          *
1063          * JCommander supports the {@code @file} syntax, which allows you to put all your options
1064          * into a file and pass this file as parameter @param expandAtSign whether to expand {@code @file}.
1065          */
expandAtSign(Boolean expand)1066         public Builder expandAtSign(Boolean expand) {
1067             jCommander.setExpandAtSign(expand);
1068             return this;
1069         }
1070 
1071         /**
1072          * Set the program name (used only in the usage).
1073          */
programName(String name)1074         public Builder programName(String name) {
1075             jCommander.setProgramName(name);
1076             return this;
1077         }
1078 
columnSize(int columnSize)1079         public Builder columnSize(int columnSize) {
1080             jCommander.setColumnSize(columnSize);
1081             return this;
1082         }
1083 
1084         /**
1085          * Define the default provider for this instance.
1086          */
defaultProvider(IDefaultProvider provider)1087         public Builder defaultProvider(IDefaultProvider provider) {
1088             jCommander.setDefaultProvider(provider);
1089             return this;
1090         }
1091 
1092         /**
1093          * Adds a factory to lookup string converters. The added factory is used prior to previously added factories.
1094          * @param factory the factory determining string converters
1095          */
addConverterFactory(IStringConverterFactory factory)1096         public Builder addConverterFactory(IStringConverterFactory factory) {
1097             jCommander.addConverterFactory(factory);
1098             return this;
1099         }
1100 
verbose(int verbose)1101         public Builder verbose(int verbose) {
1102             jCommander.setVerbose(verbose);
1103             return this;
1104         }
1105 
allowAbbreviatedOptions(boolean b)1106         public Builder allowAbbreviatedOptions(boolean b) {
1107             jCommander.setAllowAbbreviatedOptions(b);
1108             return this;
1109         }
1110 
acceptUnknownOptions(boolean b)1111         public Builder acceptUnknownOptions(boolean b) {
1112             jCommander.setAcceptUnknownOptions(b);
1113             return this;
1114         }
1115 
allowParameterOverwriting(boolean b)1116         public Builder allowParameterOverwriting(boolean b) {
1117             jCommander.setAllowParameterOverwriting(b);
1118             return this;
1119         }
1120 
atFileCharset(Charset charset)1121         public Builder atFileCharset(Charset charset) {
1122             jCommander.setAtFileCharset(charset);
1123             return this;
1124         }
1125 
addConverterInstanceFactory(IStringConverterInstanceFactory factory)1126         public Builder addConverterInstanceFactory(IStringConverterInstanceFactory factory) {
1127             jCommander.addConverterInstanceFactory(factory);
1128             return this;
1129         }
1130 
addCommand(Object command)1131         public Builder addCommand(Object command) {
1132             jCommander.addCommand(command);
1133             return this;
1134         }
1135 
addCommand(String name, Object command, String... aliases)1136         public Builder addCommand(String name, Object command, String... aliases) {
1137             jCommander.addCommand(name, command, aliases);
1138             return this;
1139         }
1140 
build()1141         public JCommander build() {
1142             if (args != null) {
1143                 jCommander.parse(args);
1144             }
1145             return jCommander;
1146         }
1147     }
1148 
1149 
1150     /**
1151      * Store the help in the passed string builder.
1152      */
usage(StringBuilder out)1153     public void usage(StringBuilder out) {
1154         usage(out, "");
1155     }
1156 
usage(StringBuilder out, String indent)1157     public void usage(StringBuilder out, String indent) {
1158         if (descriptions == null) createDescriptions();
1159         boolean hasCommands = !commands.isEmpty();
1160         boolean hasOptions = !descriptions.isEmpty();
1161 
1162         //indenting
1163         int descriptionIndent = 6;
1164         int indentCount = indent.length() + descriptionIndent;
1165 
1166         //
1167         // First line of the usage
1168         //
1169         String programName = this.programName != null ? this.programName.getDisplayName() : "<main class>";
1170         StringBuilder mainLine = new StringBuilder();
1171         mainLine.append(indent).append("Usage: ").append(programName);
1172         if (hasOptions) mainLine.append(" [options]");
1173         if (hasCommands) mainLine.append(indent).append(" [command] [command options]");
1174         if (mainParameterDescription != null) {
1175             mainLine.append(" ").append(mainParameterDescription.getDescription());
1176         }
1177         wrapDescription(out, indentCount, mainLine.toString());
1178         out.append("\n");
1179 
1180         //
1181         // Align the descriptions at the "longestName" column
1182         //
1183         int longestName = 0;
1184         List<ParameterDescription> sorted = Lists.newArrayList();
1185         for (ParameterDescription pd : fields.values()) {
1186             if (!pd.getParameter().hidden()) {
1187                 sorted.add(pd);
1188                 // + to have an extra space between the name and the description
1189                 int length = pd.getNames().length() + 2;
1190                 if (length > longestName) {
1191                     longestName = length;
1192                 }
1193             }
1194         }
1195 
1196         //
1197         // Sort the options
1198         //
1199         Collections.sort(sorted, getParameterDescriptionComparator());
1200 
1201         //
1202         // Display all the names and descriptions
1203         //
1204         if (sorted.size() > 0) out.append(indent).append("  Options:\n");
1205         for (ParameterDescription pd : sorted) {
1206             WrappedParameter parameter = pd.getParameter();
1207             out.append(indent).append("  "
1208                     + (parameter.required() ? "* " : "  ")
1209                     + pd.getNames()
1210                     + "\n");
1211             wrapDescription(out, indentCount, s(indentCount) + pd.getDescription());
1212             Object def = pd.getDefault();
1213             if (pd.isDynamicParameter()) {
1214                 out.append("\n" + s(indentCount))
1215                         .append("Syntax: " + parameter.names()[0]
1216                                 + "key" + parameter.getAssignment()
1217                                 + "value");
1218             }
1219             if (def != null && !pd.isHelp()) {
1220                 String displayedDef = Strings.isStringEmpty(def.toString())
1221                         ? "<empty string>"
1222                         : def.toString();
1223                 out.append("\n" + s(indentCount))
1224                         .append("Default: " + (parameter.password() ? "********" : displayedDef));
1225             }
1226             Class<?> type = pd.getParameterized().getType();
1227             if (type.isEnum()) {
1228                 out.append("\n" + s(indentCount))
1229                         .append("Possible Values: " + EnumSet.allOf((Class<? extends Enum>) type));
1230             }
1231             out.append("\n");
1232         }
1233 
1234         //
1235         // If commands were specified, show them as well
1236         //
1237         if (hasCommands) {
1238             out.append(indent + "  Commands:\n");
1239             // The magic value 3 is the number of spaces between the name of the option
1240             // and its description
1241             for (Map.Entry<ProgramName, JCommander> commands : this.commands.entrySet()) {
1242                 Object arg = commands.getValue().getObjects().get(0);
1243                 Parameters p = arg.getClass().getAnnotation(Parameters.class);
1244                 if (p == null || !p.hidden()) {
1245                     ProgramName progName = commands.getKey();
1246                     String dispName = progName.getDisplayName();
1247                     String description = getCommandDescription(progName.getName());
1248                     wrapDescription(out, indentCount + descriptionIndent,
1249                             indent + "    " + dispName + "      " + description);
1250                     out.append("\n");
1251 
1252                     // Options for this command
1253                     JCommander jc = findCommandByAlias(progName.getName());
1254                     jc.usage(out, indent + "      ");
1255                     out.append("\n");
1256                 }
1257             }
1258         }
1259     }
1260 
getParameterDescriptionComparator()1261     private Comparator<? super ParameterDescription> getParameterDescriptionComparator() {
1262         return options.parameterDescriptionComparator;
1263     }
1264 
setParameterDescriptionComparator(Comparator<? super ParameterDescription> c)1265     public void setParameterDescriptionComparator(Comparator<? super ParameterDescription> c) {
1266         options.parameterDescriptionComparator = c;
1267     }
1268 
setColumnSize(int columnSize)1269     public void setColumnSize(int columnSize) {
1270         options.columnSize = columnSize;
1271     }
1272 
getColumnSize()1273     public int getColumnSize() {
1274         return options.columnSize;
1275     }
1276 
1277     /**
1278      * Wrap a potentially long line to {@link #getColumnSize()}.
1279      *
1280      * @param out         the output
1281      * @param indent      the indentation in spaces for lines after the first line.
1282      * @param description the text to wrap. No extra spaces are inserted before {@code
1283      *                    description}. If the first line needs to be indented prepend the
1284      *                    correct number of spaces to {@code description}.
1285      */
wrapDescription(StringBuilder out, int indent, String description)1286     private void wrapDescription(StringBuilder out, int indent, String description) {
1287         int max = getColumnSize();
1288         String[] words = description.split(" ");
1289         int current = 0;
1290         int i = 0;
1291         while (i < words.length) {
1292             String word = words[i];
1293             if (word.length() > max || current + 1 + word.length() <= max) {
1294                 out.append(word);
1295                 current += word.length();
1296                 if (i != words.length - 1) {
1297                     out.append(" ");
1298                     current++;
1299                 }
1300             } else {
1301                 out.append("\n").append(s(indent)).append(word).append(" ");
1302                 current = indent + 1 + word.length();
1303             }
1304             i++;
1305         }
1306     }
1307 
1308     /**
1309      * @return a Collection of all the \@Parameter annotations found on the
1310      * target class. This can be used to display the usage() in a different
1311      * format (e.g. HTML).
1312      */
getParameters()1313     public List<ParameterDescription> getParameters() {
1314         return new ArrayList<>(fields.values());
1315     }
1316 
1317     /**
1318      * @return the main parameter description or null if none is defined.
1319      */
getMainParameter()1320     public ParameterDescription getMainParameter() {
1321         return mainParameterDescription;
1322     }
1323 
p(String string)1324     private void p(String string) {
1325         if (options.verbose > 0 || System.getProperty(JCommander.DEBUG_PROPERTY) != null) {
1326             getConsole().println("[JCommander] " + string);
1327         }
1328     }
1329 
1330     /**
1331      * Define the default provider for this instance.
1332      */
setDefaultProvider(IDefaultProvider defaultProvider)1333     public void setDefaultProvider(IDefaultProvider defaultProvider) {
1334         options.defaultProvider = defaultProvider;
1335     }
1336 
1337     /**
1338      * Adds a factory to lookup string converters. The added factory is used prior to previously added factories.
1339      * @param converterFactory the factory determining string converters
1340      */
addConverterFactory(final IStringConverterFactory converterFactory)1341     public void addConverterFactory(final IStringConverterFactory converterFactory) {
1342         addConverterInstanceFactory(new IStringConverterInstanceFactory() {
1343             @SuppressWarnings("unchecked")
1344             @Override
1345             public IStringConverter<?> getConverterInstance(Parameter parameter, Class<?> forType, String optionName) {
1346                 final Class<? extends IStringConverter<?>> converterClass = converterFactory.getConverter(forType);
1347                 try {
1348                     if(optionName == null) {
1349                         optionName = parameter.names().length > 0 ? parameter.names()[0] : "[Main class]";
1350                     }
1351                     return converterClass != null ? instantiateConverter(optionName, converterClass) : null;
1352                 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
1353                     throw new ParameterException(e);
1354                 }
1355             }
1356         });
1357     }
1358 
1359     /**
1360      * Adds a factory to lookup string converters. The added factory is used prior to previously added factories.
1361      * @param converterInstanceFactory the factory generating string converter instances
1362      */
addConverterInstanceFactory(IStringConverterInstanceFactory converterInstanceFactory)1363     public void addConverterInstanceFactory(IStringConverterInstanceFactory converterInstanceFactory) {
1364         options.converterInstanceFactories.add(0, converterInstanceFactory);
1365     }
1366 
findConverterInstance(Parameter parameter, Class<?> forType, String optionName)1367     private IStringConverter<?> findConverterInstance(Parameter parameter, Class<?> forType, String optionName) {
1368         for (IStringConverterInstanceFactory f : options.converterInstanceFactories) {
1369             IStringConverter<?> result = f.getConverterInstance(parameter, forType, optionName);
1370             if (result != null) return result;
1371         }
1372 
1373         return null;
1374     }
1375 
1376     /**
1377      * @param type The type of the actual parameter
1378      * @param optionName
1379      * @param value The value to convert
1380      */
convertValue(final Parameterized parameterized, Class type, String optionName, String value)1381     public Object convertValue(final Parameterized parameterized, Class type, String optionName, String value) {
1382         final Parameter annotation = parameterized.getParameter();
1383 
1384         // Do nothing if it's a @DynamicParameter
1385         if (annotation == null) return value;
1386 
1387         if(optionName == null) {
1388             optionName = annotation.names().length > 0 ? annotation.names()[0] : "[Main class]";
1389         }
1390 
1391         IStringConverter<?> converter = null;
1392         if (type.isAssignableFrom(List.class)) {
1393             // If a list converter was specified, pass the value to it for direct conversion
1394             converter = tryInstantiateConverter(optionName, annotation.listConverter());
1395         }
1396         if (type.isAssignableFrom(List.class) && converter == null) {
1397             // No list converter: use the single value converter and pass each parsed value to it individually
1398             final IParameterSplitter splitter = tryInstantiateConverter(null, annotation.splitter());
1399             converter = new DefaultListConverter(splitter, new IStringConverter() {
1400                 @Override
1401                 public Object convert(String value) {
1402                     final Type genericType = parameterized.findFieldGenericType();
1403                     return convertValue(parameterized, genericType instanceof Class ? (Class) genericType : String.class, null, value);
1404                 }
1405             });
1406         }
1407 
1408         if (converter == null) {
1409             converter = tryInstantiateConverter(optionName, annotation.converter());
1410         }
1411         if (converter == null) {
1412             converter = findConverterInstance(annotation, type, optionName);
1413         }
1414         if (converter == null && type.isEnum()) {
1415             converter = new EnumConverter(optionName, type);
1416         }
1417         if (converter == null) {
1418             converter = new StringConverter();
1419         }
1420         return converter.convert(value);
1421     }
1422 
tryInstantiateConverter(String optionName, Class<T> converterClass)1423     private static <T> T tryInstantiateConverter(String optionName, Class<T> converterClass) {
1424         if (converterClass == NoConverter.class || converterClass == null) {
1425             return null;
1426         }
1427         try {
1428             return instantiateConverter(optionName, converterClass);
1429         } catch (InstantiationException | IllegalAccessException | InvocationTargetException ignore) {
1430             return null;
1431         }
1432     }
1433 
instantiateConverter(String optionName, Class<? extends T> converterClass)1434     private static <T> T instantiateConverter(String optionName, Class<? extends T> converterClass)
1435             throws InstantiationException, IllegalAccessException,
1436             InvocationTargetException {
1437         Constructor<T> ctor = null;
1438         Constructor<T> stringCtor = null;
1439         for (Constructor<T> c : (Constructor<T>[]) converterClass.getDeclaredConstructors()) {
1440             c.setAccessible(true);
1441             Class<?>[] types = c.getParameterTypes();
1442             if (types.length == 1 && types[0].equals(String.class)) {
1443                 stringCtor = c;
1444             } else if (types.length == 0) {
1445                 ctor = c;
1446             }
1447         }
1448 
1449         return stringCtor != null
1450                 ? stringCtor.newInstance(optionName)
1451                 : ctor != null
1452                 ? ctor.newInstance()
1453                 : null;
1454     }
1455 
1456     /**
1457      * Add a command object.
1458      */
addCommand(String name, Object object)1459     public void addCommand(String name, Object object) {
1460         addCommand(name, object, new String[0]);
1461     }
1462 
addCommand(Object object)1463     public void addCommand(Object object) {
1464         Parameters p = object.getClass().getAnnotation(Parameters.class);
1465         if (p != null && p.commandNames().length > 0) {
1466             for (String commandName : p.commandNames()) {
1467                 addCommand(commandName, object);
1468             }
1469         } else {
1470             throw new ParameterException("Trying to add command " + object.getClass().getName()
1471                     + " without specifying its names in @Parameters");
1472         }
1473     }
1474 
1475     /**
1476      * Add a command object and its aliases.
1477      */
addCommand(String name, Object object, String... aliases)1478     public void addCommand(String name, Object object, String... aliases) {
1479         JCommander jc = new JCommander(options);
1480         jc.addObject(object);
1481         jc.createDescriptions();
1482         jc.setProgramName(name, aliases);
1483         ProgramName progName = jc.programName;
1484         commands.put(progName, jc);
1485 
1486     /*
1487     * Register aliases
1488     */
1489         //register command name as an alias of itself for reverse lookup
1490         //Note: Name clash check is intentionally omitted to resemble the
1491         //     original behaviour of clashing commands.
1492         //     Aliases are, however, are strictly checked for name clashes.
1493         aliasMap.put(new StringKey(name), progName);
1494         for (String a : aliases) {
1495             IKey alias = new StringKey(a);
1496             //omit pointless aliases to avoid name clash exception
1497             if (!alias.equals(name)) {
1498                 ProgramName mappedName = aliasMap.get(alias);
1499                 if (mappedName != null && !mappedName.equals(progName)) {
1500                     throw new ParameterException("Cannot set alias " + alias
1501                             + " for " + name
1502                             + " command because it has already been defined for "
1503                             + mappedName.name + " command");
1504                 }
1505                 aliasMap.put(alias, progName);
1506             }
1507         }
1508     }
1509 
getCommands()1510     public Map<String, JCommander> getCommands() {
1511         Map<String, JCommander> res = Maps.newLinkedHashMap();
1512         for (Map.Entry<ProgramName, JCommander> entry : commands.entrySet()) {
1513             res.put(entry.getKey().name, entry.getValue());
1514         }
1515         return res;
1516     }
1517 
getParsedCommand()1518     public String getParsedCommand() {
1519         return parsedCommand;
1520     }
1521 
1522     /**
1523      * The name of the command or the alias in the form it was
1524      * passed to the command line. <code>null</code> if no
1525      * command or alias was specified.
1526      *
1527      * @return Name of command or alias passed to command line. If none passed: <code>null</code>.
1528      */
getParsedAlias()1529     public String getParsedAlias() {
1530         return parsedAlias;
1531     }
1532 
1533     /**
1534      * @return n spaces
1535      */
s(int count)1536     private String s(int count) {
1537         StringBuilder result = new StringBuilder();
1538         for (int i = 0; i < count; i++) {
1539             result.append(" ");
1540         }
1541 
1542         return result.toString();
1543     }
1544 
1545     /**
1546      * @return the objects that JCommander will fill with the result of
1547      * parsing the command line.
1548      */
getObjects()1549     public List<Object> getObjects() {
1550         return objects;
1551     }
1552 
findParameterDescription(String arg)1553     private ParameterDescription findParameterDescription(String arg) {
1554         return FuzzyMap.findInMap(descriptions, new StringKey(arg),
1555                 options.caseSensitiveOptions, options.allowAbbreviatedOptions);
1556     }
1557 
findCommand(ProgramName name)1558     private JCommander findCommand(ProgramName name) {
1559         return FuzzyMap.findInMap(commands, name,
1560                 options.caseSensitiveOptions, options.allowAbbreviatedOptions);
1561     }
1562 
findProgramName(String name)1563     private ProgramName findProgramName(String name) {
1564         return FuzzyMap.findInMap(aliasMap, new StringKey(name),
1565                 options.caseSensitiveOptions, options.allowAbbreviatedOptions);
1566     }
1567 
1568     /*
1569     * Reverse lookup JCommand object by command's name or its alias
1570     */
findCommandByAlias(String commandOrAlias)1571     private JCommander findCommandByAlias(String commandOrAlias) {
1572         ProgramName progName = findProgramName(commandOrAlias);
1573         if (progName == null) {
1574             return null;
1575         }
1576         JCommander jc = findCommand(progName);
1577         if (jc == null) {
1578             throw new IllegalStateException(
1579                     "There appears to be inconsistency in the internal command database. " +
1580                             " This is likely a bug. Please report.");
1581         }
1582         return jc;
1583     }
1584 
1585     /**
1586      * Encapsulation of either a main application or an individual command.
1587      */
1588     private static final class ProgramName implements IKey {
1589         private final String name;
1590         private final List<String> aliases;
1591 
ProgramName(String name, List<String> aliases)1592         ProgramName(String name, List<String> aliases) {
1593             this.name = name;
1594             this.aliases = aliases;
1595         }
1596 
1597         @Override
getName()1598         public String getName() {
1599             return name;
1600         }
1601 
getDisplayName()1602         private String getDisplayName() {
1603             StringBuilder sb = new StringBuilder();
1604             sb.append(name);
1605             if (!aliases.isEmpty()) {
1606                 sb.append("(");
1607                 Iterator<String> aliasesIt = aliases.iterator();
1608                 while (aliasesIt.hasNext()) {
1609                     sb.append(aliasesIt.next());
1610                     if (aliasesIt.hasNext()) {
1611                         sb.append(",");
1612                     }
1613                 }
1614                 sb.append(")");
1615             }
1616             return sb.toString();
1617         }
1618 
1619         @Override
hashCode()1620         public int hashCode() {
1621             final int prime = 31;
1622             int result = 1;
1623             result = prime * result + ((name == null) ? 0 : name.hashCode());
1624             return result;
1625         }
1626 
1627         @Override
equals(Object obj)1628         public boolean equals(Object obj) {
1629             if (this == obj)
1630                 return true;
1631             if (obj == null)
1632                 return false;
1633             if (getClass() != obj.getClass())
1634                 return false;
1635             ProgramName other = (ProgramName) obj;
1636             if (name == null) {
1637                 if (other.name != null)
1638                     return false;
1639             } else if (!name.equals(other.name))
1640                 return false;
1641             return true;
1642         }
1643 
1644         /*
1645          * Important: ProgramName#toString() is used by longestName(Collection) function
1646          * to format usage output.
1647          */
1648         @Override
toString()1649         public String toString() {
1650             return getDisplayName();
1651 
1652         }
1653     }
1654 
setVerbose(int verbose)1655     public void setVerbose(int verbose) {
1656         options.verbose = verbose;
1657     }
1658 
setCaseSensitiveOptions(boolean b)1659     public void setCaseSensitiveOptions(boolean b) {
1660         options.caseSensitiveOptions = b;
1661     }
1662 
setAllowAbbreviatedOptions(boolean b)1663     public void setAllowAbbreviatedOptions(boolean b) {
1664         options.allowAbbreviatedOptions = b;
1665     }
1666 
setAcceptUnknownOptions(boolean b)1667     public void setAcceptUnknownOptions(boolean b) {
1668         options.acceptUnknownOptions = b;
1669     }
1670 
getUnknownOptions()1671     public List<String> getUnknownOptions() {
1672         return unknownArgs;
1673     }
1674 
setAllowParameterOverwriting(boolean b)1675     public void setAllowParameterOverwriting(boolean b) {
1676         options.allowParameterOverwriting = b;
1677     }
1678 
isParameterOverwritingAllowed()1679     public boolean isParameterOverwritingAllowed() {
1680         return options.allowParameterOverwriting;
1681     }
1682 
1683     /**
1684      * Sets the charset used to expand {@code @files}.
1685      * @param charset the charset
1686      */
setAtFileCharset(Charset charset)1687     public void setAtFileCharset(Charset charset) {
1688         options.atFileCharset = charset;
1689     }
1690 
1691 }
1692