001/*
002 * Copyright 2008-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 UnboundID Corp.
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<File>(5);
273    validators            = new ArrayList<ArgumentValueValidator>(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            =
296         new ArrayList<ArgumentValueValidator>(source.validators);
297    values                = new ArrayList<File>(5);
298  }
299
300
301
302  /**
303   * Indicates whether each value must refer to a file that exists.
304   *
305   * @return  {@code true} if the target files must exist, or {@code false} if
306   *          it is acceptable for values to refer to files that do not exist.
307   */
308  public boolean fileMustExist()
309  {
310    return fileMustExist;
311  }
312
313
314
315  /**
316   * Indicates whether each value must refer to a file whose parent directory
317   * exists.
318   *
319   * @return  {@code true} if the parent directory for target files must exist,
320   *          or {@code false} if it is acceptable for values to refer to files
321   *          whose parent directories do not exist.
322   */
323  public boolean parentMustExist()
324  {
325    return parentMustExist;
326  }
327
328
329
330  /**
331   * Indicates whether each value must refer to a regular file (if it exists).
332   *
333   * @return  {@code true} if each value must refer to a regular file (if it
334   *          exists), or {@code false} if it may refer to a directory.
335   */
336  public boolean mustBeFile()
337  {
338    return mustBeFile;
339  }
340
341
342
343  /**
344   * Indicates whether each value must refer to a directory (if it exists).
345   *
346   * @return  {@code true} if each value must refer to a directory (if it
347   *          exists), or {@code false} if it may refer to a regular file.
348   */
349  public boolean mustBeDirectory()
350  {
351    return mustBeDirectory;
352  }
353
354
355
356  /**
357   * Retrieves the list of default values for this argument, which will be used
358   * if no values were provided.
359   *
360   * @return   The list of default values for this argument, or {@code null} if
361   *           there are no default values.
362   */
363  public List<File> getDefaultValues()
364  {
365    return defaultValues;
366  }
367
368
369
370  /**
371   * Retrieves the directory that will serve as the base directory for relative
372   * paths, if one has been defined.
373   *
374   * @return  The directory that will serve as the base directory for relative
375   *          paths, or {@code null} if relative paths will be relative to the
376   *          current working directory.
377   */
378  public File getRelativeBaseDirectory()
379  {
380    return relativeBaseDirectory;
381  }
382
383
384
385  /**
386   * Specifies the directory that will serve as the base directory for relative
387   * paths.
388   *
389   * @param  relativeBaseDirectory  The directory that will serve as the base
390   *                                directory for relative paths.  It may be
391   *                                {@code null} if relative paths should be
392   *                                relative to the current working directory.
393   */
394  public void setRelativeBaseDirectory(final File relativeBaseDirectory)
395  {
396    this.relativeBaseDirectory = relativeBaseDirectory;
397  }
398
399
400
401  /**
402   * Updates this argument to ensure that the provided validator will be invoked
403   * for any values provided to this argument.  This validator will be invoked
404   * after all other validation has been performed for this argument.
405   *
406   * @param  validator  The argument value validator to be invoked.  It must not
407   *                    be {@code null}.
408   */
409  public void addValueValidator(final ArgumentValueValidator validator)
410  {
411    validators.add(validator);
412  }
413
414
415
416  /**
417   * {@inheritDoc}
418   */
419  @Override()
420  protected void addValue(final String valueString)
421            throws ArgumentException
422  {
423    // NOTE:  java.io.File has an extremely weird behavior.  When a File object
424    // is created from a relative path and that path contains only the filename,
425    // then calling getParent or getParentFile will return null even though it
426    // obviously has a parent.  Therefore, you must always create a File using
427    // the absolute path if you might want to get the parent.  Also, if the path
428    // is relative, then we might want to control the base to which it is
429    // relative.
430    File f = new File(valueString);
431    if (! f.isAbsolute())
432    {
433      if (relativeBaseDirectory == null)
434      {
435        f = new File(f.getAbsolutePath());
436      }
437      else
438      {
439        f = new File(new File(relativeBaseDirectory,
440             valueString).getAbsolutePath());
441      }
442    }
443
444    if (f.exists())
445    {
446      if (mustBeFile && (! f.isFile()))
447      {
448        throw new ArgumentException(ERR_FILE_VALUE_NOT_FILE.get(
449                                         getIdentifierString(),
450                                         f.getAbsolutePath()));
451      }
452      else if (mustBeDirectory && (! f.isDirectory()))
453      {
454        throw new ArgumentException(ERR_FILE_VALUE_NOT_DIRECTORY.get(
455                                         getIdentifierString(),
456                                         f.getAbsolutePath()));
457      }
458    }
459    else
460    {
461      if (fileMustExist)
462      {
463        throw new ArgumentException(ERR_FILE_DOESNT_EXIST.get(
464                                         f.getAbsolutePath(),
465                                         getIdentifierString()));
466      }
467      else if (parentMustExist)
468      {
469        final File parentFile = f.getParentFile();
470        if ((parentFile == null) ||
471            (! parentFile.exists()) ||
472            (! parentFile.isDirectory()))
473        {
474          throw new ArgumentException(ERR_FILE_PARENT_DOESNT_EXIST.get(
475                                           f.getAbsolutePath(),
476                                           getIdentifierString()));
477        }
478      }
479    }
480
481    if (values.size() >= getMaxOccurrences())
482    {
483      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
484                                       getIdentifierString()));
485    }
486
487    for (final ArgumentValueValidator v : validators)
488    {
489      v.validateArgumentValue(this, valueString);
490    }
491
492    values.add(f);
493  }
494
495
496
497  /**
498   * Retrieves the value for this argument, or the default value if none was
499   * provided.  If there are multiple values, then the first will be returned.
500   *
501   * @return  The value for this argument, or the default value if none was
502   *          provided, or {@code null} if there is no value and no default
503   *          value.
504   */
505  public File getValue()
506  {
507    if (values.isEmpty())
508    {
509      if ((defaultValues == null) || defaultValues.isEmpty())
510      {
511        return null;
512      }
513      else
514      {
515        return defaultValues.get(0);
516      }
517    }
518    else
519    {
520      return values.get(0);
521    }
522  }
523
524
525
526  /**
527   * Retrieves the set of values for this argument.
528   *
529   * @return  The set of values for this argument.
530   */
531  public List<File> getValues()
532  {
533    if (values.isEmpty() && (defaultValues != null))
534    {
535      return defaultValues;
536    }
537
538    return Collections.unmodifiableList(values);
539  }
540
541
542
543  /**
544   * Reads the contents of the file specified as the value to this argument and
545   * retrieves a list of the lines contained in it.  If there are multiple
546   * values for this argument, then the file specified as the first value will
547   * be used.
548   *
549   * @return  A list containing the lines of the target file, or {@code null} if
550   *          no values were provided.
551   *
552   * @throws  IOException  If the specified file does not exist or a problem
553   *                       occurs while reading the contents of the file.
554   */
555  public List<String> getFileLines()
556         throws IOException
557  {
558    final File f = getValue();
559    if (f == null)
560    {
561      return null;
562    }
563
564    final ArrayList<String> lines  = new ArrayList<String>();
565    final BufferedReader    reader = new BufferedReader(new FileReader(f));
566    try
567    {
568      String line = reader.readLine();
569      while (line != null)
570      {
571        lines.add(line);
572        line = reader.readLine();
573      }
574    }
575    finally
576    {
577      reader.close();
578    }
579
580    return lines;
581  }
582
583
584
585  /**
586   * Reads the contents of the file specified as the value to this argument and
587   * retrieves a list of the non-blank lines contained in it.  If there are
588   * multiple values for this argument, then the file specified as the first
589   * value will be used.
590   *
591   * @return  A list containing the non-blank lines of the target file, or
592   *          {@code null} if no values were provided.
593   *
594   * @throws  IOException  If the specified file does not exist or a problem
595   *                       occurs while reading the contents of the file.
596   */
597  public List<String> getNonBlankFileLines()
598         throws IOException
599  {
600    final File f = getValue();
601    if (f == null)
602    {
603      return null;
604    }
605
606    final ArrayList<String> lines = new ArrayList<String>();
607    final BufferedReader reader = new BufferedReader(new FileReader(f));
608    try
609    {
610      String line = reader.readLine();
611      while (line != null)
612      {
613        if (line.length() > 0)
614        {
615          lines.add(line);
616        }
617        line = reader.readLine();
618      }
619    }
620    finally
621    {
622      reader.close();
623    }
624
625    return lines;
626  }
627
628
629
630  /**
631   * Reads the contents of the file specified as the value to this argument.  If
632   * there are multiple values for this argument, then the file specified as the
633   * first value will be used.
634   *
635   * @return  A byte array containing the contents of the target file, or
636   *          {@code null} if no values were provided.
637   *
638   * @throws  IOException  If the specified file does not exist or a problem
639   *                       occurs while reading the contents of the file.
640   */
641  public byte[] getFileBytes()
642         throws IOException
643  {
644    final File f = getValue();
645    if (f == null)
646    {
647      return null;
648    }
649
650    final byte[] fileData = new byte[(int) f.length()];
651    final FileInputStream inputStream = new FileInputStream(f);
652    try
653    {
654      int startPos  = 0;
655      int length    = fileData.length;
656      int bytesRead = inputStream.read(fileData, startPos, length);
657      while ((bytesRead > 0) && (startPos < fileData.length))
658      {
659        startPos += bytesRead;
660        length   -= bytesRead;
661        bytesRead = inputStream.read(fileData, startPos, length);
662      }
663
664      if (startPos < fileData.length)
665      {
666        throw new IOException(ERR_FILE_CANNOT_READ_FULLY.get(
667                                   f.getAbsolutePath(), getIdentifierString()));
668      }
669
670      return fileData;
671    }
672    finally
673    {
674      inputStream.close();
675    }
676  }
677
678
679
680  /**
681   * {@inheritDoc}
682   */
683  @Override()
684  public List<String> getValueStringRepresentations(final boolean useDefault)
685  {
686    final List<File> files;
687    if (values.isEmpty())
688    {
689      if (useDefault)
690      {
691        files = defaultValues;
692      }
693      else
694      {
695        return Collections.emptyList();
696      }
697    }
698    else
699    {
700      files = values;
701    }
702
703    if ((files == null) || files.isEmpty())
704    {
705      return Collections.emptyList();
706    }
707
708    final ArrayList<String> valueStrings = new ArrayList<String>(files.size());
709    for (final File f : files)
710    {
711      valueStrings.add(f.getAbsolutePath());
712    }
713    return Collections.unmodifiableList(valueStrings);
714  }
715
716
717
718  /**
719   * {@inheritDoc}
720   */
721  @Override()
722  protected boolean hasDefaultValue()
723  {
724    return ((defaultValues != null) && (! defaultValues.isEmpty()));
725  }
726
727
728
729  /**
730   * {@inheritDoc}
731   */
732  @Override()
733  public String getDataTypeName()
734  {
735    if (mustBeDirectory)
736    {
737      return INFO_FILE_TYPE_PATH_DIRECTORY.get();
738    }
739    else
740    {
741      return INFO_FILE_TYPE_PATH_FILE.get();
742    }
743  }
744
745
746
747  /**
748   * {@inheritDoc}
749   */
750  @Override()
751  public String getValueConstraints()
752  {
753    final StringBuilder buffer = new StringBuilder();
754
755    if (mustBeDirectory)
756    {
757      if (fileMustExist)
758      {
759        buffer.append(INFO_FILE_CONSTRAINTS_DIR_MUST_EXIST.get());
760      }
761      else if (parentMustExist)
762      {
763        buffer.append(INFO_FILE_CONSTRAINTS_DIR_PARENT_MUST_EXIST.get());
764      }
765      else
766      {
767        buffer.append(INFO_FILE_CONSTRAINTS_DIR_MAY_EXIST.get());
768      }
769    }
770    else
771    {
772      if (fileMustExist)
773      {
774        buffer.append(INFO_FILE_CONSTRAINTS_FILE_MUST_EXIST.get());
775      }
776      else if (parentMustExist)
777      {
778        buffer.append(INFO_FILE_CONSTRAINTS_FILE_PARENT_MUST_EXIST.get());
779      }
780      else
781      {
782        buffer.append(INFO_FILE_CONSTRAINTS_FILE_MAY_EXIST.get());
783      }
784    }
785
786    if (relativeBaseDirectory != null)
787    {
788      buffer.append("  ");
789      buffer.append(INFO_FILE_CONSTRAINTS_RELATIVE_PATH_SPECIFIED_ROOT.get(
790           relativeBaseDirectory.getAbsolutePath()));
791    }
792
793    return buffer.toString();
794  }
795
796
797
798  /**
799   * {@inheritDoc}
800   */
801  @Override()
802  protected void reset()
803  {
804    super.reset();
805    values.clear();
806  }
807
808
809
810  /**
811   * {@inheritDoc}
812   */
813  @Override()
814  public FileArgument getCleanCopy()
815  {
816    return new FileArgument(this);
817  }
818
819
820
821  /**
822   * {@inheritDoc}
823   */
824  @Override()
825  protected void addToCommandLine(final List<String> argStrings)
826  {
827    if (values != null)
828    {
829      for (final File f : values)
830      {
831        argStrings.add(getIdentifierString());
832        if (isSensitive())
833        {
834          argStrings.add("***REDACTED***");
835        }
836        else
837        {
838          argStrings.add(f.getAbsolutePath());
839        }
840      }
841    }
842  }
843
844
845
846  /**
847   * {@inheritDoc}
848   */
849  @Override()
850  public void toString(final StringBuilder buffer)
851  {
852    buffer.append("FileArgument(");
853    appendBasicToStringInfo(buffer);
854
855    buffer.append(", fileMustExist=");
856    buffer.append(fileMustExist);
857    buffer.append(", parentMustExist=");
858    buffer.append(parentMustExist);
859    buffer.append(", mustBeFile=");
860    buffer.append(mustBeFile);
861    buffer.append(", mustBeDirectory=");
862    buffer.append(mustBeDirectory);
863
864    if (relativeBaseDirectory != null)
865    {
866      buffer.append(", relativeBaseDirectory='");
867      buffer.append(relativeBaseDirectory.getAbsolutePath());
868      buffer.append('\'');
869    }
870
871    if ((defaultValues != null) && (! defaultValues.isEmpty()))
872    {
873      if (defaultValues.size() == 1)
874      {
875        buffer.append(", defaultValue='");
876        buffer.append(defaultValues.get(0).toString());
877      }
878      else
879      {
880        buffer.append(", defaultValues={");
881
882        final Iterator<File> iterator = defaultValues.iterator();
883        while (iterator.hasNext())
884        {
885          buffer.append('\'');
886          buffer.append(iterator.next().toString());
887          buffer.append('\'');
888
889          if (iterator.hasNext())
890          {
891            buffer.append(", ");
892          }
893        }
894
895        buffer.append('}');
896      }
897    }
898
899    buffer.append(')');
900  }
901}