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