001/* 002 * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 */ 020 021package org.nuxeo.common.utils; 022 023import java.io.BufferedOutputStream; 024import java.io.BufferedReader; 025import java.io.Closeable; 026import java.io.File; 027import java.io.FileInputStream; 028import java.io.FileOutputStream; 029import java.io.IOException; 030import java.io.InputStream; 031import java.io.InputStreamReader; 032import java.io.OutputStream; 033import java.io.PrintWriter; 034import java.io.UnsupportedEncodingException; 035import java.net.MalformedURLException; 036import java.net.URISyntaxException; 037import java.net.URL; 038import java.net.URLDecoder; 039import java.util.ArrayList; 040import java.util.List; 041 042import org.apache.commons.io.Charsets; 043import org.apache.commons.io.IOUtils; 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046 047/** 048 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 049 */ 050public final class FileUtils { 051 052 private static final int BUFFER_SIZE = 1024 * 64; // 64K 053 054 private static final int MAX_BUFFER_SIZE = 1024 * 1024; // 64K 055 056 private static final int MIN_BUFFER_SIZE = 1024 * 8; // 64K 057 058 private static final Log log = LogFactory.getLog(FileUtils.class); 059 060 // This is an utility class 061 private FileUtils() { 062 } 063 064 public static void safeClose(Closeable stream) { 065 try { 066 stream.close(); 067 } catch (IOException e) { 068 // do nothing 069 } 070 } 071 072 private static byte[] createBuffer(int preferredSize) { 073 if (preferredSize < 1) { 074 preferredSize = BUFFER_SIZE; 075 } 076 if (preferredSize > MAX_BUFFER_SIZE) { 077 preferredSize = MAX_BUFFER_SIZE; 078 } else if (preferredSize < MIN_BUFFER_SIZE) { 079 preferredSize = MIN_BUFFER_SIZE; 080 } 081 return new byte[preferredSize]; 082 } 083 084 public static void copy(InputStream in, OutputStream out) throws IOException { 085 byte[] buffer = createBuffer(in.available()); 086 int read; 087 while ((read = in.read(buffer)) != -1) { 088 out.write(buffer, 0, read); 089 } 090 } 091 092 /** 093 * Read the byte stream as a string assuming a UTF-8 encoding. 094 * 095 * @deprecated Since 5.7. Use {@link IOUtils#toString(InputStream, java.nio.charset.Charset)} explicitly instead (or 096 * any other encoding when provided by the source of the byte stream). 097 */ 098 @Deprecated 099 public static String read(InputStream in) throws IOException { 100 // UTF-8 should be configured as the default "file.encoding" in a system property configured in the nuxeo.conf 101 // file. However this option might not be passed when running the Nuxeo as a library or using the Maven test 102 // runner. Therefore we hardcode the default charset to "UTF-8" to ensure consistency. 103 return IOUtils.toString(in, Charsets.UTF_8); 104 } 105 106 public static byte[] readBytes(URL url) throws IOException { 107 return readBytes(url.openStream()); 108 } 109 110 public static byte[] readBytes(InputStream in) throws IOException { 111 byte[] buffer = createBuffer(in.available()); 112 int w = 0; 113 try { 114 int read = 0; 115 int len; 116 do { 117 w += read; 118 len = buffer.length - w; 119 if (len <= 0) { // resize buffer 120 byte[] b = new byte[buffer.length + BUFFER_SIZE]; 121 System.arraycopy(buffer, 0, b, 0, w); 122 buffer = b; 123 len = buffer.length - w; 124 } 125 } while ((read = in.read(buffer, w, len)) != -1); 126 } finally { 127 in.close(); 128 } 129 if (buffer.length > w) { // compact buffer 130 byte[] b = new byte[w]; 131 System.arraycopy(buffer, 0, b, 0, w); 132 buffer = b; 133 } 134 return buffer; 135 } 136 137 public static String readFile(File file) throws IOException { 138 FileInputStream in = null; 139 try { 140 in = new FileInputStream(file); 141 return read(in); 142 } finally { 143 if (in != null) { 144 in.close(); 145 } 146 } 147 } 148 149 public static List<String> readLines(File file) throws IOException { 150 List<String> lines = new ArrayList<>(); 151 BufferedReader reader = null; 152 try { 153 InputStream in = new FileInputStream(file); 154 reader = new BufferedReader(new InputStreamReader(in)); 155 String line; 156 while ((line = reader.readLine()) != null) { 157 lines.add(line); 158 } 159 } finally { 160 if (reader != null) { 161 try { 162 reader.close(); 163 } catch (IOException e) { 164 } 165 } 166 } 167 return lines; 168 } 169 170 public static void writeLines(File file, List<String> lines) throws IOException { 171 PrintWriter out = null; 172 try { 173 out = new PrintWriter(new FileOutputStream(file)); 174 for (String line : lines) { 175 out.println(line); 176 } 177 } finally { 178 if (out != null) { 179 out.close(); 180 } 181 } 182 } 183 184 public static byte[] readBytes(File file) throws IOException { 185 FileInputStream in = null; 186 try { 187 in = new FileInputStream(file); 188 return readBytes(in); 189 } finally { 190 if (in != null) { 191 in.close(); 192 } 193 } 194 } 195 196 public static void writeFile(File file, byte[] buf) throws IOException { 197 writeFile(file, buf, false); 198 } 199 200 /** 201 * @param file 202 * @param buf 203 * @param append 204 * @throws IOException 205 * @since 5.5 206 */ 207 public static void writeFile(File file, byte[] buf, boolean append) throws IOException { 208 FileOutputStream fos = null; 209 try { 210 fos = new FileOutputStream(file, append); 211 fos.write(buf); 212 } finally { 213 if (fos != null) { 214 fos.close(); 215 } 216 } 217 } 218 219 public static void writeFile(File file, String buf) throws IOException { 220 writeFile(file, buf.getBytes(), false); 221 } 222 223 /** 224 * @param file 225 * @param buf 226 * @param append 227 * @throws IOException 228 * @since 5.5 229 */ 230 public static void writeFile(File file, String buf, boolean append) throws IOException { 231 writeFile(file, buf.getBytes(), append); 232 } 233 234 public static void download(URL url, File file) throws IOException { 235 InputStream in = url.openStream(); 236 OutputStream out = new FileOutputStream(file); 237 try { 238 copy(in, out); 239 } finally { 240 if (in != null) { 241 in.close(); 242 } 243 out.close(); 244 } 245 } 246 247 /** 248 * @deprecated Since 5.6. Use {@link org.apache.commons.io.FileUtils#deleteDirectory(File)} or 249 * {@link org.apache.commons.io.FileUtils#deleteQuietly(File)} instead. 250 */ 251 @Deprecated 252 public static void deleteTree(File dir) { 253 emptyDirectory(dir); 254 dir.delete(); 255 } 256 257 /** 258 * @deprecated Since 5.6. Use {@link org.apache.commons.io.FileUtils#deleteDirectory(File)} or 259 * {@link org.apache.commons.io.FileUtils#deleteQuietly(File)} instead. Warning: suggested methods will 260 * delete the root directory whereas current method doesn't. 261 */ 262 @Deprecated 263 public static void emptyDirectory(File dir) { 264 File[] files = dir.listFiles(); 265 if (files == null) { 266 return; 267 } 268 int len = files.length; 269 for (int i = 0; i < len; i++) { 270 File file = files[i]; 271 if (file.isDirectory()) { 272 deleteTree(file); 273 } else { 274 file.delete(); 275 } 276 } 277 } 278 279 public static void copyToFile(InputStream in, File file) throws IOException { 280 OutputStream out = null; 281 try { 282 out = new FileOutputStream(file); 283 byte[] buffer = createBuffer(in.available()); 284 int read; 285 while ((read = in.read(buffer)) != -1) { 286 out.write(buffer, 0, read); 287 } 288 } finally { 289 if (out != null) { 290 out.close(); 291 } 292 } 293 } 294 295 public static void append(File src, File dst) throws IOException { 296 append(src, dst, false); 297 } 298 299 public static void append(File src, File dst, boolean appendNewLine) throws IOException { 300 InputStream in = null; 301 try { 302 in = new FileInputStream(src); 303 append(in, dst, appendNewLine); 304 } finally { 305 if (in != null) { 306 in.close(); 307 } 308 } 309 } 310 311 public static void append(InputStream in, File file) throws IOException { 312 append(in, file, false); 313 } 314 315 public static void append(InputStream in, File file, boolean appendNewLine) throws IOException { 316 OutputStream out = null; 317 try { 318 out = new BufferedOutputStream(new FileOutputStream(file, true)); 319 if (appendNewLine) { 320 out.write(System.getProperty("line.separator").getBytes()); 321 } 322 byte[] buffer = new byte[BUFFER_SIZE]; 323 int read; 324 while ((read = in.read(buffer)) != -1) { 325 out.write(buffer, 0, read); 326 } 327 } finally { 328 if (out != null) { 329 out.close(); 330 } 331 } 332 } 333 334 /** 335 * Copies source to destination. If source and destination are the same, does nothing. Both single files and 336 * directories are handled. 337 * 338 * @param src the source file or directory 339 * @param dst the destination file or directory 340 * @throws IOException 341 */ 342 public static void copy(File src, File dst) throws IOException { 343 if (src.equals(dst)) { 344 return; 345 } 346 if (src.isFile()) { 347 copyFile(src, dst); 348 } else { 349 copyTree(src, dst); 350 } 351 } 352 353 public static void copy(File[] src, File dst) throws IOException { 354 for (File file : src) { 355 copy(file, dst); 356 } 357 } 358 359 public static void copyFile(File src, File dst) throws IOException { 360 if (dst.isDirectory()) { 361 dst = new File(dst, src.getName()); 362 } 363 FileInputStream in = null; 364 FileOutputStream out = null; 365 try { 366 out = new FileOutputStream(dst); 367 in = new FileInputStream(src); 368 copy(in, out); 369 } finally { 370 IOUtils.closeQuietly(in); 371 IOUtils.closeQuietly(out); 372 } 373 } 374 375 /** 376 * Copies recursively source to destination. 377 * <p> 378 * The source file is assumed to be a directory. 379 * 380 * @param src the source directory 381 * @param dst the destination directory 382 * @throws IOException 383 */ 384 public static void copyTree(File src, File dst) throws IOException { 385 if (src.isFile()) { 386 copyFile(src, dst); 387 } else if (src.isDirectory()) { 388 if (dst.exists()) { 389 dst = new File(dst, src.getName()); 390 dst.mkdir(); 391 } else { // allows renaming dest dir 392 dst.mkdirs(); 393 } 394 File[] files = src.listFiles(); 395 for (File file : files) { 396 copyTree(file, dst); 397 } 398 } 399 } 400 401 public static void copyTree(File src, File dst, PathFilter filter) throws IOException { 402 copyTree(src, dst, new Path("/"), filter); 403 } 404 405 public static void copyTree(File src, File dst, Path prefix, PathFilter filter) throws IOException { 406 if (!prefix.isAbsolute()) { 407 prefix = prefix.makeAbsolute(); 408 } 409 int rootIndex = src.getPath().length() + 1; 410 for (File file : src.listFiles()) { 411 copyTree(rootIndex, file, new File(dst, file.getName()), prefix, filter); 412 } 413 } 414 415 protected static void copyTree(int rootIndex, File src, File dst, Path prefix, PathFilter filter) 416 throws IOException { 417 if (src.isFile()) { 418 String relPath = src.getPath().substring(rootIndex); 419 if (!filter.accept(new Path(relPath))) { 420 return; 421 } 422 if (!prefix.isRoot()) { // remove prefix from path 423 String path = dst.getPath(); 424 String pff = prefix.toString(); 425 int prefixIndex = path.lastIndexOf(pff); 426 if (prefixIndex > 0) { 427 path = path.substring(0, prefixIndex) + path.substring(prefixIndex + pff.length()); 428 dst = new File(path.toString()); 429 } 430 } 431 dst.getParentFile().mkdirs(); 432 copyFile(src, dst); 433 } else if (src.isDirectory()) { 434 File[] files = src.listFiles(); 435 for (File file : files) { 436 copyTree(rootIndex, file, new File(dst, file.getName()), prefix, filter); 437 } 438 } 439 } 440 441 /** 442 * Decodes an URL path so that is can be processed as a filename later. 443 * 444 * @param url the Url to be processed. 445 * @return the decoded path. 446 */ 447 public static String getFilePathFromUrl(URL url) { 448 String path = ""; 449 if (url.getProtocol().equals("file")) { 450 try { 451 path = URLDecoder.decode(url.getPath(), "UTF-8"); 452 } catch (UnsupportedEncodingException e) { 453 log.error(e); 454 } 455 } 456 return path; 457 } 458 459 public static File getFileFromURL(URL url) { 460 File file; 461 String filename = getFilePathFromUrl(url); 462 if (filename.equals("")) { 463 file = null; 464 } else { 465 file = new File(filename); 466 } 467 return file; 468 } 469 470 public static String getParentPath(String path) { 471 int p = path.lastIndexOf(File.separator); 472 if (p == -1) { 473 return null; 474 } 475 return path.substring(0, p); 476 } 477 478 public static String getFileName(String path) { 479 int p = path.lastIndexOf(File.separator); 480 if (p == -1) { 481 return path; 482 } 483 return path.substring(p + 1); 484 } 485 486 public static String getFileExtension(String path) { 487 int p = path.lastIndexOf('.'); 488 if (p == -1) { 489 return null; 490 } 491 return path.substring(p + 1); 492 } 493 494 public static String getFileNameNoExt(String path) { 495 String name = getFileName(path); 496 int p = name.lastIndexOf('.'); 497 if (p == -1) { 498 return name; 499 } 500 return name.substring(0, p); 501 } 502 503 /** 504 * Retrieves the total path of a resource from the Thread Context. 505 * 506 * @param resource the resource name to be retrieved. 507 * @return the decoded path. 508 */ 509 public static String getResourcePathFromContext(String resource) { 510 URL url = Thread.currentThread().getContextClassLoader().getResource(resource); 511 return getFilePathFromUrl(url); 512 } 513 514 public static File getResourceFileFromContext(String resource) { 515 File file; 516 String filename = getResourcePathFromContext(resource); 517 if (filename.equals("")) { 518 file = null; 519 } else { 520 file = new File(filename); 521 } 522 return file; 523 } 524 525 public static File[] findFiles(File root, String pattern, boolean recurse) { 526 List<File> result = new ArrayList<>(); 527 if (pattern == null) { 528 if (recurse) { 529 collectFiles(root, result); 530 } else { 531 return root.listFiles(); 532 } 533 } else { 534 FileNamePattern pat = new FileNamePattern(pattern); 535 if (recurse) { 536 collectFiles(root, pat, result); 537 } else { 538 File[] files = root.listFiles(); 539 for (File file : files) { 540 if (pat.match(file.getName())) { 541 result.add(file); 542 } 543 } 544 } 545 } 546 return result.toArray(new File[result.size()]); 547 } 548 549 public static void collectFiles(File root, FileNamePattern pattern, List<File> result) { 550 File[] files = root.listFiles(); 551 for (File file : files) { 552 if (pattern.match(file.getName())) { 553 result.add(file); 554 if (file.isDirectory()) { 555 collectFiles(file, pattern, result); 556 } 557 } 558 } 559 } 560 561 public static void collectFiles(File root, List<File> result) { 562 File[] files = root.listFiles(); 563 for (File file : files) { 564 result.add(file); 565 if (file.isDirectory()) { 566 collectFiles(file, result); 567 } 568 } 569 } 570 571 public static void close(InputStream in) { 572 if (in != null) { 573 try { 574 in.close(); 575 } catch (IOException e) { 576 log.error(e); 577 } 578 } 579 } 580 581 public static void close(OutputStream out) { 582 if (out != null) { 583 try { 584 out.close(); 585 } catch (IOException e) { 586 log.error(e); 587 } 588 } 589 } 590 591 /** 592 * Create a file handler (this doesn't create a real file) given a file URI. This method can be used to create files 593 * from invalid URL strings (e.g. containing spaces ..) 594 * 595 * @return a file object 596 */ 597 public static File urlToFile(String url) throws MalformedURLException { 598 return urlToFile(new URL(url)); 599 } 600 601 public static File urlToFile(URL url) { 602 try { 603 return new File(url.toURI()); 604 } catch (URISyntaxException e) { 605 return new File(url.getPath()); 606 } 607 } 608 609 public static List<String> readLines(InputStream in) throws IOException { 610 List<String> lines = new ArrayList<>(); 611 BufferedReader reader = null; 612 try { 613 reader = new BufferedReader(new InputStreamReader(in)); 614 String line; 615 while ((line = reader.readLine()) != null) { 616 lines.add(line); 617 } 618 } finally { 619 if (reader != null) { 620 try { 621 reader.close(); 622 } catch (IOException e) { 623 } 624 } 625 } 626 return lines; 627 } 628 629 /** 630 * Compares two files content as String even if their EOL are different 631 * 632 * @param expected a file content with Windows or Unix like EOL 633 * @param source another file content with Windows or Unix like EOL 634 * @return the result of equals after replacing their EOL 635 */ 636 public static boolean areFilesContentEquals(String expected, String source) { 637 if (expected == source) { 638 return true; 639 } 640 641 if (expected == null || source == null) { 642 return false; 643 } 644 645 if (expected.length() != source.length()) { 646 // Prevent from comparing files with Windows EOL 647 return expected.replace("\r\n", "\n").equals(source.replace("\r\n", "\n")); 648 } else { 649 return expected.equals(source); 650 } 651 } 652 653}