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