001 /*
002 * Copyright 2008-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 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 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 }