|
|||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
FileOps.java | 47.7% | 54.6% | 48.2% | 51.9% |
|
1 | /*BEGIN_COPYRIGHT_BLOCK | |
2 | * | |
3 | * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu) | |
4 | * All rights reserved. | |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions are met: | |
8 | * * Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * * Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the | |
14 | * names of its contributors may be used to endorse or promote products | |
15 | * derived from this software without specific prior written permission. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
21 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | * | |
29 | * This software is Open Source Initiative approved Open Source Software. | |
30 | * Open Source Initative Approved is a trademark of the Open Source Initiative. | |
31 | * | |
32 | * This file is part of DrJava. Download the current version of this project | |
33 | * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/ | |
34 | * | |
35 | * END_COPYRIGHT_BLOCK*/ | |
36 | ||
37 | package edu.rice.cs.util; | |
38 | ||
39 | import java.io.*; | |
40 | import java.net.MalformedURLException; | |
41 | import java.net.URL; | |
42 | import java.util.*; | |
43 | import java.util.jar.*; | |
44 | ||
45 | import edu.rice.cs.drjava.DrJava; | |
46 | import edu.rice.cs.util.FileOps; | |
47 | import edu.rice.cs.util.Log; | |
48 | import edu.rice.cs.plt.io.IOUtil; | |
49 | import edu.rice.cs.plt.text.TextUtil; | |
50 | ||
51 | import static edu.rice.cs.drjava.config.OptionConstants.*; | |
52 | ||
53 | /** A class to provide some convenient file operations as static methods. | |
54 | * It's abstract to prevent (useless) instantiation, though it can be subclassed | |
55 | * to provide convenient namespace importation of its methods. | |
56 | * | |
57 | * @version $Id: FileOps.java 5439 2011-08-11 17:13:04Z rcartwright $ | |
58 | */ | |
59 | public abstract class FileOps { | |
60 | ||
61 | private static Log _log = new Log("FileOpsTest.txt", false); | |
62 | ||
63 | /** A singleton null file class. There is a separate NullFile class in this package. TODO: merge these two classes. | |
64 | * This class is used for all NullFile.ONLY references while the other is used for distinct untitled documents. | |
65 | * Both appear to define the same notion of equality. | |
66 | */ | |
67 | public static class NullFile extends File { | |
68 | ||
69 | public static final NullFile ONLY = new NullFile(); | |
70 | ||
71 | 138 | private NullFile() { super(""); } |
72 | 0 | public boolean canRead() { return false; } |
73 | 0 | public boolean canWrite() { return false; } |
74 | 0 | public int compareTo(File f) { return (f == this) ? 0 : -1; } |
75 | 0 | public boolean createNewFile() { return false; } |
76 | 0 | public boolean delete() { return false; } |
77 | 0 | public void deleteOnExit() { } |
78 | 610 | public boolean equals(Object o) { return o == this; } |
79 | 38 | public boolean exists() { return false; } |
80 | 0 | public int hashCode() { return getClass().hashCode(); } |
81 | 51 | public File getAbsoluteFile() { return this; } |
82 | 1165 | public String getAbsolutePath() { return ""; } |
83 | 9 | public File getCanonicalFile() { return this; } |
84 | 0 | public String getCanonicalPath() { return ""; } |
85 | 0 | public String getName() { return ""; } |
86 | 0 | public String getParent() { return null; } |
87 | 7 | public File getParentFile() { return null; } |
88 | 156 | public String getPath() { return ""; } |
89 | 0 | public boolean isAbsolute() { return false; } |
90 | 10 | public boolean isDirectory() { return false; } |
91 | 0 | public boolean isFile() { return false; } |
92 | 0 | public boolean isHidden() { return false; } |
93 | 0 | public long lastModified() { return 0L; } |
94 | 0 | public long length() { return 0L; } |
95 | 0 | public String[] list() { return null; } |
96 | 0 | public String[] list(FilenameFilter filter) { return null; } |
97 | 0 | public File[] listFiles() { return null; } |
98 | 0 | public File[] listFiles(FileFilter filter) { return null; } |
99 | 0 | public File[] listFiles(FilenameFilter filter) { return null; } |
100 | 0 | public boolean mkdir() { return false; } |
101 | 0 | public boolean mkdirs() { return false; } |
102 | 0 | public boolean renameTo(File dest) { return false; } |
103 | 0 | public boolean setLastModified(long time) { return false; } |
104 | 0 | public boolean setReadOnly() { return false; } |
105 | 331 | public String toString() { return ""; } |
106 | //public URI toURI() {} (Defer to super implementation.) | |
107 | //public URL toURL() {} (Defer to super implementation.) | |
108 | }; | |
109 | ||
110 | /** Special sentinal file used in FileOption and test classes among others. */ | |
111 | public static final File NULL_FILE = NullFile.ONLY; | |
112 | ||
113 | /** @deprecated For a best-attempt canonical file, use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile} instead. | |
114 | * (for example, {@code IOUtil.attemptCanonicalFile(new File(path))}) | |
115 | */ | |
116 | 0 | @Deprecated public static File makeFile(String path) { |
117 | 0 | File f = new File(path); |
118 | 0 | try { return f.getCanonicalFile(); } |
119 | 0 | catch(IOException e) { return f; } |
120 | } | |
121 | ||
122 | /** @deprecated For a best-attempt canonical file, use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile} instead. | |
123 | * (for example, {@code IOUtil.attemptCanonicalFile(new File(path))}) | |
124 | */ | |
125 | 0 | @Deprecated public static File makeFile(File parentDir, String child) { |
126 | 0 | File f = new File(parentDir, child); |
127 | 0 | try { return f.getCanonicalFile(); } |
128 | 0 | catch(IOException e) { return f; } |
129 | } | |
130 | ||
131 | /** Determines whether the specified file in within the specified file tree. | |
132 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#isMember} instead. Note that the new method does not test for | |
133 | * {@code null} values and does not convert to canonical paths -- if these things are necessary, they | |
134 | * should be done before invoking the method. | |
135 | */ | |
136 | 0 | @Deprecated public static boolean inFileTree(File f, File root) { |
137 | 0 | if (root == null || f == null) return false; |
138 | 0 | try { |
139 | 0 | if (! f.isDirectory()) f = f.getParentFile(); |
140 | 0 | String filePath = f.getCanonicalPath() + File.separator; |
141 | 0 | String projectPath = root.getCanonicalPath() + File.separator; |
142 | 0 | return (filePath.startsWith(projectPath)); |
143 | } | |
144 | 0 | catch(IOException e) { return false; } |
145 | } | |
146 | ||
147 | /** Return true if the directory ancestor is an ancestor of the file f, i.e. | |
148 | * you can get from f to ancestor by using getParentFile zero or more times. | |
149 | * @param ancestor the ancestor | |
150 | * @param f the file to test | |
151 | * @return true if ancestor is an ancestor of f. */ | |
152 | 308 | public static boolean isAncestorOf(File ancestor, File f) { |
153 | 308 | ancestor = ancestor.getAbsoluteFile(); |
154 | 308 | f = f.getAbsoluteFile(); |
155 | 308 | _log.log("ancestor = " +ancestor + " f = " + f); |
156 | 308 | while ((!ancestor.equals(f)) && (f != null)) { |
157 | 1287 | f = f.getParentFile(); |
158 | } | |
159 | 308 | return (ancestor.equals(f)); |
160 | } | |
161 | ||
162 | /** Makes a file equivalent to the given file f that is relative to base file b. In other words, | |
163 | * <code>new File(b,makeRelativeTo(base,abs)).getCanonicalPath()</code> equals | |
164 | * <code>f.getCanonicalPath()</code> | |
165 | * | |
166 | * <p>In Linux/Unix, if the file f is <code>/home/username/folder/file.java</code> and the file b is | |
167 | * <code>/home/username/folder/sublevel/file2.java</code>, then the resulting File path from this method would be | |
168 | * <code>../file.java</code> while its canoncial path would be <code>/home/username/folder/file.java</code>.</p><p> | |
169 | * Warning: making paths relative is inherently broken on some file systems, because a relative path | |
170 | * requires that both the file and the base have the same root. The Windows file system, and therefore also | |
171 | * the Java file system model, however, support multiple system roots (see {@link File#listRoots}). | |
172 | * Thus, two files with different roots cannot have a relative path. In that case the absolute path of | |
173 | * the file will be returned</p> | |
174 | * @param f The path that is to be made relative to the base file | |
175 | * @param b The file to make the next file relative to | |
176 | * @return A new file whose path is relative to the base file while the value of <code>getCanonicalPath()</code> | |
177 | * for the returned file is the same as the result of <code>getCanonicalPath()</code> for the given file. | |
178 | */ | |
179 | 0 | public static File makeRelativeTo(File f, File b) throws IOException, SecurityException { |
180 | 0 | return new File(b, stringMakeRelativeTo(f,b)); |
181 | } | |
182 | ||
183 | /** Makes a file equivalent to the given file f that is relative to base file b. In other words, | |
184 | * <code>new File(b,makeRelativeTo(base,abs)).getCanonicalPath()</code> equals | |
185 | * <code>f.getCanonicalPath()</code> | |
186 | * | |
187 | * <p>In Linux/Unix, if the file f is <code>/home/username/folder/file.java</code> and the file b is | |
188 | * <code>/home/username/folder/sublevel/file2.java</code>, then the resulting File path from this method would be | |
189 | * <code>../file.java</code> while its canoncial path would be <code>/home/username/folder/file.java</code>.</p><p> | |
190 | * Warning: making paths relative is inherently broken on some file systems, because a relative path | |
191 | * requires that both the file and the base have the same root. The Windows file system, and therefore also | |
192 | * the Java file system model, however, support multiple system roots (see {@link File#listRoots}). | |
193 | * Thus, two files with different roots cannot have a relative path. In that case the absolute path of | |
194 | * the file will be returned</p> | |
195 | * @param f The path that is to be made relative to the base file | |
196 | * @param b The file to make the next file relative to | |
197 | * @return A new file whose path is relative to the base file while the value of <code>getCanonicalPath()</code> | |
198 | * for the returned file is the same as the result of <code>getCanonicalPath()</code> for the given file. | |
199 | */ | |
200 | 154 | public static String stringMakeRelativeTo(File f, File b) throws IOException /*, SecurityException */ { |
201 | 154 | try { |
202 | ||
203 | 154 | File[] roots = File.listRoots(); |
204 | 154 | File fRoot = null; |
205 | 154 | File bRoot = null; |
206 | 154 | for(File r: roots) { |
207 | 150 | if (isAncestorOf(r, f)) { fRoot = r; } |
208 | 151 | if (isAncestorOf(r, b)) { bRoot = r; } |
209 | 150 | if ((fRoot != null) && (bRoot != null)) { break; } |
210 | } | |
211 | ||
212 | //Makes exception for //server/folder | |
213 | 154 | if (((fRoot == null) || (!fRoot.equals(bRoot))) && (!f.getAbsoluteFile().getCanonicalFile().toString().startsWith(File.separator + File.separator))) { |
214 | ||
215 | 4 | return f.getAbsoluteFile().getCanonicalFile().toString(); |
216 | } | |
217 | } | |
218 | catch(Exception e) { /* ignore, follow previous procedure */ } | |
219 | ||
220 | 150 | File base = b.getCanonicalFile(); |
221 | 150 | File abs = f.getCanonicalFile(); // If f is relative, uses current working directory ("user.dir") |
222 | 30 | if (! base.isDirectory()) base = base.getParentFile(); |
223 | ||
224 | 150 | String last = ""; |
225 | 150 | if (! abs.isDirectory()) { |
226 | 123 | String tmp = abs.getPath(); |
227 | 123 | last = tmp.substring(tmp.lastIndexOf(File.separator) + 1); |
228 | 123 | abs = abs.getParentFile(); |
229 | } | |
230 | ||
231 | // System.err.println("makeRelativeTo called; f = " + f + " = " + abs + "; b = " + b + " = " + base); | |
232 | 150 | String[] basParts = splitFile(base); |
233 | 150 | String[] absParts = splitFile(abs); |
234 | ||
235 | 150 | final StringBuilder result = new StringBuilder(); |
236 | // loop until elements differ, record what part of absParts to append | |
237 | // next find out how many .. to put in. | |
238 | 150 | int diffIndex = -1; |
239 | 150 | boolean different = false; |
240 | 150 | for (int i = 0; i < basParts.length; i++) { |
241 | 581 | if (!different && ((i >= absParts.length) || !basParts[i].equals(absParts[i]))) { |
242 | 48 | different = true; |
243 | 48 | diffIndex = i; |
244 | } | |
245 | 95 | if (different) result.append("..").append(File.separator); |
246 | } | |
247 | 102 | if (diffIndex < 0) diffIndex = basParts.length; |
248 | 150 | for (int i = diffIndex; i < absParts.length; i++) { |
249 | 357 | result.append(absParts[i]).append(File.separator); |
250 | } | |
251 | 150 | result.append(last); |
252 | // System.err.println("makeRelativeTo(" + f + ", " + b + ") = " + result); | |
253 | 150 | return result.toString(); |
254 | } | |
255 | ||
256 | /** Splits a file into an array of strings representing each parent folder of the given file. The file whose path | |
257 | * is <code>/home/username/txt.txt</code> in linux would be split into the string array: | |
258 | * {"","home","username","txt.txt"}. Delimeters are excluded. | |
259 | * @param fileToSplit the file to split into its directories. | |
260 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#fullPath} instead. It returns a list of {@code File} | |
261 | * objects rather than strings, but they appear in the same order. | |
262 | */ | |
263 | 301 | @Deprecated public static String[] splitFile(File fileToSplit) { |
264 | 301 | String path = fileToSplit.getPath(); |
265 | 301 | ArrayList<String> list = new ArrayList<String>(); |
266 | 301 | while (! path.equals("")) { |
267 | 1428 | int idx = path.indexOf(File.separator); |
268 | 1428 | if (idx < 0) { |
269 | 301 | list.add(path); |
270 | 301 | path = ""; |
271 | } | |
272 | else { | |
273 | 1127 | list.add(path.substring(0,idx)); |
274 | 1127 | path = path.substring(idx + 1); |
275 | } | |
276 | } | |
277 | 301 | return list.toArray(new String[list.size()]); |
278 | } | |
279 | ||
280 | /** List all files (that is, {@code File}s for which {@code isFile()} is {@code true}) matching the provided filter in | |
281 | * the given directory. | |
282 | * @param d The directory to search. | |
283 | * @param recur Whether subdirectories accepted by {@code f} should be recursively searched. Note that | |
284 | * subdirectories that <em>aren't</em> accepted by {@code f} will be ignored. | |
285 | * @param f The filter to apply to contained {@code File}s. | |
286 | * @return An array of Files in the directory specified; if the directory does not exist, returns an empty list. | |
287 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptListFilesAsIterable} or | |
288 | * {@link edu.rice.cs.plt.io.IOUtil#listFilesRecursively(File, FileFilter, FileFilter)} instead. | |
289 | */ | |
290 | 2 | @Deprecated public static ArrayList<File> getFilesInDir(File d, boolean recur, FileFilter f) { |
291 | 2 | ArrayList<File> l = new ArrayList<File>(); |
292 | 2 | getFilesInDir(d, l, recur, f); |
293 | 2 | return l; |
294 | } | |
295 | ||
296 | /** Helper fuction for getFilesInDir(File d, boolean recur). {@code acc} is mutated to contain | |
297 | * a list of <c>File</c>s in the directory specified, not including directories. | |
298 | */ | |
299 | 3 | private static void getFilesInDir(File d, List<File> acc, boolean recur, FileFilter filter) { |
300 | 3 | if (d.isDirectory()) { |
301 | 3 | File[] files = d.listFiles(filter); |
302 | 3 | if (files != null) { // listFiles may return null if there's an IO error |
303 | 3 | for (File f: files) { |
304 | 1 | if (f.isDirectory() && recur) getFilesInDir(f, acc, recur, filter); |
305 | 3 | else if (f.isFile()) acc.add(f); |
306 | } | |
307 | } | |
308 | } | |
309 | } | |
310 | ||
311 | /** @return the canonical file equivalent to f. Identical to f.getCanonicalFile() except it does not throw an | |
312 | * exception when the file path syntax is incorrect (or an IOException or SecurityException occurs for any | |
313 | * other reason). It returns the absolute File intead. | |
314 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile} instead. | |
315 | */ | |
316 | 0 | @Deprecated public static File getCanonicalFile(File f) { |
317 | 0 | if (f == null) return f; |
318 | 0 | try { return f.getCanonicalFile(); } |
319 | catch (IOException e) { /* fall through */ } | |
320 | catch (SecurityException e) { /* fall through */ } | |
321 | 0 | return f.getAbsoluteFile(); |
322 | } | |
323 | ||
324 | /** @return the canonical path for f. Identical to f.getCanonicalPath() except it does not throw an | |
325 | * exception when the file path syntax is incorrect; it returns the absolute path instead. | |
326 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptCanonicalFile} instead. (The result will be a | |
327 | * {@code File} instead of a {@code String}.) | |
328 | */ | |
329 | 0 | @Deprecated public static String getCanonicalPath(File f) { return getCanonicalFile(f).getPath(); } |
330 | ||
331 | /** @return the file f unchanged if f exists; otherwise returns NULL_FILE. */ | |
332 | 13 | public static File validate(File f) { |
333 | 13 | if (f.exists()) return f; |
334 | 0 | return FileOps.NULL_FILE; // This File object exists |
335 | } | |
336 | ||
337 | /** This filter checks for files with names that end in ".java". (Note that while this filter was <em>intended</em> | |
338 | * to be a {@code javax.swing.filechooser.FileFilter}, it actually implements a {@code java.io.FileFilter}, because | |
339 | * that is what {@code FileFilter} means in the context of this source file.) | |
340 | */ | |
341 | @Deprecated public static final FileFilter JAVA_FILE_FILTER = new FileFilter() { | |
342 | 12 | public boolean accept(File f){ |
343 | // Do this runaround for filesystems that are case preserving but case insensitive. Remove the last 5 | |
344 | // letters from the file name, append ".java" to the end, create a new file and see if its equivalent | |
345 | // to the original | |
346 | 12 | final StringBuilder name = new StringBuilder(f.getAbsolutePath()); |
347 | 12 | String shortName = f.getName(); |
348 | 4 | if (shortName.length() < 6) return false; |
349 | 8 | name.delete(name.length() - 5, name.length()); |
350 | 8 | name.append(".java"); |
351 | 8 | File test = new File(name.toString()); |
352 | 8 | return (test.equals(f)); |
353 | } | |
354 | /* The following method is commented out because it was inaccessible. */ | |
355 | // public String getDescription() { return "Java Source Files (*.java)"; } | |
356 | }; | |
357 | ||
358 | /** Reads the stream until it reaches EOF, and then returns the read contents as a byte array. This call may block, | |
359 | * since it will not return until EOF has been reached. | |
360 | * @param stream Input stream to read. | |
361 | * @return Byte array consisting of all data read from stream. | |
362 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#toByteArray} instead. Note that the {@code IOUtil} method will | |
363 | * not close the {@code InputStream}, while this method does. | |
364 | */ | |
365 | 0 | @Deprecated public static byte[] readStreamAsBytes(final InputStream stream) throws IOException { |
366 | 0 | BufferedInputStream buffered; |
367 | ||
368 | 0 | if (stream instanceof BufferedInputStream) buffered = (BufferedInputStream) stream; |
369 | 0 | else buffered = new BufferedInputStream(stream); |
370 | ||
371 | 0 | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
372 | ||
373 | 0 | int readVal = buffered.read(); |
374 | 0 | while (readVal != -1) { |
375 | 0 | out.write(readVal); |
376 | 0 | readVal = buffered.read(); |
377 | } | |
378 | ||
379 | 0 | stream.close(); |
380 | 0 | return out.toByteArray(); |
381 | } | |
382 | ||
383 | /** Reads the entire contents of a file and return them as canonicalized Swing Document text. All newLine sequences, | |
384 | * including "\n", "\r", and "\r\n" are converted to "\n". Characters below 32, except for newlines, are changed to spaces. */ | |
385 | 403 | public static String readFileAsSwingText(final File file) throws IOException { |
386 | 403 | FileReader reader = null; |
387 | 403 | try { |
388 | 403 | reader = new FileReader(file); |
389 | 76 | final StringBuilder buf = new StringBuilder(); |
390 | ||
391 | 76 | char pred = (char) 0; // initialize as null character |
392 | 76 | while (reader.ready()) { |
393 | 2188 | char c = (char) reader.read(); |
394 | ||
395 | 2188 | if (c == '\n' && pred == '\r') { } // do nothing ignoring second character of "\r\n"; |
396 | 0 | else if (c == '\r') buf.append('\n'); |
397 | 0 | else if ((c < 32) && (c != '\n')) buf.append(' '); |
398 | 2188 | else buf.append(c); |
399 | ||
400 | 2188 | pred = c; |
401 | } | |
402 | 76 | return buf.toString(); |
403 | } | |
404 | 76 | finally { if (reader != null) reader.close(); } |
405 | } | |
406 | ||
407 | /** Reads the entire contents of a file and return them as a String. | |
408 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#toString(File)} instead, which provides the same functionality. | |
409 | */ | |
410 | 12 | @Deprecated public static String readFileAsString(final File file) throws IOException { |
411 | 12 | FileReader reader = null; |
412 | 12 | try { |
413 | 12 | reader = new FileReader(file); |
414 | 10 | final StringBuilder buf = new StringBuilder(); |
415 | ||
416 | 10 | while (reader.ready()) { |
417 | 90 | char c = (char) reader.read(); |
418 | 90 | buf.append(c); |
419 | } | |
420 | 10 | return buf.toString(); |
421 | } | |
422 | 10 | finally { if (reader != null) reader.close(); } |
423 | } | |
424 | ||
425 | /** Copies the text of one file into another. | |
426 | * @param source the file to be copied | |
427 | * @param dest the file to be copied to | |
428 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#copyFile} instead; it scales in a much more efficiently. | |
429 | */ | |
430 | 0 | @Deprecated public static void copyFile(File source, File dest) throws IOException { |
431 | 0 | String text = readFileAsString(source); |
432 | 0 | writeStringToFile(dest, text); |
433 | } | |
434 | ||
435 | /** Creates a new temporary file and writes the given text to it. The file will be deleted on exit. | |
436 | * @param prefix Beginning part of file name, before unique number | |
437 | * @param suffix Ending part of file name, after unique number | |
438 | * @param text Text to write to file | |
439 | * @return name of the temporary file that was created | |
440 | * @deprecated Instead, create a temp file with {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempFile(String, String)}, | |
441 | * then write to it with {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String)}. | |
442 | */ | |
443 | 1 | @Deprecated public static File writeStringToNewTempFile(final String prefix, final String suffix, final String text) |
444 | throws IOException { | |
445 | ||
446 | 1 | File file = File.createTempFile(prefix, suffix); |
447 | 1 | file.deleteOnExit(); |
448 | 1 | writeStringToFile(file, text); |
449 | 1 | return file; |
450 | } | |
451 | ||
452 | /** Writes text to the file overwriting whatever was there. | |
453 | * @param file File to write to | |
454 | * @param text Test to write | |
455 | * @deprecated Use the equivalent {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String)} instead | |
456 | */ | |
457 | 14 | @Deprecated public static void writeStringToFile(File file, String text) throws IOException { |
458 | 14 | writeStringToFile(file, text, false); |
459 | } | |
460 | ||
461 | /** Writes text to the file. | |
462 | * @param file File to write to | |
463 | * @param text Text to write | |
464 | * @param append whether to append. (false=overwrite) | |
465 | * @deprecated Use the equivalent {@link edu.rice.cs.plt.io.IOUtil#writeStringToFile(File, String, boolean)} instead | |
466 | */ | |
467 | 14 | @Deprecated public static void writeStringToFile(File file, String text, boolean append) throws IOException { |
468 | 14 | FileWriter writer = null; |
469 | 14 | try { |
470 | 14 | writer = new FileWriter(file, append); |
471 | 14 | writer.write(text); |
472 | } | |
473 | 14 | finally { if (writer != null) writer.close(); } |
474 | } | |
475 | ||
476 | /** Writes text to the given file returning true if it succeeded and false if not. This is a simple wrapper for | |
477 | * writeStringToFile that doesn't throw an IOException. | |
478 | * @param file File to write to | |
479 | * @param text Text to write | |
480 | * @param append Whether to append. (false=overwrite) | |
481 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptWriteStringToFile(File, String, boolean)} instead. | |
482 | */ | |
483 | 0 | @Deprecated public static boolean writeIfPossible(File file, String text, boolean append) { |
484 | 0 | try { |
485 | 0 | writeStringToFile(file, text, append); |
486 | 0 | return true; |
487 | } | |
488 | 0 | catch(IOException e) { return false; } |
489 | } | |
490 | ||
491 | /** Create a new temporary directory. The directory will be deleted on exit, if empty. | |
492 | * (To delete it recursively on exit, use deleteDirectoryOnExit.) | |
493 | * @param name Non-unique portion of the name of the directory to create. | |
494 | * @return File representing the directory that was created. | |
495 | */ | |
496 | // * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempDirectory(String, String)} instead. | |
497 | // * Example: {@code IOUtil.createAndMarkTempDirectory(name, "")}. | |
498 | ||
499 | 126 | /* @Deprecated */ public static File createTempDirectory(final String name) throws IOException { |
500 | 126 | return createTempDirectory(name, null); |
501 | } | |
502 | ||
503 | /** Create a new temporary directory. The directory will be deleted on exit, if it only contains temp files and temp | |
504 | * directories created after it. (To delete it on exit regardless of contents, call deleteDirectoryOnExit after | |
505 | * constructing the file tree rooted at this directory. Note that createTempDirectory(..) is not much more helpful | |
506 | * than mkdir() in this context (other than generating a new temp file name) because cleanup is a manual process.) | |
507 | * @param name Non-unique portion of the name of the directory to create. | |
508 | * @param parent Parent directory to contain the new directory | |
509 | * @return File representing the directory that was created. | |
510 | */ | |
511 | // * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#createAndMarkTempDirectory(String, String, File)} instead. | |
512 | // * Example: {@code IOUtil.createAndMarkTempDirectory(name, "", parent)}. | |
513 | ||
514 | 127 | /* @Deprecated */ public static File createTempDirectory(/* final */ String name, /* final */ File parent) throws IOException { |
515 | 127 | File result = File.createTempFile(name, "", parent); |
516 | 127 | boolean success = result.delete(); |
517 | 127 | success = success && result.mkdir(); |
518 | 0 | if (! success) { throw new IOException("Attempt to create directory failed"); } |
519 | 127 | IOUtil.attemptDeleteOnExit(result); |
520 | 127 | return result; |
521 | // File file = File.createTempFile(name, "", parent); | |
522 | // file.delete(); | |
523 | // file.mkdir(); | |
524 | // file.deleteOnExit(); | |
525 | // | |
526 | // return file; | |
527 | } | |
528 | ||
529 | /** Delete the given directory including any files and directories it contains. | |
530 | * @param dir File object representing directory to delete. If, for some reason, this file object is not a | |
531 | * directory, it will still be deleted. | |
532 | * @return true if there were no problems in deleting. If it returns false, something failed and the directory | |
533 | * contents likely at least partially still exist. | |
534 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#deleteRecursively} instead | |
535 | */ | |
536 | 38 | @Deprecated public static boolean deleteDirectory(final File dir) { |
537 | // System.err.println("Deleting file or directory " + dir); | |
538 | 38 | if (! dir.isDirectory()) { |
539 | 22 | boolean res; |
540 | 22 | res = dir.delete(); |
541 | // System.err.println("Deletion of " + dir + " returned " + res); | |
542 | 22 | return res; |
543 | } | |
544 | ||
545 | 16 | boolean ret = true; |
546 | 16 | File[] childFiles = dir.listFiles(); |
547 | 16 | if (childFiles != null) { // listFiles may return null if there's an IO error |
548 | 35 | for (File f: childFiles) { ret = ret && deleteDirectory(f); } |
549 | } | |
550 | ||
551 | // Now we should have an empty directory | |
552 | 16 | ret = ret && dir.delete(); |
553 | // System.err.println("Recursive deletion of " + dir + " returned " + ret); | |
554 | 16 | return ret; |
555 | } | |
556 | ||
557 | /** Instructs Java to recursively delete the given directory and its contents when the JVM exits. | |
558 | * @param dir File object representing directory to delete. If, for some reason, this file object is not a | |
559 | * directory, it will still be deleted. | |
560 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#deleteOnExitRecursively} instead | |
561 | */ | |
562 | 4 | @Deprecated public static void deleteDirectoryOnExit(final File dir) { |
563 | ||
564 | // Delete this on exit, whether it's a directory or file | |
565 | 4 | _log.log("Deleting file/directory " + dir + " on exit"); |
566 | 4 | dir.deleteOnExit(); |
567 | ||
568 | // If it's a directory, visit its children. This recursive walk has to be done AFTER calling deleteOnExit | |
569 | // on the directory itself because Java closes the list of files to deleted on exit in reverse order. | |
570 | 4 | if (dir.isDirectory()) { |
571 | 2 | File[] childFiles = dir.listFiles(); |
572 | 2 | if (childFiles != null) { // listFiles may return null if there's an IO error |
573 | 3 | for (File f: childFiles) { deleteDirectoryOnExit(f); } |
574 | } | |
575 | } | |
576 | } | |
577 | ||
578 | /** This function starts from the given directory and finds all packages within that directory | |
579 | * @param prefix the package name of files in the given root | |
580 | * @param root the directory to start exploring from | |
581 | * @return a list of valid packages, excluding the root ("") package | |
582 | */ | |
583 | 2 | public static LinkedList<String> packageExplore(String prefix, File root) { |
584 | /* Inner holder class. */ | |
585 | class PrefixAndFile { | |
586 | public String prefix; | |
587 | public File root; | |
588 | 8 | public PrefixAndFile(String prefix, File root) { |
589 | 8 | this.root = root; |
590 | 8 | this.prefix = prefix; |
591 | } | |
592 | } | |
593 | ||
594 | // This set makes sure we don't get caught in a loop if the filesystem has symbolic links | |
595 | // that form a circle by tracking the directories we have already explored | |
596 | 2 | final Set<File> exploredDirectories = new HashSet<File>(); |
597 | ||
598 | 2 | LinkedList<String> output = new LinkedList<String>(); |
599 | 2 | Stack<PrefixAndFile> working = new Stack<PrefixAndFile>(); |
600 | 2 | working.push(new PrefixAndFile(prefix, root)); |
601 | 2 | exploredDirectories.add(root); |
602 | ||
603 | // This filter allows only directories, and accepts each directory only once | |
604 | 2 | FileFilter directoryFilter = new FileFilter(){ |
605 | 12 | public boolean accept(File f){ |
606 | 12 | boolean toReturn = f.isDirectory() && ! exploredDirectories.contains(f); |
607 | 12 | exploredDirectories.add(f); |
608 | 12 | return toReturn; |
609 | } | |
610 | /* The following method is commented out because it was inaccessible. */ | |
611 | // public String getDescription() { return "All Folders"; } | |
612 | }; | |
613 | ||
614 | // Explore each directory, adding (unique) subdirectories to the working list. If a directory has .java | |
615 | // files, add the associated package to the list of packages | |
616 | 2 | while (! working.empty()) { |
617 | 8 | PrefixAndFile current = working.pop(); |
618 | 8 | File [] subDirectories = current.root.listFiles(directoryFilter); |
619 | 8 | if (subDirectories != null) { // listFiles may return null if there's an IO error |
620 | 8 | for (File dir: subDirectories) { |
621 | 6 | PrefixAndFile paf; |
622 | // System.out.println("exploring " + dir); | |
623 | 2 | if (current.prefix.equals("")) paf = new PrefixAndFile(dir.getName(), dir); |
624 | 4 | else paf = new PrefixAndFile(current.prefix + "." + dir.getName(), dir); |
625 | 6 | working.push(paf); |
626 | } | |
627 | } | |
628 | 8 | File [] javaFiles = current.root.listFiles(JAVA_FILE_FILTER); |
629 | ||
630 | 8 | if (javaFiles != null) { // listFiles may return null if there's an IO error |
631 | //Only add package names if they have java files and are not the root package | |
632 | 8 | if (javaFiles.length != 0 && !current.prefix.equals("")) { |
633 | 5 | output.add(current.prefix); |
634 | // System.out.println("adding " + current.prefix); | |
635 | } | |
636 | } | |
637 | } | |
638 | 2 | return output; |
639 | } | |
640 | ||
641 | /** Renames the given file to the given destination. Needed since Windows does not allow a rename to overwrite an | |
642 | * existing file. | |
643 | * @param file the file to rename | |
644 | * @param dest the destination file | |
645 | * @return true iff the rename was successful | |
646 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#attemptMove}, which is equally Windows-friendly, instead. | |
647 | */ | |
648 | 130 | @Deprecated public static boolean renameFile(File file, File dest) { |
649 | 7 | if (dest.exists()) dest.delete(); |
650 | 130 | return file.renameTo(dest); |
651 | } | |
652 | ||
653 | /** This method writes files correctly; it takes care of catching errors, making backups, and keeping an unsuccessful | |
654 | * file save from destroying the old file (unless a backup is made). It makes sure that the file to be saved is not | |
655 | * read-only, throwing an IOException if it is. Note: if saving fails and a backup was being created, any existing | |
656 | * backup will be destroyed (because the backup is written before saving begins, then moved back over the original | |
657 | * file when saving fails). Since the old backup would have been destroyed anyway if saving had succeeded, this | |
658 | * behavior is appropriate. | |
659 | * @param fileSaver Keeps track of the name of the file to write, whether to back up the file, and has | |
660 | * a method that actually performs the writing of the file | |
661 | * @throws IOException if the saving or backing up of the file fails for any reason | |
662 | */ | |
663 | 95 | public static void saveFile(FileSaver fileSaver) throws IOException { |
664 | ||
665 | ||
666 | 95 | boolean makeBackup = fileSaver.shouldBackup(); |
667 | 95 | boolean success = false; |
668 | 95 | File file = fileSaver.getTargetFile(); |
669 | // System.err.println("Saving file " + file + " with backup status = " + makeBackup); | |
670 | 95 | File backup = null; |
671 | 95 | boolean tempFileUsed = true; |
672 | // file.canWrite() is false if file.exists() is false, but we want to be able to save a file that doesn't yet exist. | |
673 | 1 | if (file.exists() && ! file.canWrite()) throw new IOException("Permission denied"); |
674 | // First back up the file, if necessary. | |
675 | 94 | if (makeBackup) { |
676 | 37 | backup = fileSaver.getBackupFile(); |
677 | 37 | if (! renameFile(file, backup)) { |
678 | 0 | throw new IOException("Save failed. Could not create backup file " |
679 | + backup.getAbsolutePath() + | |
680 | "\nIt may be possible to save by disabling file backups\n"); | |
681 | } | |
682 | // System.err.println("saveFile renamed " + file + " as " + backup); | |
683 | 37 | fileSaver.backupDone(); // Why? This action may have to be reversed if writing new file fails! |
684 | // System.err.println("Contents: '" + IOUtil.toString(backup) + "'"); | |
685 | } | |
686 | ||
687 | // ScrollableDialog sd2 = new ScrollableDialog(null, "backup done in FileOps.saveFile", "", ""); | |
688 | // sd2.show(); | |
689 | ||
690 | //Create a temp file in the same directory as the file to be saved. | |
691 | //From this point forward, enclose in try...finally so that we can clean | |
692 | //up the temp file and restore the file from its backup. | |
693 | 94 | File parent = file.getParentFile(); |
694 | 94 | File tempFile = File.createTempFile("drjava", ".temp", parent); |
695 | // System.err.println("tempfileName = " + tempFile + " for backup file " + backup); | |
696 | ||
697 | // ScrollableDialog sd3 = new ScrollableDialog(null, "temp file " + tempFile + "created in FileOps.saveFile", "", ""); | |
698 | // sd3.show(); | |
699 | ||
700 | 94 | try { |
701 | /* Now, write your output to the temp file, then rename it to the correct | |
702 | name. This way, if writing fails in the middle, the old file is not | |
703 | lost. */ | |
704 | 94 | FileOutputStream fos; |
705 | 94 | try { |
706 | /* The next line will fail if we can't create the temp file. This may mean that | |
707 | * the user does not have write permission on the directory the file they | |
708 | * are editing is in. We may want to go ahead and try writing directly | |
709 | * to the target file in this case | |
710 | */ | |
711 | 94 | fos = new FileOutputStream(tempFile); |
712 | } | |
713 | catch (FileNotFoundException fnfe) { | |
714 | 0 | if (fileSaver.continueWhenTempFileCreationFails()) { |
715 | 0 | fos = new FileOutputStream(file); |
716 | 0 | tempFileUsed = false; |
717 | } | |
718 | 0 | else throw new IOException("Could not create temp file " + tempFile + " in attempt to save " + file); |
719 | } | |
720 | 94 | BufferedOutputStream bos = new BufferedOutputStream(fos); |
721 | 94 | fileSaver.saveTo(bos); |
722 | // System.err.println(bos + " written"); | |
723 | // System.err.println("Closing " + bos + " and " + fos); | |
724 | 92 | bos.close(); |
725 | // fos.close(); | |
726 | ||
727 | // System.err.println("Wrote: " + tempFile); | |
728 | 92 | if (tempFileUsed && ! renameFile(tempFile, file)) |
729 | 0 | throw new IOException("Save failed. Another process may be using " + file + "."); |
730 | // System.err.println("Renamed " + tempFile + " as " + file); | |
731 | // if (makeBackup) System.err.println("Does " + backup + " still exists? " + backup.exists()); | |
732 | 92 | success = true; |
733 | } | |
734 | finally { | |
735 | // ScrollableDialog sd4 = new ScrollableDialog(null, "finally clause reached in FileOps.saveFile", "", ""); | |
736 | // sd4.show(); | |
737 | ||
738 | 94 | if (tempFileUsed) tempFile.delete(); /* Delete the temp file */ |
739 | ||
740 | 94 | if (makeBackup) { |
741 | /* On failure, attempt to move the backup back to its original location if we | |
742 | made one. On success, register that a backup was successfully made */ | |
743 | 36 | if (success) fileSaver.backupDone(); |
744 | else { | |
745 | 1 | renameFile(backup, file); |
746 | // System.out.println("Forced to rename backup " + backup + " as file " + file); | |
747 | } | |
748 | } | |
749 | } | |
750 | } | |
751 | ||
752 | public interface FileSaver { | |
753 | ||
754 | /** This method tells what to name the backup file, if a backup is made. It may depend on getTargetFile(), so it | |
755 | * can throw an IOException. | |
756 | */ | |
757 | public abstract File getBackupFile() throws IOException; | |
758 | ||
759 | /** This method indicates whether or not a backup of the file should be made. It may depend on getTargetFile(), | |
760 | * so it can throw an IOException. | |
761 | */ | |
762 | public abstract boolean shouldBackup() throws IOException; | |
763 | ||
764 | /** This method specifies if the saving process should continue trying to save the file if the temp file that is | |
765 | * written initially cannot be created. Continue saving in this case is dangerous because the original file may | |
766 | * be lost if saving fails. | |
767 | */ | |
768 | public abstract boolean continueWhenTempFileCreationFails(); | |
769 | ||
770 | /** This method is called to tell the file saver that a backup was successfully made. */ | |
771 | public abstract void backupDone(); | |
772 | ||
773 | /** | |
774 | * This method actually writes info to a file. NOTE: It is important that this | |
775 | * method write to the stream it is passed, not the target file. If you write | |
776 | * directly to the target file, the target file will be destroyed if saving fails. | |
777 | * Also, it is important that when saving fails this method throw an IOException | |
778 | * @throws IOException when saving fails for any reason | |
779 | */ | |
780 | public abstract void saveTo(OutputStream os) throws IOException; | |
781 | ||
782 | /** This method specifies the file for saving. It should return the canonical name of the file, resolving symlinks. | |
783 | * Otherwise, the saver cannot deal correctly with symlinks. Resolving symlinks may cause an IOException, so this | |
784 | * method declares that it may throw an IOException. | |
785 | */ | |
786 | public abstract File getTargetFile() throws IOException; | |
787 | } | |
788 | ||
789 | /** This class is a default implementation of FileSaver that makes only one backup of each file per instantiation of | |
790 | * the program (following the Emacs convention). It creates a backup file named <file>~. It does not implement the | |
791 | * saveTo method. | |
792 | */ | |
793 | public abstract static class DefaultFileSaver implements FileSaver { | |
794 | ||
795 | private File outputFile = FileOps.NULL_FILE; | |
796 | private static Set<File> filesNotNeedingBackup = new HashSet<File>(); | |
797 | private boolean backupsEnabled = DrJava.getConfig().getSetting(BACKUP_FILES); // uses the config default | |
798 | ||
799 | /** This field keeps track of whether or not outputFile has been resolved to its canonical name. */ | |
800 | private boolean isCanonical = false; | |
801 | ||
802 | // /** Globally enables backups for any DefaultFileSaver that does not override the shouldBackup method. */ | |
803 | // public static void setBackupsEnabled(boolean isEnabled) { backupsEnabled = isEnabled; } | |
804 | ||
805 | 95 | public DefaultFileSaver(File file){ outputFile = file.getAbsoluteFile(); } |
806 | ||
807 | 0 | public boolean continueWhenTempFileCreationFails() { return true; } |
808 | ||
809 | 37 | public File getBackupFile() throws IOException { return new File(getTargetFile().getPath() + "~"); } |
810 | ||
811 | 92 | public boolean shouldBackup() throws IOException{ |
812 | 2 | if (! backupsEnabled) return false; |
813 | 50 | if (! getTargetFile().exists()) return false; |
814 | 4 | if (filesNotNeedingBackup.contains(getTargetFile())) return false; |
815 | 36 | return true; |
816 | } | |
817 | ||
818 | 73 | public void backupDone() { |
819 | 73 | try { filesNotNeedingBackup.add(getTargetFile()); } |
820 | 0 | catch (IOException ioe) { throw new UnexpectedException(ioe, "getTargetFile should fail earlier"); } |
821 | } | |
822 | ||
823 | 335 | public File getTargetFile() throws IOException{ |
824 | 335 | if (!isCanonical) { |
825 | 95 | outputFile = outputFile.getCanonicalFile(); |
826 | 95 | isCanonical = true; |
827 | } | |
828 | 335 | return outputFile; |
829 | } | |
830 | } | |
831 | ||
832 | /** Converts all path entries in a path string to absolute paths. The delimiter in the path string is the | |
833 | * "path.separator" property. Empty entries are equivalent to "." and thus are converted to the value of "user.dir". | |
834 | * Example: ".:drjava::/home/foo/junit.jar" with "user.dir" set to "/home/foo/bar" will be converted to | |
835 | * "/home/foo/bar:/home/foo/bar/drjava:/home/foo/bar:/home/foo/junit.jar". | |
836 | * @param path path string with entries to convert | |
837 | * @return path string with all entries as absolute paths | |
838 | * @deprecated Use {@link edu.rice.cs.plt.io.IOUtil#parsePath}, {@link edu.rice.cs.plt.io.IOUtil#getAbsoluteFiles}, | |
839 | * {@link edu.rice.cs.plt.io.IOUtil#attemptAbsoluteFiles}, and {@link edu.rice.cs.plt.io.IOUtil#pathToString}, | |
840 | * as needed, instead. | |
841 | */ | |
842 | 4 | @Deprecated public static String convertToAbsolutePathEntries(String path) { |
843 | 4 | String pathSep = System.getProperty("path.separator"); |
844 | ||
845 | // split leaves off trailing empty strings | |
846 | // (see API javadocs: "Trailing empty strings are therefore not included in the resulting array.") | |
847 | // we therefore append one element at the end and later remove it | |
848 | 4 | path += pathSep + "x"; |
849 | ||
850 | // now path ends with ":x", so we'll have an additional "x" element in the pathEntries array | |
851 | ||
852 | // split up the path into individual entries, turn all of the entries | |
853 | // into absolute paths, and put the path back together | |
854 | // EXCEPT for the last item in the array, because that's the "x" we added | |
855 | 4 | String[] pathEntries = path.split(pathSep); |
856 | 4 | final StringBuilder sb = new StringBuilder(); |
857 | 4 | for(int i = 0; i < pathEntries.length - 1; ++i) { // length-1 to ignore the last element |
858 | 12 | File f = new File(pathEntries[i]); |
859 | 12 | sb.append(f.getAbsolutePath()); |
860 | 12 | sb.append(pathSep); |
861 | } | |
862 | 4 | String reconstructedPath = sb.toString(); |
863 | ||
864 | // if the reconstructed path is non-empty, then it will have an extra | |
865 | // path separator at the end; take it off | |
866 | 4 | if (reconstructedPath.length() != 0) { |
867 | 4 | reconstructedPath = reconstructedPath.substring(0, reconstructedPath.length() - 1); |
868 | } | |
869 | ||
870 | 4 | return reconstructedPath; |
871 | } | |
872 | ||
873 | /** Return a valid directory for use, i.e. one that exists and is as "close" to the file specified. It is | |
874 | * 1) file, if file is a directory and exists | |
875 | * 2) the closest parent of file, if file is not a directory or does not exist | |
876 | * 3) "user.home" | |
877 | * @return a valid directory for use */ | |
878 | 539 | public static File getValidDirectory(final File origFile) { |
879 | 539 | File file = origFile; |
880 | ||
881 | // if it's the NULL_FILE or null, use "user.home" | |
882 | 539 | if ((file == FileOps.NULL_FILE) || (file == null)) { |
883 | 409 | file = new File(System.getProperty("user.home")); |
884 | } | |
885 | 539 | assert file != null; |
886 | ||
887 | 539 | while (file != null && ! file.exists()) { |
888 | // if the saved path doesn't exist anymore, try the parent | |
889 | //NB: getParentFile() may return null | |
890 | 1 | file = file.getParentFile(); |
891 | } | |
892 | 539 | if (file == null) { |
893 | // somehow we ended up with null, use "user.home" | |
894 | 0 | file = new File(System.getProperty("user.home")); |
895 | } | |
896 | 539 | assert file != null; |
897 | ||
898 | // if it's not a directory, try the parent | |
899 | 539 | if (! file.isDirectory()) { |
900 | 0 | if (file.getParent() != null) { |
901 | 0 | file = file.getParentFile(); |
902 | //NB: getParentFile() may return null | |
903 | 0 | if (file == null) { |
904 | // somehow we ended up with null, use "user.home" | |
905 | 0 | file = new File(System.getProperty("user.home")); |
906 | } | |
907 | 0 | assert file != null; |
908 | } | |
909 | } | |
910 | ||
911 | // this should be an existing directory now | |
912 | 539 | if (file.exists() && file.isDirectory()) return file; |
913 | ||
914 | // ye who enter here, abandon all hope... | |
915 | // the saved path didn't work, and neither did "user.home" | |
916 | 0 | throw new UnexpectedException(new IOException(origFile.getPath() |
917 | + " is not a valid directory, and all attempts " | |
918 | + "to locate a valid directory have failed. " | |
919 | + "Check your configuration.")); | |
920 | } | |
921 | ||
922 | /** Converts the abstract pathname for f into a URL. This method is included in class java.io.File as f.toURL(), but | |
923 | * has been deprecated in Java 6.0 because escape characters on some systems are not handled correctly. The workaround, | |
924 | * f.toURI().toURL(), is unsatisfactory because we rely on the old (broken) behavior: toURI() produces escape | |
925 | * characters (for example, " " becomes "%20"), which remain in the name when we attempt to convert back | |
926 | * to a filename. That is, f.toURI().toURL().getFile() may not be a valid path, even if f exists. (The correct | |
927 | * solution is to avoid trying to convert from a URL to a File, because this conversion is not guaranteed | |
928 | * to work.) | |
929 | */ | |
930 | 0 | public static URL toURL(File f) throws MalformedURLException { return f.toURI().toURL(); } |
931 | ||
932 | 0 | public static boolean makeWritable(File roFile) throws IOException { |
933 | /* Try to make the file writable. Strangely enough, there is a File.setReadOnly() method, but no built-in way to | |
934 | * make the file writable. Sun recommends deleting the read-only file (does that work on all operating systems?).*/ | |
935 | 0 | boolean shouldBackup = edu.rice.cs.drjava.DrJava.getConfig(). |
936 | getSetting(edu.rice.cs.drjava.config.OptionConstants.BACKUP_FILES); | |
937 | 0 | boolean madeBackup = false; |
938 | 0 | File backup = new File(roFile.getAbsolutePath() + "~"); |
939 | 0 | try { |
940 | 0 | boolean noBackup = true; |
941 | 0 | if (backup.exists()) { |
942 | 0 | try { noBackup = backup.delete(); } |
943 | 0 | catch(SecurityException se) { noBackup = false; } |
944 | } | |
945 | 0 | if (noBackup) { |
946 | 0 | try { |
947 | 0 | noBackup = roFile.renameTo(backup); |
948 | 0 | madeBackup = true; |
949 | 0 | roFile.createNewFile(); |
950 | } | |
951 | 0 | catch(SecurityException se) { noBackup = false; } |
952 | catch(IOException ioe) { } | |
953 | 0 | try { roFile.createNewFile(); } |
954 | catch(SecurityException se) { } | |
955 | catch(IOException ioe) { } | |
956 | } | |
957 | 0 | if (! noBackup) { |
958 | 0 | try { roFile.delete(); } |
959 | 0 | catch(SecurityException se) { return false; } |
960 | } | |
961 | 0 | try { edu.rice.cs.plt.io.IOUtil.copyFile(backup, roFile);} |
962 | 0 | catch(SecurityException se) { return false; } |
963 | 0 | catch(IOException ioe) { return false; } |
964 | 0 | return true; |
965 | } | |
966 | finally { | |
967 | 0 | if (! shouldBackup && madeBackup) { |
968 | 0 | try { backup.delete(); } |
969 | catch(Exception e) { /* not so important if we made a backup and now can't delete it */ } | |
970 | } | |
971 | } | |
972 | } | |
973 | ||
974 | /** Move f to n, recursively if necessary. | |
975 | * @param f file or directory to move | |
976 | * @param n new location and name for the file or directory | |
977 | * @return true if successful */ | |
978 | 0 | public static boolean moveRecursively(File f, File n) { |
979 | 0 | boolean res = true; |
980 | 0 | try { |
981 | 0 | if (!f.exists()) { return false; } |
982 | 0 | if (f.isFile()) { return edu.rice.cs.plt.io.IOUtil.attemptMove(f,n); } |
983 | else { | |
984 | // recursively move directory | |
985 | // first create the target directory | |
986 | 0 | if (!n.mkdir()) { return false; } |
987 | // now process children | |
988 | 0 | for(String child: f.list()) { |
989 | 0 | File oldChild = new File(f, child); |
990 | 0 | File newChild = new File(n, child); |
991 | 0 | res = res && moveRecursively(oldChild, newChild); |
992 | } | |
993 | 0 | if (! f.delete()) { return false; } |
994 | } | |
995 | } | |
996 | 0 | catch(Exception e) { return false; } |
997 | 0 | return res; |
998 | } | |
999 | ||
1000 | /** Generate a new file name that does not yet exist. Maximum of 20 attempts. | |
1001 | * Example: generateNewFileName(new File("foo.bar")) | |
1002 | * generates "foo.bar", "foo.bar-2", "foo.bar-3", and so on. | |
1003 | * @param base base name of the file | |
1004 | * @return new file name that does not yet exist | |
1005 | * @throws IOException if file name cannot be generated within 100 attempts */ | |
1006 | 0 | public static File generateNewFileName(File base) throws IOException { |
1007 | 0 | return generateNewFileName(base.getParentFile(), base.getName()); |
1008 | } | |
1009 | ||
1010 | /** Generate a new file name that does not yet exist. Maximum of 20 attempts. | |
1011 | * Example: generateNewFileName(new File("."), "foo.bar") | |
1012 | * generates "foo.bar", "foo.bar-2", "foo.bar-3", and so on. | |
1013 | * @param dir directory of the file | |
1014 | * @param name the base file name | |
1015 | * @return new file name that does not yet exist | |
1016 | * @throws IOException if file name cannot be generated within 100 attempts */ | |
1017 | 0 | public static File generateNewFileName(File dir, String name) throws IOException { |
1018 | 0 | return generateNewFileName(dir, name, "", 100); |
1019 | } | |
1020 | ||
1021 | /** Generate a new file name that does not yet exist. Maximum of 20 attempts. | |
1022 | * @param dir directory of the file | |
1023 | * @param prefix the beginning of the file name | |
1024 | * @param suffix the end of the file name | |
1025 | * @return new file name that does not yet exist | |
1026 | * @throws IOException if file name cannot be generated within 100 attempts */ | |
1027 | 0 | public static File generateNewFileName(File dir, String prefix, String suffix) throws IOException { |
1028 | 0 | return generateNewFileName(dir, prefix, suffix, 100); |
1029 | } | |
1030 | ||
1031 | /** Generate a new file name that does not yet exist. | |
1032 | * Example: generateNewFileName(new File("."), "foo", ".bar", 10) | |
1033 | * generates "foo.bar", "foo-2.bar", "foo-3.bar", and so on. | |
1034 | * @param dir directory of the file | |
1035 | * @param prefix the beginning of the file name | |
1036 | * @param suffix the end of the file name | |
1037 | * @param max maximum number of attempts | |
1038 | * @return new file name that does not yet exist | |
1039 | * @throws IOException if file name cannot be generated within max attempts */ | |
1040 | 0 | public static File generateNewFileName(File dir, String prefix, String suffix, int max) throws IOException { |
1041 | 0 | File temp = new File(dir, prefix+suffix); |
1042 | 0 | if (temp.exists()) { |
1043 | 0 | int count = 2; |
1044 | 0 | do { |
1045 | 0 | temp = new File(dir, prefix + "-" + count+suffix); |
1046 | 0 | ++count; |
1047 | 0 | } while(temp.exists() && (count<max)); |
1048 | 0 | if (temp.exists()) { throw new IOException("Could not generate a file name that did not already exist."); } |
1049 | } | |
1050 | 0 | return temp; |
1051 | } | |
1052 | ||
1053 | // public static final edu.rice.cs.util.Log LOG = new Log("shortf.txt",true); | |
1054 | ||
1055 | /** On Windows, return an 8.3 file name for the specified file. On other OSes, return the file unmodified. | |
1056 | * @param f file for which to find an 8.3 file name | |
1057 | * @return short file name for the file (or unmodified on non-Windows) | |
1058 | * @throws IOException if an 8.3 file name could not be found */ | |
1059 | 9 | public static File getShortFile(File f) throws IOException { |
1060 | 9 | if (!edu.rice.cs.drjava.platform.PlatformFactory.ONLY.isWindowsPlatform()) { return f; } |
1061 | ||
1062 | 0 | String s = ""; |
1063 | 0 | File parent = f.getParentFile(); |
1064 | ||
1065 | // find what file system root this file is on | |
1066 | 0 | File[] roots = File.listRoots(); |
1067 | 0 | File root = new File(File.separator); |
1068 | 0 | for(File r: roots) { |
1069 | 0 | if (f.getCanonicalPath().startsWith(r.getAbsolutePath())) { |
1070 | 0 | root = r; |
1071 | 0 | break; |
1072 | } | |
1073 | } | |
1074 | ||
1075 | // move up towards the root | |
1076 | 0 | while(parent != null) { |
1077 | // LOG.log("parent: "+parent); | |
1078 | 0 | try { |
1079 | // run a DIR /X /A in the directory containing f, i.e. in parent | |
1080 | 0 | Process p = new ProcessBuilder("cmd", "/C", "dir", "/X", "/A").directory(parent).redirectErrorStream(true).start(); |
1081 | ||
1082 | // read the stdout of that process | |
1083 | 0 | BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); |
1084 | 0 | String line; |
1085 | 0 | boolean found = false; |
1086 | ||
1087 | // until the stdout stream has ended | |
1088 | 0 | while((line = br.readLine()) != null) { |
1089 | 0 | if (!found) { |
1090 | // LOG.log("\tline: '"+line+"'"); | |
1091 | // the format of a line is: | |
1092 | // Volume in drive C is SYSTEM | |
1093 | // Volume Serial Number is B4ED-7405 | |
1094 | // | |
1095 | // Directory of C:\ | |
1096 | // | |
1097 | // 09/02/2009 11:02 PM <DIR> DOCUME~1 Documents and Settings | |
1098 | // 09/02/2009 11:02 PM 123 LONGFI~1 Long File Name | |
1099 | // 09/02/2009 11:02 PM <DIR> shortdir | |
1100 | // 09/02/2009 11:02 PM 123 short | |
1101 | ||
1102 | // skip empty lines | |
1103 | 0 | if (line.trim().length() == 0) continue; |
1104 | ||
1105 | // header starts with whitespace | |
1106 | 0 | if (line.startsWith(" ")) continue; |
1107 | ||
1108 | // strip off first two columns | |
1109 | 0 | int pos = line.indexOf(" "); |
1110 | 0 | if (pos == -1) continue; |
1111 | 0 | pos = line.indexOf(" ", pos+2); |
1112 | 0 | if (pos == -1) continue; |
1113 | 0 | line = line.substring(pos).trim(); |
1114 | // LOG.log("\t[1] '"+line+"'"); | |
1115 | ||
1116 | // <DIR> DOCUME~1 Documents and Settings | |
1117 | // 123 LONGFI~1 Long File Name | |
1118 | // <DIR> shortdir | |
1119 | // 123 short | |
1120 | ||
1121 | // strip off third column (<DIR> or file size) | |
1122 | 0 | pos = line.indexOf(' '); |
1123 | 0 | if (pos == -1) continue; |
1124 | 0 | line = line.substring(pos).trim(); |
1125 | // LOG.log("\t[2] '"+line+"'"); | |
1126 | ||
1127 | 0 | File shortF = null; |
1128 | // if the line ends with the file name we are looking for... | |
1129 | // do a case-insensitive comparison here to accomodate Windows | |
1130 | // we later check if it's the same file | |
1131 | 0 | if (line.toLowerCase().equals(f.getName().toLowerCase())) { |
1132 | // short file name only | |
1133 | 0 | shortF = new File(parent, line); |
1134 | // LOG.log("\t[3] shortF = "+shortF); | |
1135 | 0 | if (f.getCanonicalFile().equals(shortF.getCanonicalFile())) { |
1136 | // this is the short file name we are looking for | |
1137 | // LOG.log("\t[3a] found"); | |
1138 | 0 | found = true; |
1139 | } | |
1140 | } | |
1141 | 0 | else if (line.toLowerCase().startsWith(f.getName().toLowerCase()) && f.getName().contains("~")) { |
1142 | // perhaps already short file name of a long file name | |
1143 | 0 | shortF = new File(parent, f.getName()); |
1144 | // LOG.log("\t[4] shortF = "+shortF); | |
1145 | 0 | if (f.getCanonicalFile().equals(shortF.getCanonicalFile())) { |
1146 | // this is the short file name we are looking for | |
1147 | // LOG.log("\t[4a] found"); | |
1148 | 0 | found = true; |
1149 | } | |
1150 | } | |
1151 | 0 | else if (line.toLowerCase().endsWith(" "+f.getName().toLowerCase())) { |
1152 | // remove the long file name at the end and trim off whitespace | |
1153 | // DOCUME~1 | |
1154 | // LONGFI~1 | |
1155 | // | |
1156 | // | |
1157 | 0 | String shortLine = line.substring(0, line.length() - f.getName().length()).trim(); |
1158 | // LOG.log("\t[5] shortLine: '"+shortLine+"'"); | |
1159 | ||
1160 | 0 | if (line.length() == 0) { |
1161 | // already short | |
1162 | 0 | found = true; |
1163 | 0 | shortF = f; |
1164 | // LOG.log("\t[6] shortF = "+shortF); | |
1165 | } | |
1166 | else { | |
1167 | 0 | shortF = new File(parent, shortLine); |
1168 | // LOG.log("\t[7] shortF = "+shortF); | |
1169 | ||
1170 | // if this file exists, check that it is exactly the file we're looking for | |
1171 | 0 | if (shortF.exists()) { |
1172 | 0 | if (f.getCanonicalFile().equals(shortF.getCanonicalFile())) { |
1173 | // this is the short file name we are looking for | |
1174 | // set flag to true, but continue reading lines from the process | |
1175 | // otherwise DIR /X may block because the stdout stream is full | |
1176 | 0 | found = true; |
1177 | } | |
1178 | } | |
1179 | } | |
1180 | } | |
1181 | 0 | if (found && (shortF != null)) { |
1182 | // prepend the short file name to s | |
1183 | // LOG.log("\t[8 ] s = '"+s+"'"); | |
1184 | 0 | s = shortF.getName()+((s.length()==0)?"":(File.separator+s)); |
1185 | // LOG.log("\t[8a] s = '"+s+"'"); | |
1186 | } | |
1187 | } | |
1188 | } | |
1189 | ||
1190 | 0 | try { |
1191 | // wait until the process is done | |
1192 | 0 | p.waitFor(); |
1193 | } | |
1194 | catch(InterruptedException ie) { | |
1195 | 0 | throw new IOException("Could not get short windows file name: "+ie); |
1196 | } | |
1197 | 0 | if (!found) { |
1198 | 0 | throw new IOException("Could not get short windows file name: "+f.getAbsolutePath()+" not found"); |
1199 | } | |
1200 | } | |
1201 | catch(IOException ioe) { | |
1202 | 0 | throw new IOException("Could not get short windows file name: "+ioe); |
1203 | } | |
1204 | 0 | f = parent; |
1205 | 0 | parent = parent.getParentFile(); |
1206 | } | |
1207 | // create the short file | |
1208 | 0 | File shortF = new File(root, s); |
1209 | 0 | if (!shortF.exists()) { |
1210 | 0 | throw new IOException("Could not get short windows file name: "+shortF.getAbsolutePath()+" not found"); |
1211 | } | |
1212 | 0 | return shortF; |
1213 | } | |
1214 | ||
1215 | /** Returns the drjava.jar file. | |
1216 | * @return drjava.jar file */ | |
1217 | 2669 | public static File getDrJavaFile() { |
1218 | 2669 | String[] cps = System.getProperty("java.class.path").split(TextUtil.regexEscape(File.pathSeparator),-1); |
1219 | 2669 | File found = null; |
1220 | 2669 | for(String cp: cps) { |
1221 | 18683 | try { |
1222 | 18683 | File f = new File(cp); |
1223 | 0 | if (!f.exists()) { continue; } |
1224 | 18683 | if (f.isDirectory()) { |
1225 | // this is a directory, maybe DrJava is contained here as individual files | |
1226 | 5338 | File cf = new File(f, edu.rice.cs.drjava.DrJava.class.getName().replace('.', File.separatorChar) + ".class"); |
1227 | 5338 | if (cf.exists() && cf.isFile()) { |
1228 | 2669 | found = f; |
1229 | 2669 | break; |
1230 | } | |
1231 | } | |
1232 | 13345 | else if (f.isFile()) { |
1233 | // this is a file, it should be a jar file | |
1234 | 13345 | JarFile jf = new JarFile(f); |
1235 | // if it's not a jar file, an exception will already have been thrown | |
1236 | // so we know it is a jar file | |
1237 | // now let's check if it contains DrJava | |
1238 | 13345 | if (jf.getJarEntry(edu.rice.cs.drjava.DrJava.class.getName().replace('.', '/') + ".class") != null) { |
1239 | 0 | found = f; |
1240 | 0 | break; |
1241 | } | |
1242 | } | |
1243 | } | |
1244 | catch(IOException e) { /* ignore, we'll continue with the next classpath item */ } | |
1245 | } | |
1246 | 2669 | return found.getAbsoluteFile(); |
1247 | } | |
1248 | ||
1249 | /** Returns the current DrJava application, i.e. the drjava.jar, drjava.exe or DrJava.app file. | |
1250 | * @return DrJava application file */ | |
1251 | 0 | public static File getDrJavaApplicationFile() { |
1252 | 0 | File found = FileOps.getDrJavaFile(); |
1253 | 0 | if (found != null) { |
1254 | 0 | if (edu.rice.cs.drjava.platform.PlatformFactory.ONLY.isMacPlatform()) { |
1255 | // fix for Mac applications | |
1256 | 0 | String s = found.getAbsolutePath(); |
1257 | 0 | if (s.endsWith(".app/Contents/Resources/Java/drjava.jar")) { |
1258 | 0 | found = new File(s.substring(0, s.lastIndexOf("/Contents/Resources/Java/drjava.jar"))); |
1259 | } | |
1260 | } | |
1261 | } | |
1262 | 0 | return found.getAbsoluteFile(); |
1263 | } | |
1264 | } |
|