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 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 }