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