001/* 002 * Copyright 2008-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util.args; 022 023 024 025import java.io.Serializable; 026 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Iterator; 030import java.util.List; 031 032import com.unboundid.util.Mutable; 033import com.unboundid.util.NotExtensible; 034import com.unboundid.util.ThreadSafety; 035import com.unboundid.util.ThreadSafetyLevel; 036 037import static com.unboundid.util.args.ArgsMessages.*; 038 039 040 041/** 042 * This class defines a generic command line argument, which provides 043 * functionality applicable to all argument types. Subclasses may enforce 044 * additional constraints or provide additional functionality. 045 */ 046@NotExtensible() 047@Mutable() 048@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 049public abstract class Argument 050 implements Serializable 051{ 052 /** 053 * The serial version UID for this serializable class. 054 */ 055 private static final long serialVersionUID = -6938320885602903919L; 056 057 058 059 // Indicates whether this argument should be excluded from usage information. 060 private boolean isHidden; 061 062 // Indicates whether this argument has been registered with the argument 063 // parser. 064 private boolean isRegistered; 065 066 // Indicates whether this argument is required to be present. 067 private final boolean isRequired; 068 069 // Indicates whether values of this argument should be considered sensitive. 070 private boolean isSensitive; 071 072 // Indicates whether this argument is used to display usage information. 073 private boolean isUsageArgument; 074 075 // The maximum number of times this argument is allowed to be provided. 076 private int maxOccurrences; 077 078 // The number of times this argument was included in the provided command line 079 // arguments. 080 private int numOccurrences; 081 082 // The short identifier for this argument, or an empty list if there are none. 083 private final List<Character> shortIdentifiers; 084 085 // The long identifier(s) for this argument, or an empty list if there are 086 // none. 087 private final List<String> longIdentifiers; 088 089 // The argument group name for this argument, if any. 090 private String argumentGroupName; 091 092 // The description for this argument. 093 private final String description; 094 095 // The value placeholder for this argument, or {@code null} if it does not 096 // take a value. 097 private final String valuePlaceholder; 098 099 100 101 /** 102 * Creates a new argument with the provided information. 103 * 104 * @param shortIdentifier The short identifier for this argument. It may 105 * not be {@code null} if the long identifier is 106 * {@code null}. 107 * @param longIdentifier The long identifier for this argument. It may 108 * not be {@code null} if the short identifier is 109 * {@code null}. 110 * @param isRequired Indicates whether this argument is required to 111 * be provided. 112 * @param maxOccurrences The maximum number of times this argument may be 113 * provided on the command line. A value less than 114 * or equal to zero indicates that it may be present 115 * any number of times. 116 * @param valuePlaceholder A placeholder to display in usage information to 117 * indicate that a value must be provided. If this 118 * is {@code null}, then the argument will not be 119 * allowed to take a value. If it is not 120 * {@code null}, then the argument will be required 121 * to take a value. 122 * @param description A human-readable description for this argument. 123 * It must not be {@code null}. 124 * 125 * @throws ArgumentException If there is a problem with the definition of 126 * this argument. 127 */ 128 protected Argument(final Character shortIdentifier, 129 final String longIdentifier, 130 final boolean isRequired, final int maxOccurrences, 131 final String valuePlaceholder, final String description) 132 throws ArgumentException 133 { 134 if (description == null) 135 { 136 throw new ArgumentException(ERR_ARG_DESCRIPTION_NULL.get()); 137 } 138 139 if ((shortIdentifier == null) && (longIdentifier == null)) 140 { 141 throw new ArgumentException(ERR_ARG_NO_IDENTIFIERS.get()); 142 } 143 144 shortIdentifiers = new ArrayList<Character>(1); 145 if (shortIdentifier != null) 146 { 147 shortIdentifiers.add(shortIdentifier); 148 } 149 150 longIdentifiers = new ArrayList<String>(1); 151 if (longIdentifier != null) 152 { 153 longIdentifiers.add(longIdentifier); 154 } 155 156 this.isRequired = isRequired; 157 this.valuePlaceholder = valuePlaceholder; 158 this.description = description; 159 160 if (maxOccurrences > 0) 161 { 162 this.maxOccurrences = maxOccurrences; 163 } 164 else 165 { 166 this.maxOccurrences = Integer.MAX_VALUE; 167 } 168 169 argumentGroupName = null; 170 numOccurrences = 0; 171 isHidden = false; 172 isRegistered = false; 173 isSensitive = false; 174 isUsageArgument = false; 175 } 176 177 178 179 /** 180 * Creates a new argument with the same generic information as the provided 181 * argument. It will not be registered with any argument parser. 182 * 183 * @param source The argument to use as the source for this argument. 184 */ 185 protected Argument(final Argument source) 186 { 187 argumentGroupName = source.argumentGroupName; 188 isHidden = source.isHidden; 189 isRequired = source.isRequired; 190 isSensitive = source.isSensitive; 191 isUsageArgument = source.isUsageArgument; 192 maxOccurrences = source.maxOccurrences; 193 description = source.description; 194 valuePlaceholder = source.valuePlaceholder; 195 196 isRegistered = false; 197 numOccurrences = 0; 198 199 shortIdentifiers = new ArrayList<Character>(source.shortIdentifiers); 200 longIdentifiers = new ArrayList<String>(source.longIdentifiers); 201 } 202 203 204 205 /** 206 * Indicates whether this argument has a short identifier. 207 * 208 * @return {@code true} if it has a short identifier, or {@code false} if 209 * not. 210 */ 211 public final boolean hasShortIdentifier() 212 { 213 return (! shortIdentifiers.isEmpty()); 214 } 215 216 217 218 /** 219 * Retrieves the short identifier for this argument. If there is more than 220 * one, then the first will be returned. 221 * 222 * @return The short identifier for this argument, or {@code null} if none is 223 * defined. 224 */ 225 public final Character getShortIdentifier() 226 { 227 if (shortIdentifiers.isEmpty()) 228 { 229 return null; 230 } 231 else 232 { 233 return shortIdentifiers.get(0); 234 } 235 } 236 237 238 239 /** 240 * Retrieves the list of short identifiers for this argument. 241 * 242 * @return The list of short identifiers for this argument, or an empty list 243 * if there are none. 244 */ 245 public final List<Character> getShortIdentifiers() 246 { 247 return Collections.unmodifiableList(shortIdentifiers); 248 } 249 250 251 252 /** 253 * Adds the provided character to the set of short identifiers for this 254 * argument. Note that this must be called before this argument is registered 255 * with the argument parser. 256 * 257 * @param c The character to add to the set of short identifiers for this 258 * argument. It must not be {@code null}. 259 * 260 * @throws ArgumentException If this argument is already registered with the 261 * argument parser. 262 */ 263 public final void addShortIdentifier(final Character c) 264 throws ArgumentException 265 { 266 if (isRegistered) 267 { 268 throw new ArgumentException(ERR_ARG_ID_CHANGE_AFTER_REGISTERED.get( 269 getIdentifierString())); 270 } 271 272 shortIdentifiers.add(c); 273 } 274 275 276 277 /** 278 * Indicates whether this argument has a long identifier. 279 * 280 * @return {@code true} if it has a long identifier, or {@code false} if 281 * not. 282 */ 283 public final boolean hasLongIdentifier() 284 { 285 return (! longIdentifiers.isEmpty()); 286 } 287 288 289 290 /** 291 * Retrieves the long identifier for this argument. If it has multiple long 292 * identifiers, then the first will be returned. 293 * 294 * @return The long identifier for this argument, or {@code null} if none is 295 * defined. 296 */ 297 public final String getLongIdentifier() 298 { 299 if (longIdentifiers.isEmpty()) 300 { 301 return null; 302 } 303 else 304 { 305 return longIdentifiers.get(0); 306 } 307 } 308 309 310 311 /** 312 * Retrieves the list of long identifiers for this argument. 313 * 314 * @return The long identifier for this argument, or an empty list if there 315 * are none. 316 */ 317 public final List<String> getLongIdentifiers() 318 { 319 return Collections.unmodifiableList(longIdentifiers); 320 } 321 322 323 324 /** 325 * Adds the provided string to the set of short identifiers for this argument. 326 * Note that this must be called before this argument is registered with the 327 * argument parser. 328 * 329 * @param s The string to add to the set of short identifiers for this 330 * argument. It must not be {@code null}. 331 * 332 * @throws ArgumentException If this argument is already registered with the 333 * argument parser. 334 */ 335 public final void addLongIdentifier(final String s) 336 throws ArgumentException 337 { 338 if (isRegistered) 339 { 340 throw new ArgumentException(ERR_ARG_ID_CHANGE_AFTER_REGISTERED.get( 341 getIdentifierString())); 342 } 343 344 longIdentifiers.add(s); 345 } 346 347 348 349 /** 350 * Retrieves a string that may be used to identify this argument. If a long 351 * identifier is defined, then the value returned will be two dashes followed 352 * by that string. Otherwise, the value returned will be a single dash 353 * followed by the short identifier. 354 * 355 * @return A string that may be used to identify this argument. 356 */ 357 public final String getIdentifierString() 358 { 359 if (longIdentifiers.isEmpty()) 360 { 361 return "-" + shortIdentifiers.get(0); 362 } 363 else 364 { 365 return "--" + longIdentifiers.get(0); 366 } 367 } 368 369 370 371 /** 372 * Indicates whether this argument is required to be provided. 373 * 374 * @return {@code true} if this argument is required to be provided, or 375 * {@code false} if not. 376 */ 377 public final boolean isRequired() 378 { 379 return isRequired; 380 } 381 382 383 384 /** 385 * Retrieves the maximum number of times that this argument may be provided. 386 * 387 * @return The maximum number of times that this argument may be provided. 388 */ 389 public final int getMaxOccurrences() 390 { 391 return maxOccurrences; 392 } 393 394 395 396 /** 397 * Specifies the maximum number of times that this argument may be provided. 398 * 399 * @param maxOccurrences The maximum number of times that this argument 400 * may be provided. A value less than or equal to 401 * zero indicates that there should be no limit on the 402 * maximum number of occurrences. 403 */ 404 public final void setMaxOccurrences(final int maxOccurrences) 405 { 406 if (maxOccurrences <= 0) 407 { 408 this.maxOccurrences = Integer.MAX_VALUE; 409 } 410 else 411 { 412 this.maxOccurrences = maxOccurrences; 413 } 414 } 415 416 417 418 /** 419 * Indicates whether this argument takes a value. 420 * 421 * @return {@code true} if this argument takes a value, or {@code false} if 422 * not. 423 */ 424 public boolean takesValue() 425 { 426 return (valuePlaceholder != null); 427 } 428 429 430 431 /** 432 * Retrieves the value placeholder string for this argument. 433 * 434 * @return The value placeholder string for this argument, or {@code null} if 435 * it does not take a value. 436 */ 437 public final String getValuePlaceholder() 438 { 439 return valuePlaceholder; 440 } 441 442 443 444 /** 445 * Retrieves a list containing the string representations of the values for 446 * this argument, if any. The list returned does not necessarily need to 447 * include values that will be acceptable to the argument, but it should imply 448 * what the values are (e.g., in the case of a boolean argument that doesn't 449 * take a value, it may be the string "true" or "false" even if those values 450 * are not acceptable to the argument itself). 451 * 452 * @param useDefault Indicates whether to use any configured default value 453 * if the argument doesn't have a user-specified value. 454 * 455 * @return A string representation of the value for this argument, or an 456 * empty list if the argument does not have a value. 457 */ 458 public abstract List<String> getValueStringRepresentations( 459 final boolean useDefault); 460 461 462 463 /** 464 * Retrieves the description for this argument. 465 * 466 * @return The description for this argument. 467 */ 468 public final String getDescription() 469 { 470 return description; 471 } 472 473 474 475 /** 476 * Retrieves the name of the argument group to which this argument belongs. 477 * 478 * @return The name of the argument group to which this argument belongs, or 479 * {@code null} if this argument has not been assigned to any group. 480 */ 481 public final String getArgumentGroupName() 482 { 483 return argumentGroupName; 484 } 485 486 487 488 /** 489 * Sets the name of the argument group to which this argument belongs. If 490 * a tool updates arguments to specify an argument group for some or all of 491 * the arguments, then the usage information will have the arguments listed 492 * together in their respective groups. Note that usage arguments should 493 * generally not be assigned to an argument group. 494 * 495 * @param argumentGroupName The argument group name for this argument. It 496 * may be {@code null} if this argument should not 497 * be assigned to any particular group. 498 */ 499 public final void setArgumentGroupName(final String argumentGroupName) 500 { 501 this.argumentGroupName = argumentGroupName; 502 } 503 504 505 506 /** 507 * Indicates whether this argument should be excluded from usage information. 508 * 509 * @return {@code true} if this argument should be excluded from usage 510 * information, or {@code false} if not. 511 */ 512 public final boolean isHidden() 513 { 514 return isHidden; 515 } 516 517 518 519 /** 520 * Specifies whether this argument should be excluded from usage information. 521 * 522 * @param isHidden Specifies whether this argument should be excluded from 523 * usage information. 524 */ 525 public final void setHidden(final boolean isHidden) 526 { 527 this.isHidden = isHidden; 528 } 529 530 531 532 /** 533 * Indicates whether this argument is intended to be used to trigger the 534 * display of usage information. If a usage argument is provided on the 535 * command line, then the argument parser will not complain about missing 536 * required arguments or unresolved dependencies. 537 * 538 * @return {@code true} if this argument is a usage argument, or 539 * {@code false} if not. 540 */ 541 public final boolean isUsageArgument() 542 { 543 return isUsageArgument; 544 } 545 546 547 548 /** 549 * Specifies whether this argument should be considered a usage argument. 550 * 551 * @param isUsageArgument Specifies whether this argument should be 552 * considered a usage argument. 553 */ 554 public final void setUsageArgument(final boolean isUsageArgument) 555 { 556 this.isUsageArgument = isUsageArgument; 557 } 558 559 560 561 /** 562 * Indicates whether this argument was either included in the provided set of 563 * command line arguments or has a default value that can be used instead. 564 * This method should not be called until after the argument parser has 565 * processed the provided set of arguments. 566 * 567 * @return {@code true} if this argument was included in the provided set of 568 * command line arguments, or {@code false} if not. 569 */ 570 public final boolean isPresent() 571 { 572 return ((numOccurrences > 0) || hasDefaultValue()); 573 } 574 575 576 577 /** 578 * Retrieves the number of times that this argument was included in the 579 * provided set of command line arguments. This method should not be called 580 * until after the argument parser has processed the provided set of 581 * arguments. 582 * 583 * @return The number of times that this argument was included in the 584 * provided set of command line arguments. 585 */ 586 public final int getNumOccurrences() 587 { 588 return numOccurrences; 589 } 590 591 592 593 /** 594 * Increments the number of occurrences for this argument in the provided set 595 * of command line arguments. This method should only be called by the 596 * argument parser. 597 * 598 * @throws ArgumentException If incrementing the number of occurrences would 599 * exceed the maximum allowed number. 600 */ 601 final void incrementOccurrences() 602 throws ArgumentException 603 { 604 if (numOccurrences >= maxOccurrences) 605 { 606 throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get( 607 getIdentifierString())); 608 } 609 610 numOccurrences++; 611 } 612 613 614 615 /** 616 * Adds the provided value to the set of values for this argument. This 617 * method should only be called by the argument parser. 618 * 619 * @param valueString The string representation of the value. 620 * 621 * @throws ArgumentException If the provided value is not acceptable, if 622 * this argument does not accept values, or if 623 * this argument already has the maximum allowed 624 * number of values. 625 */ 626 protected abstract void addValue(final String valueString) 627 throws ArgumentException; 628 629 630 631 /** 632 * Indicates whether this argument has one or more default values that will be 633 * used if it is not provided on the command line. 634 * 635 * @return {@code true} if this argument has one or more default values, or 636 * {@code false} if not. 637 */ 638 protected abstract boolean hasDefaultValue(); 639 640 641 642 /** 643 * Indicates whether values of this argument are considered sensitive. 644 * Argument values that are considered sensitive will be obscured in places 645 * where they may be shown. 646 * 647 * @return {@code true} if values of this argument are considered sensitive, 648 * or {@code false} if not. 649 */ 650 public final boolean isSensitive() 651 { 652 return isSensitive; 653 } 654 655 656 657 /** 658 * Specifies whether values of this argument are considered sensitive. 659 * Argument values that are considered sensitive will be obscured in places 660 * where they may be shown. 661 * 662 * @param isSensitive Indicates whether values of this argument are 663 * considered sensitive. 664 */ 665 public final void setSensitive(final boolean isSensitive) 666 { 667 this.isSensitive = isSensitive; 668 } 669 670 671 672 /** 673 * Indicates whether this argument has been registered with the argument 674 * parser. 675 * 676 * @return {@code true} if this argument has been registered with the 677 * argument parser, or {@code false} if not. 678 */ 679 boolean isRegistered() 680 { 681 return isRegistered; 682 } 683 684 685 686 /** 687 * Specifies that this argument has been registered with the argument parser. 688 * This method should only be called by the argument parser method used to 689 * register the argument. 690 * 691 * @throws ArgumentException If this argument has already been registered. 692 */ 693 void setRegistered() 694 throws ArgumentException 695 { 696 if (isRegistered) 697 { 698 throw new ArgumentException(ERR_ARG_ALREADY_REGISTERED.get( 699 getIdentifierString())); 700 } 701 702 isRegistered = true; 703 } 704 705 706 707 /** 708 * Retrieves a concise name of the data type with which this argument is 709 * associated. 710 * 711 * @return A concise name of the data type with which this argument is 712 * associated. 713 */ 714 public abstract String getDataTypeName(); 715 716 717 718 /** 719 * Retrieves a human-readable string with information about any constraints 720 * that may be imposed for values of this argument. 721 * 722 * @return A human-readable string with information about any constraints 723 * that may be imposed for values of this argument, or {@code null} 724 * if there are none. 725 */ 726 public String getValueConstraints() 727 { 728 return null; 729 } 730 731 732 733 /** 734 * Resets this argument so that it appears in the same form as before it was 735 * used to parse arguments. Subclasses that override this method must call 736 * {@code super.reset()} to ensure that all necessary reset processing is 737 * performed. 738 */ 739 protected void reset() 740 { 741 numOccurrences = 0; 742 } 743 744 745 746 /** 747 * Creates a copy of this argument that is "clean" and appears as if it has 748 * not been used in the course of parsing an argument set. The new argument 749 * will have all of the same identifiers and constraints as this parser. 750 * 751 * @return The "clean" copy of this argument. 752 */ 753 public abstract Argument getCleanCopy(); 754 755 756 757 /** 758 * Updates the provided list to add any strings that should be included on the 759 * command line in order to represent this argument's current state. 760 * 761 * @param argStrings The list to update with the string representation of 762 * the command-line arguments. 763 */ 764 protected abstract void addToCommandLine(final List<String> argStrings); 765 766 767 768 /** 769 * Retrieves a string representation of this argument. 770 * 771 * @return A string representation of this argument. 772 */ 773 public final String toString() 774 { 775 final StringBuilder buffer = new StringBuilder(); 776 toString(buffer); 777 return buffer.toString(); 778 } 779 780 781 782 /** 783 * Appends a string representation of this argument to the provided buffer. 784 * 785 * @param buffer The buffer to which the information should be appended. 786 */ 787 public abstract void toString(final StringBuilder buffer); 788 789 790 791 /** 792 * Appends a basic set of information for this argument to the provided 793 * buffer in a form suitable for use in the {@code toString} method. 794 * 795 * @param buffer The buffer to which information should be appended. 796 */ 797 protected void appendBasicToStringInfo(final StringBuilder buffer) 798 { 799 switch (shortIdentifiers.size()) 800 { 801 case 0: 802 // Nothing to add. 803 break; 804 805 case 1: 806 buffer.append("shortIdentifier='-"); 807 buffer.append(shortIdentifiers.get(0)); 808 buffer.append('\''); 809 break; 810 811 default: 812 buffer.append("shortIdentifiers={"); 813 814 final Iterator<Character> iterator = shortIdentifiers.iterator(); 815 while (iterator.hasNext()) 816 { 817 buffer.append("'-"); 818 buffer.append(iterator.next()); 819 buffer.append('\''); 820 821 if (iterator.hasNext()) 822 { 823 buffer.append(", "); 824 } 825 } 826 buffer.append('}'); 827 break; 828 } 829 830 if (! shortIdentifiers.isEmpty()) 831 { 832 buffer.append(", "); 833 } 834 835 switch (longIdentifiers.size()) 836 { 837 case 0: 838 // Nothing to add. 839 break; 840 841 case 1: 842 buffer.append("longIdentifier='--"); 843 buffer.append(longIdentifiers.get(0)); 844 buffer.append('\''); 845 break; 846 847 default: 848 buffer.append("longIdentifiers={"); 849 850 final Iterator<String> iterator = longIdentifiers.iterator(); 851 while (iterator.hasNext()) 852 { 853 buffer.append("'--"); 854 buffer.append(iterator.next()); 855 buffer.append('\''); 856 857 if (iterator.hasNext()) 858 { 859 buffer.append(", "); 860 } 861 } 862 buffer.append('}'); 863 break; 864 } 865 866 buffer.append(", description='"); 867 buffer.append(description); 868 869 if (argumentGroupName != null) 870 { 871 buffer.append("', argumentGroup='"); 872 buffer.append(argumentGroupName); 873 } 874 875 buffer.append("', isRequired="); 876 buffer.append(isRequired); 877 878 buffer.append(", maxOccurrences="); 879 if (maxOccurrences == 0) 880 { 881 buffer.append("unlimited"); 882 } 883 else 884 { 885 buffer.append(maxOccurrences); 886 } 887 888 if (valuePlaceholder == null) 889 { 890 buffer.append(", takesValue=false"); 891 } 892 else 893 { 894 buffer.append(", takesValue=true, valuePlaceholder='"); 895 buffer.append(valuePlaceholder); 896 buffer.append('\''); 897 } 898 899 if (isHidden) 900 { 901 buffer.append(", isHidden=true"); 902 } 903 } 904}