001/*
002 * Copyright 2008-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2008-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util.args;
037
038
039
040import java.io.BufferedReader;
041import java.io.File;
042import java.io.FileInputStream;
043import java.io.FileOutputStream;
044import java.io.IOException;
045import java.io.InputStream;
046import java.io.InputStreamReader;
047import java.io.OutputStream;
048import java.io.OutputStreamWriter;
049import java.io.PrintStream;
050import java.io.PrintWriter;
051import java.io.Serializable;
052import java.nio.charset.StandardCharsets;
053import java.util.ArrayList;
054import java.util.Arrays;
055import java.util.Collection;
056import java.util.Collections;
057import java.util.HashMap;
058import java.util.HashSet;
059import java.util.Iterator;
060import java.util.LinkedHashSet;
061import java.util.LinkedHashMap;
062import java.util.List;
063import java.util.Map;
064import java.util.Set;
065
066import com.unboundid.ldap.sdk.unboundidds.tools.ToolUtils;
067import com.unboundid.util.CommandLineTool;
068import com.unboundid.util.Debug;
069import com.unboundid.util.ObjectPair;
070import com.unboundid.util.StaticUtils;
071import com.unboundid.util.ThreadSafety;
072import com.unboundid.util.ThreadSafetyLevel;
073import com.unboundid.util.Validator;
074
075import static com.unboundid.util.args.ArgsMessages.*;
076
077
078
079/**
080 * This class provides an argument parser, which may be used to process command
081 * line arguments provided to Java applications.  See the package-level Javadoc
082 * documentation for details regarding the capabilities of the argument parser.
083 */
084@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
085public final class ArgumentParser
086       implements Serializable
087{
088  /**
089   * The name of the system property that can be used to specify the default
090   * properties file that should be used to obtain the default values for
091   * arguments not specified via the command line.
092   */
093  public static final String PROPERTY_DEFAULT_PROPERTIES_FILE_PATH =
094       ArgumentParser.class.getName() + ".propertiesFilePath";
095
096
097
098  /**
099   * The name of an environment variable that can be used to specify the default
100   * properties file that should be used to obtain the default values for
101   * arguments not specified via the command line.
102   */
103  public static final String ENV_DEFAULT_PROPERTIES_FILE_PATH =
104       "UNBOUNDID_TOOL_PROPERTIES_FILE_PATH";
105
106
107
108  /**
109   * The name of the argument used to specify the path to a file to which all
110   * output should be written.
111   */
112  private static final String ARG_NAME_OUTPUT_FILE = "outputFile";
113
114
115
116  /**
117   * The name of the argument used to indicate that output should be written to
118   * both the output file and the console.
119   */
120  private static final String ARG_NAME_TEE_OUTPUT = "teeOutput";
121
122
123
124  /**
125   * The name of the argument used to specify the path to a properties file from
126   * which to obtain the default values for arguments not specified via the
127   * command line.
128   */
129  private static final String ARG_NAME_PROPERTIES_FILE_PATH =
130       "propertiesFilePath";
131
132
133
134  /**
135   * The name of the argument used to specify the path to a file to be generated
136   * with information about the properties that the tool supports.
137   */
138  private static final String ARG_NAME_GENERATE_PROPERTIES_FILE =
139       "generatePropertiesFile";
140
141
142
143  /**
144   * The name of the argument used to indicate that the tool should not use any
145   * properties file to obtain default values for arguments not specified via
146   * the command line.
147   */
148  private static final String ARG_NAME_NO_PROPERTIES_FILE = "noPropertiesFile";
149
150
151
152  /**
153   * The name of the argument used to indicate that the tool should suppress the
154   * comment that lists the argument values obtained from a properties file.
155   */
156  private static final String ARG_NAME_SUPPRESS_PROPERTIES_FILE_COMMENT =
157       "suppressPropertiesFileComment";
158
159
160
161  /**
162   * The serial version UID for this serializable class.
163   */
164  private static final long serialVersionUID = 3053102992180360269L;
165
166
167
168  // The command-line tool with which this argument parser is associated, if
169  // any.
170  private volatile CommandLineTool commandLineTool;
171
172  // The properties file used to obtain arguments for this tool.
173  private volatile File propertiesFileUsed;
174
175  // The maximum number of trailing arguments allowed to be provided.
176  private final int maxTrailingArgs;
177
178  // The minimum number of trailing arguments allowed to be provided.
179  private final int minTrailingArgs;
180
181  // The set of named arguments associated with this parser, indexed by short
182  // identifier.
183  private final LinkedHashMap<Character,Argument> namedArgsByShortID;
184
185  // The set of named arguments associated with this parser, indexed by long
186  // identifier.
187  private final LinkedHashMap<String,Argument> namedArgsByLongID;
188
189  // The set of subcommands associated with this parser, indexed by name.
190  private final LinkedHashMap<String,SubCommand> subCommandsByName;
191
192  // The full set of named arguments associated with this parser.
193  private final List<Argument> namedArgs;
194
195  // Sets of arguments in which if the key argument is provided, then at least
196  // one of the value arguments must also be provided.
197  private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets;
198
199  // Sets of arguments in which at most one argument in the list is allowed to
200  // be present.
201  private final List<Set<Argument>> exclusiveArgumentSets;
202
203  // Sets of arguments in which at least one argument in the list is required to
204  // be present.
205  private final List<Set<Argument>> requiredArgumentSets;
206
207  // A list of any arguments set from the properties file rather than explicitly
208  // provided on the command line.
209  private final List<String> argumentsSetFromPropertiesFile;
210
211  // The list of trailing arguments provided on the command line.
212  private final List<String> trailingArgs;
213
214  // The full list of subcommands associated with this argument parser.
215  private final List<SubCommand> subCommands;
216
217  // A list of additional paragraphs that make up the complete description for
218  // the associated command.
219  private final List<String> additionalCommandDescriptionParagraphs;
220
221  // The description for the associated command.
222  private final String commandDescription;
223
224  // The name for the associated command.
225  private final String commandName;
226
227  // The placeholder string for the trailing arguments.
228  private final String trailingArgsPlaceholder;
229
230  // The subcommand with which this argument parser is associated.
231  private volatile SubCommand parentSubCommand;
232
233  // The subcommand that was included in the set of command-line arguments.
234  private volatile SubCommand selectedSubCommand;
235
236
237
238  /**
239   * Creates a new instance of this argument parser with the provided
240   * information.  It will not allow unnamed trailing arguments.
241   *
242   * @param  commandName         The name of the application or utility with
243   *                             which this argument parser is associated.  It
244   *                             must not be {@code null}.
245   * @param  commandDescription  A description of the application or utility
246   *                             with which this argument parser is associated.
247   *                             It will be included in generated usage
248   *                             information.  It must not be {@code null}.
249   *
250   * @throws  ArgumentException  If either the command name or command
251   *                             description is {@code null},
252   */
253  public ArgumentParser(final String commandName,
254                        final String commandDescription)
255         throws ArgumentException
256  {
257    this(commandName, commandDescription, 0, null);
258  }
259
260
261
262  /**
263   * Creates a new instance of this argument parser with the provided
264   * information.
265   *
266   * @param  commandName              The name of the application or utility
267   *                                  with which this argument parser is
268   *                                  associated.  It must not be {@code null}.
269   * @param  commandDescription       A description of the application or
270   *                                  utility with which this argument parser is
271   *                                  associated.  It will be included in
272   *                                  generated usage information.  It must not
273   *                                  be {@code null}.
274   * @param  maxTrailingArgs          The maximum number of trailing arguments
275   *                                  that may be provided to this command.  A
276   *                                  value of zero indicates that no trailing
277   *                                  arguments will be allowed.  A value less
278   *                                  than zero will indicate that there is no
279   *                                  limit on the number of trailing arguments
280   *                                  allowed.
281   * @param  trailingArgsPlaceholder  A placeholder string that will be included
282   *                                  in usage output to indicate what trailing
283   *                                  arguments may be provided.  It must not be
284   *                                  {@code null} if {@code maxTrailingArgs} is
285   *                                  anything other than zero.
286   *
287   * @throws  ArgumentException  If either the command name or command
288   *                             description is {@code null}, or if the maximum
289   *                             number of trailing arguments is non-zero and
290   *                             the trailing arguments placeholder is
291   *                             {@code null}.
292   */
293  public ArgumentParser(final String commandName,
294                        final String commandDescription,
295                        final int maxTrailingArgs,
296                        final String trailingArgsPlaceholder)
297         throws ArgumentException
298  {
299    this(commandName, commandDescription, 0, maxTrailingArgs,
300         trailingArgsPlaceholder);
301  }
302
303
304
305  /**
306   * Creates a new instance of this argument parser with the provided
307   * information.
308   *
309   * @param  commandName              The name of the application or utility
310   *                                  with which this argument parser is
311   *                                  associated.  It must not be {@code null}.
312   * @param  commandDescription       A description of the application or
313   *                                  utility with which this argument parser is
314   *                                  associated.  It will be included in
315   *                                  generated usage information.  It must not
316   *                                  be {@code null}.
317   * @param  minTrailingArgs          The minimum number of trailing arguments
318   *                                  that must be provided for this command.  A
319   *                                  value of zero indicates that the command
320   *                                  may be invoked without any trailing
321   *                                  arguments.
322   * @param  maxTrailingArgs          The maximum number of trailing arguments
323   *                                  that may be provided to this command.  A
324   *                                  value of zero indicates that no trailing
325   *                                  arguments will be allowed.  A value less
326   *                                  than zero will indicate that there is no
327   *                                  limit on the number of trailing arguments
328   *                                  allowed.
329   * @param  trailingArgsPlaceholder  A placeholder string that will be included
330   *                                  in usage output to indicate what trailing
331   *                                  arguments may be provided.  It must not be
332   *                                  {@code null} if {@code maxTrailingArgs} is
333   *                                  anything other than zero.
334   *
335   * @throws  ArgumentException  If either the command name or command
336   *                             description is {@code null}, or if the maximum
337   *                             number of trailing arguments is non-zero and
338   *                             the trailing arguments placeholder is
339   *                             {@code null}.
340   */
341  public ArgumentParser(final String commandName,
342                        final String commandDescription,
343                        final int minTrailingArgs,
344                        final int maxTrailingArgs,
345                        final String trailingArgsPlaceholder)
346         throws ArgumentException
347  {
348    this(commandName, commandDescription, null, minTrailingArgs,
349         maxTrailingArgs, trailingArgsPlaceholder);
350  }
351
352
353
354  /**
355   * Creates a new instance of this argument parser with the provided
356   * information.
357   *
358   * @param  commandName
359   *              The name of the application or utility with which this
360   *              argument parser is associated.  It must not be {@code null}.
361   * @param  commandDescription
362   *              A description of the application or utility with which this
363   *              argument parser is associated.  It will be included in
364   *              generated usage information.  It must not be {@code null}.
365   * @param  additionalCommandDescriptionParagraphs
366   *              A list of additional paragraphs that should be included in the
367   *              tool description (with {@code commandDescription} providing
368   *              the text for the first paragraph).  This may be {@code null}
369   *              or empty if the tool description should only include a
370   *              single paragraph.
371   * @param  minTrailingArgs
372   *              The minimum number of trailing arguments that must be provided
373   *              for this command.  A value of zero indicates that the command
374   *              may be invoked without any trailing arguments.
375   * @param  maxTrailingArgs
376   *              The maximum number of trailing arguments that may be provided
377   *              to this command.  A value of zero indicates that no trailing
378   *              arguments will be allowed.  A value less than zero will
379   *              indicate that there is no limit on the number of trailing
380   *              arguments allowed.
381   * @param  trailingArgsPlaceholder
382   *              A placeholder string that will be included in usage output to
383   *              indicate what trailing arguments may be provided.  It must not
384   *              be {@code null} if {@code maxTrailingArgs} is anything other
385   *              than zero.
386   *
387   * @throws  ArgumentException  If either the command name or command
388   *                             description is {@code null}, or if the maximum
389   *                             number of trailing arguments is non-zero and
390   *                             the trailing arguments placeholder is
391   *                             {@code null}.
392   */
393  public ArgumentParser(final String commandName,
394              final String commandDescription,
395              final List<String> additionalCommandDescriptionParagraphs,
396              final int minTrailingArgs, final int maxTrailingArgs,
397              final String trailingArgsPlaceholder)
398         throws ArgumentException
399  {
400    if (commandName == null)
401    {
402      throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get());
403    }
404
405    if (commandDescription == null)
406    {
407      throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get());
408    }
409
410    if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null))
411    {
412      throw new ArgumentException(
413                     ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get());
414    }
415
416    this.commandName             = commandName;
417    this.commandDescription      = commandDescription;
418    this.trailingArgsPlaceholder = trailingArgsPlaceholder;
419
420    if (additionalCommandDescriptionParagraphs == null)
421    {
422      this.additionalCommandDescriptionParagraphs = Collections.emptyList();
423    }
424    else
425    {
426      this.additionalCommandDescriptionParagraphs =
427           Collections.unmodifiableList(
428                new ArrayList<>(additionalCommandDescriptionParagraphs));
429    }
430
431    if (minTrailingArgs >= 0)
432    {
433      this.minTrailingArgs = minTrailingArgs;
434    }
435    else
436    {
437      this.minTrailingArgs = 0;
438    }
439
440    if (maxTrailingArgs >= 0)
441    {
442      this.maxTrailingArgs = maxTrailingArgs;
443    }
444    else
445    {
446      this.maxTrailingArgs = Integer.MAX_VALUE;
447    }
448
449    if (this.minTrailingArgs > this.maxTrailingArgs)
450    {
451      throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_COUNT_MISMATCH.get(
452           this.minTrailingArgs, this.maxTrailingArgs));
453    }
454
455    namedArgsByShortID =
456         new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
457    namedArgsByLongID = new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
458    namedArgs = new ArrayList<>(20);
459    trailingArgs = new ArrayList<>(20);
460    dependentArgumentSets = new ArrayList<>(20);
461    exclusiveArgumentSets = new ArrayList<>(20);
462    requiredArgumentSets = new ArrayList<>(20);
463    parentSubCommand = null;
464    selectedSubCommand = null;
465    subCommands = new ArrayList<>(20);
466    subCommandsByName = new LinkedHashMap<>(StaticUtils.computeMapCapacity(20));
467    propertiesFileUsed = null;
468    argumentsSetFromPropertiesFile = new ArrayList<>(20);
469    commandLineTool = null;
470  }
471
472
473
474  /**
475   * Creates a new argument parser that is a "clean" copy of the provided source
476   * argument parser.
477   *
478   * @param  source      The source argument parser to use for this argument
479   *                     parser.
480   * @param  subCommand  The subcommand with which this argument parser is to be
481   *                     associated.
482   */
483  ArgumentParser(final ArgumentParser source, final SubCommand subCommand)
484  {
485    commandName             = source.commandName;
486    commandDescription      = source.commandDescription;
487    minTrailingArgs         = source.minTrailingArgs;
488    maxTrailingArgs         = source.maxTrailingArgs;
489    trailingArgsPlaceholder = source.trailingArgsPlaceholder;
490
491    additionalCommandDescriptionParagraphs =
492         source.additionalCommandDescriptionParagraphs;
493
494    propertiesFileUsed = null;
495    argumentsSetFromPropertiesFile = new ArrayList<>(20);
496    trailingArgs = new ArrayList<>(20);
497
498    namedArgs = new ArrayList<>(source.namedArgs.size());
499    namedArgsByLongID = new LinkedHashMap<>(
500         StaticUtils.computeMapCapacity(source.namedArgsByLongID.size()));
501    namedArgsByShortID = new LinkedHashMap<>(
502         StaticUtils.computeMapCapacity(source.namedArgsByShortID.size()));
503
504    final LinkedHashMap<String,Argument> argsByID = new LinkedHashMap<>(
505         StaticUtils.computeMapCapacity(source.namedArgs.size()));
506    for (final Argument sourceArg : source.namedArgs)
507    {
508      final Argument a = sourceArg.getCleanCopy();
509
510      try
511      {
512        a.setRegistered();
513      }
514      catch (final ArgumentException ae)
515      {
516        // This should never happen.
517        Debug.debugException(ae);
518      }
519
520      namedArgs.add(a);
521      argsByID.put(a.getIdentifierString(), a);
522
523      for (final Character c : a.getShortIdentifiers(true))
524      {
525        namedArgsByShortID.put(c, a);
526      }
527
528      for (final String s : a.getLongIdentifiers(true))
529      {
530        namedArgsByLongID.put(StaticUtils.toLowerCase(s), a);
531      }
532    }
533
534    dependentArgumentSets =
535         new ArrayList<>(source.dependentArgumentSets.size());
536    for (final ObjectPair<Argument,Set<Argument>> p :
537         source.dependentArgumentSets)
538    {
539      final Set<Argument> sourceSet = p.getSecond();
540      final LinkedHashSet<Argument> newSet = new LinkedHashSet<>(
541           StaticUtils.computeMapCapacity(sourceSet.size()));
542      for (final Argument a : sourceSet)
543      {
544        newSet.add(argsByID.get(a.getIdentifierString()));
545      }
546
547      final Argument sourceFirst = p.getFirst();
548      final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString());
549      dependentArgumentSets.add(
550           new ObjectPair<Argument, Set<Argument>>(newFirst, newSet));
551    }
552
553    exclusiveArgumentSets =
554         new ArrayList<>(source.exclusiveArgumentSets.size());
555    for (final Set<Argument> sourceSet : source.exclusiveArgumentSets)
556    {
557      final LinkedHashSet<Argument> newSet = new LinkedHashSet<>(
558           StaticUtils.computeMapCapacity(sourceSet.size()));
559      for (final Argument a : sourceSet)
560      {
561        newSet.add(argsByID.get(a.getIdentifierString()));
562      }
563
564      exclusiveArgumentSets.add(newSet);
565    }
566
567    requiredArgumentSets =
568         new ArrayList<>(source.requiredArgumentSets.size());
569    for (final Set<Argument> sourceSet : source.requiredArgumentSets)
570    {
571      final LinkedHashSet<Argument> newSet = new LinkedHashSet<>(
572           StaticUtils.computeMapCapacity(sourceSet.size()));
573      for (final Argument a : sourceSet)
574      {
575        newSet.add(argsByID.get(a.getIdentifierString()));
576      }
577      requiredArgumentSets.add(newSet);
578    }
579
580    parentSubCommand = subCommand;
581    selectedSubCommand = null;
582    subCommands = new ArrayList<>(source.subCommands.size());
583    subCommandsByName = new LinkedHashMap<>(
584         StaticUtils.computeMapCapacity(source.subCommandsByName.size()));
585    for (final SubCommand sc : source.subCommands)
586    {
587      subCommands.add(sc.getCleanCopy());
588      for (final String name : sc.getNames(true))
589      {
590        subCommandsByName.put(StaticUtils.toLowerCase(name), sc);
591      }
592    }
593  }
594
595
596
597  /**
598   * Retrieves the name of the application or utility with which this command
599   * line argument parser is associated.
600   *
601   * @return  The name of the application or utility with which this command
602   *          line argument parser is associated.
603   */
604  public String getCommandName()
605  {
606    return commandName;
607  }
608
609
610
611  /**
612   * Retrieves a description of the application or utility with which this
613   * command line argument parser is associated.  If the description should
614   * include multiple paragraphs, then this method will return the text for the
615   * first paragraph, and the
616   * {@link #getAdditionalCommandDescriptionParagraphs()} method should return a
617   * list with the text for all subsequent paragraphs.
618   *
619   * @return  A description of the application or utility with which this
620   *          command line argument parser is associated.
621   */
622  public String getCommandDescription()
623  {
624    return commandDescription;
625  }
626
627
628
629  /**
630   * Retrieves a list containing the the text for all subsequent paragraphs to
631   * include in the description for the application or utility with which this
632   * command line argument parser is associated.  If the description should have
633   * multiple paragraphs, then the {@link #getCommandDescription()} method will
634   * provide the text for the first paragraph and this method will provide the
635   * text for the subsequent paragraphs.  If the description should only have a
636   * single paragraph, then the text of that paragraph should be returned by the
637   * {@code getCommandDescription} method, and this method will return an empty
638   * list.
639   *
640   * @return  A list containing the text for all subsequent paragraphs to
641   *          include in the description for the application or utility with
642   *          which this command line argument parser is associated, or an empty
643   *          list if the description should only include a single paragraph.
644   */
645  public List<String> getAdditionalCommandDescriptionParagraphs()
646  {
647    return additionalCommandDescriptionParagraphs;
648  }
649
650
651
652  /**
653   * Indicates whether this argument parser allows any unnamed trailing
654   * arguments to be provided.
655   *
656   * @return  {@code true} if at least one unnamed trailing argument may be
657   *          provided, or {@code false} if not.
658   */
659  public boolean allowsTrailingArguments()
660  {
661    return (maxTrailingArgs != 0);
662  }
663
664
665
666  /**
667   * Indicates whether this argument parser requires at least unnamed trailing
668   * argument to be provided.
669   *
670   * @return  {@code true} if at least one unnamed trailing argument must be
671   *          provided, or {@code false} if the tool may be invoked without any
672   *          such arguments.
673   */
674  public boolean requiresTrailingArguments()
675  {
676    return (minTrailingArgs != 0);
677  }
678
679
680
681  /**
682   * Retrieves the placeholder string that will be provided in usage information
683   * to indicate what may be included in the trailing arguments.
684   *
685   * @return  The placeholder string that will be provided in usage information
686   *          to indicate what may be included in the trailing arguments, or
687   *          {@code null} if unnamed trailing arguments are not allowed.
688   */
689  public String getTrailingArgumentsPlaceholder()
690  {
691    return trailingArgsPlaceholder;
692  }
693
694
695
696  /**
697   * Retrieves the minimum number of unnamed trailing arguments that must be
698   * provided.
699   *
700   * @return  The minimum number of unnamed trailing arguments that must be
701   *          provided.
702   */
703  public int getMinTrailingArguments()
704  {
705    return minTrailingArgs;
706  }
707
708
709
710  /**
711   * Retrieves the maximum number of unnamed trailing arguments that may be
712   * provided.
713   *
714   * @return  The maximum number of unnamed trailing arguments that may be
715   *          provided.
716   */
717  public int getMaxTrailingArguments()
718  {
719    return maxTrailingArgs;
720  }
721
722
723
724  /**
725   * Updates this argument parser to enable support for a properties file that
726   * can be used to specify the default values for any properties that were not
727   * supplied via the command line.  This method should be invoked after the
728   * argument parser has been configured with all of the other arguments that it
729   * supports and before the {@link #parse} method is invoked.  In addition,
730   * after invoking the {@code parse} method, the caller must also invoke the
731   * {@link #getGeneratedPropertiesFile} method to determine if the only
732   * processing performed that should be performed is the generation of a
733   * properties file that will have already been performed.
734   * <BR><BR>
735   * This method will update the argument parser to add the following additional
736   * arguments:
737   * <UL>
738   *   <LI>
739   *     {@code propertiesFilePath} -- Specifies the path to the properties file
740   *     that should be used to obtain default values for any arguments not
741   *     provided on the command line.  If this is not specified and the
742   *     {@code noPropertiesFile} argument is not present, then the argument
743   *     parser may use a default properties file path specified using either
744   *     the {@code com.unboundid.util.args.ArgumentParser..propertiesFilePath}
745   *     system property or the {@code UNBOUNDID_TOOL_PROPERTIES_FILE_PATH}
746   *     environment variable.
747   *   </LI>
748   *   <LI>
749   *     {@code generatePropertiesFile} -- Indicates that the tool should
750   *     generate a properties file for this argument parser and write it to the
751   *     specified location.  The generated properties file will not have any
752   *     properties set, but will include comments that describe all of the
753   *     supported arguments, as well general information about the use of a
754   *     properties file.  If this argument is specified on the command line,
755   *     then no other arguments should be given.
756   *   </LI>
757   *   <LI>
758   *     {@code noPropertiesFile} -- Indicates that the tool should not use a
759   *     properties file to obtain default values for any arguments not provided
760   *     on the command line.
761   *   </LI>
762   * </UL>
763   *
764   * @throws  ArgumentException  If any of the arguments related to properties
765   *                             file processing conflicts with an argument that
766   *                             has already been added to the argument parser.
767   */
768  public void enablePropertiesFileSupport()
769         throws ArgumentException
770  {
771    final FileArgument propertiesFilePath = new FileArgument(null,
772         ARG_NAME_PROPERTIES_FILE_PATH, false, 1, null,
773         INFO_ARG_DESCRIPTION_PROP_FILE_PATH.get(), true, true, true, false);
774    propertiesFilePath.setUsageArgument(true);
775    propertiesFilePath.addLongIdentifier("properties-file-path", true);
776    addArgument(propertiesFilePath);
777
778    final FileArgument generatePropertiesFile = new FileArgument(null,
779         ARG_NAME_GENERATE_PROPERTIES_FILE, false, 1, null,
780         INFO_ARG_DESCRIPTION_GEN_PROP_FILE.get(), false, true, true, false);
781    generatePropertiesFile.setUsageArgument(true);
782    generatePropertiesFile.addLongIdentifier("generate-properties-file", true);
783    addArgument(generatePropertiesFile);
784
785    final BooleanArgument noPropertiesFile = new BooleanArgument(null,
786         ARG_NAME_NO_PROPERTIES_FILE, INFO_ARG_DESCRIPTION_NO_PROP_FILE.get());
787    noPropertiesFile.setUsageArgument(true);
788    noPropertiesFile.addLongIdentifier("no-properties-file", true);
789    addArgument(noPropertiesFile);
790
791    final BooleanArgument suppressPropertiesFileComment = new BooleanArgument(
792         null, ARG_NAME_SUPPRESS_PROPERTIES_FILE_COMMENT, 1,
793         INFO_ARG_DESCRIPTION_SUPPRESS_PROP_FILE_COMMENT.get());
794    suppressPropertiesFileComment.setUsageArgument(true);
795    suppressPropertiesFileComment.addLongIdentifier(
796         "suppress-properties-file-comment", true);
797    addArgument(suppressPropertiesFileComment);
798
799
800    // The propertiesFilePath and noPropertiesFile arguments cannot be used
801    // together.
802    addExclusiveArgumentSet(propertiesFilePath, noPropertiesFile);
803  }
804
805
806
807  /**
808   * Indicates whether this argument parser was used to generate a properties
809   * file.  If so, then the tool invoking the parser should return without
810   * performing any further processing.
811   *
812   * @return  A {@code File} object that represents the path to the properties
813   *          file that was generated, or {@code null} if no properties file was
814   *          generated.
815   */
816  public File getGeneratedPropertiesFile()
817  {
818    final Argument a = getNamedArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
819    if ((a == null) || (! a.isPresent()) || (! (a instanceof FileArgument)))
820    {
821      return null;
822    }
823
824    return ((FileArgument) a).getValue();
825  }
826
827
828
829  /**
830   * Retrieves the named argument with the specified short identifier.
831   *
832   * @param  shortIdentifier  The short identifier of the argument to retrieve.
833   *                          It must not be {@code null}.
834   *
835   * @return  The named argument with the specified short identifier, or
836   *          {@code null} if there is no such argument.
837   */
838  public Argument getNamedArgument(final Character shortIdentifier)
839  {
840    Validator.ensureNotNull(shortIdentifier);
841    return namedArgsByShortID.get(shortIdentifier);
842  }
843
844
845
846  /**
847   * Retrieves the named argument with the specified identifier.
848   *
849   * @param  identifier  The identifier of the argument to retrieve.  It may be
850   *                     the long identifier without any dashes, the short
851   *                     identifier character preceded by a single dash, or the
852   *                     long identifier preceded by two dashes. It must not be
853   *                     {@code null}.
854   *
855   * @return  The named argument with the specified long identifier, or
856   *          {@code null} if there is no such argument.
857   */
858  public Argument getNamedArgument(final String identifier)
859  {
860    Validator.ensureNotNull(identifier);
861
862    if (identifier.startsWith("--") && (identifier.length() > 2))
863    {
864      return namedArgsByLongID.get(
865           StaticUtils.toLowerCase(identifier.substring(2)));
866    }
867    else if (identifier.startsWith("-") && (identifier.length() == 2))
868    {
869      return namedArgsByShortID.get(identifier.charAt(1));
870    }
871    else
872    {
873      return namedArgsByLongID.get(StaticUtils.toLowerCase(identifier));
874    }
875  }
876
877
878
879  /**
880   * Retrieves the argument list argument with the specified identifier.
881   *
882   * @param  identifier  The identifier of the argument to retrieve.  It may be
883   *                     the long identifier without any dashes, the short
884   *                     identifier character preceded by a single dash, or the
885   *                     long identifier preceded by two dashes. It must not be
886   *                     {@code null}.
887   *
888   * @return  The argument list argument with the specified identifier, or
889   *          {@code null} if there is no such argument.
890   */
891  public ArgumentListArgument getArgumentListArgument(final String identifier)
892  {
893    final Argument a = getNamedArgument(identifier);
894    if (a == null)
895    {
896      return null;
897    }
898    else
899    {
900      return (ArgumentListArgument) a;
901    }
902  }
903
904
905
906  /**
907   * Retrieves the Boolean argument with the specified identifier.
908   *
909   * @param  identifier  The identifier of the argument to retrieve.  It may be
910   *                     the long identifier without any dashes, the short
911   *                     identifier character preceded by a single dash, or the
912   *                     long identifier preceded by two dashes. It must not be
913   *                     {@code null}.
914   *
915   * @return  The Boolean argument with the specified identifier, or
916   *          {@code null} if there is no such argument.
917   */
918  public BooleanArgument getBooleanArgument(final String identifier)
919  {
920    final Argument a = getNamedArgument(identifier);
921    if (a == null)
922    {
923      return null;
924    }
925    else
926    {
927      return (BooleanArgument) a;
928    }
929  }
930
931
932
933  /**
934   * Retrieves the Boolean value argument with the specified identifier.
935   *
936   * @param  identifier  The identifier of the argument to retrieve.  It may be
937   *                     the long identifier without any dashes, the short
938   *                     identifier character preceded by a single dash, or the
939   *                     long identifier preceded by two dashes. It must not be
940   *                     {@code null}.
941   *
942   * @return  The Boolean value argument with the specified identifier, or
943   *          {@code null} if there is no such argument.
944   */
945  public BooleanValueArgument getBooleanValueArgument(final String identifier)
946  {
947    final Argument a = getNamedArgument(identifier);
948    if (a == null)
949    {
950      return null;
951    }
952    else
953    {
954      return (BooleanValueArgument) a;
955    }
956  }
957
958
959
960  /**
961   * Retrieves the control argument with the specified identifier.
962   *
963   * @param  identifier  The identifier of the argument to retrieve.  It may be
964   *                     the long identifier without any dashes, the short
965   *                     identifier character preceded by a single dash, or the
966   *                     long identifier preceded by two dashes. It must not be
967   *                     {@code null}.
968   *
969   * @return  The control argument with the specified identifier, or
970   *          {@code null} if there is no such argument.
971   */
972  public ControlArgument getControlArgument(final String identifier)
973  {
974    final Argument a = getNamedArgument(identifier);
975    if (a == null)
976    {
977      return null;
978    }
979    else
980    {
981      return (ControlArgument) a;
982    }
983  }
984
985
986
987  /**
988   * Retrieves the DN argument with the specified identifier.
989   *
990   * @param  identifier  The identifier of the argument to retrieve.  It may be
991   *                     the long identifier without any dashes, the short
992   *                     identifier character preceded by a single dash, or the
993   *                     long identifier preceded by two dashes. It must not be
994   *                     {@code null}.
995   *
996   * @return  The DN argument with the specified identifier, or
997   *          {@code null} if there is no such argument.
998   */
999  public DNArgument getDNArgument(final String identifier)
1000  {
1001    final Argument a = getNamedArgument(identifier);
1002    if (a == null)
1003    {
1004      return null;
1005    }
1006    else
1007    {
1008      return (DNArgument) a;
1009    }
1010  }
1011
1012
1013
1014  /**
1015   * Retrieves the duration argument with the specified identifier.
1016   *
1017   * @param  identifier  The identifier of the argument to retrieve.  It may be
1018   *                     the long identifier without any dashes, the short
1019   *                     identifier character preceded by a single dash, or the
1020   *                     long identifier preceded by two dashes. It must not be
1021   *                     {@code null}.
1022   *
1023   * @return  The duration argument with the specified identifier, or
1024   *          {@code null} if there is no such argument.
1025   */
1026  public DurationArgument getDurationArgument(final String identifier)
1027  {
1028    final Argument a = getNamedArgument(identifier);
1029    if (a == null)
1030    {
1031      return null;
1032    }
1033    else
1034    {
1035      return (DurationArgument) a;
1036    }
1037  }
1038
1039
1040
1041  /**
1042   * Retrieves the file argument with the specified identifier.
1043   *
1044   * @param  identifier  The identifier of the argument to retrieve.  It may be
1045   *                     the long identifier without any dashes, the short
1046   *                     identifier character preceded by a single dash, or the
1047   *                     long identifier preceded by two dashes. It must not be
1048   *                     {@code null}.
1049   *
1050   * @return  The file argument with the specified identifier, or
1051   *          {@code null} if there is no such argument.
1052   */
1053  public FileArgument getFileArgument(final String identifier)
1054  {
1055    final Argument a = getNamedArgument(identifier);
1056    if (a == null)
1057    {
1058      return null;
1059    }
1060    else
1061    {
1062      return (FileArgument) a;
1063    }
1064  }
1065
1066
1067
1068  /**
1069   * Retrieves the filter argument with the specified identifier.
1070   *
1071   * @param  identifier  The identifier of the argument to retrieve.  It may be
1072   *                     the long identifier without any dashes, the short
1073   *                     identifier character preceded by a single dash, or the
1074   *                     long identifier preceded by two dashes. It must not be
1075   *                     {@code null}.
1076   *
1077   * @return  The filter argument with the specified identifier, or
1078   *          {@code null} if there is no such argument.
1079   */
1080  public FilterArgument getFilterArgument(final String identifier)
1081  {
1082    final Argument a = getNamedArgument(identifier);
1083    if (a == null)
1084    {
1085      return null;
1086    }
1087    else
1088    {
1089      return (FilterArgument) a;
1090    }
1091  }
1092
1093
1094
1095  /**
1096   * Retrieves the integer argument with the specified identifier.
1097   *
1098   * @param  identifier  The identifier of the argument to retrieve.  It may be
1099   *                     the long identifier without any dashes, the short
1100   *                     identifier character preceded by a single dash, or the
1101   *                     long identifier preceded by two dashes. It must not be
1102   *                     {@code null}.
1103   *
1104   * @return  The integer argument with the specified identifier, or
1105   *          {@code null} if there is no such argument.
1106   */
1107  public IntegerArgument getIntegerArgument(final String identifier)
1108  {
1109    final Argument a = getNamedArgument(identifier);
1110    if (a == null)
1111    {
1112      return null;
1113    }
1114    else
1115    {
1116      return (IntegerArgument) a;
1117    }
1118  }
1119
1120
1121
1122  /**
1123   * Retrieves the scope argument with the specified identifier.
1124   *
1125   * @param  identifier  The identifier of the argument to retrieve.  It may be
1126   *                     the long identifier without any dashes, the short
1127   *                     identifier character preceded by a single dash, or the
1128   *                     long identifier preceded by two dashes. It must not be
1129   *                     {@code null}.
1130   *
1131   * @return  The scope argument with the specified identifier, or
1132   *          {@code null} if there is no such argument.
1133   */
1134  public ScopeArgument getScopeArgument(final String identifier)
1135  {
1136    final Argument a = getNamedArgument(identifier);
1137    if (a == null)
1138    {
1139      return null;
1140    }
1141    else
1142    {
1143      return (ScopeArgument) a;
1144    }
1145  }
1146
1147
1148
1149  /**
1150   * Retrieves the string argument with the specified identifier.
1151   *
1152   * @param  identifier  The identifier of the argument to retrieve.  It may be
1153   *                     the long identifier without any dashes, the short
1154   *                     identifier character preceded by a single dash, or the
1155   *                     long identifier preceded by two dashes. It must not be
1156   *                     {@code null}.
1157   *
1158   * @return  The string argument with the specified identifier, or
1159   *          {@code null} if there is no such argument.
1160   */
1161  public StringArgument getStringArgument(final String identifier)
1162  {
1163    final Argument a = getNamedArgument(identifier);
1164    if (a == null)
1165    {
1166      return null;
1167    }
1168    else
1169    {
1170      return (StringArgument) a;
1171    }
1172  }
1173
1174
1175
1176  /**
1177   * Retrieves the timestamp argument with the specified identifier.
1178   *
1179   * @param  identifier  The identifier of the argument to retrieve.  It may be
1180   *                     the long identifier without any dashes, the short
1181   *                     identifier character preceded by a single dash, or the
1182   *                     long identifier preceded by two dashes. It must not be
1183   *                     {@code null}.
1184   *
1185   * @return  The timestamp argument with the specified identifier, or
1186   *          {@code null} if there is no such argument.
1187   */
1188  public TimestampArgument getTimestampArgument(final String identifier)
1189  {
1190    final Argument a = getNamedArgument(identifier);
1191    if (a == null)
1192    {
1193      return null;
1194    }
1195    else
1196    {
1197      return (TimestampArgument) a;
1198    }
1199  }
1200
1201
1202
1203  /**
1204   * Retrieves the set of named arguments defined for use with this argument
1205   * parser.
1206   *
1207   * @return  The set of named arguments defined for use with this argument
1208   *          parser.
1209   */
1210  public List<Argument> getNamedArguments()
1211  {
1212    return Collections.unmodifiableList(namedArgs);
1213  }
1214
1215
1216
1217  /**
1218   * Registers the provided argument with this argument parser.
1219   *
1220   * @param  argument  The argument to be registered.
1221   *
1222   * @throws  ArgumentException  If the provided argument conflicts with another
1223   *                             argument already registered with this parser.
1224   */
1225  public void addArgument(final Argument argument)
1226         throws ArgumentException
1227  {
1228    argument.setRegistered();
1229    for (final Character c : argument.getShortIdentifiers(true))
1230    {
1231      if (namedArgsByShortID.containsKey(c))
1232      {
1233        throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
1234      }
1235
1236      if ((parentSubCommand != null) &&
1237          (parentSubCommand.getArgumentParser().namedArgsByShortID.containsKey(
1238               c)))
1239      {
1240        throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
1241      }
1242    }
1243
1244    for (final String s : argument.getLongIdentifiers(true))
1245    {
1246      if (namedArgsByLongID.containsKey(StaticUtils.toLowerCase(s)))
1247      {
1248        throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
1249      }
1250
1251      if ((parentSubCommand != null) &&
1252          (parentSubCommand.getArgumentParser().namedArgsByLongID.containsKey(
1253                StaticUtils.toLowerCase(s))))
1254      {
1255        throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
1256      }
1257    }
1258
1259    for (final SubCommand sc : subCommands)
1260    {
1261      final ArgumentParser parser = sc.getArgumentParser();
1262      for (final Character c : argument.getShortIdentifiers(true))
1263      {
1264        if (parser.namedArgsByShortID.containsKey(c))
1265        {
1266          throw new ArgumentException(
1267               ERR_PARSER_SHORT_ID_CONFLICT_WITH_SUBCOMMAND.get(c,
1268                    sc.getPrimaryName()));
1269        }
1270      }
1271
1272      for (final String s : argument.getLongIdentifiers(true))
1273      {
1274        if (parser.namedArgsByLongID.containsKey(StaticUtils.toLowerCase(s)))
1275        {
1276          throw new ArgumentException(
1277               ERR_PARSER_LONG_ID_CONFLICT_WITH_SUBCOMMAND.get(s,
1278                    sc.getPrimaryName()));
1279        }
1280      }
1281    }
1282
1283    for (final Character c : argument.getShortIdentifiers(true))
1284    {
1285      namedArgsByShortID.put(c, argument);
1286    }
1287
1288    for (final String s : argument.getLongIdentifiers(true))
1289    {
1290      namedArgsByLongID.put(StaticUtils.toLowerCase(s), argument);
1291    }
1292
1293    namedArgs.add(argument);
1294  }
1295
1296
1297
1298  /**
1299   * Retrieves the list of dependent argument sets for this argument parser.  If
1300   * an argument contained as the first object in the pair in a dependent
1301   * argument set is provided, then at least one of the arguments in the paired
1302   * set must also be provided.
1303   *
1304   * @return  The list of dependent argument sets for this argument parser.
1305   */
1306  public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets()
1307  {
1308    return Collections.unmodifiableList(dependentArgumentSets);
1309  }
1310
1311
1312
1313  /**
1314   * Adds the provided collection of arguments as dependent upon the given
1315   * argument.  All of the arguments must have already been registered with this
1316   * argument parser using the {@link #addArgument} method.
1317   *
1318   * @param  targetArgument      The argument whose presence indicates that at
1319   *                             least one of the dependent arguments must also
1320   *                             be present.  It must not be {@code null}, and
1321   *                             it must have already been registered with this
1322   *                             argument parser.
1323   * @param  dependentArguments  The set of arguments from which at least one
1324   *                             argument must be present if the target argument
1325   *                             is present.  It must not be {@code null} or
1326   *                             empty, and all arguments must have already been
1327   *                             registered with this argument parser.
1328   */
1329  public void addDependentArgumentSet(final Argument targetArgument,
1330                   final Collection<Argument> dependentArguments)
1331  {
1332    Validator.ensureNotNull(targetArgument, dependentArguments);
1333
1334    Validator.ensureFalse(dependentArguments.isEmpty(),
1335         "The ArgumentParser.addDependentArgumentSet method must not be " +
1336              "called with an empty collection of dependentArguments");
1337
1338    Validator.ensureTrue(namedArgs.contains(targetArgument),
1339         "The ArgumentParser.addDependentArgumentSet method may only be used " +
1340              "if all of the provided arguments have already been registered " +
1341              "with the argument parser via the ArgumentParser.addArgument " +
1342              "method.  The " + targetArgument.getIdentifierString() +
1343              " argument has not been registered with the argument parser.");
1344    for (final Argument a : dependentArguments)
1345    {
1346      Validator.ensureTrue(namedArgs.contains(a),
1347           "The ArgumentParser.addDependentArgumentSet method may only be " +
1348                "used if all of the provided arguments have already been " +
1349                "registered with the argument parser via the " +
1350                "ArgumentParser.addArgument method.  The " +
1351                a.getIdentifierString() + " argument has not been registered " +
1352                "with the argument parser.");
1353    }
1354
1355    final LinkedHashSet<Argument> argSet =
1356         new LinkedHashSet<>(dependentArguments);
1357    dependentArgumentSets.add(
1358         new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1359  }
1360
1361
1362
1363  /**
1364   * Adds the provided collection of arguments as dependent upon the given
1365   * argument.  All of the arguments must have already been registered with this
1366   * argument parser using the {@link #addArgument} method.
1367   *
1368   * @param  targetArgument  The argument whose presence indicates that at least
1369   *                         one of the dependent arguments must also be
1370   *                         present.  It must not be {@code null}, and it must
1371   *                         have already been registered with this argument
1372   *                         parser.
1373   * @param  dependentArg1   The first argument in the set of arguments in which
1374   *                         at least one argument must be present if the target
1375   *                         argument is present.  It must not be {@code null},
1376   *                         and it must have already been registered with this
1377   *                         argument parser.
1378   * @param  remaining       The remaining arguments in the set of arguments in
1379   *                         which at least one argument must be present if the
1380   *                         target argument is present.  It may be {@code null}
1381   *                         or empty if no additional dependent arguments are
1382   *                         needed, but if it is non-empty then all arguments
1383   *                         must have already been registered with this
1384   *                         argument parser.
1385   */
1386  public void addDependentArgumentSet(final Argument targetArgument,
1387                                      final Argument dependentArg1,
1388                                      final Argument... remaining)
1389  {
1390    Validator.ensureNotNull(targetArgument, dependentArg1);
1391
1392    Validator.ensureTrue(namedArgs.contains(targetArgument),
1393         "The ArgumentParser.addDependentArgumentSet method may only be used " +
1394              "if all of the provided arguments have already been registered " +
1395              "with the argument parser via the ArgumentParser.addArgument " +
1396              "method.  The " + targetArgument.getIdentifierString() +
1397              " argument has not been registered with the argument parser.");
1398    Validator.ensureTrue(namedArgs.contains(dependentArg1),
1399         "The ArgumentParser.addDependentArgumentSet method may only be used " +
1400              "if all of the provided arguments have already been registered " +
1401              "with the argument parser via the ArgumentParser.addArgument " +
1402              "method.  The " + dependentArg1.getIdentifierString() +
1403              " argument has not been registered with the argument parser.");
1404    if (remaining != null)
1405    {
1406      for (final Argument a : remaining)
1407      {
1408        Validator.ensureTrue(namedArgs.contains(a),
1409             "The ArgumentParser.addDependentArgumentSet method may only be " +
1410                  "used if all of the provided arguments have already been " +
1411                  "registered with the argument parser via the " +
1412                  "ArgumentParser.addArgument method.  The " +
1413                  a.getIdentifierString() + " argument has not been " +
1414                  "registered with the argument parser.");
1415      }
1416    }
1417
1418    final LinkedHashSet<Argument> argSet =
1419         new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
1420    argSet.add(dependentArg1);
1421    if (remaining != null)
1422    {
1423      argSet.addAll(Arrays.asList(remaining));
1424    }
1425
1426    dependentArgumentSets.add(
1427         new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1428  }
1429
1430
1431
1432  /**
1433   * Adds the provided set of arguments as mutually dependent, such that if any
1434   * of the arguments is provided, then all of them must be provided.  It will
1435   * be implemented by creating multiple dependent argument sets (one for each
1436   * argument in the provided collection).
1437   *
1438   * @param  arguments  The collection of arguments to be used to create the
1439   *                    dependent argument sets.  It must not be {@code null},
1440   *                    and must contain at least two elements.
1441   */
1442  public void addMutuallyDependentArgumentSet(
1443                   final Collection<Argument> arguments)
1444  {
1445    Validator.ensureNotNullWithMessage(arguments,
1446         "ArgumentParser.addMutuallyDependentArgumentSet.arguments must not " +
1447              "be null.");
1448    Validator.ensureTrue((arguments.size() >= 2),
1449         "ArgumentParser.addMutuallyDependentArgumentSet.arguments must " +
1450              "contain at least two elements.");
1451
1452    for (final Argument a : arguments)
1453    {
1454      Validator.ensureTrue(namedArgs.contains(a),
1455           "ArgumentParser.addMutuallyDependentArgumentSet invoked with " +
1456                "argument " + a.getIdentifierString() +
1457                " that is not registered with the argument parser.");
1458    }
1459
1460    final Set<Argument> allArgsSet = new HashSet<>(arguments);
1461    for (final Argument a : allArgsSet)
1462    {
1463      final Set<Argument> dependentArgs = new HashSet<>(allArgsSet);
1464      dependentArgs.remove(a);
1465      addDependentArgumentSet(a, dependentArgs);
1466    }
1467  }
1468
1469
1470
1471  /**
1472   * Adds the provided set of arguments as mutually dependent, such that if any
1473   * of the arguments is provided, then all of them must be provided.  It will
1474   * be implemented by creating multiple dependent argument sets (one for each
1475   * argument in the provided collection).
1476   *
1477   * @param  arg1       The first argument to include in the mutually dependent
1478   *                    argument set.  It must not be {@code null}.
1479   * @param  arg2       The second argument to include in the mutually dependent
1480   *                    argument set.  It must not be {@code null}.
1481   * @param  remaining  An optional set of additional arguments to include in
1482   *                    the mutually dependent argument set.  It may be
1483   *                    {@code null} or empty if only two arguments should be
1484   *                    included in the mutually dependent argument set.
1485   */
1486  public void addMutuallyDependentArgumentSet(final Argument arg1,
1487                                              final Argument arg2,
1488                                              final Argument... remaining)
1489  {
1490    Validator.ensureNotNullWithMessage(arg1,
1491         "ArgumentParser.addMutuallyDependentArgumentSet.arg1 must not be " +
1492              "null.");
1493    Validator.ensureNotNullWithMessage(arg2,
1494         "ArgumentParser.addMutuallyDependentArgumentSet.arg2 must not be " +
1495              "null.");
1496
1497    final List<Argument> args = new ArrayList<>(10);
1498    args.add(arg1);
1499    args.add(arg2);
1500
1501    if (remaining != null)
1502    {
1503      args.addAll(Arrays.asList(remaining));
1504    }
1505
1506    addMutuallyDependentArgumentSet(args);
1507  }
1508
1509
1510
1511  /**
1512   * Retrieves the list of exclusive argument sets for this argument parser.
1513   * If an argument contained in an exclusive argument set is provided, then
1514   * none of the other arguments in that set may be provided.  It is acceptable
1515   * for none of the arguments in the set to be provided, unless the same set
1516   * of arguments is also defined as a required argument set.
1517   *
1518   * @return  The list of exclusive argument sets for this argument parser.
1519   */
1520  public List<Set<Argument>> getExclusiveArgumentSets()
1521  {
1522    return Collections.unmodifiableList(exclusiveArgumentSets);
1523  }
1524
1525
1526
1527  /**
1528   * Adds the provided collection of arguments as an exclusive argument set, in
1529   * which at most one of the arguments may be provided.  All of the arguments
1530   * must have already been registered with this argument parser using the
1531   * {@link #addArgument} method.
1532   *
1533   * @param  exclusiveArguments  The collection of arguments to form an
1534   *                             exclusive argument set.  It must not be
1535   *                             {@code null}, and all of the arguments must
1536   *                             have already been registered with this argument
1537   *                             parser.
1538   */
1539  public void addExclusiveArgumentSet(
1540                   final Collection<Argument> exclusiveArguments)
1541  {
1542    Validator.ensureNotNull(exclusiveArguments);
1543
1544    for (final Argument a : exclusiveArguments)
1545    {
1546      Validator.ensureTrue(namedArgs.contains(a),
1547           "The ArgumentParser.addExclusiveArgumentSet method may only be " +
1548                "used if all of the provided arguments have already been " +
1549                "registered with the argument parser via the " +
1550                "ArgumentParser.addArgument method.  The " +
1551                a.getIdentifierString() + " argument has not been " +
1552                "registered with the argument parser.");
1553    }
1554
1555    final LinkedHashSet<Argument> argSet =
1556         new LinkedHashSet<>(exclusiveArguments);
1557    exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1558  }
1559
1560
1561
1562  /**
1563   * Adds the provided set of arguments as an exclusive argument set, in
1564   * which at most one of the arguments may be provided.  All of the arguments
1565   * must have already been registered with this argument parser using the
1566   * {@link #addArgument} method.
1567   *
1568   * @param  arg1       The first argument to include in the exclusive argument
1569   *                    set.  It must not be {@code null}, and it must have
1570   *                    already been registered with this argument parser.
1571   * @param  arg2       The second argument to include in the exclusive argument
1572   *                    set.  It must not be {@code null}, and it must have
1573   *                    already been registered with this argument parser.
1574   * @param  remaining  Any additional arguments to include in the exclusive
1575   *                    argument set.  It may be {@code null} or empty if no
1576   *                    additional exclusive arguments are needed, but if it is
1577   *                    non-empty then all arguments must have already been
1578   *                    registered with this argument parser.
1579   */
1580  public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2,
1581                                      final Argument... remaining)
1582  {
1583    Validator.ensureNotNull(arg1, arg2);
1584
1585    Validator.ensureTrue(namedArgs.contains(arg1),
1586         "The ArgumentParser.addExclusiveArgumentSet method may only be " +
1587              "used if all of the provided arguments have already been " +
1588              "registered with the argument parser via the " +
1589              "ArgumentParser.addArgument method.  The " +
1590              arg1.getIdentifierString() + " argument has not been " +
1591              "registered with the argument parser.");
1592    Validator.ensureTrue(namedArgs.contains(arg2),
1593         "The ArgumentParser.addExclusiveArgumentSet method may only be " +
1594              "used if all of the provided arguments have already been " +
1595              "registered with the argument parser via the " +
1596              "ArgumentParser.addArgument method.  The " +
1597              arg2.getIdentifierString() + " argument has not been " +
1598              "registered with the argument parser.");
1599
1600    if (remaining != null)
1601    {
1602      for (final Argument a : remaining)
1603      {
1604        Validator.ensureTrue(namedArgs.contains(a),
1605             "The ArgumentParser.addExclusiveArgumentSet method may only be " +
1606                  "used if all of the provided arguments have already been " +
1607                  "registered with the argument parser via the " +
1608                  "ArgumentParser.addArgument method.  The " +
1609                  a.getIdentifierString() + " argument has not been " +
1610                  "registered with the argument parser.");
1611      }
1612    }
1613
1614    final LinkedHashSet<Argument> argSet =
1615         new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
1616    argSet.add(arg1);
1617    argSet.add(arg2);
1618
1619    if (remaining != null)
1620    {
1621      argSet.addAll(Arrays.asList(remaining));
1622    }
1623
1624    exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1625  }
1626
1627
1628
1629  /**
1630   * Retrieves the list of required argument sets for this argument parser.  At
1631   * least one of the arguments contained in this set must be provided.  If this
1632   * same set is also defined as an exclusive argument set, then exactly one
1633   * of those arguments must be provided.
1634   *
1635   * @return  The list of required argument sets for this argument parser.
1636   */
1637  public List<Set<Argument>> getRequiredArgumentSets()
1638  {
1639    return Collections.unmodifiableList(requiredArgumentSets);
1640  }
1641
1642
1643
1644  /**
1645   * Adds the provided collection of arguments as a required argument set, in
1646   * which at least one of the arguments must be provided.  All of the arguments
1647   * must have already been registered with this argument parser using the
1648   * {@link #addArgument} method.
1649   *
1650   * @param  requiredArguments  The collection of arguments to form an
1651   *                            required argument set.  It must not be
1652   *                            {@code null}, and all of the arguments must have
1653   *                            already been registered with this argument
1654   *                            parser.
1655   */
1656  public void addRequiredArgumentSet(
1657                   final Collection<Argument> requiredArguments)
1658  {
1659    Validator.ensureNotNull(requiredArguments);
1660
1661    for (final Argument a : requiredArguments)
1662    {
1663      Validator.ensureTrue(namedArgs.contains(a),
1664           "The ArgumentParser.addRequiredArgumentSet method may only be " +
1665                "used if all of the provided arguments have already been " +
1666                "registered with the argument parser via the " +
1667                "ArgumentParser.addArgument method.  The " +
1668                a.getIdentifierString() + " argument has not been " +
1669                "registered with the argument parser.");
1670    }
1671
1672    final LinkedHashSet<Argument> argSet =
1673         new LinkedHashSet<>(requiredArguments);
1674    requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1675  }
1676
1677
1678
1679  /**
1680   * Adds the provided set of arguments as a required argument set, in which
1681   * at least one of the arguments must be provided.  All of the arguments must
1682   * have already been registered with this argument parser using the
1683   * {@link #addArgument} method.
1684   *
1685   * @param  arg1       The first argument to include in the required argument
1686   *                    set.  It must not be {@code null}, and it must have
1687   *                    already been registered with this argument parser.
1688   * @param  arg2       The second argument to include in the required argument
1689   *                    set.  It must not be {@code null}, and it must have
1690   *                    already been registered with this argument parser.
1691   * @param  remaining  Any additional arguments to include in the required
1692   *                    argument set.  It may be {@code null} or empty if no
1693   *                    additional required arguments are needed, but if it is
1694   *                    non-empty then all arguments must have already been
1695   *                    registered with this argument parser.
1696   */
1697  public void addRequiredArgumentSet(final Argument arg1, final Argument arg2,
1698                                     final Argument... remaining)
1699  {
1700    Validator.ensureNotNull(arg1, arg2);
1701
1702    Validator.ensureTrue(namedArgs.contains(arg1),
1703         "The ArgumentParser.addRequiredArgumentSet method may only be " +
1704              "used if all of the provided arguments have already been " +
1705              "registered with the argument parser via the " +
1706              "ArgumentParser.addArgument method.  The " +
1707              arg1.getIdentifierString() + " argument has not been " +
1708              "registered with the argument parser.");
1709    Validator.ensureTrue(namedArgs.contains(arg2),
1710         "The ArgumentParser.addRequiredArgumentSet method may only be " +
1711              "used if all of the provided arguments have already been " +
1712              "registered with the argument parser via the " +
1713              "ArgumentParser.addArgument method.  The " +
1714              arg2.getIdentifierString() + " argument has not been " +
1715              "registered with the argument parser.");
1716
1717    if (remaining != null)
1718    {
1719      for (final Argument a : remaining)
1720      {
1721        Validator.ensureTrue(namedArgs.contains(a),
1722             "The ArgumentParser.addRequiredArgumentSet method may only be " +
1723                  "used if all of the provided arguments have already been " +
1724                  "registered with the argument parser via the " +
1725                  "ArgumentParser.addArgument method.  The " +
1726                  a.getIdentifierString() + " argument has not been " +
1727                  "registered with the argument parser.");
1728      }
1729    }
1730
1731    final LinkedHashSet<Argument> argSet =
1732         new LinkedHashSet<>(StaticUtils.computeMapCapacity(10));
1733    argSet.add(arg1);
1734    argSet.add(arg2);
1735
1736    if (remaining != null)
1737    {
1738      argSet.addAll(Arrays.asList(remaining));
1739    }
1740
1741    requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1742  }
1743
1744
1745
1746  /**
1747   * Indicates whether any subcommands have been registered with this argument
1748   * parser.
1749   *
1750   * @return  {@code true} if one or more subcommands have been registered with
1751   *          this argument parser, or {@code false} if not.
1752   */
1753  public boolean hasSubCommands()
1754  {
1755    return (! subCommands.isEmpty());
1756  }
1757
1758
1759
1760  /**
1761   * Retrieves the subcommand that was provided in the set of command-line
1762   * arguments, if any.
1763   *
1764   * @return  The subcommand that was provided in the set of command-line
1765   *          arguments, or {@code null} if there is none.
1766   */
1767  public SubCommand getSelectedSubCommand()
1768  {
1769    return selectedSubCommand;
1770  }
1771
1772
1773
1774  /**
1775   * Specifies the subcommand that was provided in the set of command-line
1776   * arguments.
1777   *
1778   * @param  subcommand  The subcommand that was provided in the set of
1779   *                     command-line arguments.  It may be {@code null} if no
1780   *                     subcommand should be used.
1781   */
1782  void setSelectedSubCommand(final SubCommand subcommand)
1783  {
1784    selectedSubCommand = subcommand;
1785    if (subcommand != null)
1786    {
1787      subcommand.setPresent();
1788    }
1789  }
1790
1791
1792
1793  /**
1794   * Retrieves a list of all subcommands associated with this argument parser.
1795   *
1796   * @return  A list of all subcommands associated with this argument parser, or
1797   *          an empty list if there are no associated subcommands.
1798   */
1799  public List<SubCommand> getSubCommands()
1800  {
1801    return Collections.unmodifiableList(subCommands);
1802  }
1803
1804
1805
1806  /**
1807   * Retrieves the subcommand for the provided name.
1808   *
1809   * @param  name  The name of the subcommand to retrieve.
1810   *
1811   * @return  The subcommand with the provided name, or {@code null} if there is
1812   *          no such subcommand.
1813   */
1814  public SubCommand getSubCommand(final String name)
1815  {
1816    if (name == null)
1817    {
1818      return null;
1819    }
1820
1821    return subCommandsByName.get(StaticUtils.toLowerCase(name));
1822  }
1823
1824
1825
1826  /**
1827   * Registers the provided subcommand with this argument parser.
1828   *
1829   * @param  subCommand  The subcommand to register with this argument parser.
1830   *                     It must not be {@code null}.
1831   *
1832   * @throws  ArgumentException  If this argument parser does not allow
1833   *                             subcommands, if there is a conflict between any
1834   *                             of the names of the provided subcommand and an
1835   *                             already-registered subcommand, or if there is a
1836   *                             conflict between any of the subcommand-specific
1837   *                             arguments and global arguments.
1838   */
1839  public void addSubCommand(final SubCommand subCommand)
1840         throws ArgumentException
1841  {
1842    // Ensure that the subcommand isn't already registered with an argument
1843    // parser.
1844    if (subCommand.getGlobalArgumentParser() != null)
1845    {
1846      throw new ArgumentException(
1847           ERR_PARSER_SUBCOMMAND_ALREADY_REGISTERED_WITH_PARSER.get());
1848    }
1849
1850    // Ensure that the caller isn't trying to create a nested subcommand.
1851    if (parentSubCommand != null)
1852    {
1853      throw new ArgumentException(
1854           ERR_PARSER_CANNOT_CREATE_NESTED_SUBCOMMAND.get(
1855                parentSubCommand.getPrimaryName()));
1856    }
1857
1858    // Ensure that this argument parser doesn't allow trailing arguments.
1859    if (allowsTrailingArguments())
1860    {
1861      throw new ArgumentException(
1862           ERR_PARSER_WITH_TRAILING_ARGS_CANNOT_HAVE_SUBCOMMANDS.get());
1863    }
1864
1865    // Ensure that the subcommand doesn't have any names that conflict with an
1866    // existing subcommand.
1867    for (final String name : subCommand.getNames(true))
1868    {
1869      if (subCommandsByName.containsKey(StaticUtils.toLowerCase(name)))
1870      {
1871        throw new ArgumentException(
1872             ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name));
1873      }
1874    }
1875
1876    // Register the subcommand.
1877    for (final String name : subCommand.getNames(true))
1878    {
1879      subCommandsByName.put(StaticUtils.toLowerCase(name), subCommand);
1880    }
1881    subCommands.add(subCommand);
1882    subCommand.setGlobalArgumentParser(this);
1883  }
1884
1885
1886
1887  /**
1888   * Registers the provided additional name for this subcommand.
1889   *
1890   * @param  name        The name to be registered.  It must not be
1891   *                     {@code null} or empty.
1892   * @param  subCommand  The subcommand with which the name is associated.  It
1893   *                     must not be {@code null}.
1894   *
1895   * @throws  ArgumentException  If the provided name is already in use.
1896   */
1897  void addSubCommand(final String name, final SubCommand subCommand)
1898       throws ArgumentException
1899  {
1900    final String lowerName = StaticUtils.toLowerCase(name);
1901    if (subCommandsByName.containsKey(lowerName))
1902    {
1903      throw new ArgumentException(
1904           ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name));
1905    }
1906
1907    subCommandsByName.put(lowerName, subCommand);
1908  }
1909
1910
1911
1912  /**
1913   * Retrieves the set of unnamed trailing arguments in the provided command
1914   * line arguments.
1915   *
1916   * @return  The set of unnamed trailing arguments in the provided command line
1917   *          arguments, or an empty list if there were none.
1918   */
1919  public List<String> getTrailingArguments()
1920  {
1921    return Collections.unmodifiableList(trailingArgs);
1922  }
1923
1924
1925
1926  /**
1927   * Resets this argument parser so that it appears as if it had not been used
1928   * to parse any command-line arguments.
1929   */
1930  void reset()
1931  {
1932    selectedSubCommand = null;
1933
1934    for (final Argument a : namedArgs)
1935    {
1936      a.reset();
1937    }
1938
1939    propertiesFileUsed = null;
1940    argumentsSetFromPropertiesFile.clear();
1941    trailingArgs.clear();
1942  }
1943
1944
1945
1946  /**
1947   * Clears the set of trailing arguments for this argument parser.
1948   */
1949  void resetTrailingArguments()
1950  {
1951    trailingArgs.clear();
1952  }
1953
1954
1955
1956  /**
1957   * Adds the provided value to the set of trailing arguments.
1958   *
1959   * @param  value  The value to add to the set of trailing arguments.
1960   *
1961   * @throws  ArgumentException  If the parser already has the maximum allowed
1962   *                             number of trailing arguments.
1963   */
1964  void addTrailingArgument(final String value)
1965       throws ArgumentException
1966  {
1967    if ((maxTrailingArgs > 0) && (trailingArgs.size() >= maxTrailingArgs))
1968    {
1969      throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(value,
1970           commandName, maxTrailingArgs));
1971    }
1972
1973    trailingArgs.add(value);
1974  }
1975
1976
1977
1978  /**
1979   * Retrieves the properties file that was used to obtain values for arguments
1980   * not set on the command line.
1981   *
1982   * @return  The properties file that was used to obtain values for arguments
1983   *          not set on the command line, or {@code null} if no properties file
1984   *          was used.
1985   */
1986  public File getPropertiesFileUsed()
1987  {
1988    return propertiesFileUsed;
1989  }
1990
1991
1992
1993  /**
1994   * Retrieves a list of the string representations of any arguments used for
1995   * the associated tool that were set from a properties file rather than
1996   * provided on the command line.  The values of any arguments marked as
1997   * sensitive will be obscured.
1998   *
1999   * @return  A list of the string representations any arguments used for the
2000   *          associated tool that were set from a properties file rather than
2001   *          provided on the command line, or an empty list if no arguments
2002   *          were set from a properties file.
2003   */
2004  public List<String> getArgumentsSetFromPropertiesFile()
2005  {
2006    return Collections.unmodifiableList(argumentsSetFromPropertiesFile);
2007  }
2008
2009
2010
2011  /**
2012   * Indicates whether the comment listing arguments obtained from a properties
2013   * file should be suppressed.
2014   *
2015   * @return  {@code true} if the comment listing arguments obtained from a
2016   *          properties file should be suppressed, or {@code false} if not.
2017   */
2018  public boolean suppressPropertiesFileComment()
2019  {
2020    final BooleanArgument arg =
2021         getBooleanArgument(ARG_NAME_SUPPRESS_PROPERTIES_FILE_COMMENT);
2022    return ((arg != null) && arg.isPresent());
2023  }
2024
2025
2026
2027  /**
2028   * Creates a copy of this argument parser that is "clean" and appears as if it
2029   * has not been used to parse an argument set.  The new parser will have all
2030   * of the same arguments and constraints as this parser.
2031   *
2032   * @return  The "clean" copy of this argument parser.
2033   */
2034  public ArgumentParser getCleanCopy()
2035  {
2036    return new ArgumentParser(this, null);
2037  }
2038
2039
2040
2041  /**
2042   * Parses the provided set of arguments.
2043   *
2044   * @param  args  An array containing the argument information to parse.  It
2045   *               must not be {@code null}.
2046   *
2047   * @throws  ArgumentException  If a problem occurs while attempting to parse
2048   *                             the argument information.
2049   */
2050  public void parse(final String[] args)
2051         throws ArgumentException
2052  {
2053    // Iterate through the provided args strings and process them.
2054    ArgumentParser subCommandParser    = null;
2055    boolean        inTrailingArgs      = false;
2056    boolean        skipFinalValidation = false;
2057    String         subCommandName      = null;
2058    for (int i=0; i < args.length; i++)
2059    {
2060      final String s = args[i];
2061
2062      if (inTrailingArgs)
2063      {
2064        if (maxTrailingArgs == 0)
2065        {
2066          throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
2067                                           s, commandName));
2068        }
2069        else if (trailingArgs.size() >= maxTrailingArgs)
2070        {
2071          throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s,
2072                                           commandName, maxTrailingArgs));
2073        }
2074        else
2075        {
2076          trailingArgs.add(s);
2077        }
2078      }
2079      else if (s.equals("--"))
2080      {
2081        // This signifies the end of the named arguments and the beginning of
2082        // the trailing arguments.
2083        inTrailingArgs = true;
2084      }
2085      else if (s.startsWith("--"))
2086      {
2087        // There may be an equal sign to separate the name from the value.
2088        final String argName;
2089        final int equalPos = s.indexOf('=');
2090        if (equalPos > 0)
2091        {
2092          argName = s.substring(2, equalPos);
2093        }
2094        else
2095        {
2096          argName = s.substring(2);
2097        }
2098
2099        final String lowerName = StaticUtils.toLowerCase(argName);
2100        Argument a = namedArgsByLongID.get(lowerName);
2101        if ((a == null) && (subCommandParser != null))
2102        {
2103          a = subCommandParser.namedArgsByLongID.get(lowerName);
2104        }
2105
2106        if (a == null)
2107        {
2108          throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName));
2109        }
2110        else if (a.isUsageArgument())
2111        {
2112          skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
2113        }
2114
2115        a.incrementOccurrences();
2116        if (a.takesValue())
2117        {
2118          if (equalPos > 0)
2119          {
2120            a.addValue(s.substring(equalPos+1));
2121          }
2122          else
2123          {
2124            i++;
2125            if (i >= args.length)
2126            {
2127              throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get(
2128                                               argName));
2129            }
2130            else
2131            {
2132              a.addValue(args[i]);
2133            }
2134          }
2135        }
2136        else
2137        {
2138          if (equalPos > 0)
2139          {
2140            throw new ArgumentException(
2141                           ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName));
2142          }
2143        }
2144      }
2145      else if (s.startsWith("-"))
2146      {
2147        if (s.length() == 1)
2148        {
2149          throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get());
2150        }
2151        else if (s.length() == 2)
2152        {
2153          final char c = s.charAt(1);
2154
2155          Argument a = namedArgsByShortID.get(c);
2156          if ((a == null) && (subCommandParser != null))
2157          {
2158            a = subCommandParser.namedArgsByShortID.get(c);
2159          }
2160
2161          if (a == null)
2162          {
2163            throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
2164          }
2165          else if (a.isUsageArgument())
2166          {
2167            skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
2168          }
2169
2170          a.incrementOccurrences();
2171          if (a.takesValue())
2172          {
2173            i++;
2174            if (i >= args.length)
2175            {
2176              throw new ArgumentException(
2177                             ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c));
2178            }
2179            else
2180            {
2181              a.addValue(args[i]);
2182            }
2183          }
2184        }
2185        else
2186        {
2187          char c = s.charAt(1);
2188          Argument a = namedArgsByShortID.get(c);
2189          if ((a == null) && (subCommandParser != null))
2190          {
2191            a = subCommandParser.namedArgsByShortID.get(c);
2192          }
2193
2194          if (a == null)
2195          {
2196            throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
2197          }
2198          else if (a.isUsageArgument())
2199          {
2200            skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
2201          }
2202
2203          a.incrementOccurrences();
2204          if (a.takesValue())
2205          {
2206            a.addValue(s.substring(2));
2207          }
2208          else
2209          {
2210            // The rest of the characters in the string must also resolve to
2211            // arguments that don't take values.
2212            for (int j=2; j < s.length(); j++)
2213            {
2214              c = s.charAt(j);
2215              a = namedArgsByShortID.get(c);
2216              if ((a == null) && (subCommandParser != null))
2217              {
2218                a = subCommandParser.namedArgsByShortID.get(c);
2219              }
2220
2221              if (a == null)
2222              {
2223                throw new ArgumentException(
2224                               ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s));
2225              }
2226              else if (a.isUsageArgument())
2227              {
2228                skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
2229              }
2230
2231              a.incrementOccurrences();
2232              if (a.takesValue())
2233              {
2234                throw new ArgumentException(
2235                               ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get(
2236                                    c, s));
2237              }
2238            }
2239          }
2240        }
2241      }
2242      else if (subCommands.isEmpty())
2243      {
2244        inTrailingArgs = true;
2245        if (maxTrailingArgs == 0)
2246        {
2247          throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
2248               s, commandName));
2249        }
2250        else
2251        {
2252          trailingArgs.add(s);
2253        }
2254      }
2255      else
2256      {
2257        if (selectedSubCommand == null)
2258        {
2259          subCommandName = s;
2260          selectedSubCommand =
2261               subCommandsByName.get(StaticUtils.toLowerCase(s));
2262          if (selectedSubCommand == null)
2263          {
2264            throw new ArgumentException(ERR_PARSER_NO_SUCH_SUBCOMMAND.get(s,
2265                 commandName));
2266          }
2267          else
2268          {
2269            selectedSubCommand.setPresent();
2270            subCommandParser = selectedSubCommand.getArgumentParser();
2271          }
2272        }
2273        else
2274        {
2275          throw new ArgumentException(ERR_PARSER_CONFLICTING_SUBCOMMANDS.get(
2276               subCommandName, s));
2277        }
2278      }
2279    }
2280
2281
2282    // Perform any appropriate processing related to the use of a properties
2283    // file.
2284    if (! handlePropertiesFile())
2285    {
2286      return;
2287    }
2288
2289
2290    // If a usage argument was provided, then no further validation should be
2291    // performed.
2292    if (skipFinalValidation)
2293    {
2294      return;
2295    }
2296
2297
2298    // If any subcommands are defined, then one must have been provided.
2299    if ((! subCommands.isEmpty()) && (selectedSubCommand == null))
2300    {
2301      throw new ArgumentException(
2302           ERR_PARSER_MISSING_SUBCOMMAND.get(commandName));
2303    }
2304
2305
2306    doFinalValidation(this);
2307    if (selectedSubCommand != null)
2308    {
2309      doFinalValidation(selectedSubCommand.getArgumentParser());
2310    }
2311  }
2312
2313
2314
2315  /**
2316   * Sets the command-line tool with which this argument parser is associated.
2317   *
2318   * @param  commandLineTool  The command-line tool with which this argument
2319   *                          parser is associated.  It may be {@code null} if
2320   *                          there is no associated command-line tool.
2321   */
2322  public void setCommandLineTool(final CommandLineTool commandLineTool)
2323  {
2324    this.commandLineTool = commandLineTool;
2325  }
2326
2327
2328
2329  /**
2330   * Performs the final validation for the provided argument parser.
2331   *
2332   * @param  parser  The argument parser for which to perform the final
2333   *                 validation.
2334   *
2335   * @throws  ArgumentException  If a validation problem is encountered.
2336   */
2337  private static void doFinalValidation(final ArgumentParser parser)
2338          throws ArgumentException
2339  {
2340    // Make sure that all required arguments have values.
2341    for (final Argument a : parser.namedArgs)
2342    {
2343      if (a.isRequired() && (! a.isPresent()))
2344      {
2345        throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get(
2346                                         a.getIdentifierString()));
2347      }
2348    }
2349
2350
2351    // Make sure that at least the minimum number of trailing arguments were
2352    // provided.
2353    if (parser.trailingArgs.size() < parser.minTrailingArgs)
2354    {
2355      throw new ArgumentException(ERR_PARSER_NOT_ENOUGH_TRAILING_ARGS.get(
2356           parser.commandName, parser.minTrailingArgs,
2357           parser.trailingArgsPlaceholder));
2358    }
2359
2360
2361    // Make sure that there are no dependent argument set conflicts.
2362    for (final ObjectPair<Argument,Set<Argument>> p :
2363         parser.dependentArgumentSets)
2364    {
2365      final Argument targetArg = p.getFirst();
2366      if (targetArg.getNumOccurrences() > 0)
2367      {
2368        final Set<Argument> argSet = p.getSecond();
2369        boolean found = false;
2370        for (final Argument a : argSet)
2371        {
2372          if (a.getNumOccurrences() > 0)
2373          {
2374            found = true;
2375            break;
2376          }
2377        }
2378
2379        if (! found)
2380        {
2381          if (argSet.size() == 1)
2382          {
2383            throw new ArgumentException(
2384                 ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get(
2385                      targetArg.getIdentifierString(),
2386                      argSet.iterator().next().getIdentifierString()));
2387          }
2388          else
2389          {
2390            boolean first = true;
2391            final StringBuilder buffer = new StringBuilder();
2392            for (final Argument a : argSet)
2393            {
2394              if (first)
2395              {
2396                first = false;
2397              }
2398              else
2399              {
2400                buffer.append(", ");
2401              }
2402              buffer.append(a.getIdentifierString());
2403            }
2404            throw new ArgumentException(
2405                 ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get(
2406                      targetArg.getIdentifierString(), buffer.toString()));
2407          }
2408        }
2409      }
2410    }
2411
2412
2413    // Make sure that there are no exclusive argument set conflicts.
2414    for (final Set<Argument> argSet : parser.exclusiveArgumentSets)
2415    {
2416      Argument setArg = null;
2417      for (final Argument a : argSet)
2418      {
2419        if (a.getNumOccurrences() > 0)
2420        {
2421          if (setArg == null)
2422          {
2423            setArg = a;
2424          }
2425          else
2426          {
2427            throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2428                                             setArg.getIdentifierString(),
2429                                             a.getIdentifierString()));
2430          }
2431        }
2432      }
2433    }
2434
2435    // Make sure that there are no required argument set conflicts.
2436    for (final Set<Argument> argSet : parser.requiredArgumentSets)
2437    {
2438      boolean found = false;
2439      for (final Argument a : argSet)
2440      {
2441        if (a.getNumOccurrences() > 0)
2442        {
2443          found = true;
2444          break;
2445        }
2446      }
2447
2448      if (! found)
2449      {
2450        boolean first = true;
2451        final StringBuilder buffer = new StringBuilder();
2452        for (final Argument a : argSet)
2453        {
2454          if (first)
2455          {
2456            first = false;
2457          }
2458          else
2459          {
2460            buffer.append(", ");
2461          }
2462          buffer.append(a.getIdentifierString());
2463        }
2464        throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get(
2465                                         buffer.toString()));
2466      }
2467    }
2468  }
2469
2470
2471
2472  /**
2473   * Indicates whether the provided argument is one that indicates that the
2474   * parser should skip all validation except that performed when assigning
2475   * values from command-line arguments.  Validation that will be skipped
2476   * includes ensuring that all required arguments have values, ensuring that
2477   * the minimum number of trailing arguments were provided, and ensuring that
2478   * there were no dependent/exclusive/required argument set conflicts.
2479   *
2480   * @param  a  The argument for which to make the determination.
2481   *
2482   * @return  {@code true} if the provided argument is one that indicates that
2483   *          final validation should be skipped, or {@code false} if not.
2484   */
2485  private static boolean skipFinalValidationBecauseOfArgument(final Argument a)
2486  {
2487    // We will skip final validation for all usage arguments except the ones
2488    // used for interacting with properties and output files.
2489    if (ARG_NAME_PROPERTIES_FILE_PATH.equals(a.getLongIdentifier()) ||
2490        ARG_NAME_NO_PROPERTIES_FILE.equals(a.getLongIdentifier()) ||
2491        ARG_NAME_SUPPRESS_PROPERTIES_FILE_COMMENT.equals(
2492             a.getLongIdentifier()) ||
2493        ARG_NAME_OUTPUT_FILE.equals(a.getLongIdentifier()) ||
2494        ARG_NAME_TEE_OUTPUT.equals(a.getLongIdentifier()))
2495    {
2496      return false;
2497    }
2498
2499    return a.isUsageArgument();
2500  }
2501
2502
2503
2504  /**
2505   * Performs any appropriate properties file processing for this argument
2506   * parser.
2507   *
2508   * @return  {@code true} if the tool should continue processing, or
2509   *          {@code false} if it should return immediately.
2510   *
2511   * @throws  ArgumentException  If a problem is encountered while attempting
2512   *                             to parse a properties file or update arguments
2513   *                             with the values contained in it.
2514   */
2515  private boolean handlePropertiesFile()
2516          throws ArgumentException
2517  {
2518    final BooleanArgument noPropertiesFile;
2519    final FileArgument generatePropertiesFile;
2520    final FileArgument propertiesFilePath;
2521    try
2522    {
2523      propertiesFilePath = getFileArgument(ARG_NAME_PROPERTIES_FILE_PATH);
2524      generatePropertiesFile =
2525           getFileArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
2526      noPropertiesFile = getBooleanArgument(ARG_NAME_NO_PROPERTIES_FILE);
2527    }
2528    catch (final Exception e)
2529    {
2530      Debug.debugException(e);
2531
2532      // This should only ever happen if the argument parser has an argument
2533      // with a name that conflicts with one of the properties file arguments
2534      // but isn't of the right type.  In this case, we'll assume that no
2535      // properties file will be used.
2536      return true;
2537    }
2538
2539
2540    // If any of the properties file arguments isn't defined, then we'll assume
2541    // that no properties file will be used.
2542    if ((propertiesFilePath == null) || (generatePropertiesFile == null) ||
2543        (noPropertiesFile == null))
2544    {
2545      return true;
2546    }
2547
2548
2549    // If the noPropertiesFile argument is present, then don't do anything but
2550    // make sure that neither of the other arguments was specified.
2551    if (noPropertiesFile.isPresent())
2552    {
2553      if (propertiesFilePath.isPresent())
2554      {
2555        throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2556             noPropertiesFile.getIdentifierString(),
2557             propertiesFilePath.getIdentifierString()));
2558      }
2559      else if (generatePropertiesFile.isPresent())
2560      {
2561        throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2562             noPropertiesFile.getIdentifierString(),
2563             generatePropertiesFile.getIdentifierString()));
2564      }
2565      else
2566      {
2567        return true;
2568      }
2569    }
2570
2571
2572    // If the generatePropertiesFile argument is present, then make sure the
2573    // propertiesFilePath argument is not set and generate the output.
2574    if (generatePropertiesFile.isPresent())
2575    {
2576      if (propertiesFilePath.isPresent())
2577      {
2578        throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2579             generatePropertiesFile.getIdentifierString(),
2580             propertiesFilePath.getIdentifierString()));
2581      }
2582      else
2583      {
2584        generatePropertiesFile(
2585             generatePropertiesFile.getValue().getAbsolutePath());
2586        return false;
2587      }
2588    }
2589
2590
2591    // If the propertiesFilePath argument is present, then try to make use of
2592    // the specified file.
2593    if (propertiesFilePath.isPresent())
2594    {
2595      final File propertiesFile = propertiesFilePath.getValue();
2596      if (propertiesFile.exists() && propertiesFile.isFile())
2597      {
2598        handlePropertiesFile(propertiesFilePath.getValue());
2599      }
2600      else
2601      {
2602        throw new ArgumentException(
2603             ERR_PARSER_NO_SUCH_PROPERTIES_FILE.get(
2604                  propertiesFilePath.getIdentifierString(),
2605                  propertiesFile.getAbsolutePath()));
2606      }
2607      return true;
2608    }
2609
2610
2611    // We may still use a properties file if the path was specified in either a
2612    // JVM property or an environment variable.  If both are defined, the JVM
2613    // property will take precedence.  If a property or environment variable
2614    // specifies an invalid value, then we'll just ignore it.
2615    String path = StaticUtils.getSystemProperty(
2616         PROPERTY_DEFAULT_PROPERTIES_FILE_PATH);
2617    if (path == null)
2618    {
2619      path = StaticUtils.getEnvironmentVariable(
2620           ENV_DEFAULT_PROPERTIES_FILE_PATH);
2621    }
2622
2623    if (path != null)
2624    {
2625      final File propertiesFile = new File(path);
2626      if (propertiesFile.exists() && propertiesFile.isFile())
2627      {
2628        handlePropertiesFile(propertiesFile);
2629      }
2630    }
2631
2632    return true;
2633  }
2634
2635
2636
2637  /**
2638   * Write an empty properties file for this argument parser to the specified
2639   * path.
2640   *
2641   * @param  path  The path to the properties file to be written.
2642   *
2643   * @throws  ArgumentException  If a problem is encountered while writing the
2644   *                             properties file.
2645   */
2646  private void generatePropertiesFile(final String path)
2647          throws ArgumentException
2648  {
2649    final PrintWriter w;
2650    try
2651    {
2652      // The java.util.Properties specification states that properties files
2653      // should be read using the ISO 8859-1 character set.
2654      w = new PrintWriter(new OutputStreamWriter(new FileOutputStream(path),
2655           StandardCharsets.ISO_8859_1));
2656    }
2657    catch (final Exception e)
2658    {
2659      Debug.debugException(e);
2660      throw new ArgumentException(
2661           ERR_PARSER_GEN_PROPS_CANNOT_OPEN_FILE.get(path,
2662                StaticUtils.getExceptionMessage(e)),
2663           e);
2664    }
2665
2666    try
2667    {
2668      wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_1.get(commandName));
2669      w.println('#');
2670      wrapComment(w,
2671           INFO_PARSER_GEN_PROPS_HEADER_2.get(commandName,
2672                ARG_NAME_PROPERTIES_FILE_PATH,
2673                PROPERTY_DEFAULT_PROPERTIES_FILE_PATH,
2674                ENV_DEFAULT_PROPERTIES_FILE_PATH, ARG_NAME_NO_PROPERTIES_FILE));
2675      w.println('#');
2676      wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_3.get());
2677      w.println('#');
2678
2679      wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_4.get());
2680      w.println('#');
2681      wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_5.get(commandName));
2682
2683      for (final Argument a : getNamedArguments())
2684      {
2685        writeArgumentProperties(w, null, a);
2686      }
2687
2688      for (final SubCommand sc : getSubCommands())
2689      {
2690        for (final Argument a : sc.getArgumentParser().getNamedArguments())
2691        {
2692          writeArgumentProperties(w, sc, a);
2693        }
2694      }
2695    }
2696    finally
2697    {
2698      w.close();
2699    }
2700  }
2701
2702
2703
2704  /**
2705   * Writes information about the provided argument to the given writer.
2706   *
2707   * @param  w   The writer to which the properties should be written.  It must
2708   *             not be {@code null}.
2709   * @param  sc  The subcommand with which the argument is associated.  It may
2710   *             be {@code null} if the provided argument is a global argument.
2711   * @param  a   The argument for which to write the properties.  It must not be
2712   *             {@code null}.
2713   */
2714  private void writeArgumentProperties(final PrintWriter w,
2715                                       final SubCommand sc,
2716                                       final Argument a)
2717  {
2718    if (a.isUsageArgument() || a.isHidden())
2719    {
2720      return;
2721    }
2722
2723    w.println();
2724    w.println();
2725    wrapComment(w, a.getDescription());
2726    w.println('#');
2727
2728    final String constraints = a.getValueConstraints();
2729    if ((constraints != null) && (! constraints.isEmpty()) &&
2730        (! (a instanceof BooleanArgument)))
2731    {
2732      wrapComment(w, constraints);
2733      w.println('#');
2734    }
2735
2736    final String identifier;
2737    if (a.getLongIdentifier() != null)
2738    {
2739      identifier = a.getLongIdentifier();
2740    }
2741    else
2742    {
2743      identifier = a.getIdentifierString();
2744    }
2745
2746    String placeholder = a.getValuePlaceholder();
2747    if (placeholder == null)
2748    {
2749      if (a instanceof BooleanArgument)
2750      {
2751        placeholder = "{true|false}";
2752      }
2753      else
2754      {
2755        placeholder = "";
2756      }
2757    }
2758
2759    final String propertyName;
2760    if (sc == null)
2761    {
2762      propertyName = commandName + '.' + identifier;
2763    }
2764    else
2765    {
2766      propertyName = commandName + '.' + sc.getPrimaryName() + '.' + identifier;
2767    }
2768
2769    w.println("# " + propertyName + '=' + placeholder);
2770
2771    if (a.isPresent())
2772    {
2773      for (final String s : a.getValueStringRepresentations(false))
2774      {
2775        w.println(propertyName + '=' + s);
2776      }
2777    }
2778  }
2779
2780
2781
2782  /**
2783   * Wraps the given string and writes it as a comment to the provided writer.
2784   *
2785   * @param  w  The writer to use to write the wrapped and commented string.
2786   * @param  s  The string to be wrapped and written.
2787   */
2788  private static void wrapComment(final PrintWriter w, final String s)
2789  {
2790    for (final String line : StaticUtils.wrapLine(s, 77))
2791    {
2792      w.println("# " + line);
2793    }
2794  }
2795
2796
2797
2798  /**
2799   * Reads the contents of the specified properties file and updates the
2800   * configured arguments as appropriate.
2801   *
2802   * @param  propertiesFile  The properties file to process.
2803   *
2804   * @throws  ArgumentException  If a problem is encountered while examining the
2805   *                             properties file, or while trying to assign a
2806   *                             property value to a corresponding argument.
2807   */
2808  private void handlePropertiesFile(final File propertiesFile)
2809          throws ArgumentException
2810  {
2811    final String propertiesFilePath = propertiesFile.getAbsolutePath();
2812
2813    InputStream inputStream = null;
2814    final BufferedReader reader;
2815    try
2816    {
2817      inputStream = new FileInputStream(propertiesFile);
2818
2819
2820      // Handle the case in which the properties file may be encrypted.
2821      final List<char[]> cachedPasswords;
2822      final PrintStream err;
2823      final PrintStream out;
2824      final CommandLineTool tool = commandLineTool;
2825      if (tool == null)
2826      {
2827        cachedPasswords = Collections.emptyList();
2828        out = System.out;
2829        err = System.err;
2830      }
2831      else
2832      {
2833        cachedPasswords =
2834             tool.getPasswordFileReader().getCachedEncryptionPasswords();
2835        out = tool.getOut();
2836        err = tool.getErr();
2837      }
2838
2839      final ObjectPair<InputStream,char[]> encryptionData =
2840           ToolUtils.getPossiblyPassphraseEncryptedInputStream(inputStream,
2841                cachedPasswords, true,
2842                INFO_PARSER_PROMPT_FOR_PROP_FILE_ENC_PW.get(
2843                     propertiesFile.getAbsolutePath()),
2844                ERR_PARSER_WRONG_PROP_FILE_ENC_PW.get(
2845                     propertiesFile.getAbsolutePath()),
2846                out, err);
2847
2848      inputStream = encryptionData.getFirst();
2849      if ((tool != null) && (encryptionData.getSecond() != null))
2850      {
2851        tool.getPasswordFileReader().addToEncryptionPasswordCache(
2852             encryptionData.getSecond());
2853      }
2854
2855
2856      // Handle the case in which the properties file may be compressed.
2857      inputStream = ToolUtils.getPossiblyGZIPCompressedInputStream(inputStream);
2858
2859
2860      // The java.util.Properties specification states that properties files
2861      // should be read using the ISO 8859-1 character set, and that characters
2862      // that cannot be encoded in that format should be represented using
2863      // Unicode escapes that start with a backslash, a lowercase letter "u",
2864      // and four hexadecimal digits.  To provide compatibility with the Java
2865      // Properties file format (except we also support the same property
2866      // appearing multiple times), we will also use that encoding and will
2867      // support Unicode escape sequences.
2868      reader = new BufferedReader(new InputStreamReader(inputStream,
2869           StandardCharsets.ISO_8859_1));
2870    }
2871    catch (final Exception e)
2872    {
2873      if (inputStream != null)
2874      {
2875        try
2876        {
2877          inputStream.close();
2878        }
2879        catch (final Exception e2)
2880        {
2881          Debug.debugException(e2);
2882        }
2883      }
2884
2885      Debug.debugException(e);
2886      throw new ArgumentException(
2887           ERR_PARSER_CANNOT_OPEN_PROP_FILE.get(propertiesFilePath,
2888                StaticUtils.getExceptionMessage(e)),
2889           e);
2890    }
2891
2892    try
2893    {
2894      // Read all of the lines of the file, ignoring comments and unwrapping
2895      // properties that span multiple lines.
2896      boolean lineIsContinued = false;
2897      int lineNumber = 0;
2898      final ArrayList<ObjectPair<Integer,StringBuilder>> propertyLines =
2899           new ArrayList<>(10);
2900      while (true)
2901      {
2902        String line;
2903        try
2904        {
2905          line = reader.readLine();
2906          lineNumber++;
2907        }
2908        catch (final Exception e)
2909        {
2910          Debug.debugException(e);
2911          throw new ArgumentException(
2912               ERR_PARSER_ERROR_READING_PROP_FILE.get(propertiesFilePath,
2913                    StaticUtils.getExceptionMessage(e)),
2914               e);
2915        }
2916
2917
2918        // If the line is null, then we've reached the end of the file.  If we
2919        // expect a previous line to have been continued, then this is an error.
2920        if (line == null)
2921        {
2922          if (lineIsContinued)
2923          {
2924            throw new ArgumentException(
2925                 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
2926                      (lineNumber-1), propertiesFilePath));
2927          }
2928          break;
2929        }
2930
2931
2932        // See if the line has any leading whitespace, and if so then trim it
2933        // off.  If there is leading whitespace, then make sure that we expect
2934        // the previous line to be continued.
2935        final int initialLength = line.length();
2936        line = StaticUtils.trimLeading(line);
2937        final boolean hasLeadingWhitespace = (line.length() < initialLength);
2938        if (hasLeadingWhitespace && (! lineIsContinued))
2939        {
2940          throw new ArgumentException(
2941               ERR_PARSER_PROP_FILE_UNEXPECTED_LEADING_SPACE.get(
2942                    propertiesFilePath, lineNumber));
2943        }
2944
2945
2946        // If the line is empty or starts with "#", then skip it.  But make sure
2947        // we didn't expect the previous line to be continued.
2948        if ((line.isEmpty()) || line.startsWith("#"))
2949        {
2950          if (lineIsContinued)
2951          {
2952            throw new ArgumentException(
2953                 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
2954                      (lineNumber-1), propertiesFilePath));
2955          }
2956          continue;
2957        }
2958
2959
2960        // See if the line ends with a backslash and if so then trim it off.
2961        final boolean hasTrailingBackslash = line.endsWith("\\");
2962        if (line.endsWith("\\"))
2963        {
2964          line = line.substring(0, (line.length() - 1));
2965        }
2966
2967
2968        // If the previous line needs to be continued, then append the new line
2969        // to it.  Otherwise, add it as a new line.
2970        if (lineIsContinued)
2971        {
2972          propertyLines.get(propertyLines.size() - 1).getSecond().append(line);
2973        }
2974        else
2975        {
2976          propertyLines.add(
2977               new ObjectPair<>(lineNumber, new StringBuilder(line)));
2978        }
2979
2980        lineIsContinued = hasTrailingBackslash;
2981      }
2982
2983
2984      // Parse all of the lines into a map of identifiers and their
2985      // corresponding values.
2986      propertiesFileUsed = propertiesFile;
2987      if (propertyLines.isEmpty())
2988      {
2989        return;
2990      }
2991
2992      final HashMap<String,ArrayList<String>> propertyMap =
2993           new HashMap<>(StaticUtils.computeMapCapacity(propertyLines.size()));
2994      for (final ObjectPair<Integer,StringBuilder> p : propertyLines)
2995      {
2996        lineNumber = p.getFirst();
2997        final String line = handleUnicodeEscapes(propertiesFilePath, lineNumber,
2998             p.getSecond());
2999        final int equalPos = line.indexOf('=');
3000        if (equalPos <= 0)
3001        {
3002          throw new ArgumentException(ERR_PARSER_MALFORMED_PROP_LINE.get(
3003               propertiesFilePath, lineNumber, line));
3004        }
3005
3006        final String propertyName = line.substring(0, equalPos).trim();
3007        final String propertyValue = line.substring(equalPos+1).trim();
3008        if (propertyValue.isEmpty())
3009        {
3010          // The property doesn't have a value, so we can ignore it.
3011          continue;
3012        }
3013
3014
3015        // An argument can have multiple identifiers, and we will allow any of
3016        // them to be used to reference it.  To deal with this, we'll map the
3017        // argument identifier to its corresponding argument and then use the
3018        // preferred identifier for that argument in the map.  The same applies
3019        // to subcommand names.
3020        boolean prefixedWithToolName = false;
3021        boolean prefixedWithSubCommandName = false;
3022        Argument a = getNamedArgument(propertyName);
3023        if (a == null)
3024        {
3025          // It could be that the argument name was prefixed with the tool name.
3026          // Check to see if that was the case.
3027          if (propertyName.startsWith(commandName + '.'))
3028          {
3029            prefixedWithToolName = true;
3030
3031            String basePropertyName =
3032                 propertyName.substring(commandName.length()+1);
3033            a = getNamedArgument(basePropertyName);
3034
3035            if (a == null)
3036            {
3037              final int periodPos = basePropertyName.indexOf('.');
3038              if (periodPos > 0)
3039              {
3040                final String subCommandName =
3041                     basePropertyName.substring(0, periodPos);
3042                if ((selectedSubCommand != null) &&
3043                    selectedSubCommand.hasName(subCommandName))
3044                {
3045                  prefixedWithSubCommandName = true;
3046                  basePropertyName = basePropertyName.substring(periodPos+1);
3047                  a = selectedSubCommand.getArgumentParser().getNamedArgument(
3048                       basePropertyName);
3049                }
3050              }
3051              else if (selectedSubCommand != null)
3052              {
3053                a = selectedSubCommand.getArgumentParser().getNamedArgument(
3054                     basePropertyName);
3055              }
3056            }
3057          }
3058          else if (selectedSubCommand != null)
3059          {
3060            a = selectedSubCommand.getArgumentParser().getNamedArgument(
3061                 propertyName);
3062          }
3063        }
3064
3065        if (a == null)
3066        {
3067          // This could mean that there's a typo in the property name, but it's
3068          // more likely the case that the property is for a different tool.  In
3069          // either case, we'll ignore it.
3070          continue;
3071        }
3072
3073        final String canonicalPropertyName;
3074        if (prefixedWithToolName)
3075        {
3076          if (prefixedWithSubCommandName)
3077          {
3078            canonicalPropertyName = commandName + '.' +
3079                 selectedSubCommand.getPrimaryName() + '.' +
3080                 a.getIdentifierString();
3081          }
3082          else
3083          {
3084            canonicalPropertyName = commandName + '.' + a.getIdentifierString();
3085          }
3086        }
3087        else
3088        {
3089          canonicalPropertyName = a.getIdentifierString();
3090        }
3091
3092        ArrayList<String> valueList = propertyMap.get(canonicalPropertyName);
3093        if (valueList == null)
3094        {
3095          valueList = new ArrayList<>(5);
3096          propertyMap.put(canonicalPropertyName, valueList);
3097        }
3098        valueList.add(propertyValue);
3099      }
3100
3101
3102      // Iterate through all of the named arguments for the argument parser and
3103      // see if we should use the properties to assign values to any of the
3104      // arguments that weren't provided on the command line.
3105      setArgsFromPropertiesFile(propertyMap, false);
3106
3107
3108      // If there is a selected subcommand, then iterate through all of its
3109      // arguments.
3110      if (selectedSubCommand != null)
3111      {
3112        setArgsFromPropertiesFile(propertyMap, true);
3113      }
3114    }
3115    finally
3116    {
3117      try
3118      {
3119        reader.close();
3120      }
3121      catch (final Exception e)
3122      {
3123        Debug.debugException(e);
3124      }
3125    }
3126  }
3127
3128
3129
3130  /**
3131   * Retrieves a string that contains the contents of the provided buffer, but
3132   * with any Unicode escape sequences converted to the appropriate character
3133   * representation, and any other escapes having the initial backslash
3134   * removed.
3135   *
3136   * @param  propertiesFilePath  The path to the properties file
3137   * @param  lineNumber  The line number on which the property definition
3138   *                     starts.
3139   * @param  buffer      The buffer containing the data to be processed.  It
3140   *                     must not be {@code null} but may be empty.
3141   *
3142   * @return  A string that contains the contents of the provided buffer, but
3143   *          with any Unicode escape sequences converted to the appropriate
3144   *          character representation.
3145   *
3146   * @throws  ArgumentException  If a malformed Unicode escape sequence is
3147   *                             encountered.
3148   */
3149  static String handleUnicodeEscapes(final String propertiesFilePath,
3150                                     final int lineNumber,
3151                                     final StringBuilder buffer)
3152         throws ArgumentException
3153  {
3154    int pos = 0;
3155    while (pos < buffer.length())
3156    {
3157      final char c = buffer.charAt(pos);
3158      if (c == '\\')
3159      {
3160        if (pos <= (buffer.length() - 5))
3161        {
3162          final char nextChar = buffer.charAt(pos+1);
3163          if ((nextChar == 'u') || (nextChar == 'U'))
3164          {
3165            try
3166            {
3167              final String hexDigits = buffer.substring(pos+2, pos+6);
3168              final byte[] bytes = StaticUtils.fromHex(hexDigits);
3169              final int i = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
3170              buffer.setCharAt(pos, (char) i);
3171              for (int j=0; j < 5; j++)
3172              {
3173                buffer.deleteCharAt(pos+1);
3174              }
3175            }
3176            catch (final Exception e)
3177            {
3178              Debug.debugException(e);
3179              throw new ArgumentException(
3180                   ERR_PARSER_MALFORMED_UNICODE_ESCAPE.get(propertiesFilePath,
3181                        lineNumber),
3182                   e);
3183            }
3184          }
3185          else
3186          {
3187            buffer.deleteCharAt(pos);
3188          }
3189        }
3190      }
3191
3192      pos++;
3193    }
3194
3195    return buffer.toString();
3196  }
3197
3198
3199
3200  /**
3201   * Sets the values of any arguments not provided on the command line but
3202   * defined in the properties file.
3203   *
3204   * @param  propertyMap    A map of properties read from the properties file.
3205   * @param  useSubCommand  Indicates whether to use the argument parser
3206   *                        associated with the selected subcommand rather than
3207   *                        the global argument parser.
3208   *
3209   * @throws  ArgumentException  If a problem is encountered while examining the
3210   *                             properties file, or while trying to assign a
3211   *                             property value to a corresponding argument.
3212   */
3213  private void setArgsFromPropertiesFile(
3214                    final Map<String,ArrayList<String>> propertyMap,
3215                    final boolean useSubCommand)
3216          throws ArgumentException
3217  {
3218    final ArgumentParser p;
3219    if (useSubCommand)
3220    {
3221      p = selectedSubCommand.getArgumentParser();
3222    }
3223    else
3224    {
3225      p = this;
3226    }
3227
3228
3229    for (final Argument a : p.namedArgs)
3230    {
3231      // If the argument was provided on the command line, then that will always
3232      // override anything that might be in the properties file.
3233      if (a.getNumOccurrences() > 0)
3234      {
3235        continue;
3236      }
3237
3238
3239      // If the argument is part of an exclusive argument set, and if one of
3240      // the other arguments in that set was provided on the command line, then
3241      // don't look in the properties file for a value for the argument.
3242      boolean exclusiveArgumentHasValue = false;
3243exclusiveArgumentLoop:
3244      for (final Set<Argument> exclusiveArgumentSet : exclusiveArgumentSets)
3245      {
3246        if (exclusiveArgumentSet.contains(a))
3247        {
3248          for (final Argument exclusiveArg : exclusiveArgumentSet)
3249          {
3250            if (exclusiveArg.getNumOccurrences() > 0)
3251            {
3252              exclusiveArgumentHasValue = true;
3253              break exclusiveArgumentLoop;
3254            }
3255          }
3256        }
3257      }
3258
3259      if (exclusiveArgumentHasValue)
3260      {
3261        continue;
3262      }
3263
3264
3265      // If we should use a subcommand, then see if the properties file has a
3266      // property that is specific to the selected subcommand.  Then fall back
3267      // to a property that is specific to the tool, and finally fall back to
3268      // checking for a set of values that are generic to any tool that has an
3269      // argument with that name.
3270      List<String> values = null;
3271      if (useSubCommand)
3272      {
3273        values = propertyMap.get(commandName + '.' +
3274             selectedSubCommand.getPrimaryName()  + '.' +
3275             a.getIdentifierString());
3276      }
3277
3278      if (values == null)
3279      {
3280        values = propertyMap.get(commandName + '.' + a.getIdentifierString());
3281      }
3282
3283      if (values == null)
3284      {
3285        values = propertyMap.get(a.getIdentifierString());
3286      }
3287
3288      if (values != null)
3289      {
3290        for (final String value : values)
3291        {
3292          if (a instanceof BooleanArgument)
3293          {
3294            // We'll treat this as a BooleanValueArgument.
3295            final BooleanValueArgument bva = new BooleanValueArgument(
3296                 a.getShortIdentifier(), a.getLongIdentifier(), false, null,
3297                 a.getDescription());
3298            bva.addValue(value);
3299            if (bva.getValue())
3300            {
3301              a.incrementOccurrences();
3302              argumentsSetFromPropertiesFile.add(a.getIdentifierString());
3303            }
3304          }
3305          else
3306          {
3307            a.addValue(value);
3308            a.incrementOccurrences();
3309
3310            argumentsSetFromPropertiesFile.add(a.getIdentifierString());
3311            if (a.isSensitive())
3312            {
3313              argumentsSetFromPropertiesFile.add("***REDACTED***");
3314            }
3315            else
3316            {
3317              argumentsSetFromPropertiesFile.add(value);
3318            }
3319          }
3320        }
3321      }
3322    }
3323  }
3324
3325
3326
3327  /**
3328   * Retrieves lines that make up the usage information for this program,
3329   * optionally wrapping long lines.
3330   *
3331   * @param  maxWidth  The maximum line width to use for the output.  If this is
3332   *                   less than or equal to zero, then no wrapping will be
3333   *                   performed.
3334   *
3335   * @return  The lines that make up the usage information for this program.
3336   */
3337  public List<String> getUsage(final int maxWidth)
3338  {
3339    // If a subcommand was selected, then provide usage specific to that
3340    // subcommand.
3341    if (selectedSubCommand != null)
3342    {
3343      return getSubCommandUsage(maxWidth);
3344    }
3345
3346    // First is a description of the command.
3347    final ArrayList<String> lines = new ArrayList<>(100);
3348    lines.addAll(StaticUtils.wrapLine(commandDescription, maxWidth));
3349    lines.add("");
3350
3351
3352    for (final String additionalDescriptionParagraph :
3353         additionalCommandDescriptionParagraphs)
3354    {
3355      lines.addAll(StaticUtils.wrapLine(additionalDescriptionParagraph,
3356           maxWidth));
3357      lines.add("");
3358    }
3359
3360    // If the tool supports subcommands, and if there are fewer than 10
3361    // subcommands, then display them inline.
3362    if ((! subCommands.isEmpty()) && (subCommands.size() < 10))
3363    {
3364      lines.add(INFO_USAGE_SUBCOMMANDS_HEADER.get());
3365      lines.add("");
3366
3367      for (final SubCommand sc : subCommands)
3368      {
3369        final StringBuilder nameBuffer = new StringBuilder();
3370        nameBuffer.append("  ");
3371
3372        final Iterator<String> nameIterator = sc.getNames(false).iterator();
3373        while (nameIterator.hasNext())
3374        {
3375          nameBuffer.append(nameIterator.next());
3376          if (nameIterator.hasNext())
3377          {
3378            nameBuffer.append(", ");
3379          }
3380        }
3381        lines.add(nameBuffer.toString());
3382
3383        for (final String descriptionLine :
3384             StaticUtils.wrapLine(sc.getDescription(), (maxWidth - 4)))
3385        {
3386          lines.add("    " + descriptionLine);
3387        }
3388        lines.add("");
3389      }
3390    }
3391
3392
3393    // Next comes the usage.  It may include neither, either, or both of the
3394    // set of options and trailing arguments.
3395    if (! subCommands.isEmpty())
3396    {
3397      lines.addAll(StaticUtils.wrapLine(
3398           INFO_USAGE_SUBCOMMAND_USAGE.get(commandName), maxWidth));
3399    }
3400    else if (namedArgs.isEmpty())
3401    {
3402      if (maxTrailingArgs == 0)
3403      {
3404        lines.addAll(StaticUtils.wrapLine(
3405             INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName), maxWidth));
3406      }
3407      else
3408      {
3409        lines.addAll(StaticUtils.wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get(
3410             commandName, trailingArgsPlaceholder), maxWidth));
3411      }
3412    }
3413    else
3414    {
3415      if (maxTrailingArgs == 0)
3416      {
3417        lines.addAll(StaticUtils.wrapLine(
3418             INFO_USAGE_OPTIONS_NOTRAILING.get(commandName), maxWidth));
3419      }
3420      else
3421      {
3422        lines.addAll(StaticUtils.wrapLine(INFO_USAGE_OPTIONS_TRAILING.get(
3423             commandName, trailingArgsPlaceholder), maxWidth));
3424      }
3425    }
3426
3427    if (! namedArgs.isEmpty())
3428    {
3429      lines.add("");
3430      lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
3431
3432
3433      // If there are any argument groups, then collect the arguments in those
3434      // groups.
3435      boolean hasRequired = false;
3436      final LinkedHashMap<String,List<Argument>> argumentsByGroup =
3437           new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
3438      final ArrayList<Argument> argumentsWithoutGroup =
3439           new ArrayList<>(namedArgs.size());
3440      final ArrayList<Argument> usageArguments =
3441           new ArrayList<>(namedArgs.size());
3442      for (final Argument a : namedArgs)
3443      {
3444        if (a.isHidden())
3445        {
3446          // This argument shouldn't be included in the usage output.
3447          continue;
3448        }
3449
3450        if (a.isRequired() && (! a.hasDefaultValue()))
3451        {
3452          hasRequired = true;
3453        }
3454
3455        final String argumentGroup = a.getArgumentGroupName();
3456        if (argumentGroup == null)
3457        {
3458          if (a.isUsageArgument())
3459          {
3460            usageArguments.add(a);
3461          }
3462          else
3463          {
3464            argumentsWithoutGroup.add(a);
3465          }
3466        }
3467        else
3468        {
3469          List<Argument> groupArgs = argumentsByGroup.get(argumentGroup);
3470          if (groupArgs == null)
3471          {
3472            groupArgs = new ArrayList<>(10);
3473            argumentsByGroup.put(argumentGroup, groupArgs);
3474          }
3475
3476          groupArgs.add(a);
3477        }
3478      }
3479
3480
3481      // Iterate through the defined argument groups and display usage
3482      // information for each of them.
3483      for (final Map.Entry<String,List<Argument>> e :
3484           argumentsByGroup.entrySet())
3485      {
3486        lines.add("");
3487        lines.add("  " + e.getKey());
3488        lines.add("");
3489        for (final Argument a : e.getValue())
3490        {
3491          getArgUsage(a, lines, true, maxWidth);
3492        }
3493      }
3494
3495      if (! argumentsWithoutGroup.isEmpty())
3496      {
3497        if (argumentsByGroup.isEmpty())
3498        {
3499          for (final Argument a : argumentsWithoutGroup)
3500          {
3501            getArgUsage(a, lines, false, maxWidth);
3502          }
3503        }
3504        else
3505        {
3506          lines.add("");
3507          lines.add("  " + INFO_USAGE_UNGROUPED_ARGS.get());
3508          lines.add("");
3509          for (final Argument a : argumentsWithoutGroup)
3510          {
3511            getArgUsage(a, lines, true, maxWidth);
3512          }
3513        }
3514      }
3515
3516      if (! usageArguments.isEmpty())
3517      {
3518        if (argumentsByGroup.isEmpty())
3519        {
3520          for (final Argument a : usageArguments)
3521          {
3522            getArgUsage(a, lines, false, maxWidth);
3523          }
3524        }
3525        else
3526        {
3527          lines.add("");
3528          lines.add("  " + INFO_USAGE_USAGE_ARGS.get());
3529          lines.add("");
3530          for (final Argument a : usageArguments)
3531          {
3532            getArgUsage(a, lines, true, maxWidth);
3533          }
3534        }
3535      }
3536
3537      if (hasRequired)
3538      {
3539        lines.add("");
3540        if (argumentsByGroup.isEmpty())
3541        {
3542          lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
3543        }
3544        else
3545        {
3546          lines.add("  * " + INFO_USAGE_ARG_IS_REQUIRED.get());
3547        }
3548      }
3549    }
3550
3551    return lines;
3552  }
3553
3554
3555
3556  /**
3557   * Retrieves lines that make up the usage information for the selected
3558   * subcommand.
3559   *
3560   * @param  maxWidth  The maximum line width to use for the output.  If this is
3561   *                   less than or equal to zero, then no wrapping will be
3562   *                   performed.
3563   *
3564   * @return  The lines that make up the usage information for the selected
3565   *          subcommand.
3566   */
3567  private List<String> getSubCommandUsage(final int maxWidth)
3568  {
3569    // First is a description of the subcommand.
3570    final ArrayList<String> lines = new ArrayList<>(100);
3571    lines.addAll(
3572         StaticUtils.wrapLine(selectedSubCommand.getDescription(), maxWidth));
3573    lines.add("");
3574
3575    // Next comes the usage.
3576    lines.addAll(StaticUtils.wrapLine(INFO_SUBCOMMAND_USAGE_OPTIONS.get(
3577         commandName, selectedSubCommand.getPrimaryName()), maxWidth));
3578
3579
3580    final ArgumentParser parser = selectedSubCommand.getArgumentParser();
3581    if (! parser.namedArgs.isEmpty())
3582    {
3583      lines.add("");
3584      lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
3585
3586
3587      // If there are any argument groups, then collect the arguments in those
3588      // groups.
3589      boolean hasRequired = false;
3590      final LinkedHashMap<String,List<Argument>> argumentsByGroup =
3591           new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
3592      final ArrayList<Argument> argumentsWithoutGroup =
3593           new ArrayList<>(parser.namedArgs.size());
3594      final ArrayList<Argument> usageArguments =
3595           new ArrayList<>(parser.namedArgs.size());
3596      for (final Argument a : parser.namedArgs)
3597      {
3598        if (a.isHidden())
3599        {
3600          // This argument shouldn't be included in the usage output.
3601          continue;
3602        }
3603
3604        if (a.isRequired() && (! a.hasDefaultValue()))
3605        {
3606          hasRequired = true;
3607        }
3608
3609        final String argumentGroup = a.getArgumentGroupName();
3610        if (argumentGroup == null)
3611        {
3612          if (a.isUsageArgument())
3613          {
3614            usageArguments.add(a);
3615          }
3616          else
3617          {
3618            argumentsWithoutGroup.add(a);
3619          }
3620        }
3621        else
3622        {
3623          List<Argument> groupArgs = argumentsByGroup.get(argumentGroup);
3624          if (groupArgs == null)
3625          {
3626            groupArgs = new ArrayList<>(10);
3627            argumentsByGroup.put(argumentGroup, groupArgs);
3628          }
3629
3630          groupArgs.add(a);
3631        }
3632      }
3633
3634
3635      // Iterate through the defined argument groups and display usage
3636      // information for each of them.
3637      for (final Map.Entry<String,List<Argument>> e :
3638           argumentsByGroup.entrySet())
3639      {
3640        lines.add("");
3641        lines.add("  " + e.getKey());
3642        lines.add("");
3643        for (final Argument a : e.getValue())
3644        {
3645          getArgUsage(a, lines, true, maxWidth);
3646        }
3647      }
3648
3649      if (! argumentsWithoutGroup.isEmpty())
3650      {
3651        if (argumentsByGroup.isEmpty())
3652        {
3653          for (final Argument a : argumentsWithoutGroup)
3654          {
3655            getArgUsage(a, lines, false, maxWidth);
3656          }
3657        }
3658        else
3659        {
3660          lines.add("");
3661          lines.add("  " + INFO_USAGE_UNGROUPED_ARGS.get());
3662          lines.add("");
3663          for (final Argument a : argumentsWithoutGroup)
3664          {
3665            getArgUsage(a, lines, true, maxWidth);
3666          }
3667        }
3668      }
3669
3670      if (! usageArguments.isEmpty())
3671      {
3672        if (argumentsByGroup.isEmpty())
3673        {
3674          for (final Argument a : usageArguments)
3675          {
3676            getArgUsage(a, lines, false, maxWidth);
3677          }
3678        }
3679        else
3680        {
3681          lines.add("");
3682          lines.add("  " + INFO_USAGE_USAGE_ARGS.get());
3683          lines.add("");
3684          for (final Argument a : usageArguments)
3685          {
3686            getArgUsage(a, lines, true, maxWidth);
3687          }
3688        }
3689      }
3690
3691      if (hasRequired)
3692      {
3693        lines.add("");
3694        if (argumentsByGroup.isEmpty())
3695        {
3696          lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
3697        }
3698        else
3699        {
3700          lines.add("  * " + INFO_USAGE_ARG_IS_REQUIRED.get());
3701        }
3702      }
3703    }
3704
3705    return lines;
3706  }
3707
3708
3709
3710  /**
3711   * Adds usage information for the provided argument to the given list.
3712   *
3713   * @param  a         The argument for which to get the usage information.
3714   * @param  lines     The list to which the resulting lines should be added.
3715   * @param  indent    Indicates whether to indent each line.
3716   * @param  maxWidth  The maximum width of each line, in characters.
3717   */
3718  private static void getArgUsage(final Argument a, final List<String> lines,
3719                                  final boolean indent, final int maxWidth)
3720  {
3721    final StringBuilder argLine = new StringBuilder();
3722    if (indent && (maxWidth > 10))
3723    {
3724      if (a.isRequired() && (! a.hasDefaultValue()))
3725      {
3726        argLine.append("  * ");
3727      }
3728      else
3729      {
3730        argLine.append("    ");
3731      }
3732    }
3733    else if (a.isRequired() && (! a.hasDefaultValue()))
3734    {
3735      argLine.append("* ");
3736    }
3737
3738    boolean first = true;
3739    for (final Character c : a.getShortIdentifiers(false))
3740    {
3741      if (first)
3742      {
3743        argLine.append('-');
3744        first = false;
3745      }
3746      else
3747      {
3748        argLine.append(", -");
3749      }
3750      argLine.append(c);
3751    }
3752
3753    for (final String s : a.getLongIdentifiers(false))
3754    {
3755      if (first)
3756      {
3757        argLine.append("--");
3758        first = false;
3759      }
3760      else
3761      {
3762        argLine.append(", --");
3763      }
3764      argLine.append(s);
3765    }
3766
3767    final String valuePlaceholder = a.getValuePlaceholder();
3768    if (valuePlaceholder != null)
3769    {
3770      argLine.append(' ');
3771      argLine.append(valuePlaceholder);
3772    }
3773
3774    // If we need to wrap the argument line, then align the dashes on the left
3775    // edge.
3776    int subsequentLineWidth = maxWidth - 4;
3777    if (subsequentLineWidth < 4)
3778    {
3779      subsequentLineWidth = maxWidth;
3780    }
3781    final List<String> identifierLines =
3782         StaticUtils.wrapLine(argLine.toString(), maxWidth,
3783              subsequentLineWidth);
3784    for (int i=0; i < identifierLines.size(); i++)
3785    {
3786      if (i == 0)
3787      {
3788        lines.add(identifierLines.get(0));
3789      }
3790      else
3791      {
3792        lines.add("    " + identifierLines.get(i));
3793      }
3794    }
3795
3796
3797    // The description should be wrapped, if necessary.  We'll also want to
3798    // indent it (unless someone chose an absurdly small wrap width) to make
3799    // it stand out from the argument lines.
3800    final String description = a.getDescription();
3801    if (maxWidth > 10)
3802    {
3803      final String indentString;
3804      if (indent)
3805      {
3806        indentString = "        ";
3807      }
3808      else
3809      {
3810        indentString = "    ";
3811      }
3812
3813      final List<String> descLines = StaticUtils.wrapLine(description,
3814           (maxWidth-indentString.length()));
3815      for (final String s : descLines)
3816      {
3817        lines.add(indentString + s);
3818      }
3819    }
3820    else
3821    {
3822      lines.addAll(StaticUtils.wrapLine(description, maxWidth));
3823    }
3824  }
3825
3826
3827
3828  /**
3829   * Writes usage information for this program to the provided output stream
3830   * using the UTF-8 encoding, optionally wrapping long lines.
3831   *
3832   * @param  outputStream  The output stream to which the usage information
3833   *                       should be written.  It must not be {@code null}.
3834   * @param  maxWidth      The maximum line width to use for the output.  If
3835   *                       this is less than or equal to zero, then no wrapping
3836   *                       will be performed.
3837   *
3838   * @throws  IOException  If an error occurs while attempting to write to the
3839   *                       provided output stream.
3840   */
3841  public void getUsage(final OutputStream outputStream, final int maxWidth)
3842         throws IOException
3843  {
3844    final List<String> usageLines = getUsage(maxWidth);
3845    for (final String s : usageLines)
3846    {
3847      outputStream.write(StaticUtils.getBytes(s));
3848      outputStream.write(StaticUtils.EOL_BYTES);
3849    }
3850  }
3851
3852
3853
3854  /**
3855   * Retrieves a string representation of the usage information.
3856   *
3857   * @param  maxWidth  The maximum line width to use for the output.  If this is
3858   *                   less than or equal to zero, then no wrapping will be
3859   *                   performed.
3860   *
3861   * @return  A string representation of the usage information
3862   */
3863  public String getUsageString(final int maxWidth)
3864  {
3865    final StringBuilder buffer = new StringBuilder();
3866    getUsageString(buffer, maxWidth);
3867    return buffer.toString();
3868  }
3869
3870
3871
3872  /**
3873   * Appends a string representation of the usage information to the provided
3874   * buffer.
3875   *
3876   * @param  buffer    The buffer to which the information should be appended.
3877   * @param  maxWidth  The maximum line width to use for the output.  If this is
3878   *                   less than or equal to zero, then no wrapping will be
3879   *                   performed.
3880   */
3881  public void getUsageString(final StringBuilder buffer, final int maxWidth)
3882  {
3883    for (final String line : getUsage(maxWidth))
3884    {
3885      buffer.append(line);
3886      buffer.append(StaticUtils.EOL);
3887    }
3888  }
3889
3890
3891
3892  /**
3893   * Retrieves a string representation of this argument parser.
3894   *
3895   * @return  A string representation of this argument parser.
3896   */
3897  @Override()
3898  public String toString()
3899  {
3900    final StringBuilder buffer = new StringBuilder();
3901    toString(buffer);
3902    return buffer.toString();
3903  }
3904
3905
3906
3907  /**
3908   * Appends a string representation of this argument parser to the provided
3909   * buffer.
3910   *
3911   * @param  buffer  The buffer to which the information should be appended.
3912   */
3913  public void toString(final StringBuilder buffer)
3914  {
3915    buffer.append("ArgumentParser(commandName='");
3916    buffer.append(commandName);
3917    buffer.append("', commandDescription={");
3918    buffer.append('\'');
3919    buffer.append(commandDescription);
3920    buffer.append('\'');
3921
3922    if (additionalCommandDescriptionParagraphs != null)
3923    {
3924      for (final String additionalParagraph :
3925           additionalCommandDescriptionParagraphs)
3926      {
3927        buffer.append(", '");
3928        buffer.append(additionalParagraph);
3929        buffer.append('\'');
3930      }
3931    }
3932
3933    buffer.append("}, minTrailingArgs=");
3934    buffer.append(minTrailingArgs);
3935    buffer.append(", maxTrailingArgs=");
3936    buffer.append(maxTrailingArgs);
3937
3938    if (trailingArgsPlaceholder != null)
3939    {
3940      buffer.append(", trailingArgsPlaceholder='");
3941      buffer.append(trailingArgsPlaceholder);
3942      buffer.append('\'');
3943    }
3944
3945    buffer.append(", namedArgs={");
3946
3947    final Iterator<Argument> iterator = namedArgs.iterator();
3948    while (iterator.hasNext())
3949    {
3950      iterator.next().toString(buffer);
3951      if (iterator.hasNext())
3952      {
3953        buffer.append(", ");
3954      }
3955    }
3956
3957    buffer.append('}');
3958
3959    if (! subCommands.isEmpty())
3960    {
3961      buffer.append(", subCommands={");
3962
3963      final Iterator<SubCommand> subCommandIterator = subCommands.iterator();
3964      while (subCommandIterator.hasNext())
3965      {
3966        subCommandIterator.next().toString(buffer);
3967        if (subCommandIterator.hasNext())
3968        {
3969          buffer.append(", ");
3970        }
3971      }
3972
3973      buffer.append('}');
3974    }
3975
3976    buffer.append(')');
3977  }
3978}