001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.util;
038:
039: import java.io.*;
040: import java.net.MalformedURLException;
041: import java.net.URL;
042: import java.util.*;
043:
044: import edu.rice.cs.drjava.config.FileOption;
045:
046: import edu.rice.cs.util.Log;
047:
048: import edu.rice.cs.plt.io.IOUtil;
049:
050: /** A class to provide some convenient file operations as static methods.
051: * It's abstract to prevent (useless) instantiation, though it can be subclassed
052: * to provide convenient namespace importation of its methods.
053: *
054: * @version $Id: FileOps.java 4255 2007-08-28 19:17:37Z mgricken $
055: */
056: public abstract class FileOps {
057:
058: private static Log _log = new Log("FileOpsTest.txt", false);
059:
060: /** Special File object corresponding to a dummy file. Simliar to FileOption.NULL_FILE but exists() returns false. */
061: public static final File NONEXISTENT_FILE = new File("") {
062: public String getAbsolutePath() {
063: return "";
064: }
065:
066: public String getName() {
067: return "";
068: }
069:
070: public String toString() {
071: return "";
072: }
073:
074: public boolean exists() {
075: return false;
076: }
077: };
078:
079: /** @deprecated If a best-attempt canonical file is needed, use
080: * {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile} instead
081: * (for example, {@code IOUtil.attemptCanonicalFile(new File(path))})
082: */
083: @Deprecated
084: public static File makeFile(String path) {
085: File f = new File(path);
086: try {
087: return f.getCanonicalFile();
088: } catch (IOException e) {
089: return f;
090: }
091: }
092:
093: /** @deprecated If a best-attempt canonical file is needed,
094: * use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile} instead
095: * (for example, {@code IOUtil.attemptCanonicalFile(new File(parentDir, child))})
096: */
097: @Deprecated
098: public static File makeFile(File parentDir, String child) {
099: File f = new File(parentDir, child);
100: try {
101: return f.getCanonicalFile();
102: } catch (IOException e) {
103: return f;
104: }
105: }
106:
107: /** Determines whether the specified file in within the specified file tree.
108: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#isMember} instead. Note that the
109: * replacement method does not test for {@code null} values and does not
110: * convert to canonical paths -- if these things are necessary, they should
111: * be done before invoking the method.
112: */
113: @Deprecated
114: public static boolean inFileTree(File f, File root) {
115: if (root == null || f == null)
116: return false;
117: try {
118: if (!f.isDirectory())
119: f = f.getParentFile();
120: String filePath = f.getCanonicalPath() + File.separator;
121: String projectPath = root.getCanonicalPath()
122: + File.separator;
123: return (filePath.startsWith(projectPath));
124: } catch (IOException e) {
125: return false;
126: }
127: }
128:
129: /** Makes a file equivalent to the given file f that is relative to base file b. In other words,
130: * <code>new File(b,makeRelativeTo(base,abs)).getCanonicalPath()</code> equals
131: * <code>f.getCanonicalPath()</code>
132: *
133: * <p>In Linux/Unix, if the file f is <code>/home/username/folder/file.java</code> and the file b is
134: * <code>/home/username/folder/sublevel/file2.java</code>, then the resulting File path from this method would be
135: * <code>../file.java</code> while its canoncial path would be <code>/home/username/folder/file.java</code>.</p>
136: *
137: * <p>Warning: this method is inherently broken, because it assumes a relative path exists between all
138: * files. The Java file system model, however, supports multiple system roots (see {@link File#listRoots}).
139: * Thus, two files from different "file systems" (in Windows, different drives) have no common parent.</p>
140: *
141: * @param f The path that is to be made relative to the base file
142: * @param b The file to make the next file relative to
143: * @return A new file whose path is relative to the base file while the value of <code>getCanonicalPath()</code>
144: * for the returned file is the same as the result of <code>getCanonicalPath()</code> for the given file.
145: */
146: public static File makeRelativeTo(File f, File b)
147: throws IOException, SecurityException {
148: File base = b.getCanonicalFile();
149: File abs = f.getCanonicalFile(); // If f is relative, uses current working directory ("user.dir")
150: if (!base.isDirectory())
151: base = base.getParentFile();
152:
153: String last = "";
154: if (!abs.isDirectory()) {
155: String tmp = abs.getPath();
156: last = tmp.substring(tmp.lastIndexOf(File.separator) + 1);
157: abs = abs.getParentFile();
158: }
159:
160: // System.err.println("makeRelativeTo called; f = " + f + " = " + abs + "; b = " + b + " = " + base);
161: String[] basParts = splitFile(base);
162: String[] absParts = splitFile(abs);
163:
164: final StringBuilder result = new StringBuilder();
165: // loop until elements differ, record what part of absParts to append
166: // next find out how many .. to put in.
167: int diffIndex = -1;
168: boolean different = false;
169: for (int i = 0; i < basParts.length; i++) {
170: if (!different
171: && ((i >= absParts.length) || !basParts[i]
172: .equals(absParts[i]))) {
173: different = true;
174: diffIndex = i;
175: }
176: if (different)
177: result.append("..").append(File.separator);
178: }
179: if (diffIndex < 0)
180: diffIndex = basParts.length;
181: for (int i = diffIndex; i < absParts.length; i++) {
182: result.append(absParts[i]).append(File.separator);
183: }
184: result.append(last);
185: // System.err.println("makeRelativeTo(" + f + ", " + b + ") = " + result);
186: return new File(result.toString());
187: }
188:
189: /** Splits a file into an array of strings representing each parent folder of the given file. The file whose path
190: * is <code>/home/username/txt.txt</code> in linux would be split into the string array:
191: * {"","home","username","txt.txt"}. Delimeters are excluded.
192: * @param fileToSplit the file to split into its directories.
193: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#fullPath} instead. It returns a list of {@code File}
194: * objects rather than strings, but they appear in the same order.
195: */
196: @Deprecated
197: public static String[] splitFile(File fileToSplit) {
198: String path = fileToSplit.getPath();
199: ArrayList<String> list = new ArrayList<String>();
200: while (!path.equals("")) {
201: int idx = path.indexOf(File.separator);
202: if (idx < 0) {
203: list.add(path);
204: path = "";
205: } else {
206: list.add(path.substring(0, idx));
207: path = path.substring(idx + 1);
208: }
209: }
210: return list.toArray(new String[list.size()]);
211: }
212:
213: /** List all files (that is, {@code File}s for which {@code isFile()} is {@code true}) matching the provided filter in
214: * the given directory.
215: * @param d The directory to search.
216: * @param recur Whether subdirectories accepted by {@code f} should be recursively searched. Note that
217: * subdirectories that <em>aren't</em> accepted by {@code f} will be ignored.
218: * @param f The filter to apply to contained {@code File}s.
219: * @return An array of Files in the directory specified; if the directory does not exist, returns an empty list.
220: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptListFilesAsIterable} or
221: * {@link edu.rice.cs.plt.io.IOUtil#listFilesRecursively(File, FileFilter, FileFilter)} instead.
222: */
223: @Deprecated
224: public static ArrayList<File> getFilesInDir(File d, boolean recur,
225: FileFilter f) {
226: ArrayList<File> l = new ArrayList<File>();
227: getFilesInDir(d, l, recur, f);
228: return l;
229: }
230:
231: /** Helper fuction for getFilesInDir(File d, boolean recur). {@code acc} is mutated to contain
232: * a list of <c>File</c>s in the directory specified, not including directories.
233: */
234: private static void getFilesInDir(File d, List<File> acc,
235: boolean recur, FileFilter filter) {
236: if (d.isDirectory()) {
237: File[] files = d.listFiles(filter);
238: if (files != null) { // listFiles may return null if there's an IO error
239: for (File f : files) {
240: if (f.isDirectory() && recur)
241: getFilesInDir(f, acc, recur, filter);
242: else if (f.isFile())
243: acc.add(f);
244: }
245: }
246: }
247: }
248:
249: /** @return the canonical file equivalent to f. Identical to f.getCanonicalFile() except it does not throw an
250: * exception when the file path syntax is incorrect (or an IOException or SecurityException occurs for any
251: * other reason). It returns the absolute File intead.
252: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile}, which provides the same
253: * functionality, instead.
254: */
255: @Deprecated
256: public static File getCanonicalFile(File f) {
257: if (f == null)
258: return f;
259: try {
260: return f.getCanonicalFile();
261: } catch (IOException e) { /* fall through */
262: } catch (SecurityException e) { /* fall through */
263: }
264: return f.getAbsoluteFile();
265: }
266:
267: /** @return the canonical path for f. Identical to f.getCanonicalPath() except it does not throw an
268: * exception when the file path syntax is incorrect; it returns the absolute path instead.
269: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile}, which provides the same
270: * functionality, instead. (The result will be a {@code File} instead of a {@code String}.)
271: */
272: @Deprecated
273: public static String getCanonicalPath(File f) {
274: return getCanonicalFile(f).getPath();
275: }
276:
277: /** @return the file f unchanged if f exists; otherwise returns NULL_FILE. */
278: public static File validate(File f) {
279: if (f.exists())
280: return f;
281: return FileOption.NULL_FILE; // This File object exists
282: }
283:
284: /** This filter checks for files with names that end in ".java". (Note that while this filter was <em>intended</em>
285: * to be a {@code javax.swing.filechooser.FileFilter}, it actually implements a {@code java.io.FileFilter},
286: * because thats what {@code FileFilter} means in the context of this source file.)
287: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#extensionFileFilter} instead. Example:
288: * {@code IOUtil.extensionFileFilter("java")}.
289: */
290: @Deprecated
291: public static final FileFilter JAVA_FILE_FILTER = new FileFilter() {
292: public boolean accept(File f) {
293: // Do this runaround for filesystems that are case preserving but case insensitive. Remove the last 5
294: // letters from the file name, append ".java" to the end, create a new file and see if its equivalent
295: // to the original
296: final StringBuilder name = new StringBuilder(f
297: .getAbsolutePath());
298: String shortName = f.getName();
299: if (shortName.length() < 6)
300: return false;
301: name.delete(name.length() - 5, name.length());
302: name.append(".java");
303: File test = new File(name.toString());
304: return (test.equals(f));
305: }
306:
307: public String getDescription() {
308: return "Java Source Files (*.java)";
309: }
310: };
311:
312: /** Reads the stream until it reaches EOF, and then returns the read contents as a byte array. This call may
313: * block, since it will not return until EOF has been reached.
314: * @param stream Input stream to read.
315: * @return Byte array consisting of all data read from stream.
316: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#toByteArray} instead, which provides the same functionality.
317: * Note that the {@code IOUtil} method will not close the {@code InputStream}, while this method does.
318: */
319: @Deprecated
320: public static byte[] readStreamAsBytes(final InputStream stream)
321: throws IOException {
322: BufferedInputStream buffered;
323:
324: if (stream instanceof BufferedInputStream)
325: buffered = (BufferedInputStream) stream;
326: else
327: buffered = new BufferedInputStream(stream);
328:
329: ByteArrayOutputStream out = new ByteArrayOutputStream();
330:
331: int readVal = buffered.read();
332: while (readVal != -1) {
333: out.write(readVal);
334: readVal = buffered.read();
335: }
336:
337: stream.close();
338: return out.toByteArray();
339: }
340:
341: /** Reads the entire contents of a file and return them as canonicalized Swing Document text. All newLine sequences,
342: * including "\n", "\r", and "\r\n" are converted to "\n". */
343: public static String readFileAsSwingText(final File file)
344: throws IOException {
345: FileReader reader = new FileReader(file);
346: final StringBuilder buf = new StringBuilder();
347:
348: char pred = (char) 0; // initialize as null character
349: while (reader.ready()) {
350: char c = (char) reader.read();
351:
352: if (c == '\n' && pred == '\r') {
353: } // do nothing ignoring second character of "\r\n";
354: else if (c == '\r')
355: buf.append('\n');
356: else
357: buf.append(c);
358:
359: pred = c;
360: }
361:
362: reader.close();
363: return buf.toString();
364: }
365:
366: /** Reads the entire contents of a file and return them as a String.
367: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#toString(File)} instead, which provides the same functionality.
368: */
369: @Deprecated
370: public static String readFileAsString(final File file)
371: throws IOException {
372: FileReader reader = new FileReader(file);
373: final StringBuilder buf = new StringBuilder();
374:
375: while (reader.ready()) {
376: char c = (char) reader.read();
377: buf.append(c);
378: }
379:
380: reader.close();
381: return buf.toString();
382: }
383:
384: /** Copies the text of one file into another.
385: * @param source the file to be copied
386: * @param dest the file to be copied to
387: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#copyFile} instead, which provides the same functionality
388: * but scales in a much more efficient manner.
389: */
390: @Deprecated
391: public static void copyFile(File source, File dest)
392: throws IOException {
393: String text = readFileAsString(source);
394: writeStringToFile(dest, text);
395: }
396:
397: /** Creates a new temporary file and writes the given text to it. The file will be deleted on exit.
398: * @param prefix Beginning part of file name, before unique number
399: * @param suffix Ending part of file name, after unique number
400: * @param text Text to write to file
401: * @return name of the temporary file that was created
402: * @deprecated Instead, create a temp file with {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempFile(String, String)},
403: * then write to it with {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String)}.
404: */
405: @Deprecated
406: public static File writeStringToNewTempFile(final String prefix,
407: final String suffix, final String text) throws IOException {
408:
409: File file = File.createTempFile(prefix, suffix);
410: file.deleteOnExit();
411: writeStringToFile(file, text);
412: return file;
413: }
414:
415: /** Writes text to the file overwriting whatever was there.
416: * @param file File to write to
417: * @param text Test to write
418: * @deprecated Use the equivalent {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String)} instead
419: */
420: @Deprecated
421: public static void writeStringToFile(File file, String text)
422: throws IOException {
423: writeStringToFile(file, text, false);
424: }
425:
426: /** Writes text to the file.
427: * @param file File to write to
428: * @param text Text to write
429: * @param append whether to append. (false=overwrite)
430: * @deprecated Use the equivalent {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String, boolean)} instead
431: */
432: @Deprecated
433: public static void writeStringToFile(File file, String text,
434: boolean append) throws IOException {
435: FileWriter writer = new FileWriter(file, append);
436: writer.write(text);
437: writer.close();
438: }
439:
440: /** Writes the text to the given file returning true if it happend and false if it could not. This is a
441: * simple wrapper for writeStringToFile that doesn't throw an IOException.
442: * @param file File to write to
443: * @param text Text to write
444: * @param append whether to append. (false=overwrite)
445: * @deprecated Use the equivalent {@link edu.rice.cs.plt.io.IOUtil#attemptWriteStringToFile(File, String, boolean)}
446: * instead
447: */
448: @Deprecated
449: public static boolean writeIfPossible(File file, String text,
450: boolean append) {
451: try {
452: writeStringToFile(file, text, append);
453: return true;
454: } catch (IOException e) {
455: return false;
456: }
457: }
458:
459: /** Create a new temporary directory. The directory will be deleted on exit, if empty.
460: * (To delete it recursively on exit, use deleteDirectoryOnExit.)
461: * @param name Non-unique portion of the name of the directory to create.
462: * @return File representing the directory that was created.
463: */
464: // * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempDirectory(String, String)} instead.
465: // * Example: {@code IOUtil.createAndMarkTempDirectory(name, "")}.
466: /* @Deprecated */public static File createTempDirectory(
467: final String name) throws IOException {
468: return createTempDirectory(name, null);
469: }
470:
471: /** Create a new temporary directory. The directory will be deleted on exit, if it only contains temp files and temp
472: * directories created after it. (To delete it on exit regardless of contents, call deleteDirectoryOnExit after
473: * constructing the file tree rooted at this directory. Note that createTempDirectory(..) is not much more helpful
474: * than mkdir() in this context (other than generating a new temp file name) because cleanup is a manual process.)
475: * @param name Non-unique portion of the name of the directory to create.
476: * @param parent Parent directory to contain the new directory
477: * @return File representing the directory that was created.
478: */
479: // * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempDirectory(String, String, File)} instead.
480: // * Example: {@code IOUtil.createAndMarkTempDirectory(name, "", parent)}.
481: /* @Deprecated */public static File createTempDirectory(
482: /* final */String name, /* final */File parent)
483: throws IOException {
484: File result = File.createTempFile(name, "", parent);
485: boolean success = result.delete();
486: success = success && result.mkdir();
487: if (!success) {
488: throw new IOException("Attempt to create directory failed");
489: }
490: IOUtil.attemptDeleteOnExit(result);
491: return result;
492: // File file = File.createTempFile(name, "", parent);
493: // file.delete();
494: // file.mkdir();
495: // file.deleteOnExit();
496: //
497: // return file;
498: }
499:
500: /** Delete the given directory including any files and directories it contains.
501: * @param dir File object representing directory to delete. If, for some reason, this file object is not a
502: * directory, it will still be deleted.
503: * @return true if there were no problems in deleting. If it returns false, something failed and the directory
504: * contents likely at least partially still exist.
505: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#deleteRecursively} instead
506: */
507: @Deprecated
508: public static boolean deleteDirectory(final File dir) {
509: // System.err.println("Deleting file or directory " + dir);
510: if (!dir.isDirectory()) {
511: boolean res;
512: res = dir.delete();
513: // System.err.println("Deletion of " + dir + " returned " + res);
514: return res;
515: }
516:
517: boolean ret = true;
518: File[] childFiles = dir.listFiles();
519: if (childFiles != null) { // listFiles may return null if there's an IO error
520: for (File f : childFiles) {
521: ret = ret && deleteDirectory(f);
522: }
523: }
524:
525: // Now we should have an empty directory
526: ret = ret && dir.delete();
527: // System.err.println("Recursive deletion of " + dir + " returned " + ret);
528: return ret;
529: }
530:
531: /** Instructs Java to recursively delete the given directory and its contents when the JVM exits.
532: * @param dir File object representing directory to delete. If, for some reason, this file object is not a
533: * directory, it will still be deleted.
534: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#deleteOnExitRecursively} instead
535: */
536: @Deprecated
537: public static void deleteDirectoryOnExit(final File dir) {
538:
539: // Delete this on exit, whether it's a directory or file
540: _log.log("Deleting file/directory " + dir + " on exit");
541: dir.deleteOnExit();
542:
543: // If it's a directory, visit its children. This recursive walk has to be done AFTER calling deleteOnExit
544: // on the directory itself because Java closes the list of files to deleted on exit in reverse order.
545: if (dir.isDirectory()) {
546: File[] childFiles = dir.listFiles();
547: if (childFiles != null) { // listFiles may return null if there's an IO error
548: for (File f : childFiles) {
549: deleteDirectoryOnExit(f);
550: }
551: }
552: }
553: }
554:
555: /** This function starts from the given directory and finds all packages within that directory
556: * @param prefix the package name of files in the given root
557: * @param root the directory to start exploring from
558: * @return a list of valid packages, excluding the root ("") package
559: */
560: public static LinkedList<String> packageExplore(String prefix,
561: File root) {
562: /* Inner holder class. */
563: class PrefixAndFile {
564: public String prefix;
565: public File root;
566:
567: public PrefixAndFile(String prefix, File root) {
568: this .root = root;
569: this .prefix = prefix;
570: }
571: }
572:
573: // This set makes sure we don't get caught in a loop if the filesystem has symbolic links
574: // that form a circle by tracking the directories we have already explored
575: final Set<File> exploredDirectories = new HashSet<File>();
576:
577: LinkedList<String> output = new LinkedList<String>();
578: Stack<PrefixAndFile> working = new Stack<PrefixAndFile>();
579: working.push(new PrefixAndFile(prefix, root));
580: exploredDirectories.add(root);
581:
582: // This filter allows only directories, and accepts each directory only once
583: FileFilter directoryFilter = new FileFilter() {
584: public boolean accept(File f) {
585: boolean toReturn = f.isDirectory()
586: && !exploredDirectories.contains(f);
587: exploredDirectories.add(f);
588: return toReturn;
589: }
590:
591: public String getDescription() {
592: return "All Folders";
593: }
594: };
595:
596: // Explore each directory, adding (unique) subdirectories to the working list. If a directory has .java
597: // files, add the associated package to the list of packages
598: while (!working.empty()) {
599: PrefixAndFile current = working.pop();
600: File[] subDirectories = current.root
601: .listFiles(directoryFilter);
602: if (subDirectories != null) { // listFiles may return null if there's an IO error
603: for (File dir : subDirectories) {
604: PrefixAndFile paf;
605: // System.out.println("exploring " + dir);
606: if (current.prefix.equals(""))
607: paf = new PrefixAndFile(dir.getName(), dir);
608: else
609: paf = new PrefixAndFile(current.prefix + "."
610: + dir.getName(), dir);
611: working.push(paf);
612: }
613: }
614: File[] javaFiles = current.root.listFiles(JAVA_FILE_FILTER);
615:
616: if (javaFiles != null) { // listFiles may return null if there's an IO error
617: //Only add package names if they have java files and are not the root package
618: if (javaFiles.length != 0 && !current.prefix.equals("")) {
619: output.add(current.prefix);
620: // System.out.println("adding " + current.prefix);
621: }
622: }
623: }
624: return output;
625: }
626:
627: /** Renames the given file to the given destination. Needed since Windows will not allow a rename to
628: * overwrite an existing file.
629: * @param file the file to rename
630: * @param dest the destination file
631: * @return true iff the rename was successful
632: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptMove}, which is equally Windows-friendly, instead.
633: */
634: @Deprecated
635: public static boolean renameFile(File file, File dest) {
636: if (dest.exists())
637: dest.delete();
638: return file.renameTo(dest);
639: }
640:
641: /** This method writes files correctly; it takes care of catching errors and
642: * making backups and keeping an unsuccessful file save from destroying the old
643: * file (unless a backup is made). It makes sure that the file to be saved is
644: * not read-only, throwing an IOException if it is. Note: if saving fails and a
645: * backup was being created, any existing backup will be destroyed (this is
646: * because the backup is written before saving begins, and then moved back over
647: * the original file when saving fails). As the old backup would have been destroyed
648: * anyways if saving had succeeded, I do not think that this is incorrect or
649: * unreasonable behavior.
650: * @param fileSaver keeps track of the name of the file to write, whether to back up the file, and has
651: * a method that actually performs the writing of the file
652: * @throws IOException if the saving or backing up of the file fails for any reason
653: */
654: public static void saveFile(FileSaver fileSaver) throws IOException {
655:
656: // ScrollableDialog sd1 = new ScrollableDialog(null, "saveFile (" + fileSaver + ") called in FileOps.java", "", "");
657: // sd1.show();
658: boolean makeBackup = fileSaver.shouldBackup();
659: boolean success = false;
660: File file = fileSaver.getTargetFile();
661: File backup = null;
662: boolean tempFileUsed = true;
663: // file.canWrite() is false if file.exists() is false
664: // but we want to be able to save a file that doesn't
665: // yet exist.
666: if (file.exists() && !file.canWrite())
667: throw new IOException("Permission denied");
668: /* First back up the file, if necessary */
669: if (makeBackup) {
670: backup = fileSaver.getBackupFile();
671: if (!renameFile(file, backup)) {
672: throw new IOException(
673: "Save failed. Could not create backup file "
674: + backup.getAbsolutePath()
675: + "\nIt may be possible to save by disabling file backups\n");
676: }
677: fileSaver.backupDone();
678: }
679:
680: // ScrollableDialog sd2 = new ScrollableDialog(null, "backup done in FileOps.saveFile", "", "");
681: // sd2.show();
682:
683: //Create a temp file in the same directory as the file to be saved.
684: //From this point forward, enclose in try...finally so that we can clean
685: //up the temp file and restore the file from its backup.
686: File parent = file.getParentFile();
687: File tempFile = File.createTempFile("drjava", ".temp", parent);
688:
689: // ScrollableDialog sd3 = new ScrollableDialog(null, "temp file " + tempFile + "created in FileOps.saveFile", "", "");
690: // sd3.show();
691:
692: try {
693: /* Now, write your output to the temp file, then rename it to the correct
694: name. This way, if writing fails in the middle, the old file is not
695: lost. */
696: FileOutputStream fos;
697: try {
698: /* The next line will fail if we can't create the temp file. This may mean that
699: * the user does not have write permission on the directory the file they
700: * are editing is in. We may want to go ahead and try writing directly
701: * to the target file in this case
702: */
703: fos = new FileOutputStream(tempFile);
704: } catch (FileNotFoundException fnfe) {
705: if (fileSaver.continueWhenTempFileCreationFails()) {
706: fos = new FileOutputStream(file);
707: tempFileUsed = false;
708: } else
709: throw new IOException("Could not create temp file "
710: + tempFile + " in attempt to save " + file);
711: }
712: BufferedOutputStream bos = new BufferedOutputStream(fos);
713: fileSaver.saveTo(bos);
714: bos.close();
715: fos.close();
716:
717: if (tempFileUsed && !renameFile(tempFile, file))
718: throw new IOException(
719: "Save failed. Another process may be using "
720: + file + ".");
721:
722: success = true;
723: } finally {
724: // ScrollableDialog sd4 = new ScrollableDialog(null, "finally clause reached in FileOps.saveFile", "", "");
725: // sd4.show();
726:
727: if (tempFileUsed)
728: tempFile.delete(); /* Delete the temp file */
729:
730: if (makeBackup) {
731: /* On failure, attempt to move the backup back to its original location if we
732: made one. On success, register that a backup was successfully made */
733: if (success)
734: fileSaver.backupDone();
735: else
736: renameFile(backup, file);
737: }
738: }
739: }
740:
741: public interface FileSaver {
742:
743: /** This method tells what to name the backup of the file, if a backup is to be made.
744: * It may depend on getTargetFile(), so it can thrown an IOException
745: */
746: public abstract File getBackupFile() throws IOException;
747:
748: /** This method indicates whether or not a backup of the file should be made. It
749: * may depend on getTargetFile(), so it can throw an IOException
750: */
751: public abstract boolean shouldBackup() throws IOException;
752:
753: /** This method specifies if the saving process should continue trying to save
754: * if it can not create the temp file that is written initially. If you do
755: * continue saving in this case, the original file may be lost if saving fails.
756: */
757: public abstract boolean continueWhenTempFileCreationFails();
758:
759: /** This method is called to tell the file saver that a backup was successfully made. */
760: public abstract void backupDone();
761:
762: /**
763: * This method actually writes info to a file. NOTE: It is important that this
764: * method write to the stream it is passed, not the target file. If you write
765: * directly to the target file, the target file will be destroyed if saving fails.
766: * Also, it is important that when saving fails this method throw an IOException
767: * @throws IOException when saving fails for any reason
768: */
769: public abstract void saveTo(OutputStream os) throws IOException;
770:
771: /** This method tells what the file is that we want to save to. It should
772: * use the canonical name of the file (this means resolving symlinks). Otherwise,
773: * the saver would not deal correctly with symlinks. Resolving symlinks may cause
774: * an IOException, so this method throws an IOException.
775: */
776: public abstract File getTargetFile() throws IOException;
777: }
778:
779: /** This class is a default implementation of FileSaver that makes only 1 backup
780: * of each file per instantiation of the program (following Emacs' lead). It
781: * backs up to files named <file>~. It does not implement the saveTo method.
782: */
783: public abstract static class DefaultFileSaver implements FileSaver {
784:
785: private File outputFile = null;
786: private static Set<File> filesNotNeedingBackup = new HashSet<File>();
787: private static boolean backupsEnabled = true;
788:
789: /** This field keeps track of whether or not outputFile has been resolved to its canonical name. */
790: private boolean isCanonical = false;
791:
792: /** Globally enables backups for any DefaultFileSaver that does not override the shouldBackup method. */
793: public static void setBackupsEnabled(boolean isEnabled) {
794: backupsEnabled = isEnabled;
795: }
796:
797: public DefaultFileSaver(File file) {
798: outputFile = file.getAbsoluteFile();
799: }
800:
801: public boolean continueWhenTempFileCreationFails() {
802: return true;
803: }
804:
805: public File getBackupFile() throws IOException {
806: return new File(getTargetFile().getPath() + "~");
807: }
808:
809: public boolean shouldBackup() throws IOException {
810: if (!backupsEnabled)
811: return false;
812: if (!getTargetFile().exists())
813: return false;
814: if (filesNotNeedingBackup.contains(getTargetFile()))
815: return false;
816: return true;
817: }
818:
819: public void backupDone() {
820: try {
821: filesNotNeedingBackup.add(getTargetFile());
822: } catch (IOException ioe) {
823: throw new UnexpectedException(ioe,
824: "getTargetFile should fail earlier");
825: }
826: }
827:
828: public File getTargetFile() throws IOException {
829: if (!isCanonical) {
830: outputFile = outputFile.getCanonicalFile();
831: isCanonical = true;
832: }
833: return outputFile;
834: }
835: }
836:
837: /** Convert all path entries in a path string to absolute paths. The delimiter in the path string is the
838: * "path.separator" property. Empty entries are equivalent to "." and will thus are converted to the
839: * "user.dir" (working directory).
840: * Example:
841: * ".:drjava::/home/foo/junit.jar" with "user.dir" set to "/home/foo/bar" will be converted to
842: * "/home/foo/bar:/home/foo/bar/drjava:/home/foo/bar:/home/foo/junit.jar".
843: *
844: * @param path path string with entries to convert
845: * @return path string with all entries as absolute paths
846: * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#parsePath}, {@link edu.rice.cs.plt.io.IOUtil#getAbsoluteFiles},
847: * {@link edu.rice.cs.plt.io.IOUtil#attemptAbsoluteFiles}, and {@link edu.rice.cs.plt.io.IOUtil#pathToString},
848: * as needed, instead.
849: */
850: @Deprecated
851: public static String convertToAbsolutePathEntries(String path) {
852: String pathSep = System.getProperty("path.separator");
853:
854: // split leaves off trailing empty strings
855: // (see API javadocs: "Trailing empty strings are therefore not included in the resulting array.")
856: // we therefore append one element at the end and later remove it
857: path += pathSep + "x";
858:
859: // now path ends with ":x", so we'll have an additional "x" element in the pathEntries array
860:
861: // split up the path into individual entries, turn all of the entries
862: // into absolute paths, and put the path back together
863: // EXCEPT for the last item in the array, because that's the "x" we added
864: String[] pathEntries = path.split(pathSep);
865: final StringBuilder sb = new StringBuilder();
866: for (int i = 0; i < pathEntries.length - 1; ++i) { // length-1 to ignore the last element
867: File f = new File(pathEntries[i]);
868: sb.append(f.getAbsolutePath());
869: sb.append(pathSep);
870: }
871: String reconstructedPath = sb.toString();
872:
873: // if the reconstructed path is non-empty, then it will have an extra
874: // path separator at the end; take it off
875: if (reconstructedPath.length() != 0) {
876: reconstructedPath = reconstructedPath.substring(0,
877: reconstructedPath.length() - 1);
878: }
879:
880: return reconstructedPath;
881: }
882:
883: /** Return a valid directory for use, i.e. one that exists and is as "close" to the file specified. It is
884: * 1) file, if file is a directory and exists
885: * 2) the closest parent of file, if file is not a directory or does not exist
886: * 3) "user.home"
887: * @return a valid directory for use */
888: public static File getValidDirectory(final File origFile) {
889: File file = origFile;
890:
891: // if it's the NULL_FILE or null, use "user.home"
892: if ((file == FileOption.NULL_FILE) || (file == null)) {
893: file = new File(System.getProperty("user.home"));
894: }
895: assert file != null;
896:
897: while (file != null && !file.exists()) {
898: // if the saved path doesn't exist anymore, try the parent
899: //NB: getParentFile() may return null
900: file = file.getParentFile();
901: }
902: if (file == null) {
903: // somehow we ended up with null, use "user.home"
904: file = new File(System.getProperty("user.home"));
905: }
906: assert file != null;
907:
908: // if it's not a directory, try the parent
909: if (!file.isDirectory()) {
910: if (file.getParent() != null) {
911: file = file.getParentFile();
912: //NB: getParentFile() may return null
913: if (file == null) {
914: // somehow we ended up with null, use "user.home"
915: file = new File(System.getProperty("user.home"));
916: }
917: assert file != null;
918: }
919: }
920:
921: // this should be an existing directory now
922: if (file.exists() && file.isDirectory()) {
923: return file;
924: }
925:
926: // ye who enter here, abandon all hope...
927: // the saved path didn't work, and neither did "user.home"
928: throw new UnexpectedException(new IOException(origFile
929: .getPath()
930: + " is not a valid directory, and all attempts "
931: + "to locate a valid directory have failed. "
932: + "Check your configuration."));
933: }
934:
935: /** Converts the abstract pathname for f into a URL. This method is included in class java.io.File as f.toURL(), but
936: * has been deprecated in Java 6.0 because escape characters on some systems are not handled correctly. The workaround,
937: * f.toURI().toURL(), is unsatisfactory because we rely on the old (broken) behavior: toURI() produces escape
938: * characters (for example, " " becomes "%20"), which remain in the name when we attempt to convert back
939: * to a filename. That is, f.toURI().toURL().getFile() may not be a valid path, even if f exists. (The correct
940: * solution is to avoid trying to convert from a URL to a File, because this conversion is not guaranteed
941: * to work.)
942: */
943: public static URL toURL(File f) throws MalformedURLException {
944: return f.toURL();
945: }
946: }
|