KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > rice > cs > util > FileOps


1 /*BEGIN_COPYRIGHT_BLOCK
2  *
3  * This file is part of DrJava. Download the current version of this project from http://www.drjava.org/
4  * or http://sourceforge.net/projects/drjava/
5  *
6  * DrJava Open Source License
7  *
8  * Copyright (C) 2001-2006 JavaPLT group at Rice University (javaplt@rice.edu). All rights reserved.
9  *
10  * Developed by: Java Programming Languages Team, Rice University, http://www.cs.rice.edu/~javaplt/
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
13  * documentation files (the "Software"), to deal with the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
15  * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
16  *
17  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the
18  * following disclaimers.
19  * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
20  * following disclaimers in the documentation and/or other materials provided with the distribution.
21  * - Neither the names of DrJava, the JavaPLT, Rice University, nor the names of its contributors may be used to
22  * endorse or promote products derived from this Software without specific prior written permission.
23  * - Products derived from this software may not be called "DrJava" nor use the term "DrJava" as part of their
24  * names without prior written permission from the JavaPLT group. For permission, write to javaplt@rice.edu.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
27  * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28  * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
29  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
30  * WITH THE SOFTWARE.
31  *
32  *END_COPYRIGHT_BLOCK*/

33
34 package edu.rice.cs.util;
35
36 import java.io.*;
37 import java.net.MalformedURLException JavaDoc;
38 import java.net.URL JavaDoc;
39 import java.util.*;
40
41 import edu.rice.cs.drjava.config.FileOption;
42
43 import edu.rice.cs.util.Log;
44
45 /** A class to provide some convenient file operations as static methods.
46  * It's abstract to prevent (useless) instantiation, though it can be subclassed
47  * to provide convenient namespace importation of its methods.
48  *
49  * @version $Id: FileOps.java 4083 2007-01-23 21:27:44Z dlsmith $
50  */

51 public abstract class FileOps {
52   
53   private static Log _log = new Log("FileOpsTest.txt", false);
54   
55   
56   /** Special File object corresponding to a dummy file. Simliar to FileOption.NULL_FILE but exists() returns false. */
57   public static final File NONEXISTENT_FILE = new File("") {
58     public String JavaDoc getAbsolutePath() { return ""; }
59     public String JavaDoc getName() { return ""; }
60     public String JavaDoc toString() { return ""; }
61     public boolean exists() { return false; }
62   };
63   
64   /**
65    * @deprecated If a best-attempt canonical file is needed, use
66    * {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile} instead
67    * (for example, {@code IOUtil.attemptCanonicalFile(new File(path))})
68    */

69   @Deprecated JavaDoc public static File makeFile(String JavaDoc path) {
70     File f = new File(path);
71     try { return f.getCanonicalFile(); }
72     catch(IOException e) { return f; }
73   }
74   
75   /**
76    * @deprecated If a best-attempt canonical file is needed,
77    * use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile} instead
78    * (for example, {@code IOUtil.attemptCanonicalFile(new File(parentDir, child))})
79    */

80   @Deprecated JavaDoc public static File makeFile(File parentDir, String JavaDoc child) {
81     File f = new File(parentDir, child);
82     try { return f.getCanonicalFile(); }
83     catch(IOException e) { return f; }
84   }
85   
86   /** Determines whether the specified file in within the specified file tree.
87     * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#isMember} instead. Note that the
88     * replacement method does not test for {@code null} values and does not
89     * convert to canonical paths -- if these things are necessary, they should
90     * be done before invoking the method.
91     */

92   @Deprecated JavaDoc public static boolean inFileTree(File f, File root) {
93     if (root == null || f == null) return false;
94     try {
95       if (! f.isDirectory()) f = f.getParentFile();
96       String JavaDoc filePath = f.getCanonicalPath() + File.separator;
97       String JavaDoc projectPath = root.getCanonicalPath() + File.separator;
98       return (filePath.startsWith(projectPath));
99     }
100     catch(IOException e) { return false; }
101   }
102   
103   
104   /** Makes a file equivalent to the given file f that is relative to base file b. In other words,
105    * <code>new File(b,makeRelativeTo(base,abs)).getCanonicalPath()</code> equals
106    * <code>f.getCanonicalPath()</code>
107    *
108    * <p>In Linux/Unix, if the file f is <code>/home/username/folder/file.java</code> and the file b is
109    * <code>/home/username/folder/sublevel/file2.java</code>, then the resulting File path from this method would be
110    * <code>../file.java</code> while its canoncial path would be <code>/home/username/folder/file.java</code>.</p>
111    *
112    * <p>Warning: this method is inherently broken, because it assumes a relative path exists between all
113    * files. The Java file system model, however, supports multiple system roots (see {@link File#listRoots}).
114    * Thus, two files from different "file systems" (in Windows, different drives) have no common parent.</p>
115    *
116    * @param f The path that is to be made relative to the base file
117    * @param b The file to make the next file relative to
118    * @return A new file whose path is relative to the base file while the value of <code>getCanonicalPath()</code>
119    * for the returned file is the same as the result of <code>getCanonicalPath()</code> for the given file.
120    */

121   public static File makeRelativeTo(File f, File b) throws IOException, SecurityException JavaDoc {
122     File base = b.getCanonicalFile();
123     File abs = f.getCanonicalFile(); // If f is relative, uses current working directory ("user.dir")
124
if (! base.isDirectory()) base = base.getParentFile();
125     
126     String JavaDoc last = "";
127     if (! abs.isDirectory()) {
128       String JavaDoc tmp = abs.getPath();
129       last = tmp.substring(tmp.lastIndexOf(File.separator) + 1);
130       abs = abs.getParentFile();
131     }
132     
133 // System.err.println("makeRelativeTo called; f = " + f + " = " + abs + "; b = " + b + " = " + base);
134
String JavaDoc[] basParts = splitFile(base);
135     String JavaDoc[] absParts = splitFile(abs);
136     
137     final StringBuilder JavaDoc result = new StringBuilder JavaDoc();
138     // loop until elements differ, record what part of absParts to append
139
// next find out how many .. to put in.
140
int diffIndex = -1;
141     boolean different = false;
142     for (int i = 0; i < basParts.length; i++) {
143       if (!different && ((i >= absParts.length) || !basParts[i].equals(absParts[i]))) {
144         different = true;
145         diffIndex = i;
146       }
147       if (different) result.append("..").append(File.separator);
148     }
149     if (diffIndex < 0) diffIndex = basParts.length;
150     for (int i = diffIndex; i < absParts.length; i++) {
151       result.append(absParts[i]).append(File.separator);
152     }
153     result.append(last);
154 // System.err.println("makeRelativeTo(" + f + ", " + b + ") = " + result);
155
return new File(result.toString());
156   }
157   
158   /** Splits a file into an array of strings representing each parent folder of the given file. The file whose path
159    * is <code>/home/username/txt.txt</code> in linux would be split into the string array:
160    * {&quot;&quot;,&quot;home&quot;,&quot;username&quot;,&quot;txt.txt&quot;}. Delimeters are excluded.
161    * @param fileToSplit the file to split into its directories.
162    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#fullPath} instead. It returns a list of {@code File}
163    * objects rather than strings, but they appear in the same order.
164    */

165   @Deprecated JavaDoc public static String JavaDoc[] splitFile(File fileToSplit) {
166     String JavaDoc path = fileToSplit.getPath();
167     ArrayList<String JavaDoc> list = new ArrayList<String JavaDoc>();
168     while (! path.equals("")) {
169       int idx = path.indexOf(File.separator);
170       if (idx < 0) {
171         list.add(path);
172         path = "";
173       }
174       else {
175         list.add(path.substring(0,idx));
176         path = path.substring(idx + 1);
177       }
178     }
179     return list.toArray(new String JavaDoc[list.size()]);
180   }
181   
182   /** List all files (that is, {@code File}s for which {@code isFile()} is {@code true}) matching the provided filter in
183    * the given directory.
184    * @param d The directory to search.
185    * @param recur Whether subdirectories accepted by {@code f} should be recursively searched. Note that
186    * subdirectories that <em>aren't</em> accepted by {@code f} will be ignored.
187    * @param f The filter to apply to contained {@code File}s.
188    * @return An array of Files in the directory specified; if the directory does not exist, returns an empty list.
189    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptListFilesAsIterable} or
190    * {@link edu.rice.cs.plt.io.IOUtil#listFilesRecursively(File, FileFilter, FileFilter)} instead.
191    */

192   @Deprecated JavaDoc public static ArrayList<File> getFilesInDir(File d, boolean recur, FileFilter f) {
193     ArrayList<File> l = new ArrayList<File>();
194     getFilesInDir(d, l, recur, f);
195     return l;
196   }
197   
198   /** Helper fuction for getFilesInDir(File d, boolean recur). {@code acc} is mutated to contain
199    * a list of <c>File</c>s in the directory specified, not including directories.
200    */

201   private static void getFilesInDir(File d, List<File> acc, boolean recur, FileFilter filter) {
202     if (d.isDirectory()) {
203       File[] files = d.listFiles(filter);
204       if (files != null) { // listFiles may return null if there's an IO error
205
for (File f: files) {
206           if (f.isDirectory() && recur) getFilesInDir(f, acc, recur, filter);
207           else if (f.isFile()) acc.add(f);
208         }
209       }
210     }
211   }
212   
213   /** @return the canonical file equivalent to f. Identical to f.getCanonicalFile() except it does not throw an
214    * exception when the file path syntax is incorrect (or an IOException or SecurityException occurs for any
215    * other reason). It returns the absolute File intead.
216    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile}, which provides the same
217    * functionality, instead.
218    */

219   @Deprecated JavaDoc public static File getCanonicalFile(File f) {
220     if (f == null) return f;
221     try { return f.getCanonicalFile(); }
222     catch (IOException e) { /* fall through */ }
223     catch (SecurityException JavaDoc e) { /* fall through */ }
224     return f.getAbsoluteFile();
225   }
226   
227   /** @return the canonical path for f. Identical to f.getCanonicalPath() except it does not throw an
228    * exception when the file path syntax is incorrect; it returns the absolute path instead.
229    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile}, which provides the same
230    * functionality, instead. (The result will be a {@code File} instead of a {@code String}.)
231    */

232   @Deprecated JavaDoc public static String JavaDoc getCanonicalPath(File f) { return getCanonicalFile(f).getPath(); }
233     
234   /** @return the file f unchanged if f exists; otherwise returns NULL_FILE. */
235   public static File validate(File f) {
236     if (f.exists()) return f;
237     return FileOption.NULL_FILE; // This File object exists
238
}
239   
240   /** This filter checks for files with names that end in ".java". (Note that while this filter was <em>intended</em>
241     * to be a {@code javax.swing.filechooser.FileFilter}, it actually implements a {@code java.io.FileFilter},
242     * because thats what {@code FileFilter} means in the context of this source file.)
243     * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#extensionFileFilter} instead. Example:
244     * {@code IOUtil.extensionFileFilter("java")}.
245     */

246   @Deprecated JavaDoc public static final FileFilter JAVA_FILE_FILTER = new FileFilter() {
247     public boolean accept(File f){
248       // Do this runaround for filesystems that are case preserving but case insensitive. Remove the last 5
249
// letters from the file name, append ".java" to the end, create a new file and see if its equivalent
250
// to the original
251
final StringBuilder JavaDoc name = new StringBuilder JavaDoc(f.getAbsolutePath());
252       String JavaDoc shortName = f.getName();
253       if (shortName.length() < 6) return false;
254       name.delete(name.length() - 5, name.length());
255       name.append(".java");
256       File test = new File(name.toString());
257       return (test.equals(f));
258     }
259     public String JavaDoc getDescription() { return "Java Source Files (*.java)"; }
260   };
261   
262   /** Reads the stream until it reaches EOF, and then returns the read contents as a byte array. This call may
263    * block, since it will not return until EOF has been reached.
264    * @param stream Input stream to read.
265    * @return Byte array consisting of all data read from stream.
266    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#toByteArray} instead, which provides the same functionality.
267    * Note that the {@code IOUtil} method will not close the {@code InputStream}, while this method does.
268    */

269   @Deprecated JavaDoc public static byte[] readStreamAsBytes(final InputStream stream) throws IOException {
270     BufferedInputStream buffered;
271
272     if (stream instanceof BufferedInputStream) buffered = (BufferedInputStream) stream;
273     else buffered = new BufferedInputStream(stream);
274
275     ByteArrayOutputStream out = new ByteArrayOutputStream();
276
277     int readVal = buffered.read();
278     while (readVal != -1) {
279       out.write(readVal);
280       readVal = buffered.read();
281     }
282
283     stream.close();
284     return out.toByteArray();
285   }
286
287   /** Reads the entire contents of a file and return them as a String.
288     * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#toString(File)} instead, which provides the same functionality.
289     */

290   @Deprecated JavaDoc public static String JavaDoc readFileAsString(final File file) throws IOException {
291     FileReader reader = new FileReader(file);
292     final StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
293
294     while (reader.ready()) {
295       char c = (char) reader.read();
296       buf.append(c);
297     }
298
299     reader.close();
300     return buf.toString();
301   }
302   
303   /** Copies the text of one file into another.
304    * @param source the file to be copied
305    * @param dest the file to be copied to
306    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#copyFile} instead, which provides the same functionality
307    * but scales in a much more efficient manner.
308    */

309   @Deprecated JavaDoc public static void copyFile(File source, File dest) throws IOException {
310     String JavaDoc text = readFileAsString(source);
311     writeStringToFile(dest, text);
312   }
313
314   /** Creates a new temporary file and writes the given text to it. The file will be deleted on exit.
315    * @param prefix Beginning part of file name, before unique number
316    * @param suffix Ending part of file name, after unique number
317    * @param text Text to write to file
318    * @return name of the temporary file that was created
319    * @deprecated Instead, create a temp file with {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempFile(String, String)},
320    * then write to it with {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String)}.
321    */

322   @Deprecated JavaDoc public static File writeStringToNewTempFile(final String JavaDoc prefix, final String JavaDoc suffix, final String JavaDoc text)
323     throws IOException {
324     
325     File file = File.createTempFile(prefix, suffix);
326     file.deleteOnExit();
327     writeStringToFile(file, text);
328     return file;
329   }
330
331   /** Writes text to the file overwriting whatever was there.
332    * @param file File to write to
333    * @param text Test to write
334    * @deprecated Use the equivalent {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String)} instead
335    */

336   @Deprecated JavaDoc public static void writeStringToFile(File file, String JavaDoc text) throws IOException {
337     writeStringToFile(file, text, false);
338   }
339   
340   /** Writes text to the file.
341    * @param file File to write to
342    * @param text Text to write
343    * @param append whether to append. (false=overwrite)
344    * @deprecated Use the equivalent {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String, boolean)} instead
345    */

346   @Deprecated JavaDoc public static void writeStringToFile(File file, String JavaDoc text, boolean append) throws IOException {
347     FileWriter writer = new FileWriter(file, append);
348     writer.write(text);
349     writer.close();
350   }
351   
352   /** Writes the text to the given file returning true if it happend and false if it could not. This is a
353    * simple wrapper for writeStringToFile that doesn't throw an IOException.
354    * @param file File to write to
355    * @param text Text to write
356    * @param append whether to append. (false=overwrite)
357    * @deprecated Use the equivalent {@link edu.rice.cs.plt.io.IOUtil#attemptWriteStringToFile(File, String, boolean)}
358    * instead
359    */

360   @Deprecated JavaDoc public static boolean writeIfPossible(File file, String JavaDoc text, boolean append) {
361     try {
362       writeStringToFile(file, text, append);
363       return true;
364     }
365     catch(IOException e) { return false; }
366   }
367   
368   /** Create a new temporary directory. The directory will be deleted on exit, if empty.
369    * (To delete it recursively on exit, use deleteDirectoryOnExit.)
370    * @param name Non-unique portion of the name of the directory to create.
371    * @return File representing the directory that was created.
372    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempDirectory(String, String)} instead.
373    * Example: {@code IOUtil.createAndMarkTempDirectory(name, "")}.
374    */

375   @Deprecated JavaDoc public static File createTempDirectory(final String JavaDoc name) throws IOException {
376     return createTempDirectory(name, null);
377   }
378
379   /** Create a new temporary directory. The directory will be deleted on exit, if it only contains temp files and temp
380    * directories created after it. (To delete it on exit regardless of contents, call deleteDirectoryOnExit after
381    * constructing the file tree rooted at this directory. Note that createTempDirectory(..) is not much more helpful
382    * than mkdir() in this context (other than generating a new temp file name) because cleanup is a manual process.)
383    * @param name Non-unique portion of the name of the directory to create.
384    * @param parent Parent directory to contain the new directory
385    * @return File representing the directory that was created.
386    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempDirectory(String, String, File)} instead.
387    * Example: {@code IOUtil.createAndMarkTempDirectory(name, "", parent)}.
388    */

389   @Deprecated JavaDoc public static File createTempDirectory(final String JavaDoc name, final File parent) throws IOException {
390     File file = File.createTempFile(name, "", parent);
391     file.delete();
392     file.mkdir();
393     file.deleteOnExit();
394
395     return file;
396   }
397
398   /** Delete the given directory including any files and directories it contains.
399    * @param dir File object representing directory to delete. If, for some reason, this file object is not a
400    * directory, it will still be deleted.
401    * @return true if there were no problems in deleting. If it returns false, something failed and the directory
402    * contents likely at least partially still exist.
403    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#deleteRecursively} instead
404    */

405   @Deprecated JavaDoc public static boolean deleteDirectory(final File dir) {
406 // System.err.println("Deleting file or directory " + dir);
407
if (! dir.isDirectory()) {
408       boolean res;
409       res = dir.delete();
410 // System.err.println("Deletion of " + dir + " returned " + res);
411
return res;
412     }
413
414     boolean ret = true;
415     File[] childFiles = dir.listFiles();
416     if (childFiles!=null) { // listFiles may return null if there's an IO error
417
for (File f: childFiles) { ret = ret && deleteDirectory(f); }
418     }
419     
420     // Now we should have an empty directory
421
ret = ret && dir.delete();
422 // System.err.println("Recursive deletion of " + dir + " returned " + ret);
423
return ret;
424   }
425   
426   /** Instructs Java to recursively delete the given directory and its contents when the JVM exits.
427    * @param dir File object representing directory to delete. If, for some reason, this file object is not a
428    * directory, it will still be deleted.
429    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#deleteOnExitRecursively} instead
430    */

431   @Deprecated JavaDoc public static void deleteDirectoryOnExit(final File dir) {
432
433     // Delete this on exit, whether it's a directory or file
434
_log.log("Deleting file/directory " + dir + " on exit");
435     dir.deleteOnExit();
436     
437     // If it's a directory, visit its children. This recursive walk has to be done AFTER calling deleteOnExit
438
// on the directory itself because Java closes the list of files to deleted on exit in reverse order.
439
if (dir.isDirectory()) {
440       File[] childFiles = dir.listFiles();
441       if (childFiles != null) { // listFiles may return null if there's an IO error
442
for (File f: childFiles) { deleteDirectoryOnExit(f); }
443       }
444     }
445   }
446
447   /** This function starts from the given directory and finds all packages within that directory
448    * @param prefix the package name of files in the given root
449    * @param root the directory to start exploring from
450    * @return a list of valid packages, excluding the root ("") package
451    */

452   public static LinkedList<String JavaDoc> packageExplore(String JavaDoc prefix, File root) {
453     /* Inner holder class. */
454     class PrefixAndFile {
455       public String JavaDoc prefix;
456       public File root;
457       public PrefixAndFile(String JavaDoc prefix, File root) {
458         this.root = root;
459         this.prefix = prefix;
460       }
461     }
462     
463     // This set makes sure we don't get caught in a loop if the filesystem has symbolic links
464
// that form a circle by tracking the directories we have already explored
465
final Set<File> exploredDirectories = new HashSet<File>();
466
467     LinkedList<String JavaDoc> output = new LinkedList<String JavaDoc>();
468     Stack<PrefixAndFile> working = new Stack<PrefixAndFile>();
469     working.push(new PrefixAndFile(prefix, root));
470     exploredDirectories.add(root);
471
472     // This filter allows only directories, and accepts each directory only once
473
FileFilter directoryFilter = new FileFilter(){
474       public boolean accept(File f){
475         boolean toReturn = f.isDirectory() && ! exploredDirectories.contains(f);
476         exploredDirectories.add(f);
477         return toReturn;
478       }
479       public String JavaDoc getDescription() { return "All Folders"; }
480     };
481
482     // Explore each directory, adding (unique) subdirectories to the working list. If a directory has .java
483
// files, add the associated package to the list of packages
484
while (! working.empty()) {
485       PrefixAndFile current = working.pop();
486       File [] subDirectories = current.root.listFiles(directoryFilter);
487       if (subDirectories!=null) { // listFiles may return null if there's an IO error
488
for (File dir: subDirectories) {
489           PrefixAndFile paf;
490 // System.out.println("exploring " + dir);
491
if (current.prefix.equals("")) paf = new PrefixAndFile(dir.getName(), dir);
492           else paf = new PrefixAndFile(current.prefix + "." + dir.getName(), dir);
493           working.push(paf);
494         }
495       }
496       File [] javaFiles = current.root.listFiles(JAVA_FILE_FILTER);
497
498       if (javaFiles!=null) { // listFiles may return null if there's an IO error
499
//Only add package names if they have java files and are not the root package
500
if (javaFiles.length != 0 && !current.prefix.equals("")) {
501           output.add(current.prefix);
502 // System.out.println("adding " + current.prefix);
503
}
504       }
505     }
506     return output;
507   }
508
509   /** Renames the given file to the given destination. Needed since Windows will not allow a rename to
510    * overwrite an existing file.
511    * @param file the file to rename
512    * @param dest the destination file
513    * @return true iff the rename was successful
514    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptMove}, which is equally Windows-friendly, instead.
515    */

516   @Deprecated JavaDoc public static boolean renameFile(File file, File dest) {
517     if (dest.exists()) dest.delete();
518     return file.renameTo(dest);
519   }
520
521   /** This method writes files correctly; it takes care of catching errors and
522    * making backups and keeping an unsuccessful file save from destroying the old
523    * file (unless a backup is made). It makes sure that the file to be saved is
524    * not read-only, throwing an IOException if it is. Note: if saving fails and a
525    * backup was being created, any existing backup will be destroyed (this is
526    * because the backup is written before saving begins, and then moved back over
527    * the original file when saving fails). As the old backup would have been destroyed
528    * anyways if saving had succeeded, I do not think that this is incorrect or
529    * unreasonable behavior.
530    * @param fileSaver keeps track of the name of the file to write, whether to back up the file, and has
531    * a method that actually performs the writing of the file
532    * @throws IOException if the saving or backing up of the file fails for any reason
533    */

534   public static void saveFile(FileSaver fileSaver) throws IOException {
535     
536 // ScrollableDialog sd1 = new ScrollableDialog(null, "saveFile (" + fileSaver + ") called in FileOps.java", "", "");
537
// sd1.show();
538
boolean makeBackup = fileSaver.shouldBackup();
539     boolean success = false;
540     File file = fileSaver.getTargetFile();
541     File backup = null;
542     boolean tempFileUsed = true;
543     // file.canWrite() is false if file.exists() is false
544
// but we want to be able to save a file that doesn't
545
// yet exist.
546
if (file.exists() && !file.canWrite()) throw new IOException("Permission denied");
547     /* First back up the file, if necessary */
548     if (makeBackup) {
549       backup = fileSaver.getBackupFile();
550       if (!renameFile(file, backup)){
551         throw new IOException("Save failed. Could not create backup file "
552                                 + backup.getAbsolutePath() +
553                               "\nIt may be possible to save by disabling file backups\n");
554       }
555       fileSaver.backupDone();
556     }
557     
558 // ScrollableDialog sd2 = new ScrollableDialog(null, "backup done in FileOps.saveFile", "", "");
559
// sd2.show();
560

561     //Create a temp file in the same directory as the file to be saved.
562
//From this point forward, enclose in try...finally so that we can clean
563
//up the temp file and restore the file from its backup.
564
File parent = file.getParentFile();
565     File tempFile = File.createTempFile("drjava", ".temp", parent);
566     
567 // ScrollableDialog sd3 = new ScrollableDialog(null, "temp file " + tempFile + "created in FileOps.saveFile", "", "");
568
// sd3.show();
569

570     try {
571       /* Now, write your output to the temp file, then rename it to the correct
572        name. This way, if writing fails in the middle, the old file is not
573        lost. */

574       FileOutputStream fos;
575       try {
576         /* The next line will fail if we can't create the temp file. This may mean that
577          * the user does not have write permission on the directory the file they
578          * are editing is in. We may want to go ahead and try writing directly
579          * to the target file in this case
580          */

581         fos = new FileOutputStream(tempFile);
582       }
583       catch (FileNotFoundException fnfe) {
584         if (fileSaver.continueWhenTempFileCreationFails()) {
585           fos = new FileOutputStream(file);
586           tempFileUsed = false;
587         }
588         else throw new IOException("Could not create temp file " + tempFile + " in attempt to save " + file);
589       }
590       BufferedOutputStream bos = new BufferedOutputStream(fos);
591       fileSaver.saveTo(bos);
592       bos.close();
593       fos.close();
594
595       if (tempFileUsed && !renameFile(tempFile, file))
596         throw new IOException("Save failed. Another process may be using " + file + ".");
597
598       success = true;
599     }
600     finally {
601 // ScrollableDialog sd4 = new ScrollableDialog(null, "finally clause reached in FileOps.saveFile", "", "");
602
// sd4.show();
603

604       if (tempFileUsed) tempFile.delete(); /* Delete the temp file */
605         
606       if (makeBackup) {
607         /* On failure, attempt to move the backup back to its original location if we
608          made one. On success, register that a backup was successfully made */

609         if (success) fileSaver.backupDone();
610         else renameFile(backup, file);
611       }
612     }
613   }
614
615   public interface FileSaver {
616     
617     /** This method tells what to name the backup of the file, if a backup is to be made.
618      * It may depend on getTargetFile(), so it can thrown an IOException
619      */

620     public abstract File getBackupFile() throws IOException;
621     
622     /** This method indicates whether or not a backup of the file should be made. It
623      * may depend on getTargetFile(), so it can throw an IOException
624      */

625     public abstract boolean shouldBackup() throws IOException;
626
627     /** This method specifies if the saving process should continue trying to save
628      * if it can not create the temp file that is written initially. If you do
629      * continue saving in this case, the original file may be lost if saving fails.
630      */

631     public abstract boolean continueWhenTempFileCreationFails();
632     
633     /** This method is called to tell the file saver that a backup was successfully made. */
634     public abstract void backupDone();
635
636     /**
637      * This method actually writes info to a file. NOTE: It is important that this
638      * method write to the stream it is passed, not the target file. If you write
639      * directly to the target file, the target file will be destroyed if saving fails.
640      * Also, it is important that when saving fails this method throw an IOException
641      * @throws IOException when saving fails for any reason
642      */

643     public abstract void saveTo(OutputStream os) throws IOException;
644
645     /** This method tells what the file is that we want to save to. It should
646      * use the canonical name of the file (this means resolving symlinks). Otherwise,
647      * the saver would not deal correctly with symlinks. Resolving symlinks may cause
648      * an IOException, so this method throws an IOException.
649      */

650     public abstract File getTargetFile() throws IOException;
651   }
652
653   /** This class is a default implementation of FileSaver that makes only 1 backup
654    * of each file per instantiation of the program (following Emacs' lead). It
655    * backs up to files named <file>~. It does not implement the saveTo method.
656    */

657   public abstract static class DefaultFileSaver implements FileSaver {
658
659     private File outputFile = null;
660     private static Set<File> filesNotNeedingBackup = new HashSet<File>();
661     private static boolean backupsEnabled = true;
662
663     /** This field keeps track of whether or not outputFile has been resolved to its canonical name. */
664     private boolean isCanonical = false;
665     
666     /** Globally enables backups for any DefaultFileSaver that does not override the shouldBackup method. */
667     public static void setBackupsEnabled(boolean isEnabled) { backupsEnabled = isEnabled; }
668     
669     public DefaultFileSaver(File file){ outputFile = file.getAbsoluteFile(); }
670     
671     public boolean continueWhenTempFileCreationFails(){ return true; }
672     
673     public File getBackupFile() throws IOException{ return new File(getTargetFile().getPath() + "~"); }
674
675     public boolean shouldBackup() throws IOException{
676       if (!backupsEnabled) return false;
677       if (!getTargetFile().exists()) return false;
678       if (filesNotNeedingBackup.contains(getTargetFile())) return false;
679       return true;
680     }
681     
682     public void backupDone() {
683       try { filesNotNeedingBackup.add(getTargetFile()); }
684       catch (IOException ioe) { throw new UnexpectedException(ioe, "getTargetFile should fail earlier"); }
685     }
686
687     public File getTargetFile() throws IOException{
688       if (!isCanonical) {
689         outputFile = outputFile.getCanonicalFile();
690         isCanonical = true;
691       }
692       return outputFile;
693     }
694   }
695   
696   /** Convert all path entries in a path string to absolute paths. The delimiter in the path string is the
697    * "path.separator" property. Empty entries are equivalent to "." and will thus are converted to the
698    * "user.dir" (working directory).
699    * Example:
700    * ".:drjava::/home/foo/junit.jar" with "user.dir" set to "/home/foo/bar" will be converted to
701    * "/home/foo/bar:/home/foo/bar/drjava:/home/foo/bar:/home/foo/junit.jar".
702    *
703    * @param path path string with entries to convert
704    * @return path string with all entries as absolute paths
705    * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#parsePath}, {@link edu.rice.cs.plt.io.IOUtil#getAbsoluteFiles},
706    * {@link edu.rice.cs.plt.io.IOUtil#attemptAbsoluteFiles}, and {@link edu.rice.cs.plt.io.IOUtil#pathToString},
707    * as needed, instead.
708    */

709   @Deprecated JavaDoc public static String JavaDoc convertToAbsolutePathEntries(String JavaDoc path) {
710     String JavaDoc pathSep = System.getProperty("path.separator");
711     
712     // split leaves off trailing empty strings
713
// (see API javadocs: "Trailing empty strings are therefore not included in the resulting array.")
714
// we therefore append one element at the end and later remove it
715
path += pathSep + "x";
716     
717     // now path ends with ":x", so we'll have an additional "x" element in the pathEntries array
718

719     // split up the path into individual entries, turn all of the entries
720
// into absolute paths, and put the path back together
721
// EXCEPT for the last item in the array, because that's the "x" we added
722
String JavaDoc[] pathEntries = path.split(pathSep);
723     final StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
724     for(int i = 0; i<pathEntries.length - 1; ++i) { // length-1 to ignore the last element
725
File f = new File(pathEntries[i]);
726       sb.append(f.getAbsolutePath());
727       sb.append(pathSep);
728     }
729     String JavaDoc reconstructedPath = sb.toString();
730     
731     // if the reconstructed path is non-empty, then it will have an extra
732
// path separator at the end; take it off
733
if (reconstructedPath.length()!=0) {
734       reconstructedPath = reconstructedPath.substring(0, reconstructedPath.length() - 1);
735     }
736     
737     return reconstructedPath;
738   }
739   
740   /** Return a valid directory for use, i.e. one that exists and is as "close" to the file specified. It is
741     * 1) file, if file is a directory and exists
742     * 2) the closest parent of file, if file is not a directory or does not exist
743     * 3) "user.home"
744     * @return a valid directory for use */

745   public static File getValidDirectory(File file) {
746     // if it's the NULL_FILE or null, use "user.home"
747
if ((file==FileOption.NULL_FILE)||(file==null)) {
748       file = new File(System.getProperty("user.home"));
749     }
750     while (!file.exists()) {
751       // if the saved path doesn't exist anymore, try the parent
752
file = file.getParentFile();
753     }
754     if (file==null) {
755       // somehow we ended up with null, use "user.home"
756
file = new File(System.getProperty("user.home"));
757     }
758     // if it's not a directory, try the parent
759
if (!file.isDirectory()) {
760       if (file.getParent() != null) file = file.getParentFile();
761     }
762     
763     // this should be an existing directory now
764
if (file.exists() && file.isDirectory()) {
765       return file;
766     }
767     
768     // ye who enter here, abandon all hope...
769
// the saved path didn't work, and neither did "user.home"
770
throw new UnexpectedException(new IOException("File's parent file is null"));
771   }
772   
773   /** Converts the abstract pathname for f into a URL. This method is included in class java.io.File as f.toURL(), but
774     * has been deprecated in Java 6.0 because escape characters on some systems are not handled correctly. We are not
775     * aware of any problems in DrJava due to this issue.
776     * Unfortunately, the specified workaround, using f.toURI().toURL() instead of f.toURL() breaks DrJava, presumably
777     * because of a bug in the implemenation of the methods File.toURI() and/or URI.toURL(). This method localizes the
778     * warning messages generated by the Java 6.0 compiler.
779     */

780   public static URL JavaDoc toURL(File f) throws MalformedURLException JavaDoc { return f.toURL(); }
781 }
782
Popular Tags