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