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