Clover coverage report - DrJava Test Coverage (drjava-20120422-r5456)
Coverage timestamp: Sun Apr 22 2012 03:13:25 CDT
file stats: LOC: 1,264   Methods: 83
NCLOC: 664   Classes: 5
 
 Source file Conditionals Statements Methods TOTAL
FileOps.java 47.7% 54.6% 48.2% 51.9%
coverage coverage
 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    * {&quot;&quot;,&quot;home&quot;,&quot;username&quot;,&quot;txt.txt&quot;}. 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    }