001 package edu.rice.cs.cunit.util; 002 003 import junit.framework.TestCase; 004 005 import java.io.*; 006 import java.util.ArrayList; 007 import java.util.Enumeration; 008 import java.util.HashSet; 009 import java.util.Set; 010 import java.util.jar.JarEntry; 011 import java.util.jar.JarFile; 012 import java.util.jar.JarOutputStream; 013 import java.util.zip.ZipEntry; 014 015 /** 016 * File Operations. 017 * 018 * @author Mathias Ricken 019 */ 020 public class FileOps { 021 /** 022 * Makes a file equivalent to the given file f that is relative to base file b. In other words, <code>new 023 * File(b,makeRelativeTo(base,abs)).getCanonicalPath()</code> equals <code>f.getCanonicalPath()</code><p> In 024 * Linux/Unix, if the file f is <code>/home/username/folder/file.java</code> and the file b is 025 * <code>/home/username/folder/sublevel/file2.java</code>, then the resulting File path from this method would 026 * be <code>../file.java</code> while its canoncial path would be <code>/home/username/folder/file.java</code>.</p> 027 * In Windows, a file will be made absolute if it is on a different drive than the base. 028 * 029 * @param f The path that is to be made relative to the base file 030 * @param b The file to make the next file relative to 031 * 032 * @return A new file whose path is relative to the base file while the value of <code>getCanonicalPath()</code> 033 * for the returned file is the same as the result of <code>getCanonicalPath()</code> for the given 034 * file. 035 * 036 * @throws IOException if an I/O error occurs; may happen since getCanonicalFile uses the file system 037 */ 038 public static File makeRelativeTo(File f, File b) throws IOException { 039 File base = b.getCanonicalFile(); 040 File abs = f.getCanonicalFile(); // If f is relative, uses current working directory ("user.dir") 041 if (!base.isDirectory()) { 042 base = base.getParentFile(); 043 } 044 045 File[] roots = File.listRoots(); 046 for (File r: roots) { 047 if (abs.getAbsolutePath().startsWith(r.toString())) { 048 if (!base.getAbsolutePath().startsWith(r.toString())) { 049 return abs; 050 } 051 break; 052 } 053 } 054 055 String last = ""; 056 if (!abs.isDirectory()) { 057 String tmp = abs.getPath(); 058 last = tmp.substring(tmp.lastIndexOf(File.separator) + 1); 059 abs = abs.getParentFile(); 060 } 061 062 // System.err.println("makeRelativeTo called; f = " + f + " = " + abs + "; b = " + b + " = " + base); 063 String[] basParts = splitFile(base); 064 String[] absParts = splitFile(abs); 065 066 StringBuffer result = new StringBuffer(); 067 // loop until elements differ, record what part of absParts to append 068 // next find out how many .. to put in. 069 int diffIndex = -1; 070 boolean different = false; 071 for(int i = 0; i < basParts.length; i++) { 072 if (!different && ((i >= absParts.length) || !basParts[i].equals(absParts[i]))) { 073 different = true; 074 diffIndex = i; 075 } 076 if (different) { 077 result.append("..").append(File.separator); 078 } 079 } 080 if (diffIndex < 0) { 081 diffIndex = basParts.length; 082 } 083 for(int i = diffIndex; i < absParts.length; i++) { 084 result.append(absParts[i]).append(File.separator); 085 } 086 result.append(last); 087 // System.err.println("makeRelativeTo(" + f + ", " + b + ") = " + result); 088 return new File(result.toString()); 089 } 090 091 /** 092 * Returns true if the file f is contained in the directory dir or its subdirectories. 093 * @param f the file 094 * @param dir the directory 095 * @return true if file is contained in directory or one of its subdirectories 096 */ 097 public static boolean isContainedIn(File f, File dir) { 098 try { 099 return isContainedInCanonical(f.getCanonicalFile(), dir.getCanonicalFile()); 100 } 101 catch(IOException ioe) { return false; } 102 } 103 104 /** 105 * Returns true if the file f is contained in the directory dir or its subdirectories, or if 106 * the file *is* the directory. 107 * Both the file and the directory must be canonical already. 108 * @param f the canonical file 109 * @param dir the canonical directory 110 * @return true if file is contained in directory or one of its subdirectories or the file *is* the directory 111 */ 112 public static boolean isContainedInCanonical(File f, File dir) { 113 if ((dir==null)||(f==null)) { return false; } 114 if (f.equals(dir)) { return true; } 115 File parent = f.getParentFile(); 116 return isContainedInCanonical(parent, dir); 117 } 118 119 /** 120 * Splits a file into an array of strings representing each parent folder of the given file. The file whose 121 * path is <code>/home/username/txt.txt</code> in linux would be split into the string array: 122 * {"","home","username","txt.txt"}. Delimeters are excluded. 123 * 124 * @param fileToSplit the file to split into its directories. 125 * @return array of path element names 126 */ 127 public static String[] splitFile(File fileToSplit) { 128 String path = fileToSplit.getPath(); 129 ArrayList<String> list = new ArrayList<String>(); 130 while(!path.equals("")) { 131 int idx = path.indexOf(File.separator); 132 if (idx < 0) { 133 list.add(path); 134 path = ""; 135 } 136 else { 137 list.add(path.substring(0, idx)); 138 path = path.substring(idx + 1); 139 } 140 } 141 return list.toArray(new String[list.size()]); 142 } 143 144 /** 145 * Splits a string with a list of paths, separated by pathSeparator, into an array of paths. 146 * This will correctly split a path even if it contains a drive letter, but only if 147 * File.pathSeparator ('/' or '\\') follows the drive letter. 148 * Examples (assuming the File.separatorChar is '\\'): 149 * splitPaths("C:\\foo:C:\\bar", ':', true) will be split into {"C:\\foo", "C:\\bar"}. 150 * splitPaths("C:foo:C:\\bar", ':', true) will be split into {"C", "foo", "C:\\bar"}. 151 * 152 * @param pathString the string with the list of paths, separated by pathSeparator 153 * @param pathSeparator the character separating the paths (should be either ':' or ';') 154 * @return array of paths 155 */ 156 public static String[] splitPaths(String pathString, char pathSeparator) { 157 return splitPaths(pathString, pathSeparator, true); 158 } 159 160 /** 161 * Splits a string with a list of paths, separated by pathSeparator, into an array of paths. 162 * If winDriveLetters is true, this will correctly split a path even if it contains 163 * a drive letter, but only if File.pathSeparator ('/' or '\\') follows the drive letter. 164 * Examples (assuming the File.separatorChar is '\\'): 165 * splitPaths("C:\\foo:C:\\bar", ':', true) will be split into {"C:\\foo", "C:\\bar"}. 166 * splitPaths("C:foo:C:\\bar", ':', true) will be split into {"C", "foo", "C:\\bar"}. 167 * 168 * @param pathString the string with the list of paths, separated by pathSeparator 169 * @param pathSeparator the character separating the paths (should be either ':' or ';') 170 * @param winDriveLetters treat "C:" followed by separatorChar as drive letter 171 * @return array of paths 172 */ 173 public static String[] splitPaths(String pathString, char pathSeparator, boolean winDriveLetters) { 174 // need to check if the pathSeparator is ':' and we want Windows drive letters 175 boolean checkDriveLetters = (pathSeparator ==':') && winDriveLetters; 176 ArrayList<String> list = new ArrayList<String>(); 177 int idx = 0; 178 while((idx = pathString.indexOf(pathSeparator,idx))>=0) { 179 String path = pathString.substring(0, idx); 180 if(checkDriveLetters && ((path.length()==1) && (pathString.charAt(idx+1)==File.separatorChar))) { 181 // string begins with x:\ or x:/ 182 ++idx; 183 continue; 184 } 185 list.add(path); 186 pathString = pathString.substring(idx+1); 187 idx = 0; 188 } 189 list.add(pathString); 190 return list.toArray(new String[list.size()]); 191 } 192 193 /** 194 * Unequivocally exits a program, if necessary by using Runtime.halt and not executing ShutdownHooks. 195 * @param status program's exit status 196 */ 197 public static void exit(final int status) { 198 Thread terminator = new Thread(new Runnable() { 199 public void run() { 200 System.exit(status); 201 } 202 }, "Attempt System.exit"); 203 terminator.start(); 204 try { Thread.sleep(2000); } // sleep for two seconds to allow the terminator thread to 205 catch(InterruptedException e) { /* ignore */ } 206 Runtime.getRuntime().halt(status); // force program to halt if it still alive 207 } 208 209 /** 210 * Create a new temporary directory. The directory will be deleted on exit, if empty. (To delete it recursively on 211 * exit, use deleteDirectoryOnExit.) 212 * 213 * @param name Non-unique portion of the name of the directory to create. 214 * 215 * @return File representing the directory that was created. 216 * @throws IOException 217 */ 218 public static File createTempDirectory(final String name) throws IOException { 219 return createTempDirectory(name, null); 220 } 221 222 /** 223 * Create a new temporary directory. The directory will be deleted on exit, if it only contains temp files and temp 224 * directories created after it. (To delete it on exit regardless of contents, call deleteDirectoryOnExit after 225 * constructing the file tree rooted at this directory. Note that createTempDirectory(..) is not much more helpful 226 * than mkdir() in this context (other than generating a new temp file name) because cleanup is a manual process.) 227 * 228 * @param name Non-unique portion of the name of the directory to create. 229 * @param parent Parent directory to contain the new directory 230 * 231 * @return File representing the directory that was created. 232 * @throws IOException 233 */ 234 public static File createTempDirectory(final String name, final File parent) throws IOException { 235 File file = File.createTempFile(name, "", parent); 236 file.delete(); 237 file.mkdir(); 238 file.deleteOnExit(); 239 240 return file; 241 } 242 243 /** 244 * Delete the given directory including any files and directories it contains. 245 * 246 * @param dir File object representing directory to delete. If, for some reason, this file object is not a 247 * directory, it will still be deleted. 248 * 249 * @return true if there were no problems in deleting. If it returns false, something failed and the directory 250 * contents likely at least partially still exist. 251 */ 252 public static boolean deleteDirectory(final File dir) { 253 if (!dir.isDirectory()) { 254 boolean res; 255 res = dir.delete(); 256 if (!res) { 257 System.err.println("Could not delete "+dir); 258 } 259 return res; 260 } 261 262 boolean ret = true; 263 File[] childFiles = dir.listFiles(); 264 if (childFiles != null) { // listFiles may return null if there's an IO error 265 for(File f : childFiles) { 266 ret = ret && deleteDirectory(f); 267 } 268 } 269 270 // Now we should have an empty directory 271 ret = ret && dir.delete(); 272 return ret; 273 } 274 275 /** 276 * Instructs Java to recursively delete the given directory and its contents when the JVM exits. 277 * 278 * @param dir File object representing directory to delete. If, for some reason, this file object is not a 279 * directory, it will still be deleted. 280 */ 281 public static void deleteDirectoryOnExit(final File dir) { 282 283 // Delete this on exit, whether it's a directory or file 284 dir.deleteOnExit(); 285 286 // If it's a directory, visit its children. This recursive walk has to be done AFTER calling deleteOnExit 287 // on the directory itself because Java closes the list of files to deleted on exit in reverse order. 288 if (dir.isDirectory()) { 289 File[] childFiles = dir.listFiles(); 290 if (childFiles != null) { // listFiles may return null if there's an IO error 291 for(File f : childFiles) { 292 deleteDirectoryOnExit(f); 293 } 294 } 295 } 296 } 297 298 /** 299 * Copy the the file or directory src to dst. 300 * @param src source file 301 * @param dst destination 302 * @throws IOException 303 */ 304 public static void copyFile(final File src, final File dst) throws IOException { 305 if (src.isFile()) { 306 InputStream in = new FileInputStream(src); 307 OutputStream out = new FileOutputStream(dst); 308 309 // Transfer bytes from in to out 310 byte[] buf = new byte[1024]; 311 int len; 312 while ((len = in.read(buf)) > 0) { 313 out.write(buf, 0, len); 314 } 315 in.close(); 316 out.close(); 317 } 318 else if (src.isDirectory()) { 319 dst.mkdirs(); 320 for(File f: src.listFiles()) { 321 copyFile(f, new File(dst, f.getName())); 322 } 323 } 324 } 325 326 /** 327 * Enumerate the files in src. 328 * @param src source file 329 * @return list of files in src 330 * @throws IOException 331 */ 332 public static Set<File> enumFiles(final File src) throws IOException { 333 Set<File> l = new HashSet<File>(); 334 if (src.isFile()) { 335 l.add(src); 336 } 337 else if (src.isDirectory()) { 338 for(File f: src.listFiles()) { 339 Set<File> recur = enumFiles(f); 340 l.addAll(recur); 341 } 342 } 343 return l; 344 } 345 346 /** Convert all path entries in a path string to absolute paths. The delimiter in the path string is the 347 * "path.separator" property. Empty entries are equivalent to "." and will thus are converted to the 348 * "user.dir" (working directory). 349 * Example: 350 * ".:drjava::/home/foo/junit.jar" with "user.dir" set to "/home/foo/bar" will be converted to 351 * "/home/foo/bar:/home/foo/bar/drjava:/home/foo/bar:/home/foo/junit.jar". 352 * 353 * @param path path string with entries to convert 354 * @return path string with all entries as absolute paths 355 */ 356 public static String convertToAbsolutePathEntries(String path) { 357 String pathSep = System.getProperty("path.separator"); 358 359 // split leaves off trailing empty strings 360 // (see API javadocs: "Trailing empty strings are therefore not included in the resulting array.") 361 // we therefore append one element at the end and later remove it 362 path += pathSep + "x"; 363 364 // now path ends with ":x", so we'll have an additional "x" element in the pathEntries array 365 366 // split up the path into individual entries, turn all of the entries 367 // into absolute paths, and put the path back together 368 // EXCEPT for the last item in the array, because that's the "x" we added 369 String[] pathEntries = path.split(pathSep); 370 final StringBuilder sb = new StringBuilder(); 371 for(int i=0; i<pathEntries.length-1; ++i) { // length-1 to ignore the last element 372 File f = new File(pathEntries[i]); 373 sb.append(f.getAbsolutePath()); 374 sb.append(pathSep); 375 } 376 String reconstructedPath = sb.toString(); 377 378 // if the reconstructed path is non-empty, then it will have an extra 379 // path separator at the end; take it off 380 if (reconstructedPath.length()!=0) { 381 reconstructedPath = reconstructedPath.substring(0, reconstructedPath.length()-1); 382 } 383 384 return reconstructedPath; 385 } 386 387 /** 388 * Unpack all files in the jar file to the specified directory. 389 * 390 * @param inJar the input Jar 391 * @param outDir the output directory 392 * @return number of files unpacked 393 * @throws IOException 394 */ 395 public static long unpackJar(File inJar, File outDir) throws IOException { 396 JarFile jf = null; 397 long count = 0; 398 try { 399 jf = new JarFile(inJar); 400 401 Enumeration<JarEntry> entries = jf.entries(); 402 while(entries.hasMoreElements()) { 403 JarEntry e = entries.nextElement(); 404 if (!e.isDirectory()) { 405 InputStream is = null; 406 try { 407 is = jf.getInputStream(e); 408 FileOutputStream fos = null; 409 try { 410 String name = e.getName().replace('/', File.separatorChar); 411 412 // create directories 413 int pos = name.lastIndexOf(File.separatorChar); 414 if (pos>=0) { 415 String dir = name.substring(0,pos); 416 (new File(outDir, dir)).mkdirs(); 417 } 418 419 // write file 420 fos = new FileOutputStream(new File(outDir, name)); 421 byte[] buffer = new byte[1024]; 422 int bytesRead; 423 while(-1 != (bytesRead = is.read(buffer))) { 424 fos.write(buffer, 0, bytesRead); 425 } 426 ++count; 427 } 428 finally { 429 if (fos!=null) { fos.close(); fos = null; } 430 } 431 } 432 finally { 433 if (is!=null) { is.close(); is = null; } 434 } 435 } 436 } 437 } 438 finally { 439 if (jf!=null) { jf.close(); jf = null; } 440 } 441 return count; 442 } 443 444 /** 445 * Pack all files in the directory into the specified Jar. 446 * 447 * @param inDir the input directory 448 * @param outJar the output Jar 449 * @return number of files packed 450 * @throws IOException 451 */ 452 public static long packJar(File inDir, File outJar) throws IOException { 453 FileOutputStream fos = new FileOutputStream(outJar); 454 JarOutputStream jos = new JarOutputStream(fos); 455 long count = 0; 456 457 Set<File> files = enumFiles(inDir); 458 for(File f: files) { 459 File fRel = makeRelativeTo(f, inDir); 460 FileInputStream fis = null; 461 try { 462 fis = new FileInputStream(f); 463 jos.putNextEntry(new ZipEntry(fRel.getPath().replace(File.separatorChar,'/'))); 464 byte[] buffer = new byte[1024]; 465 int bytesRead; 466 while(-1 != (bytesRead = fis.read(buffer))) { 467 jos.write(buffer, 0, bytesRead); 468 } 469 ++count; 470 } 471 finally { 472 if (fis!=null) { fis.close(); fis = null; } 473 } 474 } 475 476 jos.close(); 477 fos.close(); 478 479 return count; 480 } 481 482 public static class FileOpsTest extends TestCase { 483 public void testSplitPaths() { 484 String[] p; 485 final String fs = File.separator; 486 p = splitPaths("C:"+ fs +"foo:C:"+ fs +"bar::C:"+ fs +"foo"+ fs +"bar:", ':', true); 487 assertEquals(5, p.length); 488 assertEquals("C:"+ fs +"foo", p[0]); 489 assertEquals("C:"+ fs +"bar", p[1]); 490 assertEquals("", p[2]); 491 assertEquals("C:"+ fs +"foo"+ fs +"bar", p[3]); 492 assertEquals("", p[4]); 493 p = splitPaths("C:foo:C:bar::C:foo"+ fs +"bar:", ':', true); 494 assertEquals(8, p.length); 495 assertEquals("C", p[0]); 496 assertEquals("foo", p[1]); 497 assertEquals("C", p[2]); 498 assertEquals("bar", p[3]); 499 assertEquals("", p[4]); 500 assertEquals("C", p[5]); 501 assertEquals("foo"+ fs +"bar", p[6]); 502 assertEquals("", p[7]); 503 p = splitPaths("", ':', true); 504 assertEquals(1, p.length); 505 assertEquals("", p[0]); 506 p = splitPaths("C:"+ fs +"foo;C:"+ fs +"bar;;C:"+ fs +"foo"+ fs +"bar;", ';', true); 507 assertEquals(5, p.length); 508 assertEquals("C:"+ fs +"foo", p[0]); 509 assertEquals("C:"+ fs +"bar", p[1]); 510 assertEquals("", p[2]); 511 assertEquals("C:"+ fs +"foo"+ fs +"bar", p[3]); 512 assertEquals("", p[4]); 513 p = splitPaths("C:foo;C:bar;;C:foo"+ fs +"bar;", ';', true); 514 assertEquals(5, p.length); 515 assertEquals("C:foo", p[0]); 516 assertEquals("C:bar", p[1]); 517 assertEquals("", p[2]); 518 assertEquals("C:foo"+ fs +"bar", p[3]); 519 assertEquals("", p[4]); 520 p = splitPaths("", ':', true); 521 assertEquals(1, p.length); 522 assertEquals("", p[0]); 523 p = splitPaths("C;foo;C;bar;;C;foo/bar;", ';', true); 524 assertEquals(8, p.length); 525 assertEquals("C", p[0]); 526 assertEquals("foo", p[1]); 527 assertEquals("C", p[2]); 528 assertEquals("bar", p[3]); 529 assertEquals("", p[4]); 530 assertEquals("C", p[5]); 531 assertEquals("foo/bar", p[6]); 532 assertEquals("", p[7]); 533 534 p = splitPaths("C:\\foo:C:\\bar::C:\\foo\\bar:", ':', false); 535 assertEquals(8, p.length); 536 assertEquals("C", p[0]); 537 assertEquals("\\foo", p[1]); 538 assertEquals("C", p[2]); 539 assertEquals("\\bar", p[3]); 540 assertEquals("", p[4]); 541 assertEquals("C", p[5]); 542 assertEquals("\\foo\\bar", p[6]); 543 assertEquals("", p[7]); 544 p = splitPaths("C:foo:C:bar::C:foo\\bar:", ':', false); 545 assertEquals(8, p.length); 546 assertEquals("C", p[0]); 547 assertEquals("foo", p[1]); 548 assertEquals("C", p[2]); 549 assertEquals("bar", p[3]); 550 assertEquals("", p[4]); 551 assertEquals("C", p[5]); 552 assertEquals("foo\\bar", p[6]); 553 assertEquals("", p[7]); 554 p = splitPaths("", ':', true); 555 assertEquals(1, p.length); 556 assertEquals("", p[0]); 557 p = splitPaths("C:/foo:C:/bar::C:/foo/bar:", ':', false); 558 assertEquals(8, p.length); 559 assertEquals("C", p[0]); 560 assertEquals("/foo", p[1]); 561 assertEquals("C", p[2]); 562 assertEquals("/bar", p[3]); 563 assertEquals("", p[4]); 564 assertEquals("C", p[5]); 565 assertEquals("/foo/bar", p[6]); 566 assertEquals("", p[7]); 567 p = splitPaths("C:foo:C:bar::C:foo/bar:", ':', false); 568 assertEquals(8, p.length); 569 assertEquals("C", p[0]); 570 assertEquals("foo", p[1]); 571 assertEquals("C", p[2]); 572 assertEquals("bar", p[3]); 573 assertEquals("", p[4]); 574 assertEquals("C", p[5]); 575 assertEquals("foo/bar", p[6]); 576 assertEquals("", p[7]); 577 p = splitPaths("C:\\foo;C:\\bar;;C:\\foo\\bar;", ';', false); 578 assertEquals(5, p.length); 579 assertEquals("C:\\foo", p[0]); 580 assertEquals("C:\\bar", p[1]); 581 assertEquals("", p[2]); 582 assertEquals("C:\\foo\\bar", p[3]); 583 assertEquals("", p[4]); 584 p = splitPaths("C:foo;C:bar;;C:foo\\bar;", ';', false); 585 assertEquals(5, p.length); 586 assertEquals("C:foo", p[0]); 587 assertEquals("C:bar", p[1]); 588 assertEquals("", p[2]); 589 assertEquals("C:foo\\bar", p[3]); 590 assertEquals("", p[4]); 591 p = splitPaths("", ':', true); 592 assertEquals(1, p.length); 593 assertEquals("", p[0]); 594 p = splitPaths("C:/foo;C:/bar;;C:/foo/bar;", ';', false); 595 assertEquals(5, p.length); 596 assertEquals("C:/foo", p[0]); 597 assertEquals("C:/bar", p[1]); 598 assertEquals("", p[2]); 599 assertEquals("C:/foo/bar", p[3]); 600 assertEquals("", p[4]); 601 p = splitPaths("C;foo;C;bar;;C;foo/bar;", ';', false); 602 assertEquals(8, p.length); 603 assertEquals("C", p[0]); 604 assertEquals("foo", p[1]); 605 assertEquals("C", p[2]); 606 assertEquals("bar", p[3]); 607 assertEquals("", p[4]); 608 assertEquals("C", p[5]); 609 assertEquals("foo/bar", p[6]); 610 assertEquals("", p[7]); 611 } 612 } 613 }