001/*
002 * JPPF.
003 * Copyright (C) 2005-2018 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jppf.utils;
019
020import java.io.*;
021import java.net.*;
022import java.nio.file.*;
023import java.nio.file.attribute.BasicFileAttributes;
024import java.util.*;
025
026import org.jppf.utils.configuration.JPPFProperties;
027import org.jppf.utils.streams.StreamUtils;
028import org.slf4j.*;
029
030/**
031 * This class provides a set of utility methods for reading, writing and manipulating files.
032 * @author Laurent Cohen
033 */
034public final class FileUtils {
035  /**
036   * Logger for this class.
037   */
038  private static Logger log = LoggerFactory.getLogger(FileUtils.class);
039  /**
040   * The root folder for JPPF temporary files.
041   */
042  private static final File JPPF_TEMP_DIR = initJPPFTempDir();
043
044  /**
045   * Instantiation of this class is not permitted.
046   */
047  private FileUtils() {
048  }
049
050  /**
051   * Read the content of a specified reader into a string.
052   * @param aReader the reader to read the content from.
053   * @return the content of the file as a string.
054   * @throws IOException if the file can't be found or read.
055   */
056  public static String readTextFile(final Reader aReader) throws IOException {
057    final StringBuilder sb = new StringBuilder();
058    try (BufferedReader reader = (aReader instanceof BufferedReader) ? (BufferedReader) aReader : new BufferedReader(aReader)) {
059      String s = "";
060      while (s != null) {
061        s = reader.readLine();
062        if (s != null) sb.append(s).append('\n');
063      }
064    }
065    return sb.toString();
066  }
067
068  /**
069   * Read the content of a specified reader into a string.
070   * This method closes the reader upon terminating.
071   * @param aReader the reader to read the content from.
072   * @return the content of the file as a string.
073   * @throws IOException if the file can't be found or read.
074   */
075  public static List<String> textFileAsLines(final Reader aReader) throws IOException {
076    final List<String> lines = new ArrayList<>();
077    try (BufferedReader reader = (aReader instanceof BufferedReader) ? (BufferedReader) aReader : new BufferedReader(aReader)) {
078      String s;
079      while ((s = reader.readLine()) != null)
080        if (!"".equals(s.trim())) lines.add(s);
081    }
082    return lines;
083  }
084
085  /**
086   * Read the content of a specified file into a string.
087   * @param file the file to read.
088   * @return the content of the file as a string.
089   * @throws IOException if the file can't be found or read.
090   */
091  public static String readTextFile(final File file) throws IOException {
092    return readTextFile(new FileReader(file));
093  }
094
095  /**
096   * Read the content of a specified file into a string.
097   * @param filename the location of the file to read.
098   * @return the content of the file as a string.
099   * @throws IOException if the file can't be found or read.
100   */
101  public static String readTextFile(final String filename) throws IOException {
102    Reader reader = null;
103    final File f = new File(filename);
104    if (f.exists()) reader = new FileReader(filename);
105    else {
106      final InputStream is = FileUtils.class.getClassLoader().getResourceAsStream(filename);
107      if (is == null) return null;
108      reader = new InputStreamReader(is);
109    }
110    return readTextFile(reader);
111  }
112
113  /**
114   * Write the content of a string into a specified file.
115   * @param filename the location of the file to write to.
116   * @param content the content to write into the file.
117   * @throws IOException if the file can't be found or read.
118   */
119  public static void writeTextFile(final String filename, final String content) throws IOException {
120    writeTextFile(new FileWriter(filename), content);
121  }
122
123  /**
124   * Write the content of a string into a specified file.
125   * @param file the location of the file to write to.
126   * @param content the content to write into the file.
127   * @throws IOException if the file can't be found or read.
128   */
129  public static void writeTextFile(final File file, final String content) throws IOException {
130    writeTextFile(new FileWriter(file), content);
131  }
132
133  /**
134   * Write the content of a string into a specified file.
135   * @param dest the file to write to.
136   * @param content the content to write into the file.
137   * @throws IOException if the file can't be found or read.
138   */
139  public static void writeTextFile(final Writer dest, final String content) throws IOException {
140    try (BufferedReader reader = new BufferedReader(new StringReader(content));
141      Writer writer = (dest instanceof BufferedWriter) ? dest : new BufferedWriter(dest)) {
142      String s = "";
143      while (s != null) {
144        s = reader.readLine();
145        if (s != null) {
146          writer.write(s);
147          writer.write("\n");
148        }
149      }
150      writer.flush();
151    }
152  }
153
154  /**
155   * Get an input stream given a file.
156   * @param path the file to lookup.
157   * @return an {@link InputStream} instance, or null if the file could not be created.
158   * @throws IOException if an I/O error occurs.
159   */
160  public static InputStream getFileInputStream(final File path) throws IOException {
161    return new BufferedInputStream(new FileInputStream(path));
162  }
163
164  /**
165   * Get an input stream given a file path.
166   * This method first looks up in the file system for the specified path, then in the classpath.
167   * @param path the path to the file to lookup.
168   * @return a <code>InputStream</code> instance, or null if the file could not be found.
169   * @throws IOException if an IO error occurs while looking up the file.
170   */
171  public static InputStream getFileInputStream(final String path) throws IOException {
172    InputStream is = null;
173    final File file = new File(path);
174    if (file.exists()) is = new BufferedInputStream(new FileInputStream(file));
175    if (is == null) {
176      ClassLoader cl = Thread.currentThread().getContextClassLoader();
177      if (cl == null) cl = FileUtils.class.getClassLoader();
178      is = cl.getResourceAsStream(path);
179    }
180    return is;
181  }
182
183  /**
184   * Get an output stream given a file path.
185   * @param path the path to the file to lookup.
186   * @return an <code>OutputStream</code> instance, or null if the file could not be created.
187   * @throws IOException if an IO error occurs while looking up the file.
188   */
189  public static OutputStream getFileOutputStream(final String path) throws IOException {
190    return new BufferedOutputStream(new FileOutputStream(path));
191  }
192
193  /**
194   * Get an output stream given a file path.
195   * @param file the file to lookup.
196   * @return an <code>OutputStream</code> instance, or null if the file could not be created.
197   * @throws IOException if an IO error occurs while looking up the file.
198   */
199  public static OutputStream getFileOutputStream(final File file) throws IOException {
200    return new BufferedOutputStream(new FileOutputStream(file));
201  }
202
203  /**
204   * Get a <code>Reader</code> for the specified file path, looked up first in the file system then in the classpath.
205   * @param path the path to the file to lookup.
206   * @return a <code>Reader</code> instance, or null if the file could not be found.
207   * @throws IOException if an IO error occurs while looking up the file.
208   */
209  public static Reader getFileReader(final String path) throws IOException {
210    final InputStream is = getFileInputStream(path);
211    if (is == null) return null;
212    return new InputStreamReader(is);
213  }
214
215  /**
216   * Get a <code>Writer</code> for the specified file path.
217   * @param path the path to the file to create or open.
218   * @return a <code>Writer</code> instance.
219   * @throws IOException if an IO error occurs while creating the file writer.
220   */
221  public static Writer getFileWriter(final String path) throws IOException {
222    return new BufferedWriter(new FileWriter(path));
223  }
224
225  /**
226   * Get a list of files whose paths are found in a text file.
227   * @param fileListPath the path to the file that holds the list of documents to validate.
228   * @return the file paths as a lst of strings.
229   * @throws IOException if an error occurs while looking up or reading the file.
230   */
231  public static List<String> getFilePathList(final String fileListPath) throws IOException {
232    final List<String> filePaths = new ArrayList<>();
233    try (BufferedReader reader = new BufferedReader(new InputStreamReader(getFileInputStream(fileListPath)))) {
234      boolean end = false;
235      while (!end) {
236        final String s = reader.readLine();
237        if (s != null) filePaths.add(s);
238        else end = true;
239      }
240    }
241    return filePaths;
242  }
243
244  /**
245   * Get the extension of a file.
246   * @param file the file from which to get the extension.
247   * @return the file extension, or null if it si not a file or does not have an extension.
248   */
249  public static String getFileExtension(final File file) {
250    if ((file == null) || !file.exists() || !file.isFile()) return null;
251    String filePath = null;
252    try {
253      filePath = file.getCanonicalPath();
254    } catch(@SuppressWarnings("unused") final IOException e) {
255      return null;
256    }
257    final int idx = filePath.lastIndexOf('.');
258    if (idx >=0) return filePath.substring(idx+1);
259    return null;
260  }
261
262  /**
263   * Get the name of a file from its full path.
264   * @param filePath the file from which to get the file name.
265   * @return the file name without path information.
266   */
267  public static String getFileName(final String filePath) {
268    final int idx = getLastFileSeparatorPosition(filePath);
269    return idx >= 0 ? filePath.substring(idx + 1) : filePath;
270  }
271
272  /**
273   * Get the last position of a file separator in a file path.
274   * @param path the path to parse.
275   * @return the position as an positive integer, or -1 if no separator was found.
276   */
277  private static int getLastFileSeparatorPosition(final String path) {
278    final int idx1 = path.lastIndexOf('/');
279    final int idx2 = path.lastIndexOf('\\');
280    if ((idx1 < 0) && (idx2 < 0)) return -1;
281    return idx1 < 0 ? idx2 : (idx2 < 0 ? idx1 : Math.max(idx1, idx2));
282  }
283
284  /**
285   * Split a file into multiple files whose size is as close as possible to the specified split size.
286   * @param file the text file to split.
287   * @param splitSize the maximum number of lines of each resulting file.
288   * @throws IOException if an IO error occurs.
289   */
290  public static void splitTextFile(final String file, final int splitSize) throws IOException {
291    BufferedWriter writer = null;
292    int count = 0;
293    int size = 0;
294    try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
295      String s = "";
296      while (true) {
297        if (writer == null) {
298          final String name = file + '.' + count;
299          writer = new BufferedWriter(new FileWriter(name));
300        }
301        s = reader.readLine();
302        if (s == null) break;
303        writer.write(s + "\n");
304        size += s.length();
305        if (size >= splitSize) {
306          writer.close();
307          writer = null;
308          count++;
309          size = 0;
310        }
311      }
312      if (writer != null) writer.close();
313    }
314  }
315
316  /**
317   * Get the content of a file or resource on the classpath as an array of bytes.
318   * @param path the path of the file to read from as a string.
319   * @return a byte array with the file content.
320   * @throws IOException if an IO error occurs.
321   */
322  public static byte[] getPathAsByte(final String path) throws IOException {
323    return StreamUtils.getInputStreamAsByte(getFileInputStream(path));
324  }
325
326  /**
327   * Get the content of a file as an array of bytes.
328   * @param file the abstract path of the file to read from.
329   * @return a byte array with the file content.
330   * @throws IOException if an IO error occurs.
331   */
332  public static byte[] getFileAsByte(final File file) throws IOException {
333    final InputStream is = new BufferedInputStream(new FileInputStream(file));
334    final byte[] data = StreamUtils.getInputStreamAsByte(is);
335    return data;
336  }
337
338  /**
339   * Convert a set of file names into a set of <code>File</code> objects.
340   * @param dir the directory in which the files are located
341   * @param names the name part of each file (not the full path)
342   * @return an array of <code>File</code> objects.
343   */
344  public static File[] toFiles(final File dir, final String...names) {
345    final int len = names.length;
346    final File[] files = new File[len];
347    for (int i=0; i<len; i++) files[i] = new File(dir.getPath(), names[i]);
348    return files;
349  }
350
351  /**
352   * Convert a set of file paths into a set of URLs.
353   * @param files the files whose path is to be converted to a URL.
354   * @return an array of <code>URL</code> objects.
355   */
356  public static URL[] toURLs(final File...files) {
357    final URL[] urls = new URL[files.length];
358    for (int i=0; i<files.length; i++)
359      try {
360        urls[i] = files[i].toURI().toURL();
361      } catch(@SuppressWarnings("unused") final MalformedURLException ignored) {
362      }
363    return urls;
364  }
365
366  /**
367   * Write a byte array into an file.
368   * @param data the byte array to write.
369   * @param path the path to the file to write to.
370   * @throws IOException if an I/O error occurs.
371   */
372  public static void writeBytesToFile(final byte[] data, final File path) throws IOException {
373    final ByteArrayInputStream bais = new ByteArrayInputStream(data);
374    final OutputStream os = new BufferedOutputStream(new FileOutputStream(path));
375    StreamUtils.copyStream(bais, os);
376  }
377
378  /**
379   * Delete the specified path, recursively if this is a directory.
380   * @param path the path to delete.
381   * @return true if the folder and all contained files and subfolders were deleted, false otherwise.
382   */
383  public static boolean deletePath(final File path) {
384    return deletePath(path, false);
385  }
386
387  /**
388   * Delete the specified path, recursively if this is a directory.
389   * @param path the path to delete.
390   * @param childrenOnly when {@code true} only delete the children of the specified path, if it is a directory.
391   * @return true if the folder and all contained files and subfolders were deleted, false otherwise.
392   */
393  public static boolean deletePath(final File path, final boolean childrenOnly) {
394    if ((path == null) || !path.exists()) return false;
395    boolean success = true;
396    try {
397      if (path.isDirectory()) {
398        final File[] files = path.listFiles();
399        if (files != null) {
400          for (final File child: files) {
401            if (!deletePath(child)) success = false;
402          }
403        }
404      }
405      if (!childrenOnly) if (!path.delete()) success = false;
406    } catch (@SuppressWarnings("unused") final Exception e) {
407      success = false;
408    }
409    return success;
410  }
411
412  /**
413   * Create the folders of the specified path, if they do not all already esist.
414   * @param file the path for which to create the folders. If it is a file, then folders for its parent path are created.
415   * @throws IOException if the folders could not be created.
416   */
417  public static void mkdirs(final File file) throws IOException {
418    final File folder = file.isDirectory() ? file : file.getParentFile();
419    if (!folder.exists()) if (!folder.mkdirs()) throw new IOException("could not create folder " + folder);
420  }
421
422  /**
423   * Transform a file path into a URL.
424   * @param path the path to transform.
425   * @return the path expressed as a URL.
426   */
427  public static URL getURLFromFilePath(final String path) {
428    final File file = new File(path);
429    try {
430      return file.toURI().toURL();
431    } catch (@SuppressWarnings("unused") final MalformedURLException ignore) {
432    }
433    return null;
434  }
435
436  /**
437   * Transform a file path into a URL.
438   * @param path the path to transform.
439   * @return the path expressed as a URL.
440   */
441  public static URL getURLFromFilePath(final File path) {
442    try {
443      return path.toURI().toURL();
444    } catch (@SuppressWarnings("unused") final MalformedURLException ignore) {
445      return null;
446    }
447  }
448
449  /**
450   * Transform a file path into a URL in non URL-ncoded form.
451   * @param path the path to transform.
452   * @return the a string representing the decoded URL.
453   */
454  public static String getDecodedURLFromFilePath(final File path) {
455    try {
456      return URLDecoder.decode(path.toURI().toURL().toString(), "UTF-8");
457    } catch (@SuppressWarnings("unused") final MalformedURLException|UnsupportedEncodingException ignore) {
458      return null;
459    }
460  }
461
462  /**
463   * @return the root folder for JPPF temporary files.
464   */
465  public static File getJPPFTempDir() {
466    return JPPF_TEMP_DIR;
467  }
468
469  /**
470   * @return the root folder for JPPF temporary files.
471   */
472  private static File initJPPFTempDir() {
473    File baseDir = null;
474    String base = JPPFConfiguration.get(JPPFProperties.RESOURCE_CACHE_DIR);
475    try {
476      if (base == null) base = System.getProperty("java.io.tmpdir");
477      if (base == null) base = System.getProperty("user.home");
478      if (base == null) base = System.getProperty("user.dir");
479      if (base == null) base = ".";
480      if (!base.endsWith(File.separator)) base += File.separator;
481      base += ".jppf";
482      baseDir = new File(base, File.separator);
483      if (!baseDir.exists()) FileUtils.mkdirs(baseDir);
484      log.info("JPPF temp folder " + base);
485    } catch (final Exception e) {
486      log.error(e.getMessage(), e);
487    }
488    return baseDir;
489  }
490
491  /**
492   * Decompose a file name into name + extension.
493   * @param imageName the file name to convert.
494   * @return a Pair of name, extension.
495   */
496  public static Pair<String, String> getFileNameAndExtension(final String imageName) {
497    if (imageName == null) return null;
498    final int idx = imageName.lastIndexOf('.');
499    if (idx <= 0) return new Pair<>(imageName, null);
500    return new Pair<>(imageName.substring(0, idx), imageName.substring(idx + 1));
501  }
502
503  /**
504   * A file walker that deletes a complete file and folder hierarchy.
505   */
506  public static class DeleteFileVisitor extends SimpleFileVisitor<Path> {
507    /**
508     * Optional path matcher to filter the files to delete.
509     */
510    private final PathMatcher matcher;
511
512    /**
513     * Initialize without a matcher.
514     */
515    public DeleteFileVisitor() {
516      this(null);
517    }
518
519    /**
520     * Initialize with the specified path matcher.
521     * @param matcher anoptional path matcher to filter the files to delete.
522     */
523    public DeleteFileVisitor(final PathMatcher matcher) {
524      this.matcher = matcher;
525    }
526
527    @Override
528    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
529      if ((matcher == null) || matcher.matches(file)) Files.delete(file);
530      return FileVisitResult.CONTINUE;
531    }
532
533    @Override
534    public FileVisitResult postVisitDirectory(final Path dir, final IOException e) throws IOException {
535      if (e != null) throw e;
536      if (dir.toFile().listFiles().length <= 0) Files.delete(dir);
537      return FileVisitResult.CONTINUE;
538    }
539  }
540}