001/* 002 Licensed to the Apache Software Foundation (ASF) under one or more 003 contributor license agreements. See the NOTICE file distributed with 004 this work for additional information regarding copyright ownership. 005 The ASF licenses this file to You under the Apache License, Version 2.0 006 (the "License"); you may not use this file except in compliance with 007 the License. You may obtain a copy of the License at 008 009 http://www.apache.org/licenses/LICENSE-2.0 010 011 Unless required by applicable law or agreed to in writing, software 012 distributed under the License is distributed on an "AS IS" BASIS, 013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 See the License for the specific language governing permissions and 015 limitations under the License. 016 */ 017package Apache.CLI; 018 019import static Apache.CLI.Util.EMPTY_STRING_ARRAY; 020 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Objects; 025 026/** 027 * Describes a single command-line option. It maintains information regarding the short-name of the 028 * option, the long-name, if any exists, a flag indicating if an argument is required for this 029 * option, and a self-documenting description of the option. 030 * 031 * <BR /><BR />An Option is not created independently, but is created through an instance of 032 * {@link Options}. An Option is required to have at least a short or a long-name. 033 * 034 * <BR /><BR /><B CLASS=JDDescLabel>Note:</B> 035 * 036 * <BR />Once an {@link Option} has been added to an instance of {@link Options}, its required flag 037 * cannot be changed. 038 * 039 * @see Apache.CLI.Options 040 * @see Apache.CLI.CommandLine 041 */ 042@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="LICENSE") 043public class Option implements Cloneable, Serializable 044{ 045 /** 046 * A nested builder class to create {@code Option} instances using descriptive methods. 047 * 048 * <BR /><BR />Example usage: 049 * 050 * <DIV CLASS=EXAMPLE>{@code 051 * Option option = Option.builder("a").required(true).longOpt("arg-name").build(); 052 * }</DIV> 053 */ 054 @Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="LICENSE") 055 public static final class Builder 056 { 057 // The name of the option 058 private String option; 059 060 // Description of the option 061 private String description; 062 063 // The long representation of the option 064 private String longOption; 065 066 // The name of the argument for this option 067 private String argName; 068 069 // Specifies whether this option is required to be present 070 private boolean required; 071 072 // Specifies whether the argument value of this Option is optional 073 private boolean optionalArg; 074 075 // The number of argument values this option can have 076 private int argCount = UNINITIALIZED; 077 078 // The type of this Option 079 private Class<?> type = String.class; 080 081 // The character that is the value separator 082 private char valueSeparator; 083 084 /** 085 * Constructs a new {@code Builder} with the minimum required parameters for an 086 * {@code Option} instance. 087 * 088 * @param option short representation of the option 089 * 090 * @throws IllegalArgumentException if there are any non valid Option characters in 091 * {@code opt} 092 */ 093 private Builder(final String option) throws IllegalArgumentException 094 { option(option); } 095 096 /** 097 * Sets the display name for the argument value. 098 * @param argName the display name for the argument value. 099 * @return this builder, to allow method chaining 100 */ 101 public Builder argName(final String argName) 102 { 103 this.argName = argName; 104 return this; 105 } 106 107 /** 108 * Constructs an Option with the values declared by this {@link Builder}. 109 * @return the new {@link Option} 110 * @throws IllegalArgumentException if neither {@code opt} or {@code longOpt} has been set 111 */ 112 public Option build() 113 { 114 if (option == null && longOption == null) 115 throw new IllegalArgumentException("Either opt or longOpt must be specified"); 116 117 return new Option(this); 118 } 119 120 /** 121 * Sets the description for this option. 122 * @param description the description of the option. 123 * @return this builder, to allow method chaining 124 */ 125 public Builder desc(final String description) 126 { 127 this.description = description; 128 return this; 129 } 130 131 /** 132 * Indicates that the Option will require an argument. 133 * @return this builder, to allow method chaining 134 */ 135 public Builder hasArg() 136 { return hasArg(true); } 137 138 /** 139 * Indicates if the Option has an argument or not. 140 * @param hasArg specifies whether the Option takes an argument or not 141 * @return this builder, to allow method chaining 142 */ 143 public Builder hasArg(final boolean hasArg) 144 { 145 // set to UNINITIALIZED when no arg is specified to be compatible with OptionBuilder 146 argCount = hasArg ? 1 : Option.UNINITIALIZED; 147 return this; 148 } 149 150 /** 151 * Indicates that the Option can have unlimited argument values. 152 * @return this builder, to allow method chaining 153 */ 154 public Builder hasArgs() 155 { 156 argCount = Option.UNLIMITED_VALUES; 157 return this; 158 } 159 160 /** 161 * Sets the long name of the Option. 162 * @param longOpt the long name of the Option 163 * @return this builder, to allow method chaining 164 */ 165 public Builder longOpt(final String longOpt) 166 { 167 this.longOption = longOpt; 168 return this; 169 } 170 171 /** 172 * Sets the number of argument values the Option can take. 173 * @param argCount the number of argument values 174 * @return this builder, to allow method chaining 175 */ 176 public Builder numberOfArgs(final int argCount) 177 { 178 this.argCount = argCount; 179 return this; 180 } 181 182 /** 183 * Sets the name of the Option. 184 * @param option the name of the Option 185 * @return this builder, to allow method chaining 186 * 187 * @throws IllegalArgumentException if there are any non valid Option characters in 188 * {@code opt} 189 */ 190 public Builder option(final String option) throws IllegalArgumentException 191 { 192 this.option = OptionValidator.validate(option); 193 return this; 194 } 195 196 /** 197 * Sets whether the Option can have an optional argument. 198 * 199 * @param optionalArg specifies whether the Option can have an optional argument. 200 * @return this builder, to allow method chaining 201 */ 202 public Builder optionalArg(final boolean optionalArg) 203 { 204 this.argCount = optionalArg ? 1 : UNINITIALIZED; 205 this.optionalArg = optionalArg; 206 207 return this; 208 } 209 210 /** 211 * Marks this Option as required. 212 * @return this builder, to allow method chaining 213 */ 214 public Builder required() 215 { return required(true); } 216 217 /** 218 * Sets whether the Option is mandatory. 219 * @param required specifies whether the Option is mandatory 220 * @return this builder, to allow method chaining 221 */ 222 public Builder required(final boolean required) 223 { 224 this.required = required; 225 return this; 226 } 227 228 /** 229 * Sets the type of the Option. 230 * @param type the type of the Option 231 * @return this builder, to allow method chaining 232 */ 233 public Builder type(final Class<?> type) 234 { 235 this.type = type; 236 return this; 237 } 238 239 /** 240 * The Option will use {@code '='} as a means to separate argument value. 241 * @return this builder, to allow method chaining 242 */ 243 public Builder valueSeparator() 244 { return valueSeparator('='); } 245 246 /** 247 * The Option will use {@code sep} as a means to separate argument values. See the example 248 * below: 249 * 250 * <DIV CLASS=EXAMPLE>{@code 251 * Option opt = Option.builder("D").hasArgs().valueSeparator('=').build(); 252 * Options options = new Options(); 253 * 254 * options.addOption(opt); 255 * 256 * String[] args = {"-Dkey=value"}; 257 * CommandLineParser parser = new DefaultParser(); 258 * CommandLine line = parser.parse(options, args); 259 * 260 * // will be "key" 261 * String propertyName = line.getOptionValues("D")[0]; 262 * 263 * // will be "value" 264 * String propertyValue = line.getOptionValues("D")[1]; 265 * }</DIV> 266 * 267 * @param valueSeparator The value separator. 268 * @return this builder, to allow method chaining 269 */ 270 public Builder valueSeparator(final char valueSeparator) 271 { 272 this.valueSeparator = valueSeparator; 273 return this; 274 } 275 } 276 277 /** Specifies the number of argument values has not been specified */ 278 public static final int UNINITIALIZED = -1; 279 280 /** Specifies the number of argument values is infinite */ 281 public static final int UNLIMITED_VALUES = -2; 282 283 // The serial version UID. 284 private static final long serialVersionUID = 1L; 285 286 // Empty array. 287 static final Option[] EMPTY_ARRAY = {}; 288 289 /** 290 * Returns a {@link Builder} to create an {@link Option} using descriptive methods. 291 * @return a new {@link Builder} instance 292 */ 293 public static Builder builder() 294 { return builder(null); } 295 296 /** 297 * Returns a {@link Builder} to create an {@link Option} using descriptive methods. 298 * @param option short representation of the option 299 * @return a new {@link Builder} instance 300 * @throws IllegalArgumentException if there are any non valid Option characters in {@code opt} 301 */ 302 public static Builder builder(final String option) 303 { return new Builder(option); } 304 305 // The name of the option. */ 306 private final String option; 307 308 // The long representation of the option. */ 309 private String longOption; 310 311 // The name of the argument for this option 312 private String argName; 313 314 // Description of the option 315 private String description; 316 317 // Specifies whether this option is required to be present 318 private boolean required; 319 320 // Specifies whether the argument value of this Option is optional 321 private boolean optionalArg; 322 323 // The number of argument values this option can have 324 private int argCount = UNINITIALIZED; 325 326 // The type of this Option 327 private Class<?> type = String.class; 328 329 // The list of argument values 330 private List<String> values = new ArrayList<>(); 331 332 // The character that is the value separator. 333 private char valuesep; 334 335 /** 336 * Private constructor used by the nested Builder class. 337 * @param builder builder used to create this option 338 */ 339 private Option(final Builder builder) 340 { 341 this.argName = builder.argName; 342 this.description = builder.description; 343 this.longOption = builder.longOption; 344 this.argCount = builder.argCount; 345 this.option = builder.option; 346 this.optionalArg = builder.optionalArg; 347 this.required = builder.required; 348 this.type = builder.type; 349 this.valuesep = builder.valueSeparator; 350 } 351 352 /** 353 * Creates an Option using the specified parameters. 354 * @param option short representation of the option 355 * @param hasArg specifies whether the Option takes an argument or not 356 * @param description describes the function of the option 357 * @throws IllegalArgumentException if there are any non valid Option characters in {@code opt} 358 */ 359 public Option(final String option, final boolean hasArg, final String description) 360 throws IllegalArgumentException 361 { this(option, null, hasArg, description); } 362 363 /** 364 * Creates an Option using the specified parameters. The option does not take an argument. 365 * @param option short representation of the option 366 * @param description describes the function of the option 367 * @throws IllegalArgumentException if there are any non valid Option characters in {@code opt} 368 */ 369 public Option(final String option, final String description) throws IllegalArgumentException 370 { this(option, null, false, description); } 371 372 /** 373 * Creates an Option using the specified parameters. 374 * @param option short representation of the option 375 * @param longOption the long representation of the option 376 * @param hasArg specifies whether the Option takes an argument or not 377 * @param description describes the function of the option 378 * @throws IllegalArgumentException if there are any non valid Option characters in {@code opt} 379 */ 380 public Option( 381 final String option, 382 final String longOption, 383 final boolean hasArg, 384 final String description 385 ) 386 throws IllegalArgumentException 387 { 388 // ensure that the option is valid 389 this.option = OptionValidator.validate(option); 390 this.longOption = longOption; 391 392 // if hasArg is set then the number of arguments is 1 393 if (hasArg) this.argCount = 1; 394 395 this.description = description; 396 } 397 398 /** 399 * Tells if the option can accept more arguments. 400 * @return false if the maximum number of arguments is reached 401 */ 402 boolean acceptsArg() 403 { 404 return (hasArg() || hasArgs() || hasOptionalArg()) 405 && (argCount <= 0 || values.size() < argCount); 406 } 407 408 /** 409 * Add the value to this Option. If the number of arguments is greater than zero and there is 410 * enough space in the list then add the value. Otherwise, throw a runtime exception. 411 * 412 * @param value The value to be added to this Option 413 */ 414 private void add(final String value) 415 { 416 if (!acceptsArg()) throw new IllegalArgumentException("Cannot add value, list full."); 417 418 // store value 419 values.add(value); 420 } 421 422 /** 423 * Adds the specified value to this Option. 424 * @param value is a/the value of this Option 425 */ 426 void addValueForProcessing(final String value) 427 { 428 if (argCount == UNINITIALIZED) throw new IllegalArgumentException("NO_ARGS_ALLOWED"); 429 processValue(value); 430 } 431 432 /** 433 * Clear the Option values. After a parse is complete, these are left with data in them and they need clearing if 434 * another parse is done. 435 * 436 * See: <a href="https://issues.apache.org/jira/browse/CLI-71">CLI-71</a> 437 */ 438 void clearValues() 439 { values.clear(); } 440 441 /** 442 * A rather odd clone method - due to incorrect code in 1.0 it is public and in 1.1 rather than throwing a 443 * CloneNotSupportedException it throws a RuntimeException so as to maintain backwards compat at the API level. 444 * 445 * After calling this method, it is very likely you will want to call clearValues(). 446 * 447 * @return a clone of this Option instance 448 * @throws RuntimeException if a {@link CloneNotSupportedException} has been thrown by {@code super.clone()} 449 */ 450 @Override 451 public Object clone() 452 { 453 try 454 { 455 final Option option = (Option) super.clone(); 456 option.values = new ArrayList<>(values); 457 return option; 458 } 459 460 catch (final CloneNotSupportedException e) 461 { throw new UnsupportedOperationException(e.getMessage(), e); } 462 } 463 464 @Override 465 public boolean equals(final Object obj) 466 { 467 if (this == obj) return true; 468 if (!(obj instanceof Option)) return false; 469 470 final Option other = (Option) obj; 471 472 return Objects.equals(longOption, other.longOption) 473 && Objects.equals(option, other.option); 474 } 475 476 /** 477 * Gets the display name for the argument value. 478 * @return the display name for the argument value. 479 */ 480 public String getArgName() 481 { return argName; } 482 483 /** 484 * Gets the number of argument values this Option can take. 485 * 486 * <BR /><BR /> 487 * A value equal to the constant {@link #UNINITIALIZED} (= -1) indicates the number of 488 * arguments has not been specified. 489 * 490 * <BR /><BR /> 491 * A value equal to the constant {@link #UNLIMITED_VALUES} (= -2) indicates that this options 492 * takes an unlimited amount of values. 493 * 494 * @return num the number of argument values 495 * @see #UNINITIALIZED 496 * @see #UNLIMITED_VALUES 497 */ 498 public int getArgs() { 499 return argCount; 500 } 501 502 /** 503 * Gets the self-documenting description of this Option 504 * @return The string description of this option 505 */ 506 public String getDescription() 507 { return description; } 508 509 /** 510 * Gets the id of this Option. This is only set when the Option shortOpt is a single character. This is used for 511 * switch statements. 512 * 513 * @return the id of this Option 514 */ 515 public int getId() 516 { return getKey().charAt(0); } 517 518 /** 519 * Gets the 'unique' Option identifier. 520 * @return the 'unique' Option identifier 521 */ 522 String getKey() 523 { 524 // if 'opt' is null, then it is a 'long' option 525 return option == null ? longOption : option; 526 } 527 528 /** 529 * Gets the long name of this Option. 530 * @return Long name of this option, or null, if there is no long name 531 */ 532 public String getLongOpt() 533 { return longOption; } 534 535 /** 536 * Gets the name of this Option. 537 * 538 * <BR /><BR />It is this {@code String} which can be used with 539 * {@link CommandLine#hasOption(String opt)} and 540 * {@link CommandLine#getOptionValue(String opt)} to check for existence and argument. 541 * 542 * @return The name of this option 543 */ 544 public String getOpt() 545 { return option; } 546 547 /** 548 * Gets the type of this Option. 549 * @return The type of this option 550 */ 551 public Object getType() 552 { return type; } 553 554 /** 555 * Gets the specified value of this Option or {@code null} if there is no value. 556 * @return the value/first value of this Option or {@code null} if there is no value. 557 */ 558 public String getValue() 559 { return hasNoValues() ? null : values.get(0); } 560 561 /** 562 * Gets the specified value of this Option or {@code null} if there is no value. 563 * @param index The index of the value to be returned. 564 * @return the specified value of this Option or {@code null} if there is no value. 565 * 566 * @throws IndexOutOfBoundsException if index is less than 1 or greater than the number of the 567 * values for this Option. 568 */ 569 public String getValue(final int index) throws IndexOutOfBoundsException 570 { return hasNoValues() ? null : values.get(index); } 571 572 /** 573 * Gets the value/first value of this Option or the {@code defaultValue} if there is no value. 574 * @param defaultValue The value to be returned if there is no value. 575 * 576 * @return the value/first value of this Option or the {@code defaultValue} if there are no 577 * values. 578 */ 579 public String getValue(final String defaultValue) 580 { 581 final String value = getValue(); 582 return value != null ? value : defaultValue; 583 } 584 585 /** 586 * Gets the values of this Option as a String array or null if there are no values 587 * @return the values of this Option as a String array or null if there are no values 588 */ 589 public String[] getValues() 590 { return hasNoValues() ? null : values.toArray(EMPTY_STRING_ARRAY); } 591 592 /** 593 * Gets the value separator character. 594 * @return the value separator character. 595 */ 596 public char getValueSeparator() 597 { return valuesep; } 598 599 /** 600 * Gets the values of this Option as a List or null if there are no values. 601 * @return the values of this Option as a List or null if there are no values 602 */ 603 public List<String> getValuesList() 604 { return values; } 605 606 /** 607 * Query to see if this Option requires an argument 608 * @return boolean flag indicating if an argument is required 609 */ 610 public boolean hasArg() 611 { return argCount > 0 || argCount == UNLIMITED_VALUES; } 612 613 /** 614 * Returns whether the display name for the argument value has been set. 615 * @return if the display name for the argument value has been set. 616 */ 617 public boolean hasArgName() 618 { return argName != null && !argName.isEmpty(); } 619 620 /** 621 * Query to see if this Option can take many values. 622 * @return boolean flag indicating if multiple values are allowed 623 */ 624 public boolean hasArgs() 625 { return argCount > 1 || argCount == UNLIMITED_VALUES; } 626 627 @Override 628 public int hashCode() 629 { return Objects.hash(longOption, option); } 630 631 /** 632 * Query to see if this Option has a long name 633 * @return boolean flag indicating existence of a long name 634 */ 635 public boolean hasLongOpt() 636 { return longOption != null; } 637 638 /** 639 * Returns whether this Option has any values. 640 * @return whether this Option has any values. 641 */ 642 private boolean hasNoValues() 643 { return values.isEmpty(); } 644 645 /** @return whether this Option can have an optional argument */ 646 public boolean hasOptionalArg() 647 { return optionalArg; } 648 649 /** 650 * Return whether this Option has specified a value separator. 651 * @return whether this Option has specified a value separator. 652 */ 653 public boolean hasValueSeparator() 654 { return valuesep > 0; } 655 656 /** 657 * Query to see if this Option is mandatory 658 * @return boolean flag indicating whether this Option is mandatory 659 */ 660 public boolean isRequired() 661 { return required; } 662 663 /** 664 * Processes the value. If this Option has a value separator the value will have to be parsed 665 * into individual tokens. When n-1 tokens have been processed and there are more value 666 * separators in the value, parsing is ceased and the remaining characters are added as a 667 * single token. 668 * 669 * @param value The String to be processed. 670 */ 671 private void processValue(String value) 672 { 673 // this Option has a separator character 674 if (hasValueSeparator()) 675 { 676 // get the separator character 677 final char sep = getValueSeparator(); 678 679 // store the index for the value separator 680 int index = value.indexOf(sep); 681 682 // while there are more value separators 683 while (index != -1) 684 { 685 // next value to be added 686 if (values.size() == argCount - 1) break; 687 688 // store 689 add(value.substring(0, index)); 690 691 // parse 692 value = value.substring(index + 1); 693 694 // get new index 695 index = value.indexOf(sep); 696 } 697 } 698 699 // store the actual value or the last value that has been parsed 700 add(value); 701 } 702 703 /** 704 * Tells if the option requires more arguments to be valid. 705 * @return false if the option doesn't require more arguments 706 */ 707 boolean requiresArg() 708 { 709 if (optionalArg) return false; 710 if (argCount == UNLIMITED_VALUES) return values.isEmpty(); 711 return acceptsArg(); 712 } 713 714 /** 715 * Sets the display name for the argument value. 716 * @param argName the display name for the argument value. 717 */ 718 public void setArgName(final String argName) 719 { this.argName = argName; } 720 721 /** 722 * Sets the number of argument values this Option can take. 723 * @param num the number of argument values 724 */ 725 public void setArgs(final int num) 726 { this.argCount = num; } 727 728 /** 729 * Sets the self-documenting description of this Option 730 * @param description The description of this option 731 */ 732 public void setDescription(final String description) 733 { this.description = description; } 734 735 /** 736 * Sets the long name of this Option. 737 * @param longOpt the long name of this Option 738 */ 739 public void setLongOpt(final String longOpt) 740 { this.longOption = longOpt; } 741 742 /** 743 * Sets whether this Option can have an optional argument. 744 * @param optionalArg specifies whether the Option can have an optional argument. 745 */ 746 public void setOptionalArg(final boolean optionalArg) 747 { this.optionalArg = optionalArg; } 748 749 /** 750 * Sets whether this Option is mandatory. 751 * @param required specifies whether this Option is mandatory 752 */ 753 public void setRequired(final boolean required) 754 { this.required = required; } 755 756 /** 757 * Sets the type of this Option. 758 * @param type the type of this Option 759 */ 760 public void setType(final Class<?> type) 761 { this.type = type; } 762 763 /** 764 * Sets the value separator. For example if the argument value was a Java property, the value 765 * separator would be '='. 766 * 767 * @param sep The value separator. 768 */ 769 public void setValueSeparator(final char sep) 770 { this.valuesep = sep; } 771 772 /** 773 * Dump state, suitable for debugging. 774 * @return Stringified form of this object 775 */ 776 @Override 777 public String toString() 778 { 779 final StringBuilder buf = new StringBuilder().append("[ option: "); 780 781 buf.append(option); 782 783 if (longOption != null) buf.append(" ").append(longOption); 784 785 buf.append(" "); 786 787 if (hasArgs()) buf.append("[ARG...]"); 788 else if (hasArg()) buf.append(" [ARG]"); 789 790 buf.append(" :: ").append(description); 791 792 if (type != null) buf.append(" :: ").append(type); 793 794 buf.append(" ]"); 795 796 return buf.toString(); 797 } 798}