001: /*
002: * Copyright (C) 2007 Stephen Ostermiller
003: * http://ostermiller.org/contact.pl?regarding=Java+Utilities
004: *
005: * This program is free software; you can redistribute it and/or modify
006: * it under the terms of the GNU General Public License as published by
007: * the Free Software Foundation; either version 2 of the License, or
008: * (at your option) any later version.
009: *
010: * This program is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: * GNU General Public License for more details.
014: *
015: * See COPYING.TXT for details.
016: */
017: package com.Ostermiller.util;
018:
019: import java.util.*;
020:
021: /**
022: * A command line option used by the CommandLineOptions parser.
023: *
024: * More information about this class and code samples for suggested use are
025: * available from <a target="_top" href=
026: * "http://ostermiller.org/utils/CmdLn.html">ostermiller.org</a>.
027: *
028: * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
029: * @since ostermillerutils 1.07.00
030: */
031: public final class CmdLnOption {
032:
033: /**
034: * The list of long names that specify this option.
035: * May be null, but here must be at least one long
036: * or short name.
037: *
038: * @since ostermillerutils 1.07.00
039: */
040: private List<String> longNames;
041:
042: /**
043: * The list of short names that specify this option.
044: * May be null, but here must be at least one long
045: * or short name.
046: *
047: * @since ostermillerutils 1.07.00
048: */
049: private List<Character> shortNames;
050:
051: /**
052: * The minimum number of arguments that this
053: * option must have.
054: *
055: * @since ostermillerutils 1.07.00
056: */
057: private int minArguments = 0;
058:
059: /**
060: * The maximum number of arguments that this
061: * option may have.
062: *
063: * @since ostermillerutils 1.07.00
064: */
065: private int maxArguments = 0;
066:
067: /**
068: * Whether public setters can be called on this option.
069: * This option becomes immutable once it is used
070: * by a command line.
071: *
072: * @since ostermillerutils 1.07.00
073: */
074: private boolean mutable = true;
075:
076: /**
077: * The description of this object for use in the help
078: * message (may be null).
079: *
080: * @since ostermillerutils 1.07.00
081: */
082: private String description = null;
083:
084: /**
085: * Listener associated with this object that should
086: * be called when a result is found (may be null).
087: *
088: * @since ostermillerutils 1.07.00
089: */
090: private CmdLnListener callback;
091:
092: /**
093: * @param longNames list long names for this option
094: * @throws IllegalArgumentException if the the list does not contain at least one long name
095: *
096: * @since ostermillerutils 1.07.00
097: */
098: public CmdLnOption(String[] longNames) {
099: this (longNames, null);
100: }
101:
102: /**
103: * @param shortNames list short names for this option
104: * @throws IllegalArgumentException if the the list does not contain at least one short name
105: *
106: * @since ostermillerutils 1.07.00
107: */
108: public CmdLnOption(char[] shortNames) {
109: this (null, shortNames);
110: }
111:
112: /**
113: * @param longName the long name for this option
114: * @throws IllegalArgumentException if the name is null
115: *
116: * @since ostermillerutils 1.07.00
117: */
118: public CmdLnOption(String longName) {
119: this (longName, null);
120: }
121:
122: /**
123: * @param shortName the short name for this option
124: *
125: * @since ostermillerutils 1.07.00
126: */
127: public CmdLnOption(Character shortName) {
128: this (null, shortName);
129: }
130:
131: /**
132: * @param longNames list long names for this option
133: * @param shortNames list short names for this option
134: * @throws IllegalArgumentException if the the lists do not contain at least one name
135: *
136: * @since ostermillerutils 1.07.00
137: */
138: public CmdLnOption(String[] longNames, char[] shortNames) {
139:
140: if ((longNames == null || longNames.length == 0)
141: && (shortNames == null || shortNames.length == 0)) {
142: throw new IllegalArgumentException(
143: "At least one long name or short name must be specified");
144: }
145:
146: if (longNames == null) {
147: this .longNames = new ArrayList<String>(0);
148: } else {
149: this .longNames = new ArrayList<String>(longNames.length);
150: addLongNames(longNames);
151: }
152:
153: if (shortNames == null) {
154: this .shortNames = new ArrayList<Character>(0);
155: } else {
156: this .shortNames = new ArrayList<Character>(
157: shortNames.length);
158: addShortNames(shortNames);
159: }
160: }
161:
162: /**
163: * @param longName the long name for this option
164: * @param shortName the short name for this option
165: *
166: * @since ostermillerutils 1.07.00
167: */
168: public CmdLnOption(String longName, Character shortName) {
169:
170: if (longName == null && shortName == null) {
171: throw new IllegalArgumentException(
172: "A long name or short name must be specified");
173: }
174:
175: if (longName == null) {
176: this .longNames = new ArrayList<String>(0);
177: } else {
178: this .longNames = new ArrayList<String>(1);
179: addLongName(longName);
180: }
181:
182: if (shortName == null) {
183: this .shortNames = new ArrayList<Character>(0);
184: } else {
185: this .shortNames = new ArrayList<Character>(1);
186: addShortName(shortName);
187: }
188: }
189:
190: /**
191: * Called by the command line options parser
192: * to set this option to not modifiable.
193: *
194: * @since ostermillerutils 1.07.00
195: */
196: void setImmutable() {
197: if (!mutable) {
198: throw new IllegalStateException(
199: "Command line argument already immutable (Used by more than one CommandLineOption?)");
200: }
201: mutable = false;
202: }
203:
204: /**
205: * Throw an exception if no longer mutable
206: *
207: * @throws IllegalStateException if not mutable
208: *
209: * @since ostermillerutils 1.07.00
210: */
211: private void checkState() {
212: if (!mutable) {
213: throw new IllegalStateException(
214: "option no longer modifiable");
215: }
216: }
217:
218: /**
219: * Sets the argument bounds to require no arguments
220: * (zero arguments minimum, zero arguments maximum).
221: * This is the default state for a new command line option.
222: *
223: * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
224: * @return this command line option for method chaining
225: *
226: * @since ostermillerutils 1.07.00
227: */
228: public CmdLnOption setNoArguments() {
229: setArgumentBounds(0, 0);
230: return this ;
231: }
232:
233: /**
234: * Sets the argument bounds for a single optional argument
235: * (zero arguments minimum, one argument maximum).
236: *
237: * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
238: * @return this command line option for method chaining
239: *
240: * @since ostermillerutils 1.07.00
241: */
242: public CmdLnOption setOptionalArgument() {
243: setArgumentBounds(0, 1);
244: return this ;
245: }
246:
247: /**
248: * Sets the argument bounds for a single required argument
249: * (one argument minimum, one argument maximum).
250: *
251: * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
252: * @return this command line option for method chaining
253: *
254: * @since ostermillerutils 1.07.00
255: */
256: public CmdLnOption setRequiredArgument() {
257: setArgumentBounds(1, 1);
258: return this ;
259: }
260:
261: /**
262: * Sets the argument bounds for unlimited (but optional) arguments
263: * (zero arguments minimum, Integer.MAX_VALUE arguments maximum).
264: *
265: * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
266: * @return this command line option for method chaining
267: */
268: public CmdLnOption setUnlimitedArguments() {
269: setArgumentBounds(0, Integer.MAX_VALUE);
270: return this ;
271: }
272:
273: /**
274: * Sets the bounds for command line arguments.
275: *
276: * @param minArguments the minimum number of arguments this command line option should expect
277: * @param maxArguments the maximum number of arguments this command line option will accept
278: * @throws IllegalArgumentException if minimum arguments is negative
279: * @throws IllegalArgumentException if maximum arguments is less than minimum arguments
280: * @throws IllegalStateException if this argument has already been used in parsing CommandLineOptions
281: * @return this command line option for method chaining
282: *
283: * @since ostermillerutils 1.07.00
284: */
285: public CmdLnOption setArgumentBounds(int minArguments,
286: int maxArguments) {
287: checkState();
288: if (minArguments < 0)
289: throw new IllegalArgumentException(
290: "min arguments cannot be negative");
291: if (maxArguments < minArguments)
292: throw new IllegalArgumentException(
293: "max arguments cannot be less than min arguments");
294: this .minArguments = minArguments;
295: this .maxArguments = maxArguments;
296: return this ;
297: }
298:
299: /**
300: * @param longNames long names to be added
301: * @return this for method chaining
302: * @throws IllegalArgumentException if the name is null or blank
303: *
304: * @since ostermillerutils 1.07.00
305: */
306: public CmdLnOption addLongNames(Collection<String> longNames) {
307: checkState();
308: for (String name : longNames) {
309: addLongName(name);
310: }
311: return this ;
312: }
313:
314: /**
315: * @param longNames long names to be added
316: * @return this for method chaining
317: * @throws IllegalArgumentException if the name is null or blank
318: *
319: * @since ostermillerutils 1.07.00
320: */
321: public CmdLnOption addLongNames(String[] longNames) {
322: checkState();
323: for (String name : longNames) {
324: addLongName(name);
325: }
326: return this ;
327: }
328:
329: /**
330: * @param name long name to be added
331: * @return this for method chaining
332: * @throws IllegalArgumentException if the name is null or blank
333: *
334: * @since ostermillerutils 1.07.00
335: */
336: public CmdLnOption addLongName(String name) {
337: checkState();
338: if (name == null) {
339: throw new IllegalArgumentException(
340: "long name cannot be null");
341: }
342: if ("".equals(name)) {
343: throw new IllegalArgumentException(
344: "long name cannot be blank");
345: }
346: longNames.add(name);
347: return this ;
348: }
349:
350: /**
351: * @param shortNames short names to be added
352: * @return this for method chaining
353: * @throws IllegalArgumentException if the name is null or blank
354: *
355: * @since ostermillerutils 1.07.00
356: */
357: public CmdLnOption addShortNames(Collection<Character> shortNames) {
358: checkState();
359: for (Character name : shortNames) {
360: addShortName(name);
361: }
362: return this ;
363: }
364:
365: /**
366: * @param shortNames short names to be added
367: * @return this for method chaining
368: * @throws IllegalArgumentException if the name is null or blank
369: *
370: * @since ostermillerutils 1.07.00
371: */
372: public CmdLnOption addShortNames(char[] shortNames) {
373: checkState();
374: for (char name : shortNames) {
375: addShortName(name);
376: }
377: return this ;
378: }
379:
380: /**
381: * @param shortNames short names to be added
382: * @return this for method chaining
383: * @throws IllegalArgumentException if the name is null or blank
384: *
385: * @since ostermillerutils 1.07.00
386: */
387: public CmdLnOption addShortNames(Character[] shortNames) {
388: checkState();
389: for (char name : shortNames) {
390: addShortName(name);
391: }
392: return this ;
393: }
394:
395: /**
396: * @param name short name to be added
397: * @return this for method chaining
398: * @throws IllegalArgumentException if the name is null or blank
399: *
400: * @since ostermillerutils 1.07.00
401: */
402: public CmdLnOption addShortName(Character name) {
403: checkState();
404: if (name == null) {
405: throw new IllegalArgumentException(
406: "short name cannot be null");
407: }
408: if ("".equals(name)) {
409: throw new IllegalArgumentException(
410: "short name cannot be blank");
411: }
412: shortNames.add(name);
413: return this ;
414: }
415:
416: /**
417: * Get the first long name or null if no long names
418: *
419: * @return long name
420: *
421: * @since ostermillerutils 1.07.00
422: */
423: String getLongName() {
424: if (longNames.size() > 1)
425: return null;
426: return longNames.get(0);
427: }
428:
429: /**
430: * Get the entire list of long names
431: * @return unmodifiable list of long names
432: */
433: List<String> getLongNames() {
434: return Collections.unmodifiableList(longNames);
435: }
436:
437: /**
438: * Get the first short name or null if no short names
439: *
440: * @return short name
441: *
442: * @since ostermillerutils 1.07.00
443: */
444: Character getShortName() {
445: if (shortNames.size() > 1)
446: return null;
447: return shortNames.get(0);
448: }
449:
450: /**
451: * Get the entire list of short names
452: *
453: * @return unmodifiable list of short names
454: *
455: * @since ostermillerutils 1.07.00
456: */
457: List<Character> getShortNames() {
458: return Collections.unmodifiableList(shortNames);
459: }
460:
461: /**
462: * @return the minimum number of arguments allowed
463: *
464: * @since ostermillerutils 1.07.00
465: */
466: int getMinArguments() {
467: return minArguments;
468: }
469:
470: /**
471: * @return the maximum number of arguments allowed
472: *
473: * @since ostermillerutils 1.07.00
474: */
475: int getMaxArguments() {
476: return maxArguments;
477: }
478:
479: /**
480: * Get the length of the argument specification portion of
481: * the help message in characters. Does not include any space
482: * between the specification and the description.
483: *
484: * @param longStart What long options start with (typically "--")
485: * @param shortStart What short options start with (typically "-")
486: * @return number of characters in the argument specification
487: *
488: * @since ostermillerutils 1.07.00
489: */
490: int getHelpArgumentsLength(String longStart, String shortStart) {
491: if (description == null) {
492: return 0;
493: }
494: int length = 1;
495: if (shortStart != null && shortNames.size() > 0) {
496: length += 1;
497: length += shortStart.length();
498: length += 1;
499: }
500: if (longStart != null && longNames.size() > 0) {
501: length += 1;
502: length += longStart.length();
503: length += longNames.get(0).length();
504: }
505: if (getMaxArguments() > 0) {
506: length += 4;
507: }
508: length += 3;
509: return length;
510: }
511:
512: /**
513: * Get the help message for this option appropriate for inclusion in
514: * "print help".
515: * <p>
516: * It will be formatted like this:
517: * <pre> --option -o <?> description</pre>
518: * Two spaces at the beginning, and at least two spaces after the option
519: * specification before the description. If the indent is large,
520: * there may be more spaces before the description.
521: * <p>
522: * If it is longer that the line width and must wrap, the description will
523: * continue on the next line which will start with eight spaces.
524: *
525: * @param longStart What long options start with (typically "--")
526: * @param shortStart What short options start with (typically "-")
527: * @param indent Minimum character count at which to start the description after specifying the option
528: * @param lineWidth Character count at which to wrap (if possible)
529: * @return help message
530: *
531: * @since ostermillerutils 1.07.00
532: */
533: String getHelp(String longStart, String shortStart, int indent,
534: int lineWidth) {
535: if (description == null) {
536: return null;
537: }
538: int expectedLength = 32;
539: expectedLength += description.length();
540: StringBuffer sb = new StringBuffer(expectedLength);
541: sb.append(" ");
542: if (shortStart != null && shortNames.size() > 0) {
543: sb.append(" ").append(shortStart).append(shortNames.get(0));
544: }
545: if (longStart != null && longNames.size() > 0) {
546: sb.append(" ").append(longStart).append(longNames.get(0));
547: }
548: if (getMaxArguments() > 0) {
549: sb.append(" ").append("<?>");
550: }
551: while (indent > sb.length() + 2) {
552: sb.append(" ");
553: }
554: sb.append(" ");
555: int descriptionIndex = 0;
556: int charactersLeft = lineWidth - sb.length();
557: for (int lineNumber = 1; descriptionIndex < description
558: .length(); lineNumber++) {
559: int endIndex = descriptionIndex + charactersLeft;
560: if (endIndex > description.length()) {
561: endIndex = description.length();
562: } else {
563: if (description.charAt(endIndex) == ' ') {
564: // Space right at the break
565: } else if (description.lastIndexOf(' ', endIndex) > descriptionIndex) {
566: // Space sometime before the break
567: endIndex = description.lastIndexOf(' ', endIndex);
568: } else if (description.indexOf(' ', endIndex) != -1) {
569: // Space sometime after the break
570: endIndex = description.lastIndexOf(' ', endIndex);
571: } else {
572: // No remaining spaces
573: endIndex = description.length();
574: }
575: }
576: if (lineNumber != 1) {
577: sb.append("\n ");
578: }
579: sb.append(description.substring(descriptionIndex, endIndex)
580: .trim());
581: descriptionIndex = endIndex + 1;
582: charactersLeft = lineWidth - 8;
583: }
584:
585: return sb.toString();
586: }
587:
588: /**
589: * Get a short string description this option.
590: * It will be either the long name (if it has one)
591: * or the short name if it does not have a long name
592: *
593: * @return string representation
594: *
595: * @since ostermillerutils 1.07.00
596: */
597: @Override
598: public String toString() {
599: if (longNames.size() > 0) {
600: return longNames.get(0);
601: }
602: return shortNames.get(0).toString();
603: }
604:
605: /**
606: * Get the call back object
607: *
608: * @return the call back object
609: *
610: * @since ostermillerutils 1.07.00
611: */
612: CmdLnListener getListener() {
613: return callback;
614: }
615:
616: /**
617: * Set the call back object
618: *
619: * @param callback the call back object
620: * @return this for method chaining
621: *
622: * @since ostermillerutils 1.07.00
623: */
624: public CmdLnOption setListener(CmdLnListener callback) {
625: this .callback = callback;
626: return this ;
627: }
628:
629: /**
630: * An object that may be set by the user.
631: * Suggested use: set the user object to an enum
632: * value that can be used in a switch statement.
633: *
634: * @since ostermillerutils 1.07.00
635: */
636: private Object userObject;
637:
638: /**
639: * An object that may be set by the user.
640: * Suggested use: set the user object to an enum
641: * value that can be used in a switch statement.
642: *
643: * @since ostermillerutils 1.07.00
644: *
645: * @return the userObject
646: */
647: public Object getUserObject() {
648: return userObject;
649: }
650:
651: /**
652: * An object that may be set by the user.
653: * Suggested use: set the user object to an enum
654: * value that can be used in a switch statement.
655: *
656: * @param userObject the userObject to set
657: * @return this for method chaining
658: *
659: * @since ostermillerutils 1.07.00
660: */
661: public CmdLnOption setUserObject(Object userObject) {
662: this .userObject = userObject;
663: return this ;
664: }
665:
666: /**
667: * @return the description used in the help message or null if
668: * no description has been set.
669: *
670: * @since ostermillerutils 1.07.00
671: */
672: public String getDescription() {
673: return description;
674: }
675:
676: /**
677: * @param description the description used in the help message
678: * @return this for method chaining
679: *
680: * @since ostermillerutils 1.07.00
681: */
682: public CmdLnOption setDescription(String description) {
683: this.description = description;
684: return this;
685: }
686: }
|