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}