1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18 package org.apache.tools.ant.util;
19
20 import java.io.File;
21 import java.io.FilenameFilter;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.OutputStream;
26 import java.io.Reader;
27 import java.io.UnsupportedEncodingException;
28 import java.io.Writer;
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.text.DecimalFormat;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Random;
37 import java.util.Stack;
38 import java.util.StringTokenizer;
39 import java.util.Vector;
40
41 import org.apache.tools.ant.BuildException;
42 import org.apache.tools.ant.PathTokenizer;
43 import org.apache.tools.ant.Project;
44 import org.apache.tools.ant.launch.Locator;
45 import org.apache.tools.ant.taskdefs.condition.Os;
46 import org.apache.tools.ant.types.FilterSetCollection;
47 import org.apache.tools.ant.types.resources.FileResource;
48
49 /**
50 * This class also encapsulates methods which allow Files to be
51 * referred to using abstract path names which are translated to native
52 * system file paths at runtime as well as copying files or setting
53 * their last modification time.
54 *
55 */
56 public class FileUtils {
57 private static final int EXPAND_SPACE = 50;
58 private static final FileUtils PRIMARY_INSTANCE = new FileUtils();
59
60 //get some non-crypto-grade randomness from various places.
61 private static Random rand = new Random(System.currentTimeMillis()
62 + Runtime.getRuntime().freeMemory());
63
64 private static final boolean ON_NETWARE = Os.isFamily("netware");
65 private static final boolean ON_DOS = Os.isFamily("dos");
66 private static final boolean ON_WIN9X = Os.isFamily("win9x");
67 private static final boolean ON_WINDOWS = Os.isFamily("windows");
68
69 static final int BUF_SIZE = 8192;
70
71
72 /**
73 * The granularity of timestamps under FAT.
74 */
75 public static final long FAT_FILE_TIMESTAMP_GRANULARITY = 2000;
76
77 /**
78 * The granularity of timestamps under Unix.
79 */
80 public static final long UNIX_FILE_TIMESTAMP_GRANULARITY = 1000;
81
82 /**
83 * The granularity of timestamps under the NT File System.
84 * NTFS has a granularity of 100 nanoseconds, which is less
85 * than 1 millisecond, so we round this up to 1 millisecond.
86 */
87 public static final long NTFS_FILE_TIMESTAMP_GRANULARITY = 1;
88
89 /**
90 * A one item cache for fromUri.
91 * fromUri is called for each element when parseing ant build
92 * files. It is a costly operation. This just caches the result
93 * of the last call.
94 */
95 private Object cacheFromUriLock = new Object();
96 private String cacheFromUriRequest = null;
97 private String cacheFromUriResponse = null;
98
99 /**
100 * Factory method.
101 *
102 * @return a new instance of FileUtils.
103 * @deprecated since 1.7.
104 * Use getFileUtils instead,
105 * FileUtils do not have state.
106 */
107 public static FileUtils newFileUtils() {
108 return new FileUtils();
109 }
110
111 /**
112 * Method to retrieve The FileUtils, which is shared by all users of this
113 * method.
114 * @return an instance of FileUtils.
115 * @since Ant 1.6.3
116 */
117 public static FileUtils getFileUtils() {
118 return PRIMARY_INSTANCE;
119 }
120
121 /**
122 * Empty constructor.
123 */
124 protected FileUtils() {
125 }
126
127 /**
128 * Get the URL for a file taking into account # characters.
129 *
130 * @param file the file whose URL representation is required.
131 * @return The FileURL value.
132 * @throws MalformedURLException if the URL representation cannot be
133 * formed.
134 */
135 public URL getFileURL(File file) throws MalformedURLException {
136 return new URL(toURI(file.getAbsolutePath()));
137 }
138
139 /**
140 * Convenience method to copy a file from a source to a destination.
141 * No filtering is performed.
142 *
143 * @param sourceFile Name of file to copy from.
144 * Must not be <code>null</code>.
145 * @param destFile Name of file to copy to.
146 * Must not be <code>null</code>.
147 *
148 * @throws IOException if the copying fails.
149 */
150 public void copyFile(String sourceFile, String destFile) throws IOException {
151 copyFile(new File(sourceFile), new File(destFile), null, false, false);
152 }
153
154 /**
155 * Convenience method to copy a file from a source to a destination
156 * specifying if token filtering must be used.
157 *
158 * @param sourceFile Name of file to copy from.
159 * Must not be <code>null</code>.
160 * @param destFile Name of file to copy to.
161 * Must not be <code>null</code>.
162 * @param filters the collection of filters to apply to this copy.
163 *
164 * @throws IOException if the copying fails.
165 */
166 public void copyFile(String sourceFile, String destFile, FilterSetCollection filters)
167 throws IOException {
168 copyFile(new File(sourceFile), new File(destFile), filters, false, false);
169 }
170
171 /**
172 * Convenience method to copy a file from a source to a destination specifying if token
173 * filtering must be used and if source files may overwrite newer destination files.
174 *
175 * @param sourceFile Name of file to copy from. Must not be <code>null</code>.
176 * @param destFile Name of file to copy to. Must not be <code>null</code>.
177 * @param filters the collection of filters to apply to this copy.
178 * @param overwrite Whether or not the destination file should be overwritten if it already
179 * exists.
180 *
181 * @throws IOException if the copying fails.
182 */
183 public void copyFile(String sourceFile, String destFile, FilterSetCollection filters,
184 boolean overwrite) throws IOException {
185 copyFile(new File(sourceFile), new File(destFile), filters, overwrite, false);
186 }
187
188 /**
189 * Convenience method to copy a file from a source to a destination
190 * specifying if token
191 * filtering must be used, if source files may overwrite newer destination
192 * files and the last
193 * modified time of <code>destFile</code> file should be made equal to
194 * the last modified time
195 * of <code>sourceFile</code>.
196 *
197 * @param sourceFile Name of file to copy from. Must not be <code>null</code>.
198 * @param destFile Name of file to copy to. Must not be <code>null</code>.
199 * @param filters the collection of filters to apply to this copy.
200 * @param overwrite Whether or not the destination file should be
201 * overwritten if it already exists.
202 * @param preserveLastModified Whether or not the last modified time of
203 * the resulting file
204 * should be set to that of the source file.
205 *
206 * @throws IOException if the copying fails.
207 */
208 public void copyFile(String sourceFile, String destFile,
209 FilterSetCollection filters,
210 boolean overwrite, boolean preserveLastModified)
211 throws IOException {
212 copyFile(new File(sourceFile), new File(destFile), filters, overwrite,
213 preserveLastModified);
214 }
215
216 /**
217 * Convenience method to copy a file from a source to a destination specifying if token
218 * filtering must be used, if source files may overwrite newer destination files and the last
219 * modified time of <code>destFile</code> file should be made equal to the last modified time
220 * of <code>sourceFile</code>.
221 *
222 * @param sourceFile Name of file to copy from. Must not be <code>null</code>.
223 * @param destFile Name of file to copy to. Must not be <code>null</code>.
224 * @param filters the collection of filters to apply to this copy.
225 * @param overwrite Whether or not the destination file should be overwritten if it already
226 * exists.
227 * @param preserveLastModified Whether or not the last modified time of the resulting file
228 * should be set to that of the source file.
229 * @param encoding the encoding used to read and write the files.
230 *
231 * @throws IOException if the copying fails.
232 *
233 * @since Ant 1.5
234 */
235 public void copyFile(String sourceFile, String destFile,
236 FilterSetCollection filters, boolean overwrite,
237 boolean preserveLastModified, String encoding) throws IOException {
238 copyFile(new File(sourceFile), new File(destFile), filters,
239 overwrite, preserveLastModified, encoding);
240 }
241
242 // CheckStyle:ParameterNumberCheck OFF - bc
243 /**
244 * Convenience method to copy a file from a source to a
245 * destination specifying if token filtering must be used, if
246 * filter chains must be used, if source files may overwrite
247 * newer destination files and the last modified time of
248 * <code>destFile</code> file should be made equal
249 * to the last modified time of <code>sourceFile</code>.
250 *
251 * @param sourceFile Name of file to copy from.
252 * Must not be <code>null</code>.
253 * @param destFile Name of file to copy to.
254 * Must not be <code>null</code>.
255 * @param filters the collection of filters to apply to this copy.
256 * @param filterChains filterChains to apply during the copy.
257 * @param overwrite Whether or not the destination file should be
258 * overwritten if it already exists.
259 * @param preserveLastModified Whether or not the last modified time of
260 * the resulting file should be set to that
261 * of the source file.
262 * @param encoding the encoding used to read and write the files.
263 * @param project the project instance.
264 *
265 * @throws IOException if the copying fails.
266 *
267 * @since Ant 1.5
268 */
269 public void copyFile(String sourceFile, String destFile,
270 FilterSetCollection filters, Vector filterChains,
271 boolean overwrite, boolean preserveLastModified,
272 String encoding, Project project) throws IOException {
273 copyFile(new File(sourceFile), new File(destFile), filters, filterChains, overwrite,
274 preserveLastModified, encoding, project);
275 }
276
277 /**
278 * Convenience method to copy a file from a source to a destination specifying if token
279 * filtering must be used, if filter chains must be used, if source files may overwrite newer
280 * destination files and the last modified time of <code>destFile</code> file should be made
281 * equal to the last modified time of <code>sourceFile</code>.
282 *
283 * @param sourceFile Name of file to copy from. Must not be <code>null</code>.
284 * @param destFile Name of file to copy to. Must not be <code>null</code>.
285 * @param filters the collection of filters to apply to this copy.
286 * @param filterChains filterChains to apply during the copy.
287 * @param overwrite Whether or not the destination file should be overwritten if it already
288 * exists.
289 * @param preserveLastModified Whether or not the last modified time of the resulting file
290 * should be set to that of the source file.
291 * @param inputEncoding the encoding used to read the files.
292 * @param outputEncoding the encoding used to write the files.
293 * @param project the project instance.
294 *
295 * @throws IOException if the copying fails.
296 *
297 * @since Ant 1.6
298 */
299 public void copyFile(String sourceFile, String destFile,
300 FilterSetCollection filters, Vector filterChains,
301 boolean overwrite, boolean preserveLastModified,
302 String inputEncoding, String outputEncoding,
303 Project project) throws IOException {
304 copyFile(new File(sourceFile), new File(destFile), filters, filterChains, overwrite,
305 preserveLastModified, inputEncoding, outputEncoding, project);
306 }
307
308 /**
309 * Convenience method to copy a file from a source to a destination. No filtering is performed.
310 *
311 * @param sourceFile the file to copy from. Must not be <code>null</code>.
312 * @param destFile the file to copy to. Must not be <code>null</code>.
313 *
314 * @throws IOException if the copying fails.
315 */
316 public void copyFile(File sourceFile, File destFile) throws IOException {
317 copyFile(sourceFile, destFile, null, false, false);
318 }
319
320 /**
321 * Convenience method to copy a file from a source to a destination
322 * specifying if token filtering must be used.
323 *
324 * @param sourceFile the file to copy from.
325 * Must not be <code>null</code>.
326 * @param destFile the file to copy to.
327 * Must not be <code>null</code>.
328 * @param filters the collection of filters to apply to this copy.
329 *
330 * @throws IOException if the copying fails.
331 */
332 public void copyFile(File sourceFile, File destFile, FilterSetCollection filters)
333 throws IOException {
334 copyFile(sourceFile, destFile, filters, false, false);
335 }
336
337 /**
338 * Convenience method to copy a file from a source to a
339 * destination specifying if token filtering must be used and if
340 * source files may overwrite newer destination files.
341 *
342 * @param sourceFile the file to copy from.
343 * Must not be <code>null</code>.
344 * @param destFile the file to copy to.
345 * Must not be <code>null</code>.
346 * @param filters the collection of filters to apply to this copy.
347 * @param overwrite Whether or not the destination file should be
348 * overwritten if it already exists.
349 *
350 * @throws IOException if the copying fails.
351 */
352 public void copyFile(File sourceFile, File destFile, FilterSetCollection filters,
353 boolean overwrite) throws IOException {
354 copyFile(sourceFile, destFile, filters, overwrite, false);
355 }
356
357 /**
358 * Convenience method to copy a file from a source to a
359 * destination specifying if token filtering must be used, if
360 * source files may overwrite newer destination files and the
361 * last modified time of <code>destFile</code> file should be made equal
362 * to the last modified time of <code>sourceFile</code>.
363 *
364 * @param sourceFile the file to copy from.
365 * Must not be <code>null</code>.
366 * @param destFile the file to copy to.
367 * Must not be <code>null</code>.
368 * @param filters the collection of filters to apply to this copy.
369 * @param overwrite Whether or not the destination file should be
370 * overwritten if it already exists.
371 * @param preserveLastModified Whether or not the last modified time of
372 * the resulting file should be set to that
373 * of the source file.
374 *
375 * @throws IOException if the copying fails.
376 */
377 public void copyFile(File sourceFile, File destFile, FilterSetCollection filters,
378 boolean overwrite, boolean preserveLastModified) throws IOException {
379 copyFile(sourceFile, destFile, filters, overwrite, preserveLastModified, null);
380 }
381
382 /**
383 * Convenience method to copy a file from a source to a destination specifying if token
384 * filtering must be used, if source files may overwrite newer destination files, the last
385 * modified time of <code>destFile</code> file should be made equal to the last modified time
386 * of <code>sourceFile</code> and which character encoding to assume.
387 *
388 * @param sourceFile the file to copy from. Must not be <code>null</code>.
389 * @param destFile the file to copy to. Must not be <code>null</code>.
390 * @param filters the collection of filters to apply to this copy.
391 * @param overwrite Whether or not the destination file should be overwritten if it already
392 * exists.
393 * @param preserveLastModified Whether or not the last modified time of the resulting file
394 * should be set to that of the source file.
395 * @param encoding the encoding used to read and write the files.
396 *
397 * @throws IOException if the copying fails.
398 *
399 * @since Ant 1.5
400 */
401 public void copyFile(File sourceFile, File destFile,
402 FilterSetCollection filters, boolean overwrite,
403 boolean preserveLastModified, String encoding) throws IOException {
404 copyFile(sourceFile, destFile, filters, null, overwrite,
405 preserveLastModified, encoding, null);
406 }
407
408 /**
409 * Convenience method to copy a file from a source to a
410 * destination specifying if token filtering must be used, if
411 * filter chains must be used, if source files may overwrite
412 * newer destination files and the last modified time of
413 * <code>destFile</code> file should be made equal
414 * to the last modified time of <code>sourceFile</code>.
415 *
416 * @param sourceFile the file to copy from.
417 * Must not be <code>null</code>.
418 * @param destFile the file to copy to.
419 * Must not be <code>null</code>.
420 * @param filters the collection of filters to apply to this copy.
421 * @param filterChains filterChains to apply during the copy.
422 * @param overwrite Whether or not the destination file should be
423 * overwritten if it already exists.
424 * @param preserveLastModified Whether or not the last modified time of
425 * the resulting file should be set to that
426 * of the source file.
427 * @param encoding the encoding used to read and write the files.
428 * @param project the project instance.
429 *
430 * @throws IOException if the copying fails.
431 *
432 * @since Ant 1.5
433 */
434 public void copyFile(File sourceFile, File destFile,
435 FilterSetCollection filters, Vector filterChains,
436 boolean overwrite, boolean preserveLastModified,
437 String encoding, Project project) throws IOException {
438 copyFile(sourceFile, destFile, filters, filterChains,
439 overwrite, preserveLastModified, encoding, encoding, project);
440 }
441
442 /**
443 * Convenience method to copy a file from a source to a
444 * destination specifying if token filtering must be used, if
445 * filter chains must be used, if source files may overwrite
446 * newer destination files and the last modified time of
447 * <code>destFile</code> file should be made equal
448 * to the last modified time of <code>sourceFile</code>.
449 *
450 * @param sourceFile the file to copy from.
451 * Must not be <code>null</code>.
452 * @param destFile the file to copy to.
453 * Must not be <code>null</code>.
454 * @param filters the collection of filters to apply to this copy.
455 * @param filterChains filterChains to apply during the copy.
456 * @param overwrite Whether or not the destination file should be
457 * overwritten if it already exists.
458 * @param preserveLastModified Whether or not the last modified time of
459 * the resulting file should be set to that
460 * of the source file.
461 * @param inputEncoding the encoding used to read the files.
462 * @param outputEncoding the encoding used to write the files.
463 * @param project the project instance.
464 *
465 *
466 * @throws IOException if the copying fails.
467 *
468 * @since Ant 1.6
469 */
470 public void copyFile(File sourceFile, File destFile,
471 FilterSetCollection filters, Vector filterChains,
472 boolean overwrite, boolean preserveLastModified,
473 String inputEncoding, String outputEncoding,
474 Project project) throws IOException {
475 ResourceUtils.copyResource(
476 new FileResource(sourceFile), new FileResource(destFile),
477 filters, filterChains, overwrite, preserveLastModified,
478 inputEncoding, outputEncoding, project);
479 }
480
481 // CheckStyle:ParameterNumberCheck ON
482
483 /**
484 * Calls File.setLastModified(long time). Originally written to
485 * to dynamically bind to that call on Java1.2+.
486 *
487 * @param file the file whose modified time is to be set
488 * @param time the time to which the last modified time is to be set.
489 * if this is -1, the current time is used.
490 */
491 public void setFileLastModified(File file, long time) {
492 ResourceUtils.setLastModified(new FileResource(file), time);
493 }
494
495 /**
496 * Interpret the filename as a file relative to the given file
497 * unless the filename already represents an absolute filename.
498 * Differs from <code>new File(file, filename)</code> in that
499 * the resulting File's path will always be a normalized,
500 * absolute pathname. Also, if it is determined that
501 * <code>filename</code> is context-relative, <code>file</code>
502 * will be discarded and the reference will be resolved using
503 * available context/state information about the filesystem.
504 *
505 * @param file the "reference" file for relative paths. This
506 * instance must be an absolute file and must not contain
507 * "./" or "../" sequences (same for \ instead
508 * of /). If it is null, this call is equivalent to
509 * <code>new java.io.File(filename).getAbsoluteFile()</code>.
510 *
511 * @param filename a file name.
512 *
513 * @return an absolute file.
514 * @throws java.lang.NullPointerException if filename is null.
515 */
516 public File resolveFile(File file, String filename) {
517 if (!isAbsolutePath(filename)) {
518 char sep = File.separatorChar;
519 filename = filename.replace('/', sep).replace('\\', sep);
520 if (isContextRelativePath(filename)) {
521 file = null;
522 // on cygwin, our current directory can be a UNC;
523 // assume user.dir is absolute or all hell breaks loose...
524 String udir = System.getProperty("user.dir");
525 if (filename.charAt(0) == sep && udir.charAt(0) == sep) {
526 filename = dissect(udir)[0] + filename.substring(1);
527 }
528 }
529 filename = new File(file, filename).getAbsolutePath();
530 }
531 return normalize(filename);
532 }
533
534 /**
535 * On DOS and NetWare, the evaluation of certain file
536 * specifications is context-dependent. These are filenames
537 * beginning with a single separator (relative to current root directory)
538 * and filenames with a drive specification and no intervening separator
539 * (relative to current directory of the specified root).
540 * @param filename the filename to evaluate.
541 * @return true if the filename is relative to system context.
542 * @throws java.lang.NullPointerException if filename is null.
543 * @since Ant 1.7
544 */
545 public static boolean isContextRelativePath(String filename) {
546 if (!(ON_DOS || ON_NETWARE) || filename.length() == 0) {
547 return false;
548 }
549 char sep = File.separatorChar;
550 filename = filename.replace('/', sep).replace('\\', sep);
551 char c = filename.charAt(0);
552 int len = filename.length();
553 return (c == sep && (len == 1 || filename.charAt(1) != sep))
554 || (Character.isLetter(c) && len > 1
555 && filename.indexOf(':') == 1
556 && (len == 2 || filename.charAt(2) != sep));
557 }
558
559 /**
560 * Verifies that the specified filename represents an absolute path.
561 * Differs from new java.io.File("filename").isAbsolute() in that a path
562 * beginning with a double file separator--signifying a Windows UNC--must
563 * at minimum match "\\a\b" to be considered an absolute path.
564 * @param filename the filename to be checked.
565 * @return true if the filename represents an absolute path.
566 * @throws java.lang.NullPointerException if filename is null.
567 * @since Ant 1.6.3
568 */
569 public static boolean isAbsolutePath(String filename) {
570 int len = filename.length();
571 if (len == 0) {
572 return false;
573 }
574 char sep = File.separatorChar;
575 filename = filename.replace('/', sep).replace('\\', sep);
576 char c = filename.charAt(0);
577 if (!(ON_DOS || ON_NETWARE)) {
578 return (c == sep);
579 }
580 if (c == sep) {
581 // CheckStyle:MagicNumber OFF
582 if (!(ON_DOS && len > 4 && filename.charAt(1) == sep)) {
583 return false;
584 }
585 // CheckStyle:MagicNumber ON
586 int nextsep = filename.indexOf(sep, 2);
587 return nextsep > 2 && nextsep + 1 < len;
588 }
589 int colon = filename.indexOf(':');
590 return (Character.isLetter(c) && colon == 1
591 && filename.length() > 2 && filename.charAt(2) == sep)
592 || (ON_NETWARE && colon > 0);
593 }
594
595 /**
596 * Translate a path into its native (platform specific) format.
597 * <p>
598 * This method uses PathTokenizer to separate the input path
599 * into its components. This handles DOS style paths in a relatively
600 * sensible way. The file separators are then converted to their platform
601 * specific versions.
602 *
603 * @param toProcess The path to be translated.
604 * May be <code>null</code>.
605 *
606 * @return the native version of the specified path or
607 * an empty string if the path is <code>null</code> or empty.
608 *
609 * @since ant 1.7
610 * @see PathTokenizer
611 */
612 public static String translatePath(String toProcess) {
613 if (toProcess == null || toProcess.length() == 0) {
614 return "";
615 }
616 StringBuffer path = new StringBuffer(toProcess.length() + EXPAND_SPACE);
617 PathTokenizer tokenizer = new PathTokenizer(toProcess);
618 while (tokenizer.hasMoreTokens()) {
619 String pathComponent = tokenizer.nextToken();
620 pathComponent = pathComponent.replace('/', File.separatorChar);
621 pathComponent = pathComponent.replace('\\', File.separatorChar);
622 if (path.length() != 0) {
623 path.append(File.pathSeparatorChar);
624 }
625 path.append(pathComponent);
626 }
627 return path.toString();
628 }
629
630 /**
631 * "Normalize" the given absolute path.
632 *
633 * <p>This includes:
634 * <ul>
635 * <li>Uppercase the drive letter if there is one.</li>
636 * <li>Remove redundant slashes after the drive spec.</li>
637 * <li>Resolve all ./, .\, ../ and ..\ sequences.</li>
638 * <li>DOS style paths that start with a drive letter will have
639 * \ as the separator.</li>
640 * </ul>
641 * Unlike {@link File#getCanonicalPath()} this method
642 * specifically does not resolve symbolic links.
643 *
644 * @param path the path to be normalized.
645 * @return the normalized version of the path.
646 *
647 * @throws java.lang.NullPointerException if path is null.
648 */
649 public File normalize(final String path) {
650 Stack s = new Stack();
651 String[] dissect = dissect(path);
652 s.push(dissect[0]);
653
654 StringTokenizer tok = new StringTokenizer(dissect[1], File.separator);
655 while (tok.hasMoreTokens()) {
656 String thisToken = tok.nextToken();
657 if (".".equals(thisToken)) {
658 continue;
659 }
660 if ("..".equals(thisToken)) {
661 if (s.size() < 2) {
662 // Cannot resolve it, so skip it.
663 return new File(path);
664 }
665 s.pop();
666 } else { // plain component
667 s.push(thisToken);
668 }
669 }
670 StringBuffer sb = new StringBuffer();
671 for (int i = 0; i < s.size(); i++) {
672 if (i > 1) {
673 // not before the filesystem root and not after it, since root
674 // already contains one
675 sb.append(File.separatorChar);
676 }
677 sb.append(s.elementAt(i));
678 }
679 return new File(sb.toString());
680 }
681
682 /**
683 * Dissect the specified absolute path.
684 * @param path the path to dissect.
685 * @return String[] {root, remaining path}.
686 * @throws java.lang.NullPointerException if path is null.
687 * @since Ant 1.7
688 */
689 public String[] dissect(String path) {
690 char sep = File.separatorChar;
691 path = path.replace('/', sep).replace('\\', sep);
692
693 // make sure we are dealing with an absolute path
694 if (!isAbsolutePath(path)) {
695 throw new BuildException(path + " is not an absolute path");
696 }
697 String root = null;
698 int colon = path.indexOf(':');
699 if (colon > 0 && (ON_DOS || ON_NETWARE)) {
700
701 int next = colon + 1;
702 root = path.substring(0, next);
703 char[] ca = path.toCharArray();
704 root += sep;
705 //remove the initial separator; the root has it.
706 next = (ca[next] == sep) ? next + 1 : next;
707
708 StringBuffer sbPath = new StringBuffer();
709 // Eliminate consecutive slashes after the drive spec:
710 for (int i = next; i < ca.length; i++) {
711 if (ca[i] != sep || ca[i - 1] != sep) {
712 sbPath.append(ca[i]);
713 }
714 }
715 path = sbPath.toString();
716 } else if (path.length() > 1 && path.charAt(1) == sep) {
717 // UNC drive
718 int nextsep = path.indexOf(sep, 2);
719 nextsep = path.indexOf(sep, nextsep + 1);
720 root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path;
721 path = path.substring(root.length());
722 } else {
723 root = File.separator;
724 path = path.substring(1);
725 }
726 return new String[] {root, path};
727 }
728
729 /**
730 * Returns a VMS String representation of a <code>File</code> object.
731 * This is useful since the JVM by default internally converts VMS paths
732 * to Unix style.
733 * The returned String is always an absolute path.
734 *
735 * @param f The <code>File</code> to get the VMS path for.
736 * @return The absolute VMS path to <code>f</code>.
737 */
738 public String toVMSPath(File f) {
739 // format: "DEVICE:[DIR.SUBDIR]FILE"
740 String osPath;
741 String path = normalize(f.getAbsolutePath()).getPath();
742 String name = f.getName();
743 boolean isAbsolute = path.charAt(0) == File.separatorChar;
744 // treat directories specified using .DIR syntax as files
745 // CheckStyle:MagicNumber OFF
746 boolean isDirectory = f.isDirectory()
747 && !name.regionMatches(true, name.length() - 4, ".DIR", 0, 4);
748 // CheckStyle:MagicNumber ON
749 String device = null;
750 StringBuffer directory = null;
751 String file = null;
752
753 int index = 0;
754
755 if (isAbsolute) {
756 index = path.indexOf(File.separatorChar, 1);
757 if (index == -1) {
758 return path.substring(1) + ":[000000]";
759 }
760 device = path.substring(1, index++);
761 }
762 if (isDirectory) {
763 directory = new StringBuffer(path.substring(index).replace(File.separatorChar, '.'));
764 } else {
765 int dirEnd = path.lastIndexOf(File.separatorChar, path.length());
766 if (dirEnd == -1 || dirEnd < index) {
767 file = path.substring(index);
768 } else {
769 directory = new StringBuffer(path.substring(index, dirEnd).
770 replace(File.separatorChar, '.'));
771 index = dirEnd + 1;
772 if (path.length() > index) {
773 file = path.substring(index);
774 }
775 }
776 }
777 if (!isAbsolute && directory != null) {
778 directory.insert(0, '.');
779 }
780 osPath = ((device != null) ? device + ":" : "")
781 + ((directory != null) ? "[" + directory + "]" : "")
782 + ((file != null) ? file : "");
783 return osPath;
784 }
785
786 /**
787 * Create a File object for a temporary file in a given directory. Without
788 * actually creating the file.
789 *
790 * <p>The file denoted by the returned abstract pathname did not
791 * exist before this method was invoked, any subsequent invocation
792 * of this method will yield a different file name.</p>
793 * <p>
794 * The filename is prefixNNNNNsuffix where NNNN is a random number.
795 * </p>
796 *
797 * @param prefix prefix before the random number.
798 * @param suffix file extension; include the '.'.
799 * @param parentDir Directory to create the temporary file in;
800 * java.io.tmpdir used if not specified.
801 *
802 * @return a File reference to the new, nonexistent temporary file.
803 * @deprecated since Ant 1.7.1
804 * @since Ant 1.7
805 */
806 public File createTempFile(String prefix, String suffix, File parentDir) {
807 return createTempFile(prefix, suffix, parentDir, false);
808 }
809
810 /**
811 * Create a temporary file in a given directory.
812 *
813 * <p>The file denoted by the returned abstract pathname did not
814 * exist before this method was invoked, any subsequent invocation
815 * of this method will yield a different file name.</p>
816 *
817 * @param prefix prefix before the random number.
818 * @param suffix file extension; include the '.'.
819 * @param parentDir Directory to create the temporary file in;
820 * java.io.tmpdir used if not specified.
821 * @param deleteOnExit whether to set the tempfile for deletion on
822 * normal VM exit.
823 * @param createFile true if the file must actually be created. If false
824 * chances exist that a file with the same name is created in the time
825 * between invoking this method and the moment the file is actually created.
826 * If possible set to true.
827 * @return a File reference to the new temporary file.
828 * @since Ant 1.7.1
829 */
830 public File createTempFile(String prefix, String suffix, File parentDir,
831 boolean deleteOnExit, boolean createFile) {
832 File result = null;
833 String parent = (parentDir == null)
834 ? System.getProperty("java.io.tmpdir")
835 : parentDir.getPath();
836
837 if (createFile) {
838 try {
839 result = File.createTempFile(prefix, suffix, new File(parent));
840 } catch (IOException e) {
841 throw new BuildException("Could not create tempfile in "
842 + parent, e);
843 }
844 } else {
845 DecimalFormat fmt = new DecimalFormat("#####");
846 synchronized (rand) {
847 do {
848 result = new File(parent, prefix
849 + fmt.format(Math.abs(rand.nextInt())) + suffix);
850 } while (result.exists());
851 }
852 }
853 if (deleteOnExit) {
854 result.deleteOnExit();
855 }
856 return result;
857 }
858
859 /**
860 * Create a File object for a temporary file in a given directory. Without
861 * actually creating the file.
862 *
863 * <p>The file denoted by the returned abstract pathname did not
864 * exist before this method was invoked, any subsequent invocation
865 * of this method will yield a different file name.</p>
866 * <p>
867 * The filename is prefixNNNNNsuffix where NNNN is a random number.
868 * </p>
869 *
870 * @param prefix prefix before the random number.
871 * @param suffix file extension; include the '.'.
872 * @param parentDir Directory to create the temporary file in;
873 * java.io.tmpdir used if not specified.
874 * @param deleteOnExit whether to set the tempfile for deletion on
875 * normal VM exit.
876 *
877 * @return a File reference to the new, nonexistent temporary file.
878 * @deprecated since Ant 1.7.1
879 * @since Ant 1.7
880 */
881 public File createTempFile(String prefix, String suffix,
882 File parentDir, boolean deleteOnExit) {
883 return createTempFile(prefix, suffix, parentDir, deleteOnExit, false);
884 }
885
886 /**
887 * Compares the contents of two files.
888 *
889 * @param f1 the file whose content is to be compared.
890 * @param f2 the other file whose content is to be compared.
891 *
892 * @return true if the content of the files is the same.
893 *
894 * @throws IOException if the files cannot be read.
895 */
896 public boolean contentEquals(File f1, File f2) throws IOException {
897 return contentEquals(f1, f2, false);
898 }
899
900 /**
901 * Compares the contents of two files.
902 *
903 * @param f1 the file whose content is to be compared.
904 * @param f2 the other file whose content is to be compared.
905 * @param textfile true if the file is to be treated as a text file and
906 * differences in kind of line break are to be ignored.
907 *
908 * @return true if the content of the files is the same.
909 *
910 * @throws IOException if the files cannot be read.
911 * @since Ant 1.6.3
912 */
913 public boolean contentEquals(File f1, File f2, boolean textfile) throws IOException {
914 return ResourceUtils.contentEquals(new FileResource(f1), new FileResource(f2), textfile);
915 }
916
917 /**
918 * This was originally an emulation of {@link File#getParentFile} for JDK 1.1, but it is now
919 * implemented using that method (Ant 1.6.3 onwards).
920 *
921 * @param f the file whose parent is required.
922 * @return the given file's parent, or null if the file does not have a parent.
923 * @since 1.10
924 * @deprecated since 1.7. Just use {@link File#getParentFile} directly.
925 */
926 public File getParentFile(File f) {
927 return (f == null) ? null : f.getParentFile();
928 }
929
930 /**
931 * Read from reader till EOF.
932 * @param rdr the reader from which to read.
933 * @return the contents read out of the given reader.
934 *
935 * @throws IOException if the contents could not be read out from the
936 * reader.
937 */
938 public static String readFully(Reader rdr) throws IOException {
939 return readFully(rdr, BUF_SIZE);
940 }
941
942 /**
943 * Read from reader till EOF.
944 *
945 * @param rdr the reader from which to read.
946 * @param bufferSize the buffer size to use when reading.
947 *
948 * @return the contents read out of the given reader.
949 *
950 * @throws IOException if the contents could not be read out from the
951 * reader.
952 */
953 public static String readFully(Reader rdr, int bufferSize)
954 throws IOException {
955 if (bufferSize <= 0) {
956 throw new IllegalArgumentException("Buffer size must be greater "
957 + "than 0");
958 }
959 final char[] buffer = new char[bufferSize];
960 int bufferLength = 0;
961 StringBuffer textBuffer = null;
962 while (bufferLength != -1) {
963 bufferLength = rdr.read(buffer);
964 if (bufferLength > 0) {
965 textBuffer = (textBuffer == null) ? new StringBuffer() : textBuffer;
966 textBuffer.append(new String(buffer, 0, bufferLength));
967 }
968 }
969 return (textBuffer == null) ? null : textBuffer.toString();
970 }
971
972 /**
973 * Safe read fully - do not return a null for an empty reader.
974 * @param reader the input to read from.
975 * @return the string.
976 * @throws IOException if unable to read from reader.
977 */
978 public static String safeReadFully(Reader reader) throws IOException {
979 String ret = readFully(reader);
980 return ret == null ? "" : ret;
981 }
982
983 /**
984 * This was originally an emulation of File.createNewFile for JDK 1.1,
985 * but it is now implemented using that method (Ant 1.6.3 onwards).
986 *
987 * <p>This method has historically <strong>not</strong> guaranteed that the
988 * operation was atomic. In its current implementation it is.
989 *
990 * @param f the file to be created.
991 * @return true if the file did not exist already.
992 * @throws IOException on error.
993 * @since Ant 1.5
994 */
995 public boolean createNewFile(File f) throws IOException {
996 return f.createNewFile();
997 }
998
999 /**
1000 * Create a new file, optionally creating parent directories.
1001 *
1002 * @param f the file to be created.
1003 * @param mkdirs <code>boolean</code> whether to create parent directories.
1004 * @return true if the file did not exist already.
1005 * @throws IOException on error.
1006 * @since Ant 1.6.3
1007 */
1008 public boolean createNewFile(File f, boolean mkdirs) throws IOException {
1009 File parent = f.getParentFile();
1010 if (mkdirs && !(parent.exists())) {
1011 parent.mkdirs();
1012 }
1013 return f.createNewFile();
1014 }
1015
1016 /**
1017 * Checks whether a given file is a symbolic link.
1018 *
1019 * <p>It doesn't really test for symbolic links but whether the
1020 * canonical and absolute paths of the file are identical--this
1021 * may lead to false positives on some platforms.</p>
1022 *
1023 * @param parent the parent directory of the file to test
1024 * @param name the name of the file to test.
1025 *
1026 * @return true if the file is a symbolic link.
1027 * @throws IOException on error.
1028 * @since Ant 1.5
1029 */
1030 public boolean isSymbolicLink(File parent, String name)
1031 throws IOException {
1032 if (parent == null) {
1033 File f = new File(name);
1034 parent = f.getParentFile();
1035 name = f.getName();
1036 }
1037 File toTest = new File(parent.getCanonicalPath(), name);
1038 return !toTest.getAbsolutePath().equals(toTest.getCanonicalPath());
1039 }
1040
1041 /**
1042 * Removes a leading path from a second path.
1043 *
1044 * @param leading The leading path, must not be null, must be absolute.
1045 * @param path The path to remove from, must not be null, must be absolute.
1046 *
1047 * @return path's normalized absolute if it doesn't start with
1048 * leading; path's path with leading's path removed otherwise.
1049 *
1050 * @since Ant 1.5
1051 */
1052 public String removeLeadingPath(File leading, File path) {
1053 String l = normalize(leading.getAbsolutePath()).getAbsolutePath();
1054 String p = normalize(path.getAbsolutePath()).getAbsolutePath();
1055 if (l.equals(p)) {
1056 return "";
1057 }
1058 // ensure that l ends with a /
1059 // so we never think /foo was a parent directory of /foobar
1060 if (!l.endsWith(File.separator)) {
1061 l += File.separator;
1062 }
1063 return (p.startsWith(l)) ? p.substring(l.length()) : p;
1064 }
1065
1066 /**
1067 * Learn whether one path "leads" another.
1068 * @param leading The leading path, must not be null, must be absolute.
1069 * @param path The path to remove from, must not be null, must be absolute.
1070 * @return true if path starts with leading; false otherwise.
1071 * @since Ant 1.7
1072 */
1073 public boolean isLeadingPath(File leading, File path) {
1074 String l = normalize(leading.getAbsolutePath()).getAbsolutePath();
1075 String p = normalize(path.getAbsolutePath()).getAbsolutePath();
1076 if (l.equals(p)) {
1077 return true;
1078 }
1079 // ensure that l ends with a /
1080 // so we never think /foo was a parent directory of /foobar
1081 if (!l.endsWith(File.separator)) {
1082 l += File.separator;
1083 }
1084 return p.startsWith(l);
1085 }
1086
1087 /**
1088 * Constructs a <code>file:</code> URI that represents the
1089 * external form of the given pathname.
1090 *
1091 * <p>Will be an absolute URI if the given path is absolute.</p>
1092 *
1093 * <p>This code encodes non ASCII characters too.</p>
1094 *
1095 * <p>The coding of the output is the same as what File.toURI().toASCIIString() produces</p>
1096 *
1097 * See <a href="http://www.w3.org/TR/xml11/#dt-sysid">dt-sysid</a>
1098 * which makes some mention of how
1099 * characters not supported by URI Reference syntax should be escaped.
1100 *
1101 * @param path the path in the local file system.
1102 * @return the URI version of the local path.
1103 * @since Ant 1.6
1104 */
1105 public String toURI(String path) {
1106 // #8031: first try Java 1.4.
1107 Class uriClazz = null;
1108 try {
1109 uriClazz = Class.forName("java.net.URI");
1110 } catch (ClassNotFoundException e) {
1111 // OK, Java 1.3.
1112 }
1113 if (uriClazz != null) {
1114 try {
1115 File f = new File(path).getAbsoluteFile();
1116 java.lang.reflect.Method toURIMethod = File.class.getMethod("toURI", new Class[0]);
1117 Object uriObj = toURIMethod.invoke(f, new Object[] {});
1118 java.lang.reflect.Method toASCIIStringMethod
1119 = uriClazz.getMethod("toASCIIString", new Class[0]);
1120 return (String) toASCIIStringMethod.invoke(uriObj, new Object[] {});
1121 } catch (Exception e) {
1122 // Reflection problems? Should not happen, debug.
1123 e.printStackTrace();
1124 }
1125 }
1126 boolean isDir = new File(path).isDirectory();
1127
1128 StringBuffer sb = new StringBuffer("file:");
1129
1130 path = resolveFile(null, path).getPath();
1131 sb.append("//");
1132 // add an extra slash for filesystems with drive-specifiers
1133 if (!path.startsWith(File.separator)) {
1134 sb.append("/");
1135 }
1136 path = path.replace('\\', '/');
1137 try {
1138 sb.append(Locator.encodeURI(path));
1139 } catch (UnsupportedEncodingException exc) {
1140 throw new BuildException(exc);
1141 }
1142 if (isDir && !path.endsWith("/")) {
1143 sb.append('/');
1144 }
1145 return sb.toString();
1146 }
1147
1148 /**
1149 * Constructs a file path from a <code>file:</code> URI.
1150 *
1151 * <p>Will be an absolute path if the given URI is absolute.</p>
1152 *
1153 * <p>Swallows '%' that are not followed by two characters,
1154 * doesn't deal with non-ASCII characters.</p>
1155 *
1156 * @param uri the URI designating a file in the local filesystem.
1157 * @return the local file system path for the file.
1158 * @since Ant 1.6
1159 */
1160 public String fromURI(String uri) {
1161 synchronized (cacheFromUriLock) {
1162 if (uri.equals(cacheFromUriRequest)) {
1163 return cacheFromUriResponse;
1164 }
1165 String path = Locator.fromURI(uri);
1166 String ret = isAbsolutePath(path) ? normalize(path).getAbsolutePath() : path;
1167 cacheFromUriRequest = uri;
1168 cacheFromUriResponse = ret;
1169 return ret;
1170 }
1171 }
1172
1173 /**
1174 * Compares two filenames.
1175 *
1176 * <p>Unlike java.io.File#equals this method will try to compare
1177 * the absolute paths and "normalize" the filenames
1178 * before comparing them.</p>
1179 *
1180 * @param f1 the file whose name is to be compared.
1181 * @param f2 the other file whose name is to be compared.
1182 *
1183 * @return true if the file are for the same file.
1184 *
1185 * @since Ant 1.5.3
1186 */
1187 public boolean fileNameEquals(File f1, File f2) {
1188 return normalize(f1.getAbsolutePath()).getAbsolutePath().equals(
1189 normalize(f2.getAbsolutePath()).getAbsolutePath());
1190 }
1191
1192 /**
1193 * Renames a file, even if that involves crossing file system boundaries.
1194 *
1195 * <p>This will remove <code>to</code> (if it exists), ensure that
1196 * <code>to</code>'s parent directory exists and move
1197 * <code>from</code>, which involves deleting <code>from</code> as
1198 * well.</p>
1199 *
1200 * @param from the file to move.
1201 * @param to the new file name.
1202 *
1203 * @throws IOException if anything bad happens during this
1204 * process. Note that <code>to</code> may have been deleted
1205 * already when this happens.
1206 *
1207 * @since Ant 1.6
1208 */
1209 public void rename(File from, File to) throws IOException {
1210 from = normalize(from.getAbsolutePath()).getCanonicalFile();
1211 to = normalize(to.getAbsolutePath());
1212 if (!from.exists()) {
1213 System.err.println("Cannot rename nonexistent file " + from);
1214 return;
1215 }
1216 if (from.equals(to)) {
1217 System.err.println("Rename of " + from + " to " + to + " is a no-op.");
1218 return;
1219 }
1220 if (to.exists() && !(from.equals(to.getCanonicalFile()) || to.delete())) {
1221 throw new IOException("Failed to delete " + to + " while trying to rename " + from);
1222 }
1223 File parent = to.getParentFile();
1224 if (parent != null && !parent.exists() && !parent.mkdirs()) {
1225 throw new IOException("Failed to create directory " + parent
1226 + " while trying to rename " + from);
1227 }
1228 if (!from.renameTo(to)) {
1229 copyFile(from, to);
1230 if (!from.delete()) {
1231 throw new IOException("Failed to delete " + from + " while trying to rename it.");
1232 }
1233 }
1234 }
1235
1236 /**
1237 * Get the granularity of file timestamps. The choice is made based on OS, which is
1238 * incorrect--it should really be by filesystem. We do not have an easy way to probe for file
1239 * systems, however, so this heuristic gives us a decent default.
1240 *
1241 * @return the difference, in milliseconds, which two file timestamps must have in order for the
1242 * two files to be considered to have different timestamps.
1243 */
1244 public long getFileTimestampGranularity() {
1245 if (ON_WIN9X) {
1246 return FAT_FILE_TIMESTAMP_GRANULARITY;
1247 }
1248 if (ON_WINDOWS) {
1249 return NTFS_FILE_TIMESTAMP_GRANULARITY;
1250 }
1251 if (ON_DOS) {
1252 return FAT_FILE_TIMESTAMP_GRANULARITY;
1253 }
1254 return UNIX_FILE_TIMESTAMP_GRANULARITY;
1255 }
1256
1257 /**
1258 * test whether a file or directory exists, with an error in the
1259 * upper/lower case spelling of the name.
1260 * Using this method is only interesting on case insensitive file systems
1261 * (Windows).<br/>
1262 * It will return true only if 3 conditions are met :
1263 * <br/>
1264 * <ul>
1265 * <li>operating system is case insensitive</li>
1266 * <li>file exists</li>
1267 * <li>actual name from directory reading is different from the
1268 * supplied argument</li>
1269 * </ul>
1270 * <br/>
1271 * the purpose is to identify files or directories on case-insensitive
1272 * filesystems whose case is not what is expected.<br/>
1273 * Possibly to rename them afterwards to the desired upper/lowercase
1274 * combination.
1275 * <br/>
1276 * @param localFile file to test
1277 * @return true if the file exists and the case of the actual file
1278 * is not the case of the parameter
1279 * @since Ant 1.7.1
1280 */
1281 public boolean hasErrorInCase(File localFile) {
1282 localFile = normalize(localFile.getAbsolutePath());
1283 if (!localFile.exists()) {
1284 return false;
1285 }
1286 final String localFileName = localFile.getName();
1287 FilenameFilter ff = new FilenameFilter () {
1288 public boolean accept(File dir, String name) {
1289 return name.equalsIgnoreCase(localFileName) && (!name.equals(localFileName));
1290 }
1291 };
1292 String[] names = localFile.getParentFile().list(ff);
1293 return names != null && names.length == 1;
1294 }
1295
1296 /**
1297 * Returns true if the source is older than the dest.
1298 * If the dest file does not exist, then the test returns false; it is
1299 * implicitly not up do date.
1300 * @param source source file (should be the older).
1301 * @param dest dest file (should be the newer).
1302 * @param granularity an offset added to the source time.
1303 * @return true if the source is older than the dest after accounting
1304 * for granularity.
1305 * @since Ant 1.6.3
1306 */
1307 public boolean isUpToDate(File source, File dest, long granularity) {
1308 //do a check for the destination file existing
1309 if (!dest.exists()) {
1310 //if it does not, then the file is not up to date.
1311 return false;
1312 }
1313 long sourceTime = source.lastModified();
1314 long destTime = dest.lastModified();
1315 return isUpToDate(sourceTime, destTime, granularity);
1316 }
1317
1318 /**
1319 * Returns true if the source is older than the dest.
1320 * @param source source file (should be the older).
1321 * @param dest dest file (should be the newer).
1322 * @return true if the source is older than the dest, taking the granularity into account.
1323 * @since Ant 1.6.3
1324 */
1325 public boolean isUpToDate(File source, File dest) {
1326 return isUpToDate(source, dest, getFileTimestampGranularity());
1327 }
1328
1329 /**
1330 * Compare two timestamps for being up to date using
1331 * the specified granularity.
1332 *
1333 * @param sourceTime timestamp of source file.
1334 * @param destTime timestamp of dest file.
1335 * @param granularity os/filesys granularity.
1336 * @return true if the dest file is considered up to date.
1337 */
1338 public boolean isUpToDate(long sourceTime, long destTime, long granularity) {
1339 return destTime != -1 && destTime >= sourceTime + granularity;
1340 }
1341
1342 /**
1343 * Compare two timestamps for being up to date using the
1344 * current granularity.
1345 *
1346 * @param sourceTime timestamp of source file.
1347 * @param destTime timestamp of dest file.
1348 * @return true if the dest file is considered up to date.
1349 */
1350 public boolean isUpToDate(long sourceTime, long destTime) {
1351 return isUpToDate(sourceTime, destTime, getFileTimestampGranularity());
1352 }
1353
1354 /**
1355 * Close a Writer without throwing any exception if something went wrong.
1356 * Do not attempt to close it if the argument is null.
1357 * @param device output writer, can be null.
1358 */
1359 public static void close(Writer device) {
1360 if (null != device) {
1361 try {
1362 device.close();
1363 } catch (IOException e) {
1364 //ignore
1365 }
1366 }
1367 }
1368
1369 /**
1370 * Close a Reader without throwing any exception if something went wrong.
1371 * Do not attempt to close it if the argument is null.
1372 *
1373 * @param device Reader, can be null.
1374 */
1375 public static void close(Reader device) {
1376 if (null != device) {
1377 try {
1378 device.close();
1379 } catch (IOException e) {
1380 //ignore
1381 }
1382 }
1383 }
1384
1385 /**
1386 * Close a stream without throwing any exception if something went wrong.
1387 * Do not attempt to close it if the argument is null.
1388 *
1389 * @param device stream, can be null.
1390 */
1391 public static void close(OutputStream device) {
1392 if (null != device) {
1393 try {
1394 device.close();
1395 } catch (IOException e) {
1396 //ignore
1397 }
1398 }
1399 }
1400
1401 /**
1402 * Close a stream without throwing any exception if something went wrong.
1403 * Do not attempt to close it if the argument is null.
1404 *
1405 * @param device stream, can be null.
1406 */
1407 public static void close(InputStream device) {
1408 if (null != device) {
1409 try {
1410 device.close();
1411 } catch (IOException e) {
1412 //ignore
1413 }
1414 }
1415 }
1416
1417 /**
1418 * Delete the file with {@link File#delete()} if the argument is not null.
1419 * Do nothing on a null argument.
1420 * @param file file to delete.
1421 */
1422 public static void delete(File file) {
1423 if (file != null) {
1424 file.delete();
1425 }
1426 }
1427
1428 /**
1429 * Calculates the relative path between two files.
1430 * <p>
1431 * Implementation note:<br/> This function may throw an IOException if an I/O error occurs
1432 * because its use of the canonical pathname may require filesystem queries.
1433 * </p>
1434 *
1435 * @param fromFile the <code>File</code> to calculate the path from
1436 * @param toFile the <code>File</code> to calculate the path to
1437 * @return the relative path between the files
1438 * @throws Exception for undocumented reasons
1439 * @see File#getCanonicalPath()
1440 *
1441 * @since Ant 1.7
1442 */
1443 public static String getRelativePath(File fromFile, File toFile) throws Exception {
1444 String fromPath = fromFile.getCanonicalPath();
1445 String toPath = toFile.getCanonicalPath();
1446
1447 // build the path stack info to compare
1448 String[] fromPathStack = getPathStack(fromPath);
1449 String[] toPathStack = getPathStack(toPath);
1450
1451 if (0 < toPathStack.length && 0 < fromPathStack.length) {
1452 if (!fromPathStack[0].equals(toPathStack[0])) {
1453 // not the same device (would be "" on Linux/Unix)
1454
1455 return getPath(Arrays.asList(toPathStack));
1456 }
1457 } else {
1458 // no comparison possible
1459 return getPath(Arrays.asList(toPathStack));
1460 }
1461
1462 int minLength = Math.min(fromPathStack.length, toPathStack.length);
1463 int same = 1; // Used outside the for loop
1464
1465 // get index of parts which are equal
1466 for (;
1467 same < minLength && fromPathStack[same].equals(toPathStack[same]);
1468 same++) {
1469 // Do nothing
1470 }
1471
1472 List relativePathStack = new ArrayList();
1473
1474 // if "from" part is longer, fill it up with ".."
1475 // to reach path which is equal to both paths
1476 for (int i = same; i < fromPathStack.length; i++) {
1477 relativePathStack.add("..");
1478 }
1479
1480 // fill it up path with parts which were not equal
1481 for (int i = same; i < toPathStack.length; i++) {
1482 relativePathStack.add(toPathStack[i]);
1483 }
1484
1485 return getPath(relativePathStack);
1486 }
1487
1488 /**
1489 * Gets all names of the path as an array of <code>String</code>s.
1490 *
1491 * @param path to get names from
1492 * @return <code>String</code>s, never <code>null</code>
1493 *
1494 * @since Ant 1.7
1495 */
1496 public static String[] getPathStack(String path) {
1497 String normalizedPath = path.replace(File.separatorChar, '/');
1498
1499 // since Java 1.4
1500 //return normalizedPath.split("/");
1501 // workaround for Java 1.2-1.3
1502 Object[] tokens = StringUtils.split(normalizedPath, '/').toArray();
1503 String[] rv = new String[tokens.length];
1504 System.arraycopy(tokens, 0, rv, 0, tokens.length);
1505
1506 return rv;
1507 }
1508
1509 /**
1510 * Gets path from a <code>List</code> of <code>String</code>s.
1511 *
1512 * @param pathStack <code>List</code> of <code>String</code>s to be concatenated as a path.
1513 * @return <code>String</code>, never <code>null</code>
1514 *
1515 * @since Ant 1.7
1516 */
1517 public static String getPath(List pathStack) {
1518 // can safely use '/' because Windows understands '/' as separator
1519 return getPath(pathStack, '/');
1520 }
1521
1522 /**
1523 * Gets path from a <code>List</code> of <code>String</code>s.
1524 *
1525 * @param pathStack <code>List</code> of <code>String</code>s to be concated as a path.
1526 * @param separatorChar <code>char</code> to be used as separator between names in path
1527 * @return <code>String</code>, never <code>null</code>
1528 *
1529 * @since Ant 1.7
1530 */
1531 public static String getPath(final List pathStack, final char separatorChar) {
1532 final StringBuffer buffer = new StringBuffer();
1533
1534 final Iterator iter = pathStack.iterator();
1535 if (iter.hasNext()) {
1536 buffer.append(iter.next());
1537 }
1538 while (iter.hasNext()) {
1539 buffer.append(separatorChar);
1540 buffer.append(iter.next());
1541 }
1542 return buffer.toString();
1543 }
1544
1545 /**
1546 * Get the default encoding.
1547 * This is done by opening an InputStreamReader on
1548 * a dummy InputStream and getting the encoding.
1549 * Could use System.getProperty("file.encoding"), but cannot
1550 * see where this is documented.
1551 * @return the default file encoding.
1552 */
1553 public String getDefaultEncoding() {
1554 InputStreamReader is = new InputStreamReader(
1555 new InputStream() {
1556 public int read() {
1557 return -1;
1558 }
1559 });
1560 try {
1561 return is.getEncoding();
1562 } finally {
1563 close(is);
1564 }
1565 }
1566 }