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