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 */
021 package com.unboundid.util.args;
022
023
024
025 import java.io.Serializable;
026
027 import java.util.ArrayList;
028 import java.util.Collections;
029 import java.util.Iterator;
030 import java.util.List;
031
032 import com.unboundid.util.Mutable;
033 import com.unboundid.util.NotExtensible;
034 import com.unboundid.util.ThreadSafety;
035 import com.unboundid.util.ThreadSafetyLevel;
036
037 import 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)
049 public 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 }