001/*
002 * Copyright 2008-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-2024 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-2024 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.IOException;
044import java.io.InputStream;
045import java.io.InputStreamReader;
046import java.security.GeneralSecurityException;
047import java.util.ArrayList;
048import java.util.Collections;
049import java.util.Iterator;
050import java.util.List;
051
052import com.unboundid.ldap.sdk.unboundidds.tools.ToolUtils;
053import com.unboundid.util.ByteStringBuffer;
054import com.unboundid.util.Mutable;
055import com.unboundid.util.NotNull;
056import com.unboundid.util.Nullable;
057import com.unboundid.util.ObjectPair;
058import com.unboundid.util.StaticUtils;
059import com.unboundid.util.ThreadSafety;
060import com.unboundid.util.ThreadSafetyLevel;
061
062import static com.unboundid.util.args.ArgsMessages.*;
063
064
065
066/**
067 * This class defines an argument that is intended to hold values which refer to
068 * files on the local filesystem.  File arguments must take values, and it is
069 * possible to restrict the values to files that exist, or whose parent exists.
070 */
071@Mutable()
072@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
073public final class FileArgument
074       extends Argument
075{
076  /**
077   * A pre-allocated list of potential passphrases that may be used when trying
078   * to read from an encrypted file.  The passphrase is not expected to be
079   * correct, but this list is used to ensure that the LDAP SDK does not
080   * unexpectedly prompt for an encryption passphrase if a file is encrypted and
081   * the key is not found in a Ping Identity server's encryption settings
082   * database.
083   */
084  @NotNull private static final List<char[]>
085       POTENTIAL_PASSPHRASES_TO_AVOID_PROMPTING =
086            Collections.singletonList(new char[0]);
087
088
089
090  /**
091   * The serial version UID for this serializable class.
092   */
093  private static final long serialVersionUID = 741228505566416489L;
094
095
096
097  // Indicates whether values must represent files that exist.
098  private final boolean fileMustExist;
099
100  // Indicates whether the provided value must be a directory if it exists.
101  private final boolean mustBeDirectory;
102
103  // Indicates whether the provided value must be a regular file if it exists.
104  private final boolean mustBeFile;
105
106  // Indicates whether values must represent files with parent directories that
107  // exist.
108  private final boolean parentMustExist;
109
110  // The set of values assigned to this argument.
111  @NotNull private final ArrayList<File> values;
112
113  // The path to the directory that will serve as the base directory for
114  // relative paths.
115  @Nullable private File relativeBaseDirectory;
116
117  // The argument value validators that have been registered for this argument.
118  @NotNull private final List<ArgumentValueValidator> validators;
119
120  // The list of default values for this argument.
121  @Nullable private final List<File> defaultValues;
122
123
124
125  /**
126   * Creates a new file argument with the provided information.  It will not
127   * be required, will permit at most one occurrence, will use a default
128   * placeholder, will not have any default values, and will not impose any
129   * constraints on the kinds of values it can have.
130   *
131   * @param  shortIdentifier   The short identifier for this argument.  It may
132   *                           not be {@code null} if the long identifier is
133   *                           {@code null}.
134   * @param  longIdentifier    The long identifier for this argument.  It may
135   *                           not be {@code null} if the short identifier is
136   *                           {@code null}.
137   * @param  description       A human-readable description for this argument.
138   *                           It must not be {@code null}.
139   *
140   * @throws  ArgumentException  If there is a problem with the definition of
141   *                             this argument.
142   */
143  public FileArgument(@Nullable final Character shortIdentifier,
144                      @Nullable final String longIdentifier,
145                      @NotNull final String description)
146         throws ArgumentException
147  {
148    this(shortIdentifier, longIdentifier, false, 1, null, description);
149  }
150
151
152
153  /**
154   * Creates a new file argument with the provided information.  There will not
155   * be any default values or constraints on the kinds of values it can have.
156   *
157   * @param  shortIdentifier   The short identifier for this argument.  It may
158   *                           not be {@code null} if the long identifier is
159   *                           {@code null}.
160   * @param  longIdentifier    The long identifier for this argument.  It may
161   *                           not be {@code null} if the short identifier is
162   *                           {@code null}.
163   * @param  isRequired        Indicates whether this argument is required to
164   *                           be provided.
165   * @param  maxOccurrences    The maximum number of times this argument may be
166   *                           provided on the command line.  A value less than
167   *                           or equal to zero indicates that it may be present
168   *                           any number of times.
169   * @param  valuePlaceholder  A placeholder to display in usage information to
170   *                           indicate that a value must be provided.  It may
171   *                           be {@code null} if a default placeholder should
172   *                           be used.
173   * @param  description       A human-readable description for this argument.
174   *                           It must not be {@code null}.
175   *
176   * @throws  ArgumentException  If there is a problem with the definition of
177   *                             this argument.
178   */
179  public FileArgument(@Nullable final Character shortIdentifier,
180                      @Nullable final String longIdentifier,
181                      final boolean isRequired, final int maxOccurrences,
182                      @Nullable final String valuePlaceholder,
183                      @NotNull final String description)
184         throws ArgumentException
185  {
186    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
187         valuePlaceholder, description, false, false, false, false, null);
188  }
189
190
191
192  /**
193   * Creates a new file argument with the provided information.  It will not
194   * have any default values.
195   *
196   * @param  shortIdentifier   The short identifier for this argument.  It may
197   *                           not be {@code null} if the long identifier is
198   *                           {@code null}.
199   * @param  longIdentifier    The long identifier for this argument.  It may
200   *                           not be {@code null} if the short identifier is
201   *                           {@code null}.
202   * @param  isRequired        Indicates whether this argument is required to
203   *                           be provided.
204   * @param  maxOccurrences    The maximum number of times this argument may be
205   *                           provided on the command line.  A value less than
206   *                           or equal to zero indicates that it may be present
207   *                           any number of times.
208   * @param  valuePlaceholder  A placeholder to display in usage information to
209   *                           indicate that a value must be provided.  It may
210   *                           be {@code null} if a default placeholder should
211   *                           be used.
212   * @param  description       A human-readable description for this argument.
213   *                           It must not be {@code null}.
214   * @param  fileMustExist     Indicates whether each value must refer to a file
215   *                           that exists.
216   * @param  parentMustExist   Indicates whether each value must refer to a file
217   *                           whose parent directory exists.
218   * @param  mustBeFile        Indicates whether each value must refer to a
219   *                           regular file, if it exists.
220   * @param  mustBeDirectory   Indicates whether each value must refer to a
221   *                           directory, if it exists.
222   *
223   * @throws  ArgumentException  If there is a problem with the definition of
224   *                             this argument.
225   */
226  public FileArgument(@Nullable final Character shortIdentifier,
227                      @Nullable final String longIdentifier,
228                      final boolean isRequired, final int maxOccurrences,
229                      @Nullable final String valuePlaceholder,
230                      @NotNull final String description,
231                      final boolean fileMustExist,
232                      final boolean parentMustExist, final boolean mustBeFile,
233                      final boolean mustBeDirectory)
234         throws ArgumentException
235  {
236    this(shortIdentifier, longIdentifier, isRequired, maxOccurrences,
237         valuePlaceholder, description, fileMustExist, parentMustExist,
238         mustBeFile, mustBeDirectory, null);
239  }
240
241
242
243  /**
244   * Creates a new file argument with the provided information.
245   *
246   * @param  shortIdentifier   The short identifier for this argument.  It may
247   *                           not be {@code null} if the long identifier is
248   *                           {@code null}.
249   * @param  longIdentifier    The long identifier for this argument.  It may
250   *                           not be {@code null} if the short identifier is
251   *                           {@code null}.
252   * @param  isRequired        Indicates whether this argument is required to
253   *                           be provided.
254   * @param  maxOccurrences    The maximum number of times this argument may be
255   *                           provided on the command line.  A value less than
256   *                           or equal to zero indicates that it may be present
257   *                           any number of times.
258   * @param  valuePlaceholder  A placeholder to display in usage information to
259   *                           indicate that a value must be provided.  It may
260   *                           be {@code null} if a default placeholder should
261   *                           be used.
262   * @param  description       A human-readable description for this argument.
263   *                           It must not be {@code null}.
264   * @param  fileMustExist     Indicates whether each value must refer to a file
265   *                           that exists.
266   * @param  parentMustExist   Indicates whether each value must refer to a file
267   *                           whose parent directory exists.
268   * @param  mustBeFile        Indicates whether each value must refer to a
269   *                           regular file, if it exists.
270   * @param  mustBeDirectory   Indicates whether each value must refer to a
271   *                           directory, if it exists.
272   * @param  defaultValues     The set of default values to use for this
273   *                           argument if no values were provided.
274   *
275   * @throws  ArgumentException  If there is a problem with the definition of
276   *                             this argument.
277   */
278  public FileArgument(@Nullable final Character shortIdentifier,
279                      @Nullable final String longIdentifier,
280                      final boolean isRequired, final int maxOccurrences,
281                      @Nullable final String valuePlaceholder,
282                      @NotNull final String description,
283                      final boolean fileMustExist,
284                      final boolean parentMustExist, final boolean mustBeFile,
285                      final boolean mustBeDirectory,
286                      @Nullable final List<File> defaultValues)
287         throws ArgumentException
288  {
289    super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
290         (valuePlaceholder == null)
291              ? INFO_PLACEHOLDER_PATH.get()
292              : valuePlaceholder,
293         description);
294
295    if (mustBeFile && mustBeDirectory)
296    {
297      throw new ArgumentException(ERR_FILE_CANNOT_BE_FILE_AND_DIRECTORY.get(
298                                       getIdentifierString()));
299    }
300
301    this.fileMustExist   = fileMustExist;
302    this.parentMustExist = parentMustExist;
303    this.mustBeFile      = mustBeFile;
304    this.mustBeDirectory = mustBeDirectory;
305
306    if ((defaultValues == null) || defaultValues.isEmpty())
307    {
308      this.defaultValues = null;
309    }
310    else
311    {
312      this.defaultValues = Collections.unmodifiableList(defaultValues);
313    }
314
315    values                = new ArrayList<>(5);
316    validators            = new ArrayList<>(5);
317    relativeBaseDirectory = null;
318  }
319
320
321
322  /**
323   * Creates a new file argument that is a "clean" copy of the provided source
324   * argument.
325   *
326   * @param  source  The source argument to use for this argument.
327   */
328  private FileArgument(@NotNull final FileArgument source)
329  {
330    super(source);
331
332    fileMustExist         = source.fileMustExist;
333    mustBeDirectory       = source.mustBeDirectory;
334    mustBeFile            = source.mustBeFile;
335    parentMustExist       = source.parentMustExist;
336    defaultValues         = source.defaultValues;
337    relativeBaseDirectory = source.relativeBaseDirectory;
338    validators            = new ArrayList<>(source.validators);
339    values                = new ArrayList<>(5);
340  }
341
342
343
344  /**
345   * Indicates whether each value must refer to a file that exists.
346   *
347   * @return  {@code true} if the target files must exist, or {@code false} if
348   *          it is acceptable for values to refer to files that do not exist.
349   */
350  public boolean fileMustExist()
351  {
352    return fileMustExist;
353  }
354
355
356
357  /**
358   * Indicates whether each value must refer to a file whose parent directory
359   * exists.
360   *
361   * @return  {@code true} if the parent directory for target files must exist,
362   *          or {@code false} if it is acceptable for values to refer to files
363   *          whose parent directories do not exist.
364   */
365  public boolean parentMustExist()
366  {
367    return parentMustExist;
368  }
369
370
371
372  /**
373   * Indicates whether each value must refer to a regular file (if it exists).
374   *
375   * @return  {@code true} if each value must refer to a regular file (if it
376   *          exists), or {@code false} if it may refer to a directory.
377   */
378  public boolean mustBeFile()
379  {
380    return mustBeFile;
381  }
382
383
384
385  /**
386   * Indicates whether each value must refer to a directory (if it exists).
387   *
388   * @return  {@code true} if each value must refer to a directory (if it
389   *          exists), or {@code false} if it may refer to a regular file.
390   */
391  public boolean mustBeDirectory()
392  {
393    return mustBeDirectory;
394  }
395
396
397
398  /**
399   * Retrieves the list of default values for this argument, which will be used
400   * if no values were provided.
401   *
402   * @return   The list of default values for this argument, or {@code null} if
403   *           there are no default values.
404   */
405  @Nullable()
406  public List<File> getDefaultValues()
407  {
408    return defaultValues;
409  }
410
411
412
413  /**
414   * Retrieves the directory that will serve as the base directory for relative
415   * paths, if one has been defined.
416   *
417   * @return  The directory that will serve as the base directory for relative
418   *          paths, or {@code null} if relative paths will be relative to the
419   *          current working directory.
420   */
421  @Nullable()
422  public File getRelativeBaseDirectory()
423  {
424    return relativeBaseDirectory;
425  }
426
427
428
429  /**
430   * Specifies the directory that will serve as the base directory for relative
431   * paths.
432   *
433   * @param  relativeBaseDirectory  The directory that will serve as the base
434   *                                directory for relative paths.  It may be
435   *                                {@code null} if relative paths should be
436   *                                relative to the current working directory.
437   */
438  public void setRelativeBaseDirectory(
439                   @Nullable final File relativeBaseDirectory)
440  {
441    this.relativeBaseDirectory = relativeBaseDirectory;
442  }
443
444
445
446  /**
447   * Updates this argument to ensure that the provided validator will be invoked
448   * for any values provided to this argument.  This validator will be invoked
449   * after all other validation has been performed for this argument.
450   *
451   * @param  validator  The argument value validator to be invoked.  It must not
452   *                    be {@code null}.
453   */
454  public void addValueValidator(@NotNull final ArgumentValueValidator validator)
455  {
456    validators.add(validator);
457  }
458
459
460
461  /**
462   * {@inheritDoc}
463   */
464  @Override()
465  protected void addValue(@NotNull final String valueString)
466            throws ArgumentException
467  {
468    // NOTE:  java.io.File has an extremely weird behavior.  When a File object
469    // is created from a relative path and that path contains only the filename,
470    // then calling getParent or getParentFile will return null even though it
471    // obviously has a parent.  Therefore, you must always create a File using
472    // the absolute path if you might want to get the parent.  Also, if the path
473    // is relative, then we might want to control the base to which it is
474    // relative.
475    File f = new File(valueString);
476    if (! f.isAbsolute())
477    {
478      if (relativeBaseDirectory == null)
479      {
480        f = new File(f.getAbsolutePath());
481      }
482      else
483      {
484        f = new File(new File(relativeBaseDirectory,
485             valueString).getAbsolutePath());
486      }
487    }
488
489    if (f.exists())
490    {
491      if (mustBeFile && (! f.isFile()))
492      {
493        throw new ArgumentException(ERR_FILE_VALUE_NOT_FILE.get(
494                                         getIdentifierString(),
495                                         f.getAbsolutePath()));
496      }
497      else if (mustBeDirectory && (! f.isDirectory()))
498      {
499        throw new ArgumentException(ERR_FILE_VALUE_NOT_DIRECTORY.get(
500                                         getIdentifierString(),
501                                         f.getAbsolutePath()));
502      }
503    }
504    else
505    {
506      if (fileMustExist)
507      {
508        throw new ArgumentException(ERR_FILE_DOESNT_EXIST.get(
509                                         f.getAbsolutePath(),
510                                         getIdentifierString()));
511      }
512      else if (parentMustExist)
513      {
514        final File parentFile = f.getAbsoluteFile().getParentFile();
515        if ((parentFile == null) ||
516            (! parentFile.exists()) ||
517            (! parentFile.isDirectory()))
518        {
519          throw new ArgumentException(ERR_FILE_PARENT_DOESNT_EXIST.get(
520                                           f.getAbsolutePath(),
521                                           getIdentifierString()));
522        }
523      }
524    }
525
526    if (values.size() >= getMaxOccurrences())
527    {
528      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
529                                       getIdentifierString()));
530    }
531
532    for (final ArgumentValueValidator v : validators)
533    {
534      v.validateArgumentValue(this, valueString);
535    }
536
537    values.add(f);
538  }
539
540
541
542  /**
543   * Retrieves the value for this argument, or the default value if none was
544   * provided.  If there are multiple values, then the first will be returned.
545   *
546   * @return  The value for this argument, or the default value if none was
547   *          provided, or {@code null} if there is no value and no default
548   *          value.
549   */
550  @Nullable()
551  public File getValue()
552  {
553    if (values.isEmpty())
554    {
555      if ((defaultValues == null) || defaultValues.isEmpty())
556      {
557        return null;
558      }
559      else
560      {
561        return defaultValues.get(0);
562      }
563    }
564    else
565    {
566      return values.get(0);
567    }
568  }
569
570
571
572  /**
573   * Retrieves the set of values for this argument.
574   *
575   * @return  The set of values for this argument.
576   */
577  @NotNull()
578  public List<File> getValues()
579  {
580    if (values.isEmpty() && (defaultValues != null))
581    {
582      return defaultValues;
583    }
584
585    return Collections.unmodifiableList(values);
586  }
587
588
589
590  /**
591   * Retrieves an input stream that may be used to read the contents of the file
592   * specified as the value to this argument.  If there are multiple values for
593   * this argument, then the file specified as the first value will be used.
594   *
595   * @return  An input stream that may be used to read the data from the file,
596   *          or {@code null} if no values were provided.
597   *
598   * @throws  IOException  If the specified file does not exist or if a problem
599   *                       occurs while obtaining the input stream.
600   */
601  @Nullable()
602  public InputStream getFileInputStream()
603         throws IOException
604  {
605    final File f = getValue();
606    if (f == null)
607    {
608      return null;
609    }
610
611    InputStream inputStream = new FileInputStream(f);
612
613    boolean closeStream = true;
614    try
615    {
616      final List<char[]> potentialPassphrases = new ArrayList<>();
617
618
619      try
620      {
621        final ObjectPair<InputStream,char[]> streamPair =
622             ToolUtils.getPossiblyPassphraseEncryptedInputStream(inputStream,
623                  POTENTIAL_PASSPHRASES_TO_AVOID_PROMPTING, false,
624                  INFO_FILE_ENTER_ENC_PW.get(f.getAbsolutePath()),
625                  ERR_FILE_WRONG_ENC_PW.get(f.getAbsolutePath()), System.out,
626                  System.err);
627        inputStream = streamPair.getFirst();
628      }
629      catch (final GeneralSecurityException e)
630      {
631        throw new IOException(
632             ERR_FILE_CANNOT_DECRYPT.get(f.getAbsolutePath(),
633                  StaticUtils.getExceptionMessage(e)),
634             e);
635      }
636
637      inputStream = ToolUtils.getPossiblyGZIPCompressedInputStream(inputStream);
638      closeStream = false;
639      return inputStream;
640    }
641    finally
642    {
643      if (closeStream)
644      {
645        inputStream.close();
646      }
647    }
648  }
649
650
651
652  /**
653   * Reads the contents of the file specified as the value to this argument and
654   * retrieves a list of the lines contained in it.  If there are multiple
655   * values for this argument, then the file specified as the first value will
656   * be used.
657   *
658   * @return  A list containing the lines of the target file, or {@code null} if
659   *          no values were provided.
660   *
661   * @throws  IOException  If the specified file does not exist or a problem
662   *                       occurs while reading the contents of the file.
663   */
664  @Nullable()
665  public List<String> getFileLines()
666         throws IOException
667  {
668    return getFileLines(true);
669  }
670
671
672
673  /**
674   * Reads the contents of the file specified as the value to this argument and
675   * retrieves a list of the non-blank lines contained in it.  If there are
676   * multiple values for this argument, then the file specified as the first
677   * value will be used.
678   *
679   * @return  A list containing the non-blank lines of the target file, or
680   *          {@code null} if no values were provided.
681   *
682   * @throws  IOException  If the specified file does not exist or a problem
683   *                       occurs while reading the contents of the file.
684   */
685  @Nullable()
686  public List<String> getNonBlankFileLines()
687         throws IOException
688  {
689    return getFileLines(false);
690  }
691
692
693
694  /**
695   * Reads the contents of the file specified as the value to this argument and
696   * retrieves a list of the lines contained in it, optionally omitting blank
697   * lines.  If there are multiple values for this argument, then the file
698   * specified as the first value will be used.
699   *
700   * @param  includeBlank  Indicates whether to include blank lines in the
701   *                       list that is returned.
702   *
703   * @return  A list containing the lines of the target file, or {@code null} if
704   *          no values were provided.
705   *
706   * @throws  IOException  If the specified file does not exist or a problem
707   *                       occurs while reading the contents of the file.
708   */
709  @Nullable()
710  private List<String> getFileLines(final boolean includeBlank)
711          throws IOException
712  {
713    final InputStream inputStream = getFileInputStream();
714    if (inputStream == null)
715    {
716      return null;
717    }
718
719    try (InputStreamReader inputStreamReader =
720              new InputStreamReader(inputStream);
721         BufferedReader bufferedReader = new BufferedReader(inputStreamReader))
722    {
723      final List<String> lines = new ArrayList<>();
724      while (true)
725      {
726        final String line = bufferedReader.readLine();
727        if (line == null)
728        {
729          return lines;
730        }
731
732        if (includeBlank || (! line.isEmpty()))
733        {
734          lines.add(line);
735        }
736      }
737    }
738    finally
739    {
740      inputStream.close();
741    }
742  }
743
744
745
746  /**
747   * Reads the contents of the file specified as the value to this argument.  If
748   * there are multiple values for this argument, then the file specified as the
749   * first value will be used.
750   *
751   * @return  A byte array containing the contents of the target file, or
752   *          {@code null} if no values were provided.
753   *
754   * @throws  IOException  If the specified file does not exist or a problem
755   *                       occurs while reading the contents of the file.
756   */
757  @Nullable()
758  public byte[] getFileBytes()
759         throws IOException
760  {
761    final InputStream inputStream = getFileInputStream();
762    if (inputStream == null)
763    {
764      return null;
765    }
766
767    try
768    {
769      final ByteStringBuffer buffer = new ByteStringBuffer();
770      buffer.readFrom(inputStream);
771      return buffer.toByteArray();
772    }
773    finally
774    {
775      inputStream.close();
776    }
777  }
778
779
780
781  /**
782   * {@inheritDoc}
783   */
784  @Override()
785  @NotNull()
786  public List<String> getValueStringRepresentations(final boolean useDefault)
787  {
788    final List<File> files;
789    if (values.isEmpty())
790    {
791      if (useDefault)
792      {
793        files = defaultValues;
794      }
795      else
796      {
797        return Collections.emptyList();
798      }
799    }
800    else
801    {
802      files = values;
803    }
804
805    if ((files == null) || files.isEmpty())
806    {
807      return Collections.emptyList();
808    }
809
810    final ArrayList<String> valueStrings = new ArrayList<>(files.size());
811    for (final File f : files)
812    {
813      valueStrings.add(f.getAbsolutePath());
814    }
815    return Collections.unmodifiableList(valueStrings);
816  }
817
818
819
820  /**
821   * {@inheritDoc}
822   */
823  @Override()
824  protected boolean hasDefaultValue()
825  {
826    return ((defaultValues != null) && (! defaultValues.isEmpty()));
827  }
828
829
830
831  /**
832   * {@inheritDoc}
833   */
834  @Override()
835  @NotNull()
836  public String getDataTypeName()
837  {
838    if (mustBeDirectory)
839    {
840      return INFO_FILE_TYPE_PATH_DIRECTORY.get();
841    }
842    else
843    {
844      return INFO_FILE_TYPE_PATH_FILE.get();
845    }
846  }
847
848
849
850  /**
851   * {@inheritDoc}
852   */
853  @Override()
854  @NotNull()
855  public String getValueConstraints()
856  {
857    final StringBuilder buffer = new StringBuilder();
858
859    if (mustBeDirectory)
860    {
861      if (fileMustExist)
862      {
863        buffer.append(INFO_FILE_CONSTRAINTS_DIR_MUST_EXIST.get());
864      }
865      else if (parentMustExist)
866      {
867        buffer.append(INFO_FILE_CONSTRAINTS_DIR_PARENT_MUST_EXIST.get());
868      }
869      else
870      {
871        buffer.append(INFO_FILE_CONSTRAINTS_DIR_MAY_EXIST.get());
872      }
873    }
874    else
875    {
876      if (fileMustExist)
877      {
878        buffer.append(INFO_FILE_CONSTRAINTS_FILE_MUST_EXIST.get());
879      }
880      else if (parentMustExist)
881      {
882        buffer.append(INFO_FILE_CONSTRAINTS_FILE_PARENT_MUST_EXIST.get());
883      }
884      else
885      {
886        buffer.append(INFO_FILE_CONSTRAINTS_FILE_MAY_EXIST.get());
887      }
888    }
889
890    if (relativeBaseDirectory != null)
891    {
892      buffer.append("  ");
893      buffer.append(INFO_FILE_CONSTRAINTS_RELATIVE_PATH_SPECIFIED_ROOT.get(
894           relativeBaseDirectory.getAbsolutePath()));
895    }
896
897    return buffer.toString();
898  }
899
900
901
902  /**
903   * {@inheritDoc}
904   */
905  @Override()
906  protected void reset()
907  {
908    super.reset();
909    values.clear();
910  }
911
912
913
914  /**
915   * {@inheritDoc}
916   */
917  @Override()
918  @NotNull()
919  public FileArgument getCleanCopy()
920  {
921    return new FileArgument(this);
922  }
923
924
925
926  /**
927   * {@inheritDoc}
928   */
929  @Override()
930  protected void addToCommandLine(@NotNull final List<String> argStrings)
931  {
932    for (final File f : values)
933    {
934      argStrings.add(getIdentifierString());
935      if (isSensitive())
936      {
937        argStrings.add("***REDACTED***");
938      }
939      else
940      {
941        argStrings.add(f.getAbsolutePath());
942      }
943    }
944  }
945
946
947
948  /**
949   * {@inheritDoc}
950   */
951  @Override()
952  public void toString(@NotNull final StringBuilder buffer)
953  {
954    buffer.append("FileArgument(");
955    appendBasicToStringInfo(buffer);
956
957    buffer.append(", fileMustExist=");
958    buffer.append(fileMustExist);
959    buffer.append(", parentMustExist=");
960    buffer.append(parentMustExist);
961    buffer.append(", mustBeFile=");
962    buffer.append(mustBeFile);
963    buffer.append(", mustBeDirectory=");
964    buffer.append(mustBeDirectory);
965
966    if (relativeBaseDirectory != null)
967    {
968      buffer.append(", relativeBaseDirectory='");
969      buffer.append(relativeBaseDirectory.getAbsolutePath());
970      buffer.append('\'');
971    }
972
973    if ((defaultValues != null) && (! defaultValues.isEmpty()))
974    {
975      if (defaultValues.size() == 1)
976      {
977        buffer.append(", defaultValue='");
978        buffer.append(defaultValues.get(0).toString());
979      }
980      else
981      {
982        buffer.append(", defaultValues={");
983
984        final Iterator<File> iterator = defaultValues.iterator();
985        while (iterator.hasNext())
986        {
987          buffer.append('\'');
988          buffer.append(iterator.next().toString());
989          buffer.append('\'');
990
991          if (iterator.hasNext())
992          {
993            buffer.append(", ");
994          }
995        }
996
997        buffer.append('}');
998      }
999    }
1000
1001    buffer.append(')');
1002  }
1003}