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
19package com.beust.jcommander;
20
21import java.io.BufferedReader;
22import java.io.FileReader;
23import java.io.IOException;
24import java.lang.reflect.Constructor;
25import java.lang.reflect.InvocationTargetException;
26import java.lang.reflect.Method;
27import java.lang.reflect.ParameterizedType;
28import java.lang.reflect.Type;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collections;
32import java.util.Comparator;
33import java.util.EnumSet;
34import java.util.Iterator;
35import java.util.LinkedList;
36import java.util.List;
37import java.util.Locale;
38import java.util.Map;
39import java.util.ResourceBundle;
40
41import com.beust.jcommander.FuzzyMap.IKey;
42import com.beust.jcommander.converters.IParameterSplitter;
43import com.beust.jcommander.converters.NoConverter;
44import com.beust.jcommander.converters.StringConverter;
45import com.beust.jcommander.internal.Console;
46import com.beust.jcommander.internal.DefaultConsole;
47import com.beust.jcommander.internal.DefaultConverterFactory;
48import com.beust.jcommander.internal.JDK6Console;
49import com.beust.jcommander.internal.Lists;
50import com.beust.jcommander.internal.Maps;
51import 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 */
65public 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 {
167    CONVERTER_FACTORIES.addFirst(new DefaultConverterFactory());
168  };
169
170  /**
171   * Creates a new un-configured JCommander object.
172   */
173  public JCommander() {
174  }
175
176  /**
177   * @param object The arg object expected to contain {@link Parameter} annotations.
178   */
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   */
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   */
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   */
208  public JCommander(Object object, String... args) {
209    addObject(object);
210    parse(args);
211  }
212
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
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
257  public final void setDescriptionsBundle(ResourceBundle bundle) {
258    m_bundle = bundle;
259  }
260
261  /**
262   * Parse and validate the command line parameters.
263   */
264  public void parse(String... args) {
265    parse(true /* validate */, args);
266  }
267
268  /**
269   * Parse the command line parameters without validating them.
270   */
271  public void parseWithoutValidation(String... args) {
272    parse(false /* no validation */, args);
273  }
274
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
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
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   */
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
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   */
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
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
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
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   */
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
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
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   */
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   */
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   */
518  private void createDescriptions() {
519    m_descriptions = Maps.newHashMap();
520
521    for (Object object : m_objects) {
522      addDescription(object);
523    }
524  }
525
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
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   */
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
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   */
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
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
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   */
893  private char[] readPassword(String description, boolean echoInput) {
894    getConsole().print(description + ": ");
895    return getConsole().readPassword(echoInput);
896  }
897
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   */
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
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
1048  public void usage(StringBuilder out) {
1049    usage(out, "");
1050  }
1051
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
1147  private Comparator<? super ParameterDescription> getParameterDescriptionComparator() {
1148    return m_parameterDescriptionComparator;
1149  }
1150
1151  public void setParameterDescriptionComparator(Comparator<? super ParameterDescription> c) {
1152    m_parameterDescriptionComparator = c;
1153  }
1154
1155  public void setColumnSize(int columnSize) {
1156    m_columnSize = columnSize;
1157  }
1158
1159  public int getColumnSize() {
1160    return m_columnSize;
1161  }
1162
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   */
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   */
1193  public ParameterDescription getMainParameter() {
1194    return m_mainParameterDescription;
1195  }
1196
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   */
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
1214  public void addConverterFactory(IStringConverterFactory converterFactory) {
1215    CONVERTER_FACTORIES.addFirst(converterFactory);
1216  }
1217
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
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   */
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   */
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
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   */
1363  public void addCommand(String name, Object object) {
1364    addCommand(name, object, new String[0]);
1365  }
1366
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   */
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
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
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   */
1433  public String getParsedAlias() {
1434    return m_parsedAlias;
1435  }
1436
1437  /**
1438   * @return n spaces
1439   */
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   */
1453  public List<Object> getObjects() {
1454    return m_objects;
1455  }
1456
1457  private ParameterDescription findParameterDescription(String arg) {
1458    return FuzzyMap.findInMap(m_descriptions, new StringKey(arg), m_caseSensitiveOptions,
1459        m_allowAbbreviatedOptions);
1460  }
1461
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
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  */
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
1506    ProgramName(String name, List<String> aliases) {
1507      m_name = name;
1508      m_aliases = aliases;
1509    }
1510
1511    @Override
1512    public String getName() {
1513      return m_name;
1514    }
1515
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
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
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
1563    public String toString() {
1564      return getDisplayName();
1565
1566    }
1567  }
1568
1569  public void setVerbose(int verbose) {
1570    m_verbose = verbose;
1571  }
1572
1573  public void setCaseSensitiveOptions(boolean b) {
1574    m_caseSensitiveOptions = b;
1575  }
1576
1577  public void setAllowAbbreviatedOptions(boolean b) {
1578    m_allowAbbreviatedOptions = b;
1579  }
1580
1581  public void setAcceptUnknownOptions(boolean b) {
1582    m_acceptUnknownOptions = b;
1583  }
1584
1585  public List<String> getUnknownOptions() {
1586    return m_unknownArgs;
1587  }
1588  public void setAllowParameterOverwriting(boolean b) {
1589    m_allowParameterOverwriting = b;
1590  }
1591
1592  public boolean isParameterOverwritingAllowed() {
1593    return m_allowParameterOverwriting;
1594  }
1595//  public void setCaseSensitiveCommands(boolean b) {
1596//    m_caseSensitiveCommands = b;
1597//  }
1598}
1599
1600