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 */
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 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 }