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