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