001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.util.args;
028 import org.opends.messages.Message;
029 import org.opends.messages.MessageBuilder;
030
031 import static org.opends.messages.UtilityMessages.*;
032 import static org.opends.server.tools.ToolConstants.*;
033 import static org.opends.server.util.ServerConstants.*;
034 import static org.opends.server.util.StaticUtils.*;
035
036 import java.io.FileInputStream;
037 import java.io.IOException;
038 import java.io.OutputStream;
039 import java.util.ArrayList;
040 import java.util.Collection;
041 import java.util.Collections;
042 import java.util.HashMap;
043 import java.util.LinkedList;
044 import java.util.Map;
045 import java.util.Properties;
046 import java.util.SortedMap;
047 import java.util.TreeMap;
048
049 import org.opends.server.core.DirectoryServer;
050 import org.opends.server.util.SetupUtils;
051
052
053
054 /**
055 * This class defines a variant of the argument parser that can be used with
056 * applications that use subcommands to customize their behavior and that have a
057 * different set of options per subcommand (e.g, "cvs checkout" takes different
058 * options than "cvs commit"). This parser also has the ability to use global
059 * options that will always be applicable regardless of the subcommand in
060 * addition to the subcommand-specific arguments. There must not be any
061 * conflicts between the global options and the option for any subcommand, but
062 * it is allowed to re-use subcommand-specific options for different purposes
063 * between different subcommands.
064 */
065 public class SubCommandArgumentParser extends ArgumentParser
066 {
067 // The argument that will be used to trigger the display of usage information.
068 private Argument usageArgument;
069
070 // The arguments that will be used to trigger the display of usage
071 // information for groups of sub-commands.
072 private Map<Argument, Collection<SubCommand>> usageGroupArguments;
073
074 // The set of unnamed trailing arguments that were provided for this parser.
075 private ArrayList<String> trailingArguments;
076
077 // Indicates whether subcommand and long argument names should be treated in a
078 // case-sensitive manner.
079 private boolean longArgumentsCaseSensitive;
080
081 // Indicates whether the usage information has been displayed.
082 private boolean usageOrVersionDisplayed;
083
084 // The set of global arguments defined for this parser, referenced by short
085 // ID.
086 private HashMap<Character,Argument> globalShortIDMap;
087
088 // The set of global arguments defined for this parser, referenced by
089 // argument name.
090 private HashMap<String,Argument> globalArgumentMap;
091
092 // The set of global arguments defined for this parser, referenced by long
093 // ID.
094 private HashMap<String,Argument> globalLongIDMap;
095
096 // The set of subcommands defined for this parser, referenced by subcommand
097 // name.
098 private SortedMap<String,SubCommand> subCommands;
099
100 // The total set of global arguments defined for this parser.
101 private LinkedList<Argument> globalArgumentList;
102
103 // The output stream to which usage information should be printed.
104 private OutputStream usageOutputStream;
105
106 // The fully-qualified name of the Java class that should be invoked to launch
107 // the program with which this argument parser is associated.
108 private String mainClassName;
109
110 // A human-readable description for the tool, which will be included when
111 // displaying usage information.
112 private Message toolDescription;
113
114 // The raw set of command-line arguments that were provided.
115 private String[] rawArguments;
116
117 // The subcommand requested by the user as part of the command-line arguments.
118 private SubCommand subCommand;
119
120 //Indicates whether the version argument was provided.
121 private boolean versionPresent;
122
123 private final static String INDENT = " ";
124 private final static int MAX_LENGTH = SetupUtils.isWindows() ? 79 : 80;
125
126
127 /**
128 * Creates a new instance of this subcommand argument parser with no
129 * arguments.
130 *
131 * @param mainClassName The fully-qualified name of the Java
132 * class that should be invoked to launch
133 * the program with which this argument
134 * parser is associated.
135 * @param toolDescription A human-readable description for the
136 * tool, which will be included when
137 * displaying usage information.
138 * @param longArgumentsCaseSensitive Indicates whether subcommand and long
139 * argument names should be treated in a
140 * case-sensitive manner.
141 */
142 public SubCommandArgumentParser(String mainClassName, Message toolDescription,
143 boolean longArgumentsCaseSensitive)
144 {
145 super(mainClassName, toolDescription, longArgumentsCaseSensitive);
146 this.mainClassName = mainClassName;
147 this.toolDescription = toolDescription;
148 this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
149
150 trailingArguments = new ArrayList<String>();
151 globalArgumentList = new LinkedList<Argument>();
152 globalArgumentMap = new HashMap<String,Argument>();
153 globalShortIDMap = new HashMap<Character,Argument>();
154 globalLongIDMap = new HashMap<String,Argument>();
155 usageGroupArguments = new HashMap<Argument, Collection<SubCommand>>();
156 subCommands = new TreeMap<String,SubCommand>();
157 usageOrVersionDisplayed = false;
158 rawArguments = null;
159 subCommand = null;
160 usageArgument = null;
161 usageOutputStream = null;
162 }
163
164
165
166 /**
167 * Retrieves the fully-qualified name of the Java class that should be invoked
168 * to launch the program with which this argument parser is associated.
169 *
170 * @return The fully-qualified name of the Java class that should be invoked
171 * to launch the program with which this argument parser is
172 * associated.
173 */
174 public String getMainClassName()
175 {
176 return mainClassName;
177 }
178
179
180
181 /**
182 * Retrieves a human-readable description for this tool, which should be
183 * included at the top of the command-line usage information.
184 *
185 * @return A human-readable description for this tool, or {@code null} if
186 * none is available.
187 */
188 public Message getToolDescription()
189 {
190 return toolDescription;
191 }
192
193
194
195 /**
196 * Indicates whether subcommand names and long argument strings should be
197 * treated in a case-sensitive manner.
198 *
199 * @return <CODE>true</CODE> if subcommand names and long argument strings
200 * should be treated in a case-sensitive manner, or
201 * <CODE>false</CODE> if they should not.
202 */
203 public boolean longArgumentsCaseSensitive()
204 {
205 return longArgumentsCaseSensitive;
206 }
207
208
209
210 /**
211 * Retrieves the list of all global arguments that have been defined for this
212 * argument parser.
213 *
214 * @return The list of all global arguments that have been defined for this
215 * argument parser.
216 */
217 public LinkedList<Argument> getGlobalArgumentList()
218 {
219 return globalArgumentList;
220 }
221
222
223
224 /**
225 * Indicates whether this argument parser contains a global argument with the
226 * specified name.
227 *
228 * @param argumentName The name for which to make the determination.
229 *
230 * @return <CODE>true</CODE> if a global argument exists with the specified
231 * name, or <CODE>false</CODE> if not.
232 */
233 public boolean hasGlobalArgument(String argumentName)
234 {
235 return globalArgumentMap.containsKey(argumentName);
236 }
237
238
239
240 /**
241 * Retrieves the global argument with the specified name.
242 *
243 * @param name The name of the global argument to retrieve.
244 *
245 * @return The global argument with the specified name, or <CODE>null</CODE>
246 * if there is no such argument.
247 */
248 public Argument getGlobalArgument(String name)
249 {
250 return globalArgumentMap.get(name);
251 }
252
253
254
255 /**
256 * Retrieves the set of global arguments mapped by the short identifier that
257 * may be used to reference them. Note that arguments that do not have a
258 * short identifier will not be present in this list.
259 *
260 * @return The set of global arguments mapped by the short identifier that
261 * may be used to reference them.
262 */
263 public HashMap<Character,Argument> getGlobalArgumentsByShortID()
264 {
265 return globalShortIDMap;
266 }
267
268
269
270 /**
271 * Indicates whether this argument parser has a global argument with the
272 * specified short ID.
273 *
274 * @param shortID The short ID character for which to make the
275 * determination.
276 *
277 * @return <CODE>true</CODE> if a global argument exists with the specified
278 * short ID, or <CODE>false</CODE> if not.
279 */
280 public boolean hasGlobalArgumentWithShortID(Character shortID)
281 {
282 return globalShortIDMap.containsKey(shortID);
283 }
284
285
286
287 /**
288 * Retrieves the global argument with the specified short identifier.
289 *
290 * @param shortID The short identifier for the global argument to retrieve.
291 *
292 * @return The global argument with the specified short identifier, or
293 * <CODE>null</CODE> if there is no such argument.
294 */
295 public Argument getGlobalArgumentForShortID(Character shortID)
296 {
297 return globalShortIDMap.get(shortID);
298 }
299
300
301
302 /**
303 * Retrieves the set of global arguments mapped by the long identifier that
304 * may be used to reference them. Note that arguments that do not have a long
305 * identifier will not be present in this list.
306 *
307 * @return The set of global arguments mapped by the long identifier that may
308 * be used to reference them.
309 */
310 public HashMap<String,Argument> getGlobalArgumentsByLongID()
311 {
312 return globalLongIDMap;
313 }
314
315
316
317 /**
318 * Indicates whether this argument parser has a global argument with the
319 * specified long ID.
320 *
321 * @param longID The long ID string for which to make the determination.
322 *
323 * @return <CODE>true</CODE> if a global argument exists with the specified
324 * long ID, or <CODE>false</CODE> if not.
325 */
326 public boolean hasGlobalArgumentWithLongID(String longID)
327 {
328 return globalLongIDMap.containsKey(longID);
329 }
330
331
332
333 /**
334 * Retrieves the global argument with the specified long identifier.
335 *
336 * @param longID The long identifier for the global argument to retrieve.
337 *
338 * @return The global argument with the specified long identifier, or
339 * <CODE>null</CODE> if there is no such argument.
340 */
341 public Argument getGlobalArgumentForLongID(String longID)
342 {
343 return globalLongIDMap.get(longID);
344 }
345
346
347
348 /**
349 * Retrieves the set of subcommands defined for this argument parser,
350 * referenced by subcommand name.
351 *
352 * @return The set of subcommands defined for this argument parser,
353 * referenced by subcommand name.
354 */
355 public SortedMap<String,SubCommand> getSubCommands()
356 {
357 return subCommands;
358 }
359
360
361
362 /**
363 * Indicates whether this argument parser has a subcommand with the specified
364 * name.
365 *
366 * @param name The subcommand name for which to make the determination.
367 *
368 * @return <CODE>true</CODE> if this argument parser has a subcommand with
369 * the specified name, or <CODE>false</CODE> if it does not.
370 */
371 public boolean hasSubCommand(String name)
372 {
373 return subCommands.containsKey(name);
374 }
375
376
377
378 /**
379 * Retrieves the subcommand with the specified name.
380 *
381 * @param name The name of the subcommand to retrieve.
382 *
383 * @return The subcommand with the specified name, or <CODE>null</CODE> if no
384 * such subcommand is defined.
385 */
386 public SubCommand getSubCommand(String name)
387 {
388 return subCommands.get(name);
389 }
390
391
392
393 /**
394 * Retrieves the subcommand that was selected in the set of command-line
395 * arguments.
396 *
397 * @return The subcommand that was selected in the set of command-line
398 * arguments, or <CODE>null</CODE> if none was selected.
399 */
400 public SubCommand getSubCommand()
401 {
402 return subCommand;
403 }
404
405
406
407 /**
408 * Retrieves the raw set of arguments that were provided.
409 *
410 * @return The raw set of arguments that were provided, or <CODE>null</CODE>
411 * if the argument list has not yet been parsed.
412 */
413 public String[] getRawArguments()
414 {
415 return rawArguments;
416 }
417
418
419
420 /**
421 * Adds the provided argument to the set of global arguments handled by this
422 * parser.
423 *
424 * @param argument The argument to be added.
425 *
426 * @throws ArgumentException If the provided argument conflicts with another
427 * global or subcommand argument that has already
428 * been defined.
429 */
430 public void addGlobalArgument(Argument argument)
431 throws ArgumentException
432 {
433 addGlobalArgument(argument, null);
434 }
435
436
437 /**
438 * Adds the provided argument to the set of global arguments handled by this
439 * parser.
440 *
441 * @param argument The argument to be added.
442 * @param group The argument group to which the argument belongs.
443 * @throws ArgumentException If the provided argument conflicts with another
444 * global or subcommand argument that has already
445 * been defined.
446 */
447 public void addGlobalArgument(Argument argument, ArgumentGroup group)
448 throws ArgumentException
449 {
450
451 String argumentName = argument.getName();
452 if (globalArgumentMap.containsKey(argumentName))
453 {
454 Message message =
455 ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_NAME.get(argumentName);
456 throw new ArgumentException(message);
457 }
458 for (SubCommand s : subCommands.values())
459 {
460 if (s.getArgumentForName(argumentName) != null)
461 {
462 Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_NAME_SUBCMD_CONFLICT.get(
463 argumentName, s.getName());
464 throw new ArgumentException(message);
465 }
466 }
467
468
469 Character shortID = argument.getShortIdentifier();
470 if (shortID != null)
471 {
472 if (globalShortIDMap.containsKey(shortID))
473 {
474 String name = globalShortIDMap.get(shortID).getName();
475
476 Message message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_SHORT_ID.get(
477 String.valueOf(shortID), argumentName, name);
478 throw new ArgumentException(message);
479 }
480
481 for (SubCommand s : subCommands.values())
482 {
483 if (s.getArgument(shortID) != null)
484 {
485 String cmdName = s.getName();
486 String name = s.getArgument(shortID).getName();
487
488 Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_SHORT_ID_CONFLICT.get(
489 String.valueOf(shortID), argumentName, name, cmdName);
490 throw new ArgumentException(message);
491 }
492 }
493 }
494
495
496 String longID = argument.getLongIdentifier();
497 if (longID != null)
498 {
499 if (! longArgumentsCaseSensitive)
500 {
501 longID = toLowerCase(longID);
502 }
503
504 if (globalLongIDMap.containsKey(longID))
505 {
506 String name = globalLongIDMap.get(longID).getName();
507
508 Message message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_LONG_ID.get(
509 argument.getLongIdentifier(), argumentName, name);
510 throw new ArgumentException(message);
511 }
512
513 for (SubCommand s : subCommands.values())
514 {
515 if (s.getArgument(longID) != null)
516 {
517 String cmdName = s.getName();
518 String name = s.getArgument(longID).getName();
519
520 Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_LONG_ID_CONFLICT.get(
521 argument.getLongIdentifier(), argumentName, name, cmdName);
522 throw new ArgumentException(message);
523 }
524 }
525 }
526
527
528 if (shortID != null)
529 {
530 globalShortIDMap.put(shortID, argument);
531 }
532
533 if (longID != null)
534 {
535 globalLongIDMap.put(longID, argument);
536 }
537
538 globalArgumentList.add(argument);
539
540 if (group == null) {
541 group = getStandardGroup(argument);
542 }
543 group.addArgument(argument);
544 argumentGroups.add(group);
545 }
546
547 /**
548 * Removes the provided argument from the set of global arguments handled by
549 * this parser.
550 *
551 * @param argument The argument to be removed.
552 */
553 protected void removeGlobalArgument(Argument argument)
554 {
555 String argumentName = argument.getName();
556 globalArgumentMap.remove(argumentName);
557
558 Character shortID = argument.getShortIdentifier();
559 if (shortID != null)
560 {
561 globalShortIDMap.remove(shortID);
562 }
563
564 String longID = argument.getLongIdentifier();
565 if (longID != null)
566 {
567 if (! longArgumentsCaseSensitive)
568 {
569 longID = toLowerCase(longID);
570 }
571
572 globalLongIDMap.remove(longID);
573 }
574
575 globalArgumentList.remove(argument);
576 }
577
578
579 /**
580 * Sets the provided argument as one which will automatically
581 * trigger the output of full usage information if it is provided on
582 * the command line and no further argument validation will be
583 * performed.
584 * <p>
585 * If sub-command groups are defined using the
586 * {@link #setUsageGroupArgument(Argument, Collection)} method, then
587 * this usage argument, when specified, will result in usage
588 * information being displayed which does not include information on
589 * sub-commands.
590 * <p>
591 * Note that the caller will still need to add this argument to the
592 * parser with the {@link #addGlobalArgument(Argument)} method, and
593 * the argument should not be required and should not take a value.
594 * Also, the caller will still need to check for the presence of the
595 * usage argument after calling {@link #parseArguments(String[])} to
596 * know that no further processing will be required.
597 *
598 * @param argument
599 * The argument whose presence should automatically trigger
600 * the display of full usage information.
601 * @param outputStream
602 * The output stream to which the usage information should
603 * be written.
604 */
605 public void setUsageArgument(Argument argument, OutputStream outputStream) {
606 usageArgument = argument;
607 usageOutputStream = outputStream;
608
609 usageGroupArguments.put(argument, Collections.<SubCommand>emptySet());
610 }
611
612
613
614 /**
615 * Sets the provided argument as one which will automatically
616 * trigger the output of partial usage information if it is provided
617 * on the command line and no further argument validation will be
618 * performed.
619 * <p>
620 * Partial usage information will include a usage synopsis, a
621 * summary of each of the sub-commands listed in the provided
622 * sub-commands collection, and a summary of the global options.
623 * <p>
624 * Note that the caller will still need to add this argument to the
625 * parser with the {@link #addGlobalArgument(Argument)} method, and
626 * the argument should not be required and should not take a value.
627 * Also, the caller will still need to check for the presence of the
628 * usage argument after calling {@link #parseArguments(String[])} to
629 * know that no further processing will be required.
630 *
631 * @param argument
632 * The argument whose presence should automatically trigger
633 * the display of partial usage information.
634 * @param subCommands
635 * The list of sub-commands which should have their usage
636 * displayed.
637 */
638 public void setUsageGroupArgument(Argument argument,
639 Collection<SubCommand> subCommands) {
640 usageGroupArguments.put(argument, subCommands);
641 }
642
643
644 /**
645 * Parses the provided set of arguments and updates the information associated
646 * with this parser accordingly.
647 *
648 * @param rawArguments The raw set of arguments to parse.
649 *
650 * @throws ArgumentException If a problem was encountered while parsing the
651 * provided arguments.
652 */
653 public void parseArguments(String[] rawArguments)
654 throws ArgumentException
655 {
656 parseArguments(rawArguments, null);
657 }
658
659
660
661 /**
662 * Parses the provided set of arguments and updates the information associated
663 * with this parser accordingly. Default values for unspecified arguments
664 * may be read from the specified properties file.
665 *
666 * @param rawArguments The set of raw arguments to parse.
667 * @param propertiesFile The path to the properties file to use to
668 * obtain default values for unspecified
669 * properties.
670 * @param requirePropertiesFile Indicates whether the parsing should fail if
671 * the provided properties file does not exist
672 * or is not accessible.
673 *
674 * @throws ArgumentException If a problem was encountered while parsing the
675 * provided arguments or interacting with the
676 * properties file.
677 */
678 public void parseArguments(String[] rawArguments, String propertiesFile,
679 boolean requirePropertiesFile)
680 throws ArgumentException
681 {
682 this.rawArguments = rawArguments;
683
684 Properties argumentProperties = null;
685
686 try
687 {
688 Properties p = new Properties();
689 FileInputStream fis = new FileInputStream(propertiesFile);
690 p.load(fis);
691 fis.close();
692 argumentProperties = p;
693 }
694 catch (Exception e)
695 {
696 if (requirePropertiesFile)
697 {
698 Message message = ERR_SUBCMDPARSER_CANNOT_READ_PROPERTIES_FILE.get(
699 String.valueOf(propertiesFile), getExceptionMessage(e));
700 throw new ArgumentException(message, e);
701 }
702 }
703
704 parseArguments(rawArguments, argumentProperties);
705 }
706
707
708
709 /**
710 * Parses the provided set of arguments and updates the information associated
711 * with this parser accordingly. Default values for unspecified arguments may
712 * be read from the specified properties if any are provided.
713 *
714 * @param rawArguments The set of raw arguments to parse.
715 * @param argumentProperties A set of properties that may be used to provide
716 * default values for arguments not included in
717 * the given raw arguments.
718 *
719 * @throws ArgumentException If a problem was encountered while parsing the
720 * provided arguments.
721 */
722 public void parseArguments(String[] rawArguments,
723 Properties argumentProperties)
724 throws ArgumentException
725 {
726 this.rawArguments = rawArguments;
727 this.subCommand = null;
728 this.trailingArguments = new ArrayList<String>();
729 this.usageOrVersionDisplayed = false;
730
731 boolean inTrailingArgs = false;
732
733 int numArguments = rawArguments.length;
734 for (int i=0; i < numArguments; i++)
735 {
736 String arg = rawArguments[i];
737
738 if (inTrailingArgs)
739 {
740 trailingArguments.add(arg);
741 if ((subCommand.getMaxTrailingArguments() > 0) &&
742 (trailingArguments.size() > subCommand.getMaxTrailingArguments()))
743 {
744 Message message = ERR_ARGPARSER_TOO_MANY_TRAILING_ARGS.get(
745 subCommand.getMaxTrailingArguments());
746 throw new ArgumentException(message);
747 }
748
749 continue;
750 }
751
752 if (arg.equals("--"))
753 {
754 inTrailingArgs = true;
755 }
756 else if (arg.startsWith("--"))
757 {
758 // This indicates that we are using the long name to reference the
759 // argument. It may be in any of the following forms:
760 // --name
761 // --name value
762 // --name=value
763
764 String argName = arg.substring(2);
765 String argValue = null;
766 int equalPos = argName.indexOf('=');
767 if (equalPos < 0)
768 {
769 // This is fine. The value is not part of the argument name token.
770 }
771 else if (equalPos == 0)
772 {
773 // The argument starts with "--=", which is not acceptable.
774 Message message = ERR_SUBCMDPARSER_LONG_ARG_WITHOUT_NAME.get(arg);
775 throw new ArgumentException(message);
776 }
777 else
778 {
779 // The argument is in the form --name=value, so parse them both out.
780 argValue = argName.substring(equalPos+1);
781 argName = argName.substring(0, equalPos);
782 }
783
784 // If we're not case-sensitive, then convert the name to lowercase.
785 String origArgName = argName;
786 if (! longArgumentsCaseSensitive)
787 {
788 argName = toLowerCase(argName);
789 }
790
791 // See if the specified name references a global argument. If not, then
792 // see if it references a subcommand argument.
793 Argument a = globalLongIDMap.get(argName);
794 if (a == null)
795 {
796 if (subCommand == null)
797 {
798 if (argName.equals("help"))
799 {
800 // "--help" will always be interpreted as requesting usage
801 // information.
802 try
803 {
804 getUsage(usageOutputStream);
805 } catch (Exception e) {}
806
807 return;
808 }
809 else
810 if (argName.equals(OPTION_LONG_PRODUCT_VERSION))
811 {
812 // "--version" will always be interpreted as requesting usage
813 // information.
814 try
815 {
816 versionPresent = true;
817 DirectoryServer.printVersion(usageOutputStream);
818 usageOrVersionDisplayed = true ;
819 } catch (Exception e) {}
820
821 return;
822 }
823 else
824 {
825 // There is no such global argument.
826 Message message =
827 ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_LONG_ID.get(
828 origArgName);
829 throw new ArgumentException(message);
830 }
831 }
832 else
833 {
834 a = subCommand.getArgument(argName);
835 if (a == null)
836 {
837 if (argName.equals("help"))
838 {
839 // "--help" will always be interpreted as requesting usage
840 // information.
841 try
842 {
843 getUsage(usageOutputStream);
844 } catch (Exception e) {}
845
846 return;
847 }
848 else
849 if (argName.equals(OPTION_LONG_PRODUCT_VERSION))
850 {
851 // "--version" will always be interpreted as requesting usage
852 // information.
853 try
854 {
855 versionPresent = true;
856 DirectoryServer.printVersion(usageOutputStream);
857 usageOrVersionDisplayed = true ;
858 } catch (Exception e) {}
859
860 return;
861 }
862 else
863 {
864 // There is no such global or subcommand argument.
865 Message message =
866 ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_LONG_ID.get(origArgName);
867 throw new ArgumentException(message);
868 }
869 }
870 }
871 }
872
873 a.setPresent(true);
874
875 // If this is a usage argument, then immediately stop and print
876 // usage information.
877 if (usageGroupArguments.containsKey(a))
878 {
879 try
880 {
881 getUsage(a, usageOutputStream);
882 } catch (Exception e) {}
883
884 return;
885 }
886
887 // See if the argument takes a value. If so, then make sure one was
888 // provided. If not, then make sure none was provided.
889 if (a.needsValue())
890 {
891 if (argValue == null)
892 {
893 if ((i+1) == numArguments)
894 {
895 Message message =
896 ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_LONG_ID.
897 get(argName);
898 throw new ArgumentException(message);
899 }
900
901 argValue = rawArguments[++i];
902 }
903
904 MessageBuilder invalidReason = new MessageBuilder();
905 if (! a.valueIsAcceptable(argValue, invalidReason))
906 {
907 Message message = ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_LONG_ID.
908 get(argValue, argName, invalidReason.toString());
909 throw new ArgumentException(message);
910 }
911
912 // If the argument already has a value, then make sure it is
913 // acceptable to have more than one.
914 if (a.hasValue() && (! a.isMultiValued()))
915 {
916 Message message =
917 ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_LONG_ID.get(origArgName);
918 throw new ArgumentException(message);
919 }
920
921 a.addValue(argValue);
922 }
923 else
924 {
925 if (argValue != null)
926 {
927 Message message =
928 ERR_SUBCMDPARSER_ARG_FOR_LONG_ID_DOESNT_TAKE_VALUE.get(
929 origArgName);
930 throw new ArgumentException(message);
931 }
932 }
933 }
934 else if (arg.startsWith("-"))
935 {
936 // This indicates that we are using the 1-character name to reference
937 // the argument. It may be in any of the following forms:
938 // -n
939 // -nvalue
940 // -n value
941 if (arg.equals("-"))
942 {
943 Message message = ERR_SUBCMDPARSER_INVALID_DASH_AS_ARGUMENT.get();
944 throw new ArgumentException(message);
945 }
946
947 char argCharacter = arg.charAt(1);
948 String argValue;
949 if (arg.length() > 2)
950 {
951 argValue = arg.substring(2);
952 }
953 else
954 {
955 argValue = null;
956 }
957
958
959 // Get the argument with the specified short ID. It may be either a
960 // global argument or a subcommand-specific argument.
961 Argument a = globalShortIDMap.get(argCharacter);
962 if (a == null)
963 {
964 if (subCommand == null)
965 {
966 if (argCharacter == '?')
967 {
968 // "-?" will always be interpreted as requesting usage.
969 try
970 {
971 getUsage(usageOutputStream);
972 if (usageArgument != null)
973 {
974 usageArgument.setPresent(true);
975 }
976 } catch (Exception e) {}
977
978 return;
979 }
980 else
981 if (argCharacter == OPTION_SHORT_PRODUCT_VERSION)
982 {
983 // "-V" will always be interpreted as requesting
984 // version information except if it's already defined.
985 boolean dashVAccepted = true;
986 if (globalShortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION))
987 {
988 dashVAccepted = false;
989 }
990 else
991 {
992 for (SubCommand subCmd : subCommands.values())
993 {
994 if (subCmd.getArgument(OPTION_SHORT_PRODUCT_VERSION) != null)
995 {
996 dashVAccepted = false;
997 break;
998 }
999 }
1000 }
1001 if (dashVAccepted)
1002 {
1003 usageOrVersionDisplayed = true;
1004 versionPresent = true;
1005 try
1006 {
1007 DirectoryServer.printVersion(usageOutputStream);
1008 }
1009 catch (Exception e)
1010 {
1011 }
1012 return;
1013 }
1014 else
1015 {
1016 // -V is defined in another suncommand, so we can
1017 // accepted it as the version information argument
1018 Message message =
1019 ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID.
1020 get(String.valueOf(argCharacter));
1021 throw new ArgumentException(message);
1022 }
1023 }
1024 else
1025 {
1026 // There is no such argument registered.
1027 Message message =
1028 ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID.
1029 get(String.valueOf(argCharacter));
1030 throw new ArgumentException(message);
1031 }
1032 }
1033 else
1034 {
1035 a = subCommand.getArgument(argCharacter);
1036 if (a == null)
1037 {
1038 if (argCharacter == '?')
1039 {
1040 // "-?" will always be interpreted as requesting usage.
1041 try
1042 {
1043 getUsage(usageOutputStream);
1044 } catch (Exception e) {}
1045
1046 return;
1047 }
1048 else
1049 if (argCharacter == OPTION_SHORT_PRODUCT_VERSION)
1050 {
1051 // "-V" will always be interpreted as requesting
1052 // version information except if it's already defined.
1053 boolean dashVAccepted = true;
1054 if (globalShortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION))
1055 {
1056 dashVAccepted = false;
1057 }
1058 else
1059 {
1060 for (SubCommand subCmd : subCommands.values())
1061 {
1062 if (subCmd.getArgument(OPTION_SHORT_PRODUCT_VERSION)!=null)
1063 {
1064 dashVAccepted = false;
1065 break;
1066 }
1067 }
1068 }
1069 if (dashVAccepted)
1070 {
1071 usageOrVersionDisplayed = true;
1072 versionPresent = true;
1073 try
1074 {
1075 DirectoryServer.printVersion(usageOutputStream);
1076 }
1077 catch (Exception e)
1078 {
1079 }
1080 return;
1081 }
1082 }
1083 else
1084 {
1085 // There is no such argument registered.
1086 Message message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID.get(
1087 String.valueOf(argCharacter));
1088 throw new ArgumentException(message);
1089 }
1090 }
1091 }
1092 }
1093
1094 a.setPresent(true);
1095
1096 // If this is the usage argument, then immediately stop and print
1097 // usage information.
1098 if (usageGroupArguments.containsKey(a))
1099 {
1100 try
1101 {
1102 getUsage(a, usageOutputStream);
1103 } catch (Exception e) {}
1104
1105 return;
1106 }
1107
1108 // See if the argument takes a value. If so, then make sure one was
1109 // provided. If not, then make sure none was provided.
1110 if (a.needsValue())
1111 {
1112 if (argValue == null)
1113 {
1114 if ((i+1) == numArguments)
1115 {
1116 Message message =
1117 ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_SHORT_ID.
1118 get(String.valueOf(argCharacter));
1119 throw new ArgumentException(message);
1120 }
1121
1122 argValue = rawArguments[++i];
1123 }
1124
1125 MessageBuilder invalidReason = new MessageBuilder();
1126 if (! a.valueIsAcceptable(argValue, invalidReason))
1127 {
1128 Message message = ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_SHORT_ID.
1129 get(argValue, String.valueOf(argCharacter),
1130 invalidReason.toString());
1131 throw new ArgumentException(message);
1132 }
1133
1134 // If the argument already has a value, then make sure it is
1135 // acceptable to have more than one.
1136 if (a.hasValue() && (! a.isMultiValued()))
1137 {
1138 Message message = ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_SHORT_ID.get(
1139 String.valueOf(argCharacter));
1140 throw new ArgumentException(message);
1141 }
1142
1143 a.addValue(argValue);
1144 }
1145 else
1146 {
1147 if (argValue != null)
1148 {
1149 // If we've gotten here, then it means that we're in a scenario like
1150 // "-abc" where "a" is a valid argument that doesn't take a value.
1151 // However, this could still be valid if all remaining characters in
1152 // the value are also valid argument characters that don't take
1153 // values.
1154 int valueLength = argValue.length();
1155 for (int j=0; j < valueLength; j++)
1156 {
1157 char c = argValue.charAt(j);
1158 Argument b = globalShortIDMap.get(c);
1159 if (b == null)
1160 {
1161 if (subCommand == null)
1162 {
1163 Message message =
1164 ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID.
1165 get(String.valueOf(argCharacter));
1166 throw new ArgumentException(message);
1167 }
1168 else
1169 {
1170 b = subCommand.getArgument(c);
1171 if (b == null)
1172 {
1173 Message message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID.
1174 get(String.valueOf(argCharacter));
1175 throw new ArgumentException(message);
1176 }
1177 }
1178 }
1179
1180 if (b.needsValue())
1181 {
1182 // This means we're in a scenario like "-abc" where b is a
1183 // valid argument that takes a value. We don't support that.
1184 Message message = ERR_SUBCMDPARSER_CANT_MIX_ARGS_WITH_VALUES.
1185 get(String.valueOf(argCharacter), argValue,
1186 String.valueOf(c));
1187 throw new ArgumentException(message);
1188 }
1189 else
1190 {
1191 b.setPresent(true);
1192
1193 // If this is the usage argument, then immediately stop and
1194 // print usage information.
1195 if (usageGroupArguments.containsKey(b))
1196 {
1197 try
1198 {
1199 getUsage(b, usageOutputStream);
1200 } catch (Exception e) {}
1201
1202 return;
1203 }
1204 }
1205 }
1206 }
1207 }
1208 }
1209 else if (subCommand != null)
1210 {
1211 // It's not a short or long identifier and the sub-command has
1212 // already been specified, so it must be the first trailing argument.
1213 if (subCommand.allowsTrailingArguments())
1214 {
1215 trailingArguments.add(arg);
1216 inTrailingArgs = true;
1217 }
1218 else
1219 {
1220 // Trailing arguments are not allowed for this sub-command.
1221 Message message = ERR_ARGPARSER_DISALLOWED_TRAILING_ARGUMENT.get(arg);
1222 throw new ArgumentException(message);
1223 }
1224 }
1225 else
1226 {
1227 // It must be the sub-command.
1228 String nameToCheck = arg;
1229 if (! longArgumentsCaseSensitive)
1230 {
1231 nameToCheck = toLowerCase(arg);
1232 }
1233
1234 SubCommand sc = subCommands.get(nameToCheck);
1235 if (sc == null)
1236 {
1237 Message message = ERR_SUBCMDPARSER_INVALID_ARGUMENT.get(arg);
1238 throw new ArgumentException(message);
1239 }
1240 else
1241 {
1242 subCommand = sc;
1243 }
1244 }
1245 }
1246
1247 // If we have a sub-command and it allows trailing arguments and
1248 // there is a minimum number, then make sure at least that many
1249 // were provided.
1250 if (subCommand != null)
1251 {
1252 int minTrailingArguments = subCommand.getMinTrailingArguments();
1253 if (subCommand.allowsTrailingArguments() && (minTrailingArguments > 0))
1254 {
1255 if (trailingArguments.size() < minTrailingArguments)
1256 {
1257 Message message = ERR_ARGPARSER_TOO_FEW_TRAILING_ARGUMENTS.get(
1258 minTrailingArguments);
1259 throw new ArgumentException(message);
1260 }
1261 }
1262 }
1263
1264 // If we don't have the argumentProperties, try to load a properties file.
1265 if (argumentProperties == null)
1266 {
1267 argumentProperties = checkExternalProperties();
1268 }
1269
1270 // Iterate through all the global arguments and make sure that they have
1271 // values or a suitable default is available.
1272 for (Argument a : globalArgumentList)
1273 {
1274 if (! a.isPresent())
1275 {
1276 // See if there is a value in the properties that can be used
1277 if ((argumentProperties != null) && (a.getPropertyName() != null))
1278 {
1279 String value = argumentProperties.getProperty(a.getPropertyName()
1280 .toLowerCase());
1281 MessageBuilder invalidReason = new MessageBuilder();
1282 if (value != null)
1283 {
1284 Boolean addValue = true;
1285 if (!( a instanceof BooleanArgument))
1286 {
1287 addValue = a.valueIsAcceptable(value, invalidReason);
1288 }
1289 if (addValue)
1290 {
1291 a.addValue(value);
1292 if (a.needsValue())
1293 {
1294 a.setPresent(true);
1295 }
1296 a.setValueSetByProperty(true);
1297 }
1298 }
1299 }
1300 }
1301
1302 if ((! a.isPresent()) && a.needsValue())
1303 {
1304 // ISee if the argument defines a default.
1305 if (a.getDefaultValue() != null)
1306 {
1307 a.addValue(a.getDefaultValue());
1308 }
1309
1310 // If there is still no value and the argument is required, then that's
1311 // a problem.
1312 if ((! a.hasValue()) && a.isRequired())
1313 {
1314 Message message =
1315 ERR_SUBCMDPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName());
1316 throw new ArgumentException(message);
1317 }
1318 }
1319 }
1320
1321
1322 // Iterate through all the subcommand-specific arguments and make sure that
1323 // they have values or a suitable default is available.
1324 if (subCommand != null)
1325 {
1326 for (Argument a : subCommand.getArguments())
1327 {
1328 if (! a.isPresent())
1329 {
1330 // See if there is a value in the properties that can be used
1331 if ((argumentProperties != null) && (a.getPropertyName() != null))
1332 {
1333 String value = argumentProperties.getProperty(a.getPropertyName()
1334 .toLowerCase());
1335 MessageBuilder invalidReason = new MessageBuilder();
1336 if (value != null)
1337 {
1338 Boolean addValue = true;
1339 if (!( a instanceof BooleanArgument))
1340 {
1341 addValue = a.valueIsAcceptable(value, invalidReason);
1342 }
1343 if (addValue)
1344 {
1345 a.addValue(value);
1346 if (a.needsValue())
1347 {
1348 a.setPresent(true);
1349 }
1350 a.setValueSetByProperty(true);
1351 }
1352 }
1353 }
1354 }
1355
1356 if ((! a.isPresent()) && a.needsValue())
1357 {
1358 // See if the argument defines a default.
1359 if (a.getDefaultValue() != null)
1360 {
1361 a.addValue(a.getDefaultValue());
1362 }
1363
1364 // If there is still no value and the argument is required, then
1365 // that's a problem.
1366 if ((! a.hasValue()) && a.isRequired())
1367 {
1368 Message message =
1369 ERR_SUBCMDPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName());
1370 throw new ArgumentException(message);
1371 }
1372 }
1373 }
1374 }
1375 }
1376
1377
1378
1379 /**
1380 * Appends usage information for the specified subcommand to the provided
1381 * buffer.
1382 *
1383 * @param buffer The buffer to which the usage information should be
1384 * appended.
1385 * @param subCommand The subcommand for which to display the usage
1386 * information.
1387 */
1388 public void getSubCommandUsage(MessageBuilder buffer, SubCommand subCommand)
1389 {
1390 usageOrVersionDisplayed = true;
1391 String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
1392 if ((scriptName == null) || (scriptName.length() == 0))
1393 {
1394 scriptName = "java " + mainClassName;
1395 }
1396 buffer.append(INFO_ARGPARSER_USAGE.get());
1397 buffer.append(" ");
1398 buffer.append(scriptName);
1399
1400 buffer.append(" ");
1401 buffer.append(subCommand.getName());
1402 buffer.append(" ").append(INFO_SUBCMDPARSER_OPTIONS.get());
1403 if (subCommand.allowsTrailingArguments()) {
1404 buffer.append(' ');
1405 buffer.append(subCommand.getTrailingArgumentsDisplayName());
1406 }
1407 buffer.append(EOL);
1408 buffer.append(subCommand.getDescription());
1409 buffer.append(EOL);
1410
1411 if ( ! globalArgumentList.isEmpty())
1412 {
1413 buffer.append(EOL);
1414 buffer.append(INFO_GLOBAL_OPTIONS.get());
1415 buffer.append(EOL);
1416 buffer.append(" ");
1417 buffer.append(INFO_GLOBAL_OPTIONS_REFERENCE.get(scriptName));
1418 buffer.append(EOL);
1419 }
1420
1421 if ( ! subCommand.getArguments().isEmpty() )
1422 {
1423 buffer.append(EOL);
1424 buffer.append(INFO_SUBCMD_OPTIONS.get());
1425 buffer.append(EOL);
1426 }
1427 for (Argument a : subCommand.getArguments())
1428 {
1429 // If this argument is hidden, then skip it.
1430 if (a.isHidden())
1431 {
1432 continue;
1433 }
1434
1435
1436 // Write a line with the short and/or long identifiers that may be used
1437 // for the argument.
1438 Character shortID = a.getShortIdentifier();
1439 String longID = a.getLongIdentifier();
1440 if (shortID != null)
1441 {
1442 int currentLength = buffer.length();
1443
1444 if (a.equals(usageArgument))
1445 {
1446 buffer.append("-?, ");
1447 }
1448
1449 buffer.append("-");
1450 buffer.append(shortID.charValue());
1451
1452 if (a.needsValue() && longID == null)
1453 {
1454 buffer.append(" ");
1455 buffer.append(a.getValuePlaceholder());
1456 }
1457
1458 if (longID != null)
1459 {
1460 StringBuilder newBuffer = new StringBuilder();
1461 newBuffer.append(", --");
1462 newBuffer.append(longID);
1463
1464 if (a.needsValue())
1465 {
1466 newBuffer.append(" ");
1467 newBuffer.append(a.getValuePlaceholder());
1468 }
1469
1470 int lineLength = (buffer.length() - currentLength) +
1471 newBuffer.length();
1472 if (lineLength > MAX_LENGTH)
1473 {
1474 buffer.append(EOL);
1475 buffer.append(newBuffer.toString());
1476 }
1477 else
1478 {
1479 buffer.append(newBuffer.toString());
1480 }
1481 }
1482
1483 buffer.append(EOL);
1484 }
1485 else
1486 {
1487 if (longID != null)
1488 {
1489 if (a.equals(usageArgument))
1490 {
1491 buffer.append("-?, ");
1492 }
1493 buffer.append("--");
1494 buffer.append(longID);
1495
1496 if (a.needsValue())
1497 {
1498 buffer.append(" ");
1499 buffer.append(a.getValuePlaceholder());
1500 }
1501
1502 buffer.append(EOL);
1503 }
1504 }
1505
1506
1507 // Write one or more lines with the description of the argument. We will
1508 // indent the description five characters and try our best to wrap at or
1509 // before column 79 so it will be friendly to 80-column displays.
1510 Message description = a.getDescription();
1511 int maxLength = MAX_LENGTH - INDENT.length() - 1;
1512 if (description.length() <= maxLength)
1513 {
1514 buffer.append(INDENT);
1515 buffer.append(description);
1516 buffer.append(EOL);
1517 }
1518 else
1519 {
1520 String s = description.toString();
1521 while (s.length() > maxLength)
1522 {
1523 int spacePos = s.lastIndexOf(' ', maxLength);
1524 if (spacePos > 0)
1525 {
1526 buffer.append(INDENT);
1527 buffer.append(s.substring(0, spacePos).trim());
1528 s = s.substring(spacePos+1).trim();
1529 buffer.append(EOL);
1530 }
1531 else
1532 {
1533 // There are no spaces in the first 74 columns. See if there is one
1534 // after that point. If so, then break there. If not, then don't
1535 // break at all.
1536 spacePos = s.indexOf(' ');
1537 if (spacePos > 0)
1538 {
1539 buffer.append(INDENT);
1540 buffer.append(s.substring(0, spacePos).trim());
1541 s = s.substring(spacePos+1).trim();
1542 buffer.append(EOL);
1543 }
1544 else
1545 {
1546 buffer.append(INDENT);
1547 buffer.append(s);
1548 s = "";
1549 buffer.append(EOL);
1550 }
1551 }
1552 }
1553
1554 if (s.length() > 0)
1555 {
1556 buffer.append(" ");
1557 buffer.append(s);
1558 buffer.append(EOL);
1559 }
1560 }
1561 }
1562 }
1563
1564
1565
1566 /**
1567 * Retrieves a string containing usage information based on the defined
1568 * arguments.
1569 *
1570 * @return A string containing usage information based on the defined
1571 * arguments.
1572 */
1573 public String getUsage()
1574 {
1575 MessageBuilder buffer = new MessageBuilder();
1576
1577 if (subCommand == null) {
1578 if (usageGroupArguments.size() > 1) {
1579 // We have sub-command groups, so don't display any
1580 // sub-commands by default.
1581 getFullUsage(Collections.<SubCommand> emptySet(), true, buffer);
1582 } else {
1583 // No grouping, so display all sub-commands.
1584 getFullUsage(subCommands.values(), true, buffer);
1585 }
1586 } else {
1587 getSubCommandUsage(buffer, subCommand);
1588 }
1589
1590 return buffer.toMessage().toString();
1591 }
1592
1593
1594
1595 /**
1596 * Retrieves a string describing how the user can get more help.
1597 *
1598 * @return A string describing how the user can get more help.
1599 */
1600 public Message getHelpUsageReference()
1601 {
1602 usageOrVersionDisplayed = true;
1603 String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
1604 if ((scriptName == null) || (scriptName.length() == 0))
1605 {
1606 scriptName = "java " + mainClassName;
1607 }
1608
1609 MessageBuilder buffer = new MessageBuilder();
1610 buffer.append(INFO_GLOBAL_HELP_REFERENCE.get(scriptName));
1611 buffer.append(EOL);
1612 return buffer.toMessage();
1613 }
1614
1615
1616
1617 /**
1618 * Retrieves the set of unnamed trailing arguments that were
1619 * provided on the command line.
1620 *
1621 * @return The set of unnamed trailing arguments that were provided
1622 * on the command line.
1623 */
1624 public ArrayList<String> getTrailingArguments()
1625 {
1626 return trailingArguments;
1627 }
1628
1629
1630
1631 /**
1632 * Indicates whether the usage information has been displayed to the end user
1633 * either by an explicit argument like "-H" or "--help", or by a built-in
1634 * argument like "-?".
1635 *
1636 * @return {@code true} if the usage information has been displayed, or
1637 * {@code false} if not.
1638 */
1639 public boolean usageOrVersionDisplayed()
1640 {
1641 return usageOrVersionDisplayed;
1642 }
1643
1644
1645
1646 /**
1647 * Adds the provided subcommand to this argument parser. This is only
1648 * intended for use by the <CODE>SubCommand</CODE> constructor and does not
1649 * do any validation of its own to ensure that there are no conflicts with the
1650 * subcommand or any of its arguments.
1651 *
1652 * @param subCommand The subcommand to add to this argument parser.
1653 */
1654 void addSubCommand(SubCommand subCommand)
1655 {
1656 subCommands.put(toLowerCase(subCommand.getName()), subCommand);
1657 }
1658
1659
1660
1661 // Get usage for a specific usage argument.
1662 private void getUsage(Argument a, OutputStream outputStream)
1663 throws IOException {
1664 MessageBuilder buffer = new MessageBuilder();
1665
1666 if (a.equals(usageArgument) && subCommand != null) {
1667 getSubCommandUsage(buffer, subCommand);
1668 } else if (a.equals(usageArgument) && usageGroupArguments.size() <= 1) {
1669 // No groups - so display all sub-commands.
1670 getFullUsage(subCommands.values(), true, buffer);
1671 } else if (a.equals(usageArgument)) {
1672 // Using groups - so display all sub-commands group help.
1673 getFullUsage(Collections.<SubCommand> emptySet(), true, buffer);
1674 } else {
1675 // Requested help on specific group - don't display global
1676 // options.
1677 getFullUsage(usageGroupArguments.get(a), false, buffer);
1678 }
1679
1680 outputStream.write(getBytes(buffer.toString()));
1681 }
1682
1683
1684 /**
1685 * {@inheritDoc}
1686 */
1687 public void getUsage(OutputStream outputStream)
1688 throws IOException {
1689 outputStream.write(getBytes(String.valueOf(getUsage())));
1690 }
1691
1692
1693
1694 // Appends complete usage information for the specified set of
1695 // sub-commands.
1696 private void getFullUsage(Collection<SubCommand> c,
1697 boolean showGlobalOptions, MessageBuilder buffer) {
1698 usageOrVersionDisplayed = true;
1699 if ((toolDescription != null) && (toolDescription.length() > 0))
1700 {
1701 buffer.append(wrapText(toolDescription, MAX_LENGTH - 1));
1702 buffer.append(EOL);
1703 buffer.append(EOL);
1704 }
1705
1706 String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
1707 if ((scriptName == null) || (scriptName.length() == 0))
1708 {
1709 scriptName = "java " + mainClassName;
1710 }
1711 buffer.append(INFO_ARGPARSER_USAGE.get());
1712 buffer.append(" ");
1713 buffer.append(scriptName);
1714
1715 if (subCommands.isEmpty())
1716 {
1717 buffer.append(" "+INFO_SUBCMDPARSER_OPTIONS.get());
1718 }
1719 else
1720 {
1721 buffer.append(" "+INFO_SUBCMDPARSER_SUBCMD_AND_OPTIONS.get());
1722 }
1723
1724 if (!subCommands.isEmpty())
1725 {
1726 buffer.append(EOL);
1727 buffer.append(EOL);
1728 if (c.isEmpty())
1729 {
1730 buffer.append(INFO_SUBCMDPARSER_SUBCMD_HELP_HEADING.get());
1731 }
1732 else
1733 {
1734 buffer.append(INFO_SUBCMDPARSER_SUBCMD_HEADING.get());
1735 }
1736 buffer.append(EOL);
1737 }
1738
1739 if (c.isEmpty()) {
1740 // Display usage arguments (except the default one).
1741 for (Argument a : globalArgumentList) {
1742 if (a.isHidden()) {
1743 continue;
1744 }
1745
1746 if (usageGroupArguments.containsKey(a)) {
1747 if (!a.equals(usageArgument)) {
1748 printArgumentUsage(a, buffer);
1749 }
1750 }
1751 }
1752 } else {
1753 boolean isFirst = true;
1754 for (SubCommand sc : c) {
1755 if (sc.isHidden()) {
1756 continue;
1757 }
1758 if (isFirst)
1759 {
1760 buffer.append(EOL);
1761 }
1762 buffer.append(sc.getName());
1763 buffer.append(EOL);
1764 indentAndWrap(Message.raw(INDENT), sc.getDescription(), buffer);
1765 buffer.append(EOL);
1766 isFirst = false;
1767 }
1768 }
1769
1770 buffer.append(EOL);
1771
1772 if (showGlobalOptions) {
1773 if (subCommands.isEmpty())
1774 {
1775 buffer.append(INFO_SUBCMDPARSER_WHERE_OPTIONS_INCLUDE.get());
1776 buffer.append(EOL);
1777 }
1778 else
1779 {
1780 buffer.append(INFO_SUBCMDPARSER_GLOBAL_HEADING.get());
1781 buffer.append(EOL);
1782 }
1783 buffer.append(EOL);
1784
1785 boolean printGroupHeaders = printUsageGroupHeaders();
1786
1787 // Display non-usage arguments.
1788 for (ArgumentGroup argGroup : argumentGroups)
1789 {
1790 if (argGroup.containsArguments() && printGroupHeaders)
1791 {
1792 // Print the groups description if any
1793 Message groupDesc = argGroup.getDescription();
1794 if (groupDesc != null && !Message.EMPTY.equals(groupDesc)) {
1795 buffer.append(EOL);
1796 buffer.append(wrapText(groupDesc.toString(), MAX_LENGTH - 1));
1797 buffer.append(EOL);
1798 buffer.append(EOL);
1799 }
1800 }
1801
1802 for (Argument a : argGroup.getArguments()) {
1803 if (a.isHidden()) {
1804 continue;
1805 }
1806
1807 if (!usageGroupArguments.containsKey(a)) {
1808 printArgumentUsage(a, buffer);
1809 }
1810 }
1811 }
1812
1813 // Finally print default usage argument.
1814 if (usageArgument != null) {
1815 printArgumentUsage(usageArgument, buffer);
1816 } else {
1817 buffer.append("-?");
1818 }
1819 buffer.append(EOL);
1820 }
1821 }
1822
1823
1824
1825 /**
1826 * Appends argument usage information to the provided buffer.
1827 *
1828 * @param a
1829 * The argument to handle.
1830 * @param buffer
1831 * The buffer to which the usage information should be
1832 * appended.
1833 */
1834 private void printArgumentUsage(Argument a, MessageBuilder buffer) {
1835 String value;
1836 if (a.needsValue())
1837 {
1838 Message pHolder = a.getValuePlaceholder();
1839 if (pHolder == null)
1840 {
1841 value = " {value}";
1842 }
1843 else
1844 {
1845 value = " " + pHolder;
1846 }
1847 }
1848 else
1849 {
1850 value = "";
1851 }
1852
1853 Character shortIDChar = a.getShortIdentifier();
1854 if (shortIDChar != null)
1855 {
1856 if (a.equals(usageArgument))
1857 {
1858 buffer.append("-?, ");
1859 }
1860 buffer.append("-");
1861 buffer.append(shortIDChar);
1862
1863 String longIDString = a.getLongIdentifier();
1864 if (longIDString != null)
1865 {
1866 buffer.append(", --");
1867 buffer.append(longIDString);
1868 }
1869 buffer.append(value);
1870 }
1871 else
1872 {
1873 String longIDString = a.getLongIdentifier();
1874 if (longIDString != null)
1875 {
1876 if (a.equals(usageArgument))
1877 {
1878 buffer.append("-?, ");
1879 }
1880 buffer.append("--");
1881 buffer.append(longIDString);
1882 buffer.append(value);
1883 }
1884 }
1885
1886 buffer.append(EOL);
1887 indentAndWrap(Message.raw(INDENT), a.getDescription(), buffer);
1888 }
1889
1890
1891
1892 /**
1893 * Write one or more lines with the description of the argument. We will
1894 * indent the description five characters and try our best to wrap at or
1895 * before column 79 so it will be friendly to 80-column displays.
1896 */
1897 private void indentAndWrap(Message indent, Message text,
1898 MessageBuilder buffer)
1899 {
1900 int actualSize = MAX_LENGTH - indent.length();
1901 if (text.length() <= actualSize)
1902 {
1903 buffer.append(indent);
1904 buffer.append(text);
1905 buffer.append(EOL);
1906 }
1907 else
1908 {
1909 String s = text.toString();
1910 while (s.length() > actualSize)
1911 {
1912 int spacePos = s.lastIndexOf(' ', actualSize);
1913 if (spacePos > 0)
1914 {
1915 buffer.append(indent);
1916 buffer.append(s.substring(0, spacePos).trim());
1917 s = s.substring(spacePos + 1).trim();
1918 buffer.append(EOL);
1919 }
1920 else
1921 {
1922 // There are no spaces in the first actualSize -1 columns. See
1923 // if there is one after that point. If so, then break there.
1924 // If not, then don't break at all.
1925 spacePos = s.indexOf(' ');
1926 if (spacePos > 0)
1927 {
1928 buffer.append(indent);
1929 buffer.append(s.substring(0, spacePos).trim());
1930 s = s.substring(spacePos + 1).trim();
1931 buffer.append(EOL);
1932 }
1933 else
1934 {
1935 buffer.append(indent);
1936 buffer.append(s);
1937 s = "";
1938 buffer.append(EOL);
1939 }
1940 }
1941 }
1942
1943 if (s.length() > 0)
1944 {
1945 buffer.append(indent);
1946 buffer.append(s);
1947 buffer.append(EOL);
1948 }
1949 }
1950 }
1951
1952 /**
1953 * Returns whether the usage argument was provided or not. This method
1954 * should be called after a call to parseArguments.
1955 * @return <CODE>true</CODE> if the usage argument was provided and
1956 * <CODE>false</CODE> otherwise.
1957 */
1958 public boolean isUsageArgumentPresent()
1959 {
1960 boolean isUsageArgumentPresent = false;
1961 if (usageArgument != null)
1962 {
1963 isUsageArgumentPresent = usageArgument.isPresent();
1964 }
1965 return isUsageArgumentPresent;
1966 }
1967
1968 /**
1969 * Returns whether the version argument was provided or not. This method
1970 * should be called after a call to parseArguments.
1971 * @return <CODE>true</CODE> if the version argument was provided and
1972 * <CODE>false</CODE> otherwise.
1973 */
1974 public boolean isVersionArgumentPresent()
1975 {
1976 boolean isPresent;
1977 if (!super.isVersionArgumentPresent())
1978 {
1979 isPresent = versionPresent;
1980 }
1981 else
1982 {
1983 isPresent = true;
1984 }
1985 return isPresent;
1986 }
1987 }
1988