001/* 002 * Copyright 2008-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 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 list of default values for this argument. 081 private final List<File> defaultValues; 082 083 084 085 /** 086 * Creates a new file argument with the provided information. There will not 087 * be any default values or constraints on the kinds of values it can have. 088 * 089 * @param shortIdentifier The short identifier for this argument. It may 090 * not be {@code null} if the long identifier is 091 * {@code null}. 092 * @param longIdentifier The long identifier for this argument. It may 093 * not be {@code null} if the short identifier is 094 * {@code null}. 095 * @param isRequired Indicates whether this argument is required to 096 * be provided. 097 * @param maxOccurrences The maximum number of times this argument may be 098 * provided on the command line. A value less than 099 * or equal to zero indicates that it may be present 100 * any number of times. 101 * @param valuePlaceholder A placeholder to display in usage information to 102 * indicate that a value must be provided. It must 103 * not be {@code null}. 104 * @param description A human-readable description for this argument. 105 * It must not be {@code null}. 106 * 107 * @throws ArgumentException If there is a problem with the definition of 108 * this argument. 109 */ 110 public FileArgument(final Character shortIdentifier, 111 final String longIdentifier, final boolean isRequired, 112 final int maxOccurrences, final String valuePlaceholder, 113 final String description) 114 throws ArgumentException 115 { 116 this(shortIdentifier, longIdentifier, isRequired, maxOccurrences, 117 valuePlaceholder, description, false, false, false, false, null); 118 } 119 120 121 122 /** 123 * Creates a new file argument with the provided information. It will not 124 * have any default values. 125 * 126 * @param shortIdentifier The short identifier for this argument. It may 127 * not be {@code null} if the long identifier is 128 * {@code null}. 129 * @param longIdentifier The long identifier for this argument. It may 130 * not be {@code null} if the short identifier is 131 * {@code null}. 132 * @param isRequired Indicates whether this argument is required to 133 * be provided. 134 * @param maxOccurrences The maximum number of times this argument may be 135 * provided on the command line. A value less than 136 * or equal to zero indicates that it may be present 137 * any number of times. 138 * @param valuePlaceholder A placeholder to display in usage information to 139 * indicate that a value must be provided. It must 140 * not be {@code null}. 141 * @param description A human-readable description for this argument. 142 * It must not be {@code null}. 143 * @param fileMustExist Indicates whether each value must refer to a file 144 * that exists. 145 * @param parentMustExist Indicates whether each value must refer to a file 146 * whose parent directory exists. 147 * @param mustBeFile Indicates whether each value must refer to a 148 * regular file, if it exists. 149 * @param mustBeDirectory Indicates whether each value must refer to a 150 * directory, if it exists. 151 * 152 * @throws ArgumentException If there is a problem with the definition of 153 * this argument. 154 */ 155 public FileArgument(final Character shortIdentifier, 156 final String longIdentifier, final boolean isRequired, 157 final int maxOccurrences, final String valuePlaceholder, 158 final String description, final boolean fileMustExist, 159 final boolean parentMustExist, final boolean mustBeFile, 160 final boolean mustBeDirectory) 161 throws ArgumentException 162 { 163 this(shortIdentifier, longIdentifier, isRequired, maxOccurrences, 164 valuePlaceholder, description, fileMustExist, parentMustExist, 165 mustBeFile, mustBeDirectory, null); 166 } 167 168 169 170 /** 171 * Creates a new file argument with the provided information. 172 * 173 * @param shortIdentifier The short identifier for this argument. It may 174 * not be {@code null} if the long identifier is 175 * {@code null}. 176 * @param longIdentifier The long identifier for this argument. It may 177 * not be {@code null} if the short identifier is 178 * {@code null}. 179 * @param isRequired Indicates whether this argument is required to 180 * be provided. 181 * @param maxOccurrences The maximum number of times this argument may be 182 * provided on the command line. A value less than 183 * or equal to zero indicates that it may be present 184 * any number of times. 185 * @param valuePlaceholder A placeholder to display in usage information to 186 * indicate that a value must be provided. It must 187 * not be {@code null}. 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 * @param defaultValues The set of default values to use for this 199 * argument if no values were provided. 200 * 201 * @throws ArgumentException If there is a problem with the definition of 202 * this argument. 203 */ 204 public FileArgument(final Character shortIdentifier, 205 final String longIdentifier, final boolean isRequired, 206 final int maxOccurrences, final String valuePlaceholder, 207 final String description, final boolean fileMustExist, 208 final boolean parentMustExist, final boolean mustBeFile, 209 final boolean mustBeDirectory, 210 final List<File> defaultValues) 211 throws ArgumentException 212 { 213 super(shortIdentifier, longIdentifier, isRequired, maxOccurrences, 214 valuePlaceholder, description); 215 216 if (valuePlaceholder == null) 217 { 218 throw new ArgumentException(ERR_ARG_MUST_TAKE_VALUE.get( 219 getIdentifierString())); 220 } 221 222 if (mustBeFile && mustBeDirectory) 223 { 224 throw new ArgumentException(ERR_FILE_CANNOT_BE_FILE_AND_DIRECTORY.get( 225 getIdentifierString())); 226 } 227 228 this.fileMustExist = fileMustExist; 229 this.parentMustExist = parentMustExist; 230 this.mustBeFile = mustBeFile; 231 this.mustBeDirectory = mustBeDirectory; 232 233 if ((defaultValues == null) || defaultValues.isEmpty()) 234 { 235 this.defaultValues = null; 236 } 237 else 238 { 239 this.defaultValues = Collections.unmodifiableList(defaultValues); 240 } 241 242 values = new ArrayList<File>(); 243 relativeBaseDirectory = null; 244 } 245 246 247 248 /** 249 * Creates a new file argument that is a "clean" copy of the provided source 250 * argument. 251 * 252 * @param source The source argument to use for this argument. 253 */ 254 private FileArgument(final FileArgument source) 255 { 256 super(source); 257 258 fileMustExist = source.fileMustExist; 259 mustBeDirectory = source.mustBeDirectory; 260 mustBeFile = source.mustBeFile; 261 parentMustExist = source.parentMustExist; 262 defaultValues = source.defaultValues; 263 relativeBaseDirectory = source.relativeBaseDirectory; 264 values = new ArrayList<File>(); 265 } 266 267 268 269 /** 270 * Indicates whether each value must refer to a file that exists. 271 * 272 * @return {@code true} if the target files must exist, or {@code false} if 273 * it is acceptable for values to refer to files that do not exist. 274 */ 275 public boolean fileMustExist() 276 { 277 return fileMustExist; 278 } 279 280 281 282 /** 283 * Indicates whether each value must refer to a file whose parent directory 284 * exists. 285 * 286 * @return {@code true} if the parent directory for target files must exist, 287 * or {@code false} if it is acceptable for values to refer to files 288 * whose parent directories do not exist. 289 */ 290 public boolean parentMustExist() 291 { 292 return parentMustExist; 293 } 294 295 296 297 /** 298 * Indicates whether each value must refer to a regular file (if it exists). 299 * 300 * @return {@code true} if each value must refer to a regular file (if it 301 * exists), or {@code false} if it may refer to a directory. 302 */ 303 public boolean mustBeFile() 304 { 305 return mustBeFile; 306 } 307 308 309 310 /** 311 * Indicates whether each value must refer to a directory (if it exists). 312 * 313 * @return {@code true} if each value must refer to a directory (if it 314 * exists), or {@code false} if it may refer to a regular file. 315 */ 316 public boolean mustBeDirectory() 317 { 318 return mustBeDirectory; 319 } 320 321 322 323 /** 324 * Retrieves the list of default values for this argument, which will be used 325 * if no values were provided. 326 * 327 * @return The list of default values for this argument, or {@code null} if 328 * there are no default values. 329 */ 330 public List<File> getDefaultValues() 331 { 332 return defaultValues; 333 } 334 335 336 337 /** 338 * Retrieves the directory that will serve as the base directory for relative 339 * paths, if one has been defined. 340 * 341 * @return The directory that will serve as the base directory for relative 342 * paths, or {@code null} if relative paths will be relative to the 343 * current working directory. 344 */ 345 public File getRelativeBaseDirectory() 346 { 347 return relativeBaseDirectory; 348 } 349 350 351 352 /** 353 * Specifies the directory that will serve as the base directory for relative 354 * paths. 355 * 356 * @param relativeBaseDirectory The directory that will serve as the base 357 * directory for relative paths. It may be 358 * {@code null} if relative paths should be 359 * relative to the current working directory. 360 */ 361 public void setRelativeBaseDirectory(final File relativeBaseDirectory) 362 { 363 this.relativeBaseDirectory = relativeBaseDirectory; 364 } 365 366 367 368 /** 369 * {@inheritDoc} 370 */ 371 @Override() 372 protected void addValue(final String valueString) 373 throws ArgumentException 374 { 375 // NOTE: java.io.File has an extremely weird behavior. When a File object 376 // is created from a relative path and that path contains only the filename, 377 // then calling getParent or getParentFile will return null even though it 378 // obviously has a parent. Therefore, you must always create a File using 379 // the absolute path if you might want to get the parent. Also, if the path 380 // is relative, then we might want to control the base to which it is 381 // relative. 382 File f = new File(valueString); 383 if (! f.isAbsolute()) 384 { 385 if (relativeBaseDirectory == null) 386 { 387 f = new File(f.getAbsolutePath()); 388 } 389 else 390 { 391 f = new File(new File(relativeBaseDirectory, 392 valueString).getAbsolutePath()); 393 } 394 } 395 396 if (f.exists()) 397 { 398 if (mustBeFile && (! f.isFile())) 399 { 400 throw new ArgumentException(ERR_FILE_VALUE_NOT_FILE.get( 401 getIdentifierString(), 402 f.getAbsolutePath())); 403 } 404 else if (mustBeDirectory && (! f.isDirectory())) 405 { 406 throw new ArgumentException(ERR_FILE_VALUE_NOT_DIRECTORY.get( 407 getIdentifierString(), 408 f.getAbsolutePath())); 409 } 410 } 411 else 412 { 413 if (fileMustExist) 414 { 415 throw new ArgumentException(ERR_FILE_DOESNT_EXIST.get( 416 f.getAbsolutePath(), 417 getIdentifierString())); 418 } 419 else if (parentMustExist) 420 { 421 final File parentFile = f.getParentFile(); 422 if ((parentFile == null) || 423 (! parentFile.exists()) || 424 (! parentFile.isDirectory())) 425 { 426 throw new ArgumentException(ERR_FILE_PARENT_DOESNT_EXIST.get( 427 f.getAbsolutePath(), 428 getIdentifierString())); 429 } 430 } 431 } 432 433 if (values.size() >= getMaxOccurrences()) 434 { 435 throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get( 436 getIdentifierString())); 437 } 438 439 values.add(f); 440 } 441 442 443 444 /** 445 * Retrieves the value for this argument, or the default value if none was 446 * provided. If there are multiple values, then the first will be returned. 447 * 448 * @return The value for this argument, or the default value if none was 449 * provided, or {@code null} if there is no value and no default 450 * value. 451 */ 452 public File getValue() 453 { 454 if (values.isEmpty()) 455 { 456 if ((defaultValues == null) || defaultValues.isEmpty()) 457 { 458 return null; 459 } 460 else 461 { 462 return defaultValues.get(0); 463 } 464 } 465 else 466 { 467 return values.get(0); 468 } 469 } 470 471 472 473 /** 474 * Retrieves the set of values for this argument. 475 * 476 * @return The set of values for this argument. 477 */ 478 public List<File> getValues() 479 { 480 if (values.isEmpty() && (defaultValues != null)) 481 { 482 return defaultValues; 483 } 484 485 return Collections.unmodifiableList(values); 486 } 487 488 489 490 /** 491 * Reads the contents of the file specified as the value to this argument and 492 * retrieves a list of the lines contained in it. If there are multiple 493 * values for this argument, then the file specified as the first value will 494 * be used. 495 * 496 * @return A list containing the lines of the target file, or {@code null} if 497 * no values were provided. 498 * 499 * @throws IOException If the specified file does not exist or a problem 500 * occurs while reading the contents of the file. 501 */ 502 public List<String> getFileLines() 503 throws IOException 504 { 505 final File f = getValue(); 506 if (f == null) 507 { 508 return null; 509 } 510 511 final ArrayList<String> lines = new ArrayList<String>(); 512 final BufferedReader reader = new BufferedReader(new FileReader(f)); 513 try 514 { 515 String line = reader.readLine(); 516 while (line != null) 517 { 518 lines.add(line); 519 line = reader.readLine(); 520 } 521 } 522 finally 523 { 524 reader.close(); 525 } 526 527 return lines; 528 } 529 530 531 532 /** 533 * Reads the contents of the file specified as the value to this argument and 534 * retrieves a list of the non-blank lines contained in it. If there are 535 * multiple values for this argument, then the file specified as the first 536 * value will be used. 537 * 538 * @return A list containing the non-blank lines of the target file, or 539 * {@code null} if no values were provided. 540 * 541 * @throws IOException If the specified file does not exist or a problem 542 * occurs while reading the contents of the file. 543 */ 544 public List<String> getNonBlankFileLines() 545 throws IOException 546 { 547 final File f = getValue(); 548 if (f == null) 549 { 550 return null; 551 } 552 553 final ArrayList<String> lines = new ArrayList<String>(); 554 final BufferedReader reader = new BufferedReader(new FileReader(f)); 555 try 556 { 557 String line = reader.readLine(); 558 while (line != null) 559 { 560 if (line.length() > 0) 561 { 562 lines.add(line); 563 } 564 line = reader.readLine(); 565 } 566 } 567 finally 568 { 569 reader.close(); 570 } 571 572 return lines; 573 } 574 575 576 577 /** 578 * Reads the contents of the file specified as the value to this argument. If 579 * there are multiple values for this argument, then the file specified as the 580 * first value will be used. 581 * 582 * @return A byte array containing the contents of the target file, or 583 * {@code null} if no values were provided. 584 * 585 * @throws IOException If the specified file does not exist or a problem 586 * occurs while reading the contents of the file. 587 */ 588 public byte[] getFileBytes() 589 throws IOException 590 { 591 final File f = getValue(); 592 if (f == null) 593 { 594 return null; 595 } 596 597 final byte[] fileData = new byte[(int) f.length()]; 598 final FileInputStream inputStream = new FileInputStream(f); 599 try 600 { 601 int startPos = 0; 602 int length = fileData.length; 603 int bytesRead = inputStream.read(fileData, startPos, length); 604 while ((bytesRead > 0) && (startPos < fileData.length)) 605 { 606 startPos += bytesRead; 607 length -= bytesRead; 608 bytesRead = inputStream.read(fileData, startPos, length); 609 } 610 611 if (startPos < fileData.length) 612 { 613 throw new IOException(ERR_FILE_CANNOT_READ_FULLY.get( 614 f.getAbsolutePath(), getIdentifierString())); 615 } 616 617 return fileData; 618 } 619 finally 620 { 621 inputStream.close(); 622 } 623 } 624 625 626 627 /** 628 * {@inheritDoc} 629 */ 630 @Override() 631 protected boolean hasDefaultValue() 632 { 633 return ((defaultValues != null) && (! defaultValues.isEmpty())); 634 } 635 636 637 638 /** 639 * {@inheritDoc} 640 */ 641 @Override() 642 public String getDataTypeName() 643 { 644 if (mustBeDirectory) 645 { 646 return INFO_FILE_TYPE_PATH_DIRECTORY.get(); 647 } 648 else 649 { 650 return INFO_FILE_TYPE_PATH_FILE.get(); 651 } 652 } 653 654 655 656 /** 657 * {@inheritDoc} 658 */ 659 @Override() 660 public String getValueConstraints() 661 { 662 final StringBuilder buffer = new StringBuilder(); 663 664 if (mustBeDirectory) 665 { 666 if (fileMustExist) 667 { 668 buffer.append(INFO_FILE_CONSTRAINTS_DIR_MUST_EXIST.get()); 669 } 670 else if (parentMustExist) 671 { 672 buffer.append(INFO_FILE_CONSTRAINTS_DIR_PARENT_MUST_EXIST.get()); 673 } 674 else 675 { 676 buffer.append(INFO_FILE_CONSTRAINTS_DIR_MAY_EXIST.get()); 677 } 678 } 679 else 680 { 681 if (fileMustExist) 682 { 683 buffer.append(INFO_FILE_CONSTRAINTS_FILE_MUST_EXIST.get()); 684 } 685 else if (parentMustExist) 686 { 687 buffer.append(INFO_FILE_CONSTRAINTS_FILE_PARENT_MUST_EXIST.get()); 688 } 689 else 690 { 691 buffer.append(INFO_FILE_CONSTRAINTS_FILE_MAY_EXIST.get()); 692 } 693 } 694 695 if (relativeBaseDirectory != null) 696 { 697 buffer.append(" "); 698 buffer.append(INFO_FILE_CONSTRAINTS_RELATIVE_PATH_SPECIFIED_ROOT.get( 699 relativeBaseDirectory.getAbsolutePath())); 700 } 701 702 return buffer.toString(); 703 } 704 705 706 707 /** 708 * {@inheritDoc} 709 */ 710 @Override() 711 public FileArgument getCleanCopy() 712 { 713 return new FileArgument(this); 714 } 715 716 717 718 /** 719 * {@inheritDoc} 720 */ 721 @Override() 722 public void toString(final StringBuilder buffer) 723 { 724 buffer.append("FileArgument("); 725 appendBasicToStringInfo(buffer); 726 727 buffer.append(", fileMustExist="); 728 buffer.append(fileMustExist); 729 buffer.append(", parentMustExist="); 730 buffer.append(parentMustExist); 731 buffer.append(", mustBeFile="); 732 buffer.append(mustBeFile); 733 buffer.append(", mustBeDirectory="); 734 buffer.append(mustBeDirectory); 735 736 if (relativeBaseDirectory != null) 737 { 738 buffer.append(", relativeBaseDirectory='"); 739 buffer.append(relativeBaseDirectory.getAbsolutePath()); 740 buffer.append('\''); 741 } 742 743 if ((defaultValues != null) && (! defaultValues.isEmpty())) 744 { 745 if (defaultValues.size() == 1) 746 { 747 buffer.append(", defaultValue='"); 748 buffer.append(defaultValues.get(0).toString()); 749 } 750 else 751 { 752 buffer.append(", defaultValues={"); 753 754 final Iterator<File> iterator = defaultValues.iterator(); 755 while (iterator.hasNext()) 756 { 757 buffer.append('\''); 758 buffer.append(iterator.next().toString()); 759 buffer.append('\''); 760 761 if (iterator.hasNext()) 762 { 763 buffer.append(", "); 764 } 765 } 766 767 buffer.append('}'); 768 } 769 } 770 771 buffer.append(')'); 772 } 773}