001package co.codewizards.cloudstore.core.util; 002 003import static co.codewizards.cloudstore.core.io.StreamUtil.*; 004import static co.codewizards.cloudstore.core.oio.OioFileFactory.*; 005import static java.util.Objects.*; 006 007import java.io.BufferedInputStream; 008import java.io.FileFilter; 009import java.io.FileNotFoundException; 010import java.io.IOException; 011import java.io.InputStream; 012import java.io.InputStreamReader; 013import java.io.OutputStream; 014import java.io.OutputStreamWriter; 015import java.io.Reader; 016import java.io.StreamTokenizer; 017import java.io.StringReader; 018import java.io.StringWriter; 019import java.io.UnsupportedEncodingException; 020import java.io.Writer; 021import java.util.LinkedList; 022import java.util.Map; 023import java.util.Properties; 024import java.util.StringTokenizer; 025 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import co.codewizards.cloudstore.core.Uid; 030import co.codewizards.cloudstore.core.oio.File; 031import co.codewizards.cloudstore.core.progress.ProgressMonitor; 032 033public final class IOUtil { 034 /** 035 * UTF-8 character set name. 036 */ 037 public static final String CHARSET_NAME_UTF_8 = "UTF-8"; 038 039 /** 040 * 1 GB in bytes. 041 * This holds the result of the calculation 1 * 1024 * 1024 * 1024 042 */ 043 public static final long GIGABYTE = 1 * 1024 * 1024 * 1024; 044 045 public static final String COLLISION_FILE_NAME_INFIX = ".collision"; 046 047 private static File tempDir = null; 048 049 private static final Logger logger = LoggerFactory.getLogger(IOUtil.class); 050 051 private IOUtil() { } 052 053 /** 054 * This method finds - if possible - a relative path for addressing 055 * the given <code>file</code> from the given <code>baseDir</code>. 056 * <p> 057 * If it is not possible to denote <code>file</code> relative to <code>baseDir</code>, 058 * the absolute path of <code>file</code> will be returned. 059 * </p> 060 * <p> 061 * Examples: 062 * <ul> 063 * <li> 064 * <code>baseDir="/home/marco"</code><br/> 065 * <code>file="temp/jfire/jboss/bin/run.sh"</code><br/> 066 * or<br/> 067 * <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/> 068 * <code>result="temp/jfire/jboss/bin/run.sh"</code> 069 * </li> 070 * <li> 071 * <code>baseDir="/home/marco/workspace.jfire/JFireBase"</code><br/> 072 * <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/> 073 * or<br/> 074 * <code>file="../../temp/jfire/jboss/bin/run.sh"</code><br/> 075 * <code>result="../../temp/jfire/jboss/bin/run.sh"</code> 076 * </li> 077 * <li> 078 * <code>baseDir="/tmp/workspace.jfire/JFireBase"</code><br/> 079 * <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/> 080 * <code>result="/home/marco/temp/jfire/jboss/bin/run.sh"</code> (absolute, because relative is not possible) 081 * </li> 082 * </ul> 083 * </p> 084 * 085 * @param baseDir This directory is used as start for the relative path. It can be seen as the working directory 086 * from which to point to <code>file</code>. 087 * @param file The file to point to. 088 * @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path, 089 * if a relative path cannot be formulated (i.e. they have no directory in common). 090 * @throws IOException In case of an error 091 * @deprecated User {@link File#relativize(File)} 092 */ 093 @Deprecated 094 public static String getRelativePath(File baseDir, final String file) 095 throws IOException 096 { 097 // When passing the current working dir by "newFile(".").getAbsoluteFile()" to this method, it 098 // generates 2 "../" where there should be only one "../", because it counts the single "." at the end as subdirectory. 099 // Therefore, we now call once simplifyPath before we start. 100 // Maybe we don't need to call simplifyPath in _getRelativePath anymore. Need to check later. Marco. 101 // Additionally, this method did not work without baseDir being an absolute path. Therefore, I now check and make it absolute, if it is not yet. 102 if (baseDir.isAbsolute()) 103 baseDir = createFile(simplifyPath(baseDir)); 104 else 105 baseDir = createFile(simplifyPath(baseDir.getAbsoluteFile())); 106 107 108 File absFile; 109 final File tmpF = createFile(file); 110 if (tmpF.isAbsolute()) 111 absFile = tmpF; 112 else 113 absFile = createFile(baseDir, file); 114 115 final File dest = absFile; 116 File b = baseDir; 117 String up = ""; 118 while (b.getParentFile() != null) { 119 final String res = _getRelativePath(b, dest.getAbsolutePath()); 120 if (res != null) 121 return up + res; 122 123// up = "../" + up; 124 up = ".." + FILE_SEPARATOR + up; 125 b = b.getParentFile(); 126 } 127 128 return absFile.getAbsolutePath(); 129 } 130 131 /** 132 * This method finds - if possible - a relative path for addressing 133 * the given <code>file</code> from the given <code>baseDir</code>. 134 * <p> 135 * If it is not possible to denote <code>file</code> relative to <code>baseDir</code>, 136 * the absolute path of <code>file</code> will be returned. 137 * </p> 138 * See {@link getRelativePath} for examples. 139 * 140 * @param baseDir This directory is used as start for the relative path. It can be seen as the working directory 141 * from which to point to <code>file</code>. 142 * @param file The file to point to. 143 * @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path, 144 * if a relative path cannot be formulated (i.e. they have no directory in common). 145 * @throws IOException In case of an error 146 * @deprecated User {@link File#relativize(File)} 147 */ 148 @Deprecated 149 public static String getRelativePath(final File baseDir, final File file) 150 throws IOException 151 { 152 return getRelativePath(baseDir, file.getPath()); 153 } 154 155 /** 156 * This method finds - if possible - a relative path for addressing 157 * the given <code>file</code> from the given <code>baseDir</code>. 158 * <p> 159 * If it is not possible to denote <code>file</code> relative to <code>baseDir</code>, 160 * the absolute path of <code>file</code> will be returned. 161 * </p> 162 * See {@link getRelativePath} for examples. 163 * 164 * @param baseDir This directory is used as start for the relative path. It can be seen as the working directory 165 * from which to point to <code>file</code>. 166 * @param file The file to point to. 167 * @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path, 168 * if a relative path cannot be formulated (i.e. they have no directory in common). 169 * @throws IOException In case of an error 170 * @deprecated User {@link File#relativize(File)} 171 */ 172 @Deprecated 173 public static String getRelativePath(final String baseDir, final String file) 174 throws IOException 175 { 176 return getRelativePath(createFile(baseDir), file); 177 } 178 179 /** 180 * This method is a helper method used by {@link #getRelativePath(File, String) }. It will 181 * only return a relative path, if <code>file</code> is a child node of <code>baseDir</code>. 182 * Otherwise it returns <code>null</code>. 183 * 184 * @param baseDir The directory to which the resulting path will be relative. 185 * @param file The file to which the resulting path will point. 186 */ 187 private static String _getRelativePath(final File baseDir, final String file) 188 throws IOException 189 { 190 // we make baseDir now absolute in getRelativePath(...) 191// if (!baseDir.isAbsolute()) 192// throw new IllegalArgumentException("baseDir \""+baseDir.getPath()+"\" is not absolute!"); 193 194 File absFile; 195 final File tmpF = createFile(file); 196 if (tmpF.isAbsolute()) 197 absFile = tmpF; 198 else 199 absFile = createFile(baseDir, file); 200 201 String absFileStr = null; 202 String baseDirStr = null; 203 for (int mode_base = 0; mode_base < 2; ++mode_base) { 204 switch (mode_base) { 205 case 0: 206 baseDirStr = simplifyPath(baseDir); 207 break; 208 case 1: 209 baseDirStr = baseDir.getCanonicalPath(); 210 break; 211 default: 212 throw new IllegalStateException("this should never happen!"); 213 } 214 215 for (int mode_abs = 0; mode_abs < 2; ++mode_abs) { 216 baseDirStr = addFinalSlash(baseDirStr); 217 218 switch (mode_abs) { 219 case 0: 220 absFileStr = simplifyPath(absFile); 221 break; 222 case 1: 223 absFileStr = absFile.getCanonicalPath(); 224 break; 225 default: 226 throw new IllegalStateException("this should never happen!"); 227 } 228 229 if (!absFileStr.startsWith(baseDirStr)) { 230 if (mode_base >= 1 && mode_abs >= 1) 231 return null; 232 // throw new IllegalArgumentException("file \""+file+"\" is not a child of baseDir \""+baseDir.getCanonicalPath()+"\"!"); 233 } 234 else 235 break; 236 } // for (int mode_abs = 0; mode_abs < 2; ++mode_abs) { 237 } // for (int mode = 0; mode < 2; ++mode) { 238 239 if (baseDirStr == null) 240 throw new NullPointerException("baseDirStr == null"); 241 242 if (absFileStr == null) 243 throw new NullPointerException("absFileStr == null"); 244 245 return absFileStr.substring(baseDirStr.length(), absFileStr.length()); 246 } 247 248 /** 249 * This method removes double slashes, single-dot-directories and double-dot-directories 250 * from a path. This means, it does nearly the same as <code>File.getCanonicalPath</code>, but 251 * it does not resolve symlinks. This is essential for the method <code>getRelativePath</code>, 252 * because this method first tries it with a simplified path before using the canonical path 253 * (prevents problems with iteration through directories, where there are symlinks). 254 * <p> 255 * Please note that this method makes the given path absolute! 256 * 257 * @param path A path to simplify, e.g. "/../opt/java/jboss/../jboss/./bin/././../server/default/lib/." 258 * @return the simplified string (absolute path), e.g. "/opt/java/jboss/server/default/lib" 259 */ 260 public static String simplifyPath(final File path) 261 { 262 logger.debug("simplifyPath: path='{}'", path); 263 final LinkedList<String> dirs = new LinkedList<String>(); 264 265 final String pathStr = path.getAbsolutePath(); 266 final boolean startWithSeparator = pathStr.startsWith(FILE_SEPARATOR); 267 268 final StringTokenizer tk = new StringTokenizer(pathStr, FILE_SEPARATOR, false); 269 while (tk.hasMoreTokens()) { 270 final String dir = tk.nextToken(); 271 if (".".equals(dir)) 272 ;// nothing 273 else if ("..".equals(dir)) { 274 if (!dirs.isEmpty()) 275 dirs.removeLast(); 276 } 277 else 278 dirs.addLast(dir); 279 } 280 logger.debug("simplifyPath: dirs='{}'", dirs); 281 282 final StringBuffer sb = new StringBuffer(); 283 for (final String dir : dirs) { 284 if (startWithSeparator || sb.length() > 0) 285 sb.append(FILE_SEPARATOR); 286 sb.append(dir); 287 } 288 289 return sb.toString(); 290 } 291 292 /** 293 * Transfer data between streams. 294 * @param in The input stream 295 * @param out The output stream 296 * @param inputOffset How many bytes to skip before transferring 297 * @param inputLen How many bytes to transfer. -1 = all 298 * @return The number of bytes transferred 299 * @throws IOException if an error occurs. 300 */ 301 public static long transferStreamData(final InputStream in, final OutputStream out, final long inputOffset, final long inputLen) 302 throws java.io.IOException 303 { 304 return transferStreamData(in, out, inputOffset, inputLen, null); 305 } 306 307 public static long transferStreamData(final InputStream in, final OutputStream out, final long inputOffset, final long inputLen, final ProgressMonitor monitor) 308 throws java.io.IOException 309 { 310 final byte[] buf = new byte[4 * 4096]; 311 312 if (monitor!= null) { 313 if (inputLen < 0) 314 monitor.beginTask("Copying data", 100); 315 else 316 monitor.beginTask("Copying data", (int)(inputLen / buf.length) + 1); 317 } 318 try { 319 int bytesRead; 320 int transferred = 0; 321 322 //skip offset 323 if(inputOffset > 0) 324 if(in.skip(inputOffset) != inputOffset) 325 throw new IOException("Input skip failed (offset "+inputOffset+")"); 326 327 while (true) { 328 if(inputLen >= 0) 329 bytesRead = in.read(buf, 0, (int)Math.min(buf.length, inputLen-transferred)); 330 else 331 bytesRead = in.read(buf); 332 333 if (bytesRead < 0) 334 break; 335 336 if (bytesRead > 0) { 337 out.write(buf, 0, bytesRead); 338 339 if (monitor != null && inputLen >= 0) 340 monitor.worked(1); 341 342 transferred += bytesRead; 343 } 344 345 if(inputLen >= 0 && transferred >= inputLen) 346 break; 347 } 348 out.flush(); 349 350 return transferred; 351 } finally { 352 if (monitor!= null) { 353 if (inputLen < 0) 354 monitor.worked(100); 355 356 monitor.done(); 357 } 358 } 359 } 360 361 /** 362 * Transfer all available data from an {@link InputStream} to an {@link OutputStream}. 363 * <p> 364 * This is a convenience method for <code>transferStreamData(in, out, 0, -1)</code> 365 * @param in The stream to read from 366 * @param out The stream to write to 367 * @return The number of bytes transferred 368 * @throws IOException In case of an error 369 */ 370 public static long transferStreamData(final InputStream in, final OutputStream out) 371 throws java.io.IOException 372 { 373 return transferStreamData(in, out, 0, -1); 374 } 375 376 /** 377 * This method deletes the given directory recursively. If the given parameter 378 * specifies a file and no directory, it will be deleted anyway. If one or more 379 * files or subdirectories cannot be deleted, the method still continues and tries 380 * to delete as many files/subdirectories as possible. 381 * <p> 382 * Before diving into a subdirectory, it first tries to delete <code>dir</code>. If this 383 * succeeds, it does not try to dive into it. This way, this implementation is symlink-safe. 384 * Hence, if <code>dir</code> denotes a symlink to another directory, this method does 385 * normally not delete the real contents of the directory. 386 * </p> 387 * <p> 388 * <b>Warning!</b> It sometimes still deletes the contents! This happens, if the user has no permissions 389 * onto a symlinked directory (thus, the deletion before dive-in fails), but its contents. In this case, 390 * the method will dive into the directory (because the deletion failed and it therefore assumes it to be a real 391 * directory) and delete the contents. 392 * </p> 393 * <p> 394 * We might later extend this method to do further checks (maybe call an OS program like we do in 395 * {@link #createSymlink(File, File, boolean)}). 396 * </p> 397 * 398 * @param dir The directory or file to delete 399 * @return <code>true</code> if the file or directory does not exist anymore. 400 * This means it either was not existing already before or it has been 401 * successfully deleted. <code>false</code> if the directory could not be 402 * deleted. 403 * @deprecated user File.deleteRecursively instead 404 */ 405 @Deprecated 406 public static boolean deleteDirectoryRecursively(final File dir) 407 { 408 if (!dir.existsNoFollow()) 409 return true; 410 411 // If we're running this on linux (that's what I just tested ;) and dir denotes a symlink, 412 // we must not dive into it and delete its contents! We can instead directly delete dir. 413 // There is no way in Java (except for calling system tools) to find out whether it is a symlink, 414 // but we can simply delete it. If the deletion succeeds, it was a symlink or emtpy directory, otherwise it's a non-empty directory. 415 // This way, we don't delete the contents in symlinks and thus prevent data loss! 416 try { 417 if (dir.delete()) 418 return true; 419 } catch(final SecurityException e) { 420 // ignore according to docs. 421 return false; // or should we really ignore this security exception and delete the contents?!?!?! To return false instead is definitely safer. 422 } 423 424 if (dir.isDirectory()) { 425 final File[] content = dir.listFiles(); 426 for (final File f : content) { 427 if (f.isDirectory()) 428 deleteDirectoryRecursively(f); 429 else 430 try { 431 f.delete(); 432 } catch(final SecurityException e) { 433 // ignore according to docs. 434 } 435 } 436 } 437 438 try { 439 return dir.delete(); 440 } catch(final SecurityException e) { 441 return false; 442 } 443 } 444 445 /** 446 * This method deletes the given directory recursively. If the given parameter 447 * specifies a file and no directory, it will be deleted anyway. If one or more 448 * files or subdirectories cannot be deleted, the method still continues and tries 449 * to delete as many files/subdirectories as possible. 450 * 451 * @param dir The directory or file to delete 452 * @return True, if the file or directory does not exist anymore. This means it either 453 * was not existing already before or it has been successfully deleted. False, if the 454 * directory could not be deleted. 455 * @deprecated Plz use File.deleteRecursively instead! 456 */ 457 @Deprecated 458 public static boolean deleteDirectoryRecursively(final String dir) 459 { 460 final File dirF = createFile(dir); 461 return deleteDirectoryRecursively(dirF); 462 } 463 464 /** 465 * Tries to find a unique, not existing folder name under the given root folder 466 * suffixed with a number. When found, the directory will be created. 467 * It will start with 0 and make Integer.MAX_VALUE number 468 * of iterations maximum. The first not existing foldername will be returned. 469 * If no foldername could be found after the maximum iterations a {@link IOException} 470 * will be thrown. 471 * <p> 472 * Note that the creation of the directory is not completely safe. This method is 473 * synchronized, but other processes could "steal" the unique filename. 474 * 475 * @param rootFolder The rootFolder to find a unique subfolder for 476 * @param prefix A prefix for the folder that has to be found. 477 * @return A File pointing to an unique (not existing) Folder under the given rootFolder and with the given prefix 478 * @throws IOException in case of an error 479 */ 480 public static synchronized File createUniqueIncrementalFolder(final File rootFolder, final String prefix) throws IOException 481 { 482 for(int n=0; n<Integer.MAX_VALUE; n++) { 483 final File f = createFile(rootFolder, String.format("%s%x", prefix, n)); 484 if(!f.exists()) { 485 if(!f.mkdirs()) 486 throw new IOException("The directory "+f.getAbsolutePath()+" could not be created"); 487 return f; 488 } 489 } 490 throw new IOException("Iterated to Integer.MAX_VALUE and could not find a unique folder!"); 491 } 492 493 /** 494 * Tries to find a unique, not existing folder name under the given root folder with a random 495 * number (in hex format) added to the given prefix. When found, the directory will be created. 496 * <p> 497 * The method will try to find a name for <code>maxIterations</code> number 498 * of itarations and use random numbers from 0 to <code>uniqueOutOf</code>. 499 * <p> 500 * Note that the creation of the directory is not completely safe. This method is 501 * synchronized, but other processes could "steal" the unique filename. 502 * 503 * @param rootFolder The rootFolder to find a unique subfolder for 504 * @param prefix A prefix for the folder that has to be found. 505 * @param maxIterations The maximum number of itarations this method shoud do. 506 * If after them still no unique folder could be found, a {@link IOException} 507 * is thrown. 508 * @param uniqueOutOf The range of random numbers to apply (0 - given value) 509 * 510 * @return A File pointing to an unique folder under the given rootFolder and with the given prefix 511 * @throws IOException in case of an error 512 */ 513 public static synchronized File createUniqueRandomFolder(final File rootFolder, final String prefix, final long maxIterations, final long uniqueOutOf) throws IOException 514 { 515 long count = 0; 516 while(++count <= maxIterations) { 517 final File f = createFile(rootFolder, String.format("%s%x", prefix, (long)(Math.random() * uniqueOutOf))); 518 if(!f.exists()) { 519 if(!f.mkdirs()) 520 throw new IOException("The directory "+f.getAbsolutePath()+" could not be created"); 521 return f; 522 } 523 } 524 throw new IOException("Reached end of maxIteration("+maxIterations+"), but could not acquire a unique fileName for folder "+rootFolder); 525 } 526 527 /** 528 * Tries to find a unique, not existing folder name under the given root folder with a random 529 * number (in hex format) added to the given prefix. When found, the directory will be created. 530 * <p> 531 * This is a convenience method for {@link createUniqueRandomFolder} 532 * and calls it with 10000 as maxIterations and 10000 as number range. 533 * <p> 534 * Note that this method might throw a {@link IOException} 535 * if it will not find a unique name within 10000 iterations. 536 * <p> 537 * Note that the creation of the directory is not completely safe. This method is 538 * synchronized, but other processes could "steal" the unique filename. 539 * 540 * @param rootFolder The rootFolder to find a unique subfolder for 541 * @param prefix A prefix for the folder that has to be found. 542 * @return A File pointing to an unique (non-existing) Folder under the given rootFolder and with the given prefix 543 * @throws IOException in case of an error 544 */ 545 public static File createUniqueRandomFolder(final File rootFolder, final String prefix) throws IOException 546 { 547 return createUniqueRandomFolder(rootFolder, prefix, 10000, 10000); 548 } 549 550 /** 551 * Get a file object from a base directory and a list of subdirectories or files. 552 * @param file The base directory 553 * @param subDirs The subdirectories or files 554 * @return The new file instance 555 */ 556 public static File getFile(final File file, final String ... subDirs) 557 { 558 File f = file; 559 for (final String subDir : subDirs) 560 f = createFile(f, subDir); 561 return f; 562 } 563 564 /** 565 * Write text to a file. 566 * @param file The file to write the text to 567 * @param text The text to write 568 * @param encoding The caracter set to use as file encoding (e.g. "UTF-8") 569 * @throws IOException in case of an io error 570 * @throws FileNotFoundException if the file exists but is a directory 571 * rather than a regular file, does not exist but cannot 572 * be created, or cannot be opened for any other reason 573 * @throws UnsupportedEncodingException If the named encoding is not supported 574 */ 575 public static void writeTextFile(final File file, final String text, final String encoding) 576 throws IOException, FileNotFoundException, UnsupportedEncodingException 577 { 578 OutputStream out = null; 579 OutputStreamWriter w = null; 580 try { 581 out = castStream(file.createOutputStream()); 582 w = new OutputStreamWriter(out, encoding); 583 w.write(text); 584 } finally { 585 if (w != null) w.close(); 586 if (out != null) out.close(); 587 } 588 } 589 590 /** 591 * Read a text file and return the contents as string. 592 * @param f The file to read, maximum size 1 GB 593 * @param encoding The file encoding, e.g. "UTF-8" 594 * @throws FileNotFoundException if the file was not found 595 * @throws IOException in case of an io error 596 * @throws UnsupportedEncodingException If the named encoding is not supported 597 * @return The contents of the text file 598 */ 599 public static String readTextFile(final File f, final String encoding) 600 throws FileNotFoundException, IOException, UnsupportedEncodingException 601 { 602 if (f.length() > GIGABYTE) 603 throw new IllegalArgumentException("File exceeds " + GIGABYTE + " bytes: " + f.getAbsolutePath()); 604 605 final StringBuffer sb = new StringBuffer(); 606 final InputStream fin = castStream(f.createInputStream()); 607 try { 608 final InputStreamReader reader = new InputStreamReader(fin, encoding); 609 try { 610 final char[] cbuf = new char[1024]; 611 int bytesRead; 612 while (true) { 613 bytesRead = reader.read(cbuf); 614 if (bytesRead <= 0) 615 break; 616 else 617 sb.append(cbuf, 0, bytesRead); 618 } 619 } finally { 620 reader.close(); 621 } 622 } finally { 623 fin.close(); 624 } 625 return sb.toString(); 626 } 627 628 /** 629 * Read a UTF-8 encoded text file and return the contents as string. 630 * <p>For other encodings, use {@link readTextFile}. 631 * @param f The file to read, maximum size 1 GB 632 * @throws FileNotFoundException if the file was not found 633 * @throws IOException in case of an io error 634 * @return The contents of the text file 635 */ 636 public static String readTextFile(final File f) 637 throws FileNotFoundException, IOException 638 { 639 return readTextFile(f, CHARSET_NAME_UTF_8); 640 } 641 642 /** 643 * Write text to a file using UTF-8 encoding. 644 * @param file The file to write the text to 645 * @param text The text to write 646 * @throws IOException in case of an io error 647 * @throws FileNotFoundException if the file exists but is a directory 648 * rather than a regular file, does not exist but cannot 649 * be created, or cannot be opened for any other reason 650 */ 651 public static void writeTextFile(final File file, final String text) 652 throws IOException, FileNotFoundException, UnsupportedEncodingException 653 { 654 writeTextFile(file, text, CHARSET_NAME_UTF_8); 655 } 656 657 /** 658 * Read a text file from an {@link InputStream} using 659 * the given encoding. 660 * <p> 661 * This method does NOT close the input stream! 662 * @param in The stream to read from. It will not be closed by this operation. 663 * @param encoding The charset used for decoding, e.g. "UTF-8" 664 * @return The contents of the input stream file 665 */ 666 public static String readTextFile(final InputStream in, final String encoding) 667 throws FileNotFoundException, IOException 668 { 669 final StringBuffer sb = new StringBuffer(); 670 final InputStreamReader reader = new InputStreamReader(in, encoding); 671 final char[] cbuf = new char[1024]; 672 int bytesRead; 673 while (true) { 674 bytesRead = reader.read(cbuf); 675 if (bytesRead <= 0) 676 break; 677 else 678 sb.append(cbuf, 0, bytesRead); 679 680 if (sb.length() > GIGABYTE) 681 throw new IllegalArgumentException("Text exceeds " + GIGABYTE + " bytes!"); 682 } 683 return sb.toString(); 684 } 685 686 /** 687 * Read a text file from an {@link InputStream} using 688 * UTF-8 encoding. 689 * <p> 690 * This method does NOT close the input stream! 691 * @param in The stream to read from. It will not be closed by this operation. 692 * @return The contents of the input stream file 693 */ 694 public static String readTextFile(final InputStream in) 695 throws FileNotFoundException, IOException 696 { 697 return readTextFile(in, CHARSET_NAME_UTF_8); 698 } 699 700 /** 701 * Get the extension of a filename. 702 * @param fileName A file name (might contain the full path) or <code>null</code>. 703 * @return <code>null</code>, if the given <code>fileName</code> doesn't contain 704 * a dot (".") or if the given <code>fileName</code> is <code>null</code>. Otherwise, 705 * returns all AFTER the last dot. 706 */ 707 public static String getFileExtension(final String fileName) 708 { 709 if (fileName == null) 710 return null; 711 712 final int lastIndex = fileName.lastIndexOf("."); 713 if (lastIndex < 0) 714 return null; 715 716 return fileName.substring(lastIndex + 1); 717 } 718 719 /** 720 * Get a filename without extension. 721 * @param fileName A file name (might contain the full path) or <code>null</code>. 722 * @return all before the last dot (".") or the full <code>fileName</code> if no dot exists. 723 * Returns <code>null</code>, if the given <code>fileName</code> is <code>null</code>. 724 */ 725 public static String getFileNameWithoutExtension(final String fileName) 726 { 727 if (fileName == null) 728 return null; 729 730 final int lastIndex = fileName.lastIndexOf("."); 731 if (lastIndex < 0) 732 return fileName; 733 734 return fileName.substring(0, lastIndex); 735 } 736 737 /** 738 * Get the temporary directory. 739 * <p> 740 * Note, that you should better use {@link #getUserTempDir()} in many situations 741 * since there is solely one global temp directory in GNU/Linux and you might run into permissions trouble 742 * and other collisions when using the global temp directory with a hardcoded static subdir. 743 * </p> 744 * 745 * @return The temporary directory. 746 * @see #getUserTempDir() 747 */ 748 public static File getTempDir() 749 { 750 if(tempDir == null) 751 tempDir = createFile(System.getProperty("java.io.tmpdir")); 752 return tempDir; 753 } 754 755 /** 756 * Get a user-dependent temp directory in every operating system and create it, if it does not exist. 757 * 758 * @param prefix <code>null</code> or a prefix to be added before the user-name. 759 * @param suffix <code>null</code> or a suffix to be added after the user-name. 760 * @return a user-dependent temp directory, which is created if it does not yet exist. 761 * @throws IOException if the directory does not exist and cannot be created. 762 */ 763 public static File createUserTempDir(final String prefix, final String suffix) 764 throws IOException 765 { 766 final File userTempDir = getUserTempDir(prefix, suffix); 767 if (userTempDir.isDirectory()) 768 return userTempDir; 769 770 if (userTempDir.exists()) { 771 if (!userTempDir.isDirectory()) // two processes might call this method simultaneously - thus we must check again; in case it was just created. 772 throw new IOException("The user-dependent temp directory's path exists already, but is no directory: " + userTempDir.getPath()); 773 } 774 775 if (!userTempDir.mkdirs()) { 776 if (!userTempDir.isDirectory()) 777 throw new IOException("The user-dependent temp directory could not be created: " + userTempDir.getPath()); 778 } 779 780 return userTempDir; 781 } 782 783 /** 784 * Get a user-dependent temp directory in every operating system. Given the same prefix and suffix, the resulting 785 * directory will always be the same on subsequent calls - even across different VMs. 786 * <p> 787 * Since many operating systems (for example GNU/Linux and other unixes) use one 788 * global temp directory for all users, you might run into collisions when using hard-coded sub-directories within the 789 * temp dir. Permission problems can cause exceptions and multiple users sharing the same directory 790 * at the same time might even lead to heisenbugs. Therefore, this method encodes the user 791 * name into a subdirectory under the system's temp dir. 792 * </p> 793 * <p> 794 * In order to prevent illegal directory names to be generated, this method encodes the user name (including the 795 * pre- and suffixes passed) using a {@link ParameterCoderMinusHex} which encodes all chars except 796 * '0'...'9', 'A'...'Z', 'a'...'z', '.', '_' and '+'. 797 * </p> 798 * 799 * @param prefix <code>null</code> or a prefix to be added before the user-name. 800 * @param suffix <code>null</code> or a suffix to be added after the user-name. 801 * @return a user-dependent temp directory. 802 * @see #getTempDir() 803 * @see #createUserTempDir(String, String) 804 */ 805 public static File getUserTempDir(final String prefix, final String suffix) 806 { 807 // In GNU/Linux, there is exactly one temp-directory for all users; hence we need to put the current OS user's name into the path. 808 String userNameDir = (prefix == null ? "" : prefix) + String.valueOf(getUserName()) + (suffix == null ? "" : suffix); 809 810 // the user name might contain illegal characters (in windows) => we encode basically all characters. 811 userNameDir = UrlEncoder.encode(userNameDir.replace('*', '_')); 812 813 return createFile(IOUtil.getTempDir(), userNameDir); 814 } 815 816 private static String getUserName() 817 { 818 return System.getProperty("user.name"); //$NON-NLS-1$ 819 } 820 821 public static File getUserHome() 822 { 823 final String userHome = System.getProperty("user.home"); //$NON-NLS-1$ 824 if (userHome == null) 825 throw new IllegalStateException("System property user.home is not set! This should never happen!"); //$NON-NLS-1$ 826 827 return createFile(userHome); 828 } 829 830 /** 831 * Compares two InputStreams. The amount of bytes given in the parameter 832 * <code>length</code> are consumed from the streams, no matter if their 833 * contents are equal or not. 834 * 835 * @param inputStream1 the first InputStream to compare. 836 * @param inputStream2 the second InputStream to compare. 837 * @return true if both InputStreams contain the identical data or false if not 838 * @throws IOException if an I/O error occurs while reading <code>length</code> bytes 839 * from one of the input streams. 840 */ 841 public static boolean compareInputStreams(final InputStream inputStream1, final InputStream inputStream2) 842 throws IOException 843 { 844 requireNonNull(inputStream1, "inputStream1"); 845 requireNonNull(inputStream2, "inputStream2"); 846 847 // We use a BufferedInputStream, if the given stream does not support mark. This is, because we assume that it is 848 // already using a buffer, if it does support mark. For example, ByteArrayInputStream and BufferedInputStream do support 849 // mark. Both should not be decorated. FileInputStream does not support mark => it should definitely be decorated. 850 final InputStream in1 = inputStream1.markSupported() ? inputStream1 : new BufferedInputStream(inputStream1); 851 final InputStream in2 = inputStream2.markSupported() ? inputStream2 : new BufferedInputStream(inputStream2); 852 853 int b1; 854 while ((b1 = in1.read()) >= 0) { 855 final int b2 = in2.read(); 856 if (b1 != b2) 857 return false; 858 } 859 return in2.read() < 0; 860 } 861 862 /** 863 * Compare the contents of two given files. 864 * @param f1 the first file 865 * @param f2 the second file 866 * @return <code>true</code> if the files have same size and their contents are equal - 867 * <code>false</code> otherwise. 868 * @throws FileNotFoundException If one of the files could not be found. 869 * @throws IOException If reading one of the files failed. 870 */ 871 public static boolean compareFiles(final File f1, final File f2) throws FileNotFoundException, IOException 872 { 873 if(!f1.exists()) 874 throw new FileNotFoundException(f1.getAbsolutePath()); 875 if(!f2.exists()) 876 throw new FileNotFoundException(f2.getAbsolutePath()); 877 if(f1.equals(f2)) 878 return true; 879 if(f1.length() != f2.length()) 880 return false; 881 try (final InputStream in1 = castStream(f1.createInputStream())) { 882 try (final InputStream in2 = castStream(f2.createInputStream())) { 883 return compareInputStreams(in1, in2); 884 } 885 } 886 } 887 888 /** 889 * Copy a directory recursively. 890 * @param sourceDirectory The source directory 891 * @param destinationDirectory The destination directory 892 * @throws IOException in case of an error 893 */ 894 public static void copyDirectory(final File sourceDirectory, final File destinationDirectory) throws IOException 895 { 896 copyDirectory(sourceDirectory, destinationDirectory, null); 897 } 898 public static void copyDirectory(final File sourceDirectory, final File destinationDirectory, final FileFilter fileFilter) throws IOException 899 { 900 if(!sourceDirectory.exists() || !sourceDirectory.isDirectory()) 901 throw new IOException("No such source directory: "+sourceDirectory.getAbsolutePath()); 902 if(destinationDirectory.exists()) { 903 if(!destinationDirectory.isDirectory()) 904 throw new IOException("Destination exists but is not a directory: "+sourceDirectory.getAbsolutePath()); 905 } else 906 destinationDirectory.mkdirs(); 907 908 final File[] files = sourceDirectory.listFiles(fileFilter); 909 for (final File file : files) { 910 final File destinationFile = createFile(destinationDirectory, file.getName()); 911 if(file.isDirectory()) { 912 if (destinationDirectory.getAbsoluteFile().equals(file.getAbsoluteFile())) 913 logger.warn("copyDirectory: Skipping directory, because it equals the destination: {}", file.getAbsoluteFile()); 914 else 915 copyDirectory(file, destinationFile, fileFilter); 916 } 917 else 918 copyFile(file, destinationFile); 919 } 920 921 destinationDirectory.setLastModified(sourceDirectory.lastModified()); 922 } 923 924 /** 925 * Copy a resource loaded by the class loader of a given class to a file. 926 * <p> 927 * This is a convenience method for <code>copyResource(sourceResClass, sourceResName, newFile(destinationFilename))</code>. 928 * @param sourceResClass The class whose class loader to use. If the class 929 * was loaded using the bootstrap class loaderClassloader.getSystemResourceAsStream 930 * will be used. See {@link Class#getResourceAsStream(String)} for details. 931 * @param sourceResName The name of the resource 932 * @param destinationFilename Where to copy the contents of the resource 933 * @throws IOException in case of an error 934 */ 935 public static void copyResource(final Class<?> sourceResClass, final String sourceResName, final String destinationFilename) 936 throws IOException 937 { 938 copyResource(sourceResClass, sourceResName, createFile(destinationFilename)); 939 } 940 941 942 /** 943 * Copy a resource loaded by the class loader of a given class to a file. 944 * @param sourceResClass The class whose class loader to use. If the class 945 * was loaded using the bootstrap class loaderClassloader.getSystemResourceAsStream 946 * will be used. See {@link Class#getResourceAsStream(String)} for details. 947 * @param sourceResName The name of the resource 948 * @param destinationFile Where to copy the contents of the resource 949 * @throws IOException in case of an error 950 */ 951 public static void copyResource(final Class<?> sourceResClass, final String sourceResName, final File destinationFile) 952 throws IOException 953 { 954 InputStream source = null; 955 OutputStream destination = null; 956 try{ 957 source = sourceResClass.getResourceAsStream(sourceResName); 958 if (source == null) 959 throw new FileNotFoundException("Class " + sourceResClass.getName() + " could not find resource " + sourceResName); 960 961 if (destinationFile.exists()) { 962 if (destinationFile.isFile()) { 963 if (!destinationFile.canWrite()) 964 throw new IOException("FileCopy: destination file is unwriteable: " + destinationFile.getCanonicalPath()); 965 } else 966 throw new IOException("FileCopy: destination is not a file: " + destinationFile.getCanonicalPath()); 967 } else { 968 final File parentdir = destinationFile.getAbsoluteFile().getParentFile(); 969 if (parentdir == null || !parentdir.exists()) 970 throw new IOException("FileCopy: destination's parent directory doesn't exist: " + destinationFile.getCanonicalPath()); 971 if (!parentdir.canWrite()) 972 throw new IOException("FileCopy: destination's parent directory is unwriteable: " + destinationFile.getCanonicalPath()); 973 } 974 destination = castStream(destinationFile.createOutputStream()); 975 transferStreamData(source,destination); 976 } finally { 977 if (source != null) 978 try { source.close(); } catch (final IOException e) { ; } 979 980 if (destination != null) 981 try { destination.close(); } catch (final IOException e) { ; } 982 } 983 } 984 985 /** 986 * Copy a file. 987 * @param sourceFile The source file to copy 988 * @param destinationFile To which file to copy the source 989 * @throws IOException in case of an error 990 */ 991 public static void copyFile(final File sourceFile, final File destinationFile) 992 throws IOException 993 { 994 copyFile(sourceFile, destinationFile, null); 995 } 996 public static void copyFile(final File sourceFile, final File destinationFile, final ProgressMonitor monitor) 997 throws IOException 998 { 999 InputStream source = null; 1000 OutputStream destination = null; 1001 1002 try { 1003 // First make sure the specified source file 1004 // exists, is a file, and is readable. 1005 if (!sourceFile.exists() || !sourceFile.isFile()) 1006 throw new IOException("FileCopy: no such source file: "+sourceFile.getCanonicalPath()); 1007 if (!sourceFile.canRead()) 1008 throw new IOException("FileCopy: source file is unreadable: "+sourceFile.getCanonicalPath()); 1009 1010 // If the destination exists, make sure it is a writeable file. If the destination doesn't 1011 // exist, make sure the directory exists and is writeable. 1012 if (destinationFile.exists()) { 1013 if (destinationFile.isFile()) { 1014 if (!destinationFile.canWrite()) 1015 throw new IOException("FileCopy: destination file is unwriteable: " + destinationFile.getCanonicalPath()); 1016 } else 1017 throw new IOException("FileCopy: destination is not a file: " + destinationFile.getCanonicalPath()); 1018 } else { 1019 final File parentdir = destinationFile.getParentFile(); 1020 if (parentdir == null || !parentdir.exists()) 1021 throw new IOException("FileCopy: destination directory doesn't exist: " + 1022 destinationFile.getCanonicalPath()); 1023 if (!parentdir.canWrite()) 1024 throw new IOException("FileCopy: destination directory is unwriteable: " + 1025 destinationFile.getCanonicalPath()); 1026 } 1027 // If we've gotten this far, then everything is okay; we can 1028 // copy the file. 1029 source = castStream(sourceFile.createInputStream()); 1030 destination = castStream(destinationFile.createOutputStream()); 1031 transferStreamData(source, destination, 0, sourceFile.length(), monitor); 1032 // No matter what happens, always close any streams we've opened. 1033 } finally { 1034 if (source != null) 1035 try { source.close(); } catch (final IOException e) { ; } 1036 if (destination != null) 1037 try { destination.close(); } catch (final IOException e) { ; } 1038 } 1039 1040 // copy the timestamp 1041 destinationFile.setLastModified(sourceFile.lastModified()); 1042 } 1043 1044 /** 1045 * Add a trailing file separator character to the 1046 * given directory name if it does not already 1047 * end with one. 1048 * @see File#separator 1049 * @param directory A directory name 1050 * @return the directory name anding with a file seperator 1051 */ 1052 public static String addFinalSlash(final String directory) 1053 { 1054 if (directory.endsWith(FILE_SEPARATOR)) 1055 return directory; 1056 else 1057 return directory + FILE_SEPARATOR; 1058 } 1059 1060 private static enum ParserExpects { 1061 NORMAL, 1062 BRACKET_OPEN, 1063 VARIABLE, 1064 BRACKET_CLOSE 1065 } 1066 1067 1068 /** 1069 * Generate a file from a template. The template file can contain variables which are formatted <code>"${variable}"</code>. 1070 * All those variables will be replaced, for which a value has been passed in the map <code>variables</code>. 1071 * <p> 1072 * Example:<br/> 1073 * <pre> 1074 * *** 1075 * Dear ${receipient.fullName}, 1076 * this is a spam mail trying to sell you ${product.name} for a very cheap price. 1077 * Best regards, ${sender.fullName} 1078 * *** 1079 * </pre> 1080 * <br/> 1081 * In order to generate a file from the above template, the map <code>variables</code> needs to contain values for these keys: 1082 * <ul> 1083 * <li>receipient.fullName</li> 1084 * <li>product.name</li> 1085 * <li>sender.fullName</li> 1086 * </ul> 1087 * </p> 1088 * <p> 1089 * If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be 1090 * logged). 1091 * </p> 1092 * 1093 * @param destinationFile The file (absolute!) that shall be created out of the template. 1094 * @param templateFile The template file to use. Must not be <code>null</code>. 1095 * @param characterSet The charset to use for the input and output file. Use <code>null</code> for the default charset. 1096 * @param variables This map defines what variable has to be replaced by what value. The 1097 * key is the variable name (without '$' and brackets '{', '}'!) and the value is the 1098 * value that will replace the variable in the output. This argument must not be <code>null</code>. 1099 * In order to allow passing {@link Properties} directly, this has been redefined as <code>Map<?,?></code> 1100 * from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code> 1101 * value are ignored. Values that are not of type <code>String</code> are converted using the 1102 * {@link Object#toString() toString()} method. 1103 */ 1104 public static void replaceTemplateVariables(final File destinationFile, final File templateFile, final String characterSet, final Map<?, ?> variables) 1105 throws IOException 1106 { 1107 if (!destinationFile.isAbsolute()) 1108 throw new IllegalArgumentException("destinationFile is not absolute: " + destinationFile.getPath()); 1109 1110 logger.info("Creating destination file \""+destinationFile.getAbsolutePath()+"\" from template \""+templateFile.getAbsolutePath()+"\"."); 1111 final File destinationDirectory = destinationFile.getParentFile(); 1112 if (!destinationDirectory.exists()) { 1113 logger.info("Directory for destination file does not exist. Creating it: " + destinationDirectory.getAbsolutePath()); 1114 if (!destinationDirectory.mkdirs()) 1115 logger.error("Creating directory for destination file failed: " + destinationDirectory.getAbsolutePath()); 1116 } 1117 1118 // Create and configure StreamTokenizer to read template file. 1119 final InputStream fin = castStream(templateFile.createInputStream()); 1120 try { 1121 final Reader fr = characterSet != null ? new InputStreamReader(fin, characterSet) : new InputStreamReader(fin); 1122 try { 1123 // Create FileWriter 1124 final OutputStream fos = castStream(destinationFile.createOutputStream()); 1125 try { 1126 final Writer fw = characterSet != null ? new OutputStreamWriter(fos, characterSet) : new OutputStreamWriter(fos); 1127 try { 1128 replaceTemplateVariables(fw, fr, variables); 1129 } finally { 1130 fw.close(); 1131 } 1132 } finally { 1133 fos.close(); 1134 } 1135 } finally { 1136 fr.close(); 1137 } 1138 } finally { 1139 fin.close(); 1140 } 1141 } 1142 1143 /** 1144 * Replace variables (formatted <code>"${variable}"</code>) with their values 1145 * passed in the map <code>variables</code>. 1146 * <p> 1147 * Example:<br/> 1148 * <pre> 1149 * *** 1150 * Dear ${receipient.fullName}, 1151 * this is a spam mail trying to sell you ${product.name} for a very cheap price. 1152 * Best regards, ${sender.fullName} 1153 * *** 1154 * </pre> 1155 * <br/> 1156 * In order to generate a text from the above template, the map <code>variables</code> needs to contain values for these keys: 1157 * <ul> 1158 * <li>receipient.fullName</li> 1159 * <li>product.name</li> 1160 * <li>sender.fullName</li> 1161 * </ul> 1162 * </p> 1163 * <p> 1164 * If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be 1165 * logged). 1166 * </p> 1167 * 1168 * @param template the input text containing variables (or not). 1169 * @param variables This map defines what variable has to be replaced by what value. The 1170 * key is the variable name (without '$' and brackets '{', '}'!) and the value is the 1171 * value that will replace the variable in the output. This argument must not be <code>null</code>. 1172 * In order to allow passing {@link Properties} directly, this has been redefined as <code>Map<?,?></code> 1173 * from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code> 1174 * value are ignored. Values that are not of type <code>String</code> are converted using the 1175 * {@link Object#toString() toString()} method. 1176 * @return the processed text (i.e. the same as the template, but all known variables replaced by their values). 1177 */ 1178 public static String replaceTemplateVariables(final String template, final Map<?, ?> variables) 1179 { 1180 try { 1181 final StringReader r = new StringReader(template); 1182 final StringWriter w = new StringWriter(); 1183 try { 1184 replaceTemplateVariables(w, r, variables); 1185 w.flush(); 1186 return w.toString(); 1187 } finally { 1188 r.close(); 1189 w.close(); 1190 } 1191 } catch (final IOException x) { 1192 throw new RuntimeException(x); // StringReader/Writer should *NEVER* throw any IOException since it's working in-memory only. 1193 } 1194 } 1195 1196 /** 1197 * Copy contents from the given reader to the given writer while 1198 * replacing variables which are formatted <code>"${variable}"</code> with their values 1199 * passed in the map <code>variables</code>. 1200 * <p> 1201 * Example:<br/> 1202 * <pre> 1203 * *** 1204 * Dear ${receipient.fullName}, 1205 * this is a spam mail trying to sell you ${product.name} for a very cheap price. 1206 * Best regards, ${sender.fullName} 1207 * *** 1208 * </pre> 1209 * <br/> 1210 * In order to generate a file from the above template, the map <code>variables</code> needs to contain values for these keys: 1211 * <ul> 1212 * <li>receipient.fullName</li> 1213 * <li>product.name</li> 1214 * <li>sender.fullName</li> 1215 * </ul> 1216 * </p> 1217 * <p> 1218 * If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be 1219 * logged). 1220 * </p> 1221 * 1222 * @param writer The writer to write the output to. 1223 * @param reader The template file to use. Must not be <code>null</code>. 1224 * @param variables This map defines what variable has to be replaced by what value. The 1225 * key is the variable name (without '$' and brackets '{', '}'!) and the value is the 1226 * value that will replace the variable in the output. This argument must not be <code>null</code>. 1227 * In order to allow passing {@link Properties} directly, this has been redefined as <code>Map<?,?></code> 1228 * from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code> 1229 * value are ignored. Values that are not of type <code>String</code> are converted using the 1230 * {@link Object#toString() toString()} method. 1231 */ 1232 public static void replaceTemplateVariables(final Writer writer, final Reader reader, final Map<?, ?> variables) 1233 throws IOException 1234 { 1235 final StreamTokenizer stk = new StreamTokenizer(reader); 1236 stk.resetSyntax(); 1237 stk.wordChars(0, Integer.MAX_VALUE); 1238 stk.ordinaryChar('$'); 1239 stk.ordinaryChar('{'); 1240 stk.ordinaryChar('}'); 1241 stk.ordinaryChar('\n'); 1242 1243 // Read, parse and replace variables from template and write to FileWriter fw. 1244 String variableName = null; 1245 final StringBuilder tmpBuf = new StringBuilder(); 1246 ParserExpects parserExpects = ParserExpects.NORMAL; 1247 while (stk.nextToken() != StreamTokenizer.TT_EOF) { 1248 String stringToWrite = null; 1249 1250 if (stk.ttype == StreamTokenizer.TT_WORD) { 1251 switch (parserExpects) { 1252 case VARIABLE: 1253 parserExpects = ParserExpects.BRACKET_CLOSE; 1254 variableName = stk.sval; 1255 tmpBuf.append(variableName); 1256 break; 1257 case NORMAL: 1258 stringToWrite = stk.sval; 1259 break; 1260 default: 1261 parserExpects = ParserExpects.NORMAL; 1262 stringToWrite = tmpBuf.toString() + stk.sval; 1263 tmpBuf.setLength(0); 1264 } 1265 } 1266 else if (stk.ttype == '\n') { 1267 stringToWrite = new String(Character.toChars(stk.ttype)); 1268 1269 // These chars are not valid within a variable, so we reset the variable parsing, if we're currently parsing one. 1270 // This helps keeping the tmpBuf small (to check for rowbreaks is not really necessary). 1271 if (parserExpects != ParserExpects.NORMAL) { 1272 parserExpects = ParserExpects.NORMAL; 1273 tmpBuf.append(stringToWrite); 1274 stringToWrite = tmpBuf.toString(); 1275 tmpBuf.setLength(0); 1276 } 1277 } 1278 else if (stk.ttype == '$') { 1279 if (parserExpects != ParserExpects.NORMAL) { 1280 stringToWrite = tmpBuf.toString(); 1281 tmpBuf.setLength(0); 1282 } 1283 tmpBuf.appendCodePoint(stk.ttype); 1284 parserExpects = ParserExpects.BRACKET_OPEN; 1285 } 1286 else if (stk.ttype == '{') { 1287 switch (parserExpects) { 1288 case NORMAL: 1289 stringToWrite = new String(Character.toChars(stk.ttype)); 1290 break; 1291 case BRACKET_OPEN: 1292 tmpBuf.appendCodePoint(stk.ttype); 1293 parserExpects = ParserExpects.VARIABLE; 1294 break; 1295 default: 1296 parserExpects = ParserExpects.NORMAL; 1297 tmpBuf.appendCodePoint(stk.ttype); 1298 stringToWrite = tmpBuf.toString(); 1299 tmpBuf.setLength(0); 1300 } 1301 } 1302 else if (stk.ttype == '}') { 1303 switch (parserExpects) { 1304 case NORMAL: 1305 stringToWrite = new String(Character.toChars(stk.ttype)); 1306 break; 1307 case BRACKET_CLOSE: 1308 parserExpects = ParserExpects.NORMAL; 1309 tmpBuf.appendCodePoint(stk.ttype); 1310 1311 if (variableName == null) 1312 throw new IllegalStateException("variableName is null!!!"); 1313 1314 final Object variableValue = variables.get(variableName); 1315 stringToWrite = variableValue == null ? null : variableValue.toString(); 1316 if (stringToWrite == null) { 1317 logger.warn("Variable " + tmpBuf.toString() + " occuring in template is unknown!"); 1318 stringToWrite = tmpBuf.toString(); 1319 } 1320 else 1321 stringToWrite = replaceTemplateVariables(stringToWrite, variables); // recursively resolve variables! 1322 1323 tmpBuf.setLength(0); 1324 break; 1325 default: 1326 parserExpects = ParserExpects.NORMAL; 1327 tmpBuf.appendCodePoint(stk.ttype); 1328 stringToWrite = tmpBuf.toString(); 1329 tmpBuf.setLength(0); 1330 } 1331 } 1332 1333 if (stringToWrite != null) 1334 writer.write(stringToWrite); 1335 } // while (stk.nextToken() != StreamTokenizer.TT_EOF) { 1336 } 1337 1338 public static byte[] getBytesFromFile(final File file) throws IOException { 1339 // Get the size of the file 1340 final long length = file.length(); 1341 final byte[] bytes = new byte[(int)length]; 1342 1343 final InputStream is = castStream(file.createInputStream()); 1344 try { 1345 // Read in the bytes 1346 int offset = 0; 1347 int numRead = 0; 1348 while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length-offset)) >= 0) { 1349 offset += numRead; 1350 } 1351 1352 // Ensure all the bytes have been read in 1353 if (offset < bytes.length) { 1354 throw new IOException("Could not completely read file "+file.getName()); 1355 } 1356 } finally { 1357 // Close the input stream and return bytes 1358 is.close(); 1359 } 1360 return bytes; 1361 } 1362 1363 public static File createCollisionFile(final File file) { 1364 final File parentFile = file.getParentFile(); 1365 final String fileName = file.getName(); 1366 1367 String fileExtension = nullToEmptyString(getFileExtension(fileName)); 1368 if (!fileExtension.isEmpty()) 1369 fileExtension = '.' + fileExtension; 1370 1371 final File result = createFile(parentFile, 1372 String.format("%s.%s%s%s", 1373 fileName, 1374 Long.toString(System.currentTimeMillis(), 36) + new Uid(), 1375 COLLISION_FILE_NAME_INFIX, 1376 fileExtension)); 1377 return result; 1378 } 1379 1380 private static String nullToEmptyString(final String s) { 1381 return s == null ? "" : s; 1382 } 1383 1384 public static void deleteOrFail(final File file) throws IOException { 1385 file.delete(); 1386 if (file.isSymbolicLink() || file.exists()) 1387 throw new IOException("Could not delete file (it still exists after deletion): " + file); 1388 } 1389 1390 public static int readOrFail(final InputStream in) throws IOException { 1391 final int read = in.read(); 1392 if (read < 0) 1393 throw new IOException("Premature end of stream!"); 1394 1395 return read; 1396 } 1397 1398 public static void readOrFail(final InputStream in, final byte[] buf, int off, int len) throws IOException { 1399 requireNonNull(buf, "buf"); 1400 if (off < 0) 1401 throw new IllegalArgumentException("off < 0"); 1402 1403 if (len == 0) 1404 return; 1405 1406 if (len < 0) 1407 throw new IllegalArgumentException("len < 0"); 1408 1409 int bytesRead; 1410 while ((bytesRead = in.read(buf, off, len)) >= 0) { 1411 if (bytesRead > 0) { 1412 off += bytesRead; 1413 len -= bytesRead; 1414 } 1415 if (len < 0) 1416 throw new IllegalStateException("len < 0"); 1417 1418 if (len == 0) 1419 return; 1420 } 1421 throw new IOException("Premature end of stream!"); 1422 } 1423 1424 public static byte[] longToBytes(final long value) { 1425 final byte[] bytes = new byte[8]; 1426 for (int i = 0; i < bytes.length; ++i) 1427 bytes[i] = (byte) (value >>> (8 * (bytes.length - 1 - i))); 1428 1429 return bytes; 1430 } 1431 public static long bytesToLong(final byte[] bytes) { 1432 requireNonNull(bytes, "bytes"); 1433 if (bytes.length != 8) 1434 throw new IllegalArgumentException("bytes.length != 8"); 1435 1436 long value = 0; 1437 for (int i = 0; i < bytes.length; ++i) 1438 value |= ((long) (bytes[i] & 0xff)) << (8 * (bytes.length - 1 - i)); 1439 1440 return value; 1441 } 1442 1443 public static byte[] intToBytes(final int value) { 1444 final byte[] bytes = new byte[4]; 1445 for (int i = 0; i < bytes.length; ++i) 1446 bytes[i] = (byte) (value >>> (8 * (bytes.length - 1 - i))); 1447 1448 return bytes; 1449 } 1450 public static int bytesToInt(final byte[] bytes) { 1451 requireNonNull(bytes, "bytes"); 1452 if (bytes.length != 4) 1453 throw new IllegalArgumentException("bytes.length != 4"); 1454 1455 int value = 0; 1456 for (int i = 0; i < bytes.length; ++i) 1457 value |= ((long) (bytes[i] & 0xff)) << (8 * (bytes.length - 1 - i)); 1458 1459 return value; 1460 } 1461}