/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.utils.file; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.text.TextUtils; import com.jcwhatever.nucleus.utils.validate.IValidator; import javax.annotation.Nullable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * File handling utilities. */ public final class FileUtils { private FileUtils() {} /** * Specifies how sub directories are traversed when searching for files. */ public enum DirectoryTraversal { /** * Do not traverse sub directories. */ NONE, /** * Traverse all sub directories. */ RECURSIVE } /** * Get all non-directory files in a folder. * * @param folder The folder to search for files in. * @param traversal The directory traversal of the search. */ public static List<File> getFiles(File folder, DirectoryTraversal traversal) { return getFiles(folder, traversal, null); } /** * Get all non-directory files in a folder. * * @param folder The folder to search for files in. * @param traversal The directory traversal of the search. * @param fileValidator The validator used to validate files. */ public static List<File> getFiles(File folder, DirectoryTraversal traversal, @Nullable IValidator<File> fileValidator) { PreCon.notNull(folder); PreCon.isValid(folder.isDirectory(), "folder argument must be a folder."); PreCon.notNull(traversal); File[] files = folder.listFiles(); if (files == null) return new ArrayList<>(0); List<File> results = new ArrayList<File>(files.length); for (File file : files) { if (file.isDirectory() && traversal == DirectoryTraversal.RECURSIVE) { List<File> traversed = getFiles(file, DirectoryTraversal.RECURSIVE, fileValidator); results.addAll(traversed); } else if (!file.isDirectory()) { if (fileValidator != null && !fileValidator.isValid(file)) continue; results.add(file); } } return results; } /** * Get the extension of a file, not including a dot. * * @param file The file to check. */ public static String getFileExtension(File file) { PreCon.notNull(file); return getFileExtension(file.getName()); } /** * Get the extension of a file name, not including a dot. * * @param fileName The file name to check. */ public static String getFileExtension(String fileName) { PreCon.notNull(fileName); int i = fileName.lastIndexOf('.'); if (i != -1) { return fileName.substring(i + 1); } return ""; } /** * Get the name of a file not including the extension. * * @param file The file to check. */ public static String getNameWithoutExtension(File file) { PreCon.notNull(file); return getNameWithoutExtension(file.getName()); } /** * Get the name of a file not including the extension. * * @param fileName The file name to check. */ public static String getNameWithoutExtension(String fileName) { PreCon.notNull(fileName); int i = fileName.lastIndexOf('.'); if (i != -1) { return fileName.substring(0, i); } return fileName; } /** * Get the relative path of a file using a base path to specify the * absolute portion. * * @param base The absolute portion of the path. * @param path The absolute path to convert to a relative path. */ public static String getRelative(File base, File path) { String absBase = base.getAbsolutePath(); String absPath = path.getAbsolutePath(); if (absPath.indexOf(absBase) != 0) return absPath; return absPath.substring(absBase.length() + (absPath.equals(absBase) ? 0 : 1)); } /** * Get a text file from a class resource. * * @param cls The class to get a resource stream from. * @param resourcePath The path of the file within the class jar file. * @param charSet The encoding type used by the text file. * * @return Null if resource not found. * * @throws java.lang.IllegalArgumentException */ @Nullable public static String scanTextFile(Class<?> cls, String resourcePath, Charset charSet) { return scanTextFile(cls, resourcePath, charSet, null); } /** * Get a text file from a class resource. * * @param cls The class to get a resource stream from. * @param resourcePath The path of the file within the class jar file. * @param charSet The encoding type used by the text file. * @param lineValidator Optional validator to use for each scanned line. * Returning false excludes the line from the result. * * @return Null if resource not found. * * @throws java.lang.IllegalArgumentException */ @Nullable public static String scanTextFile(Class<?> cls, String resourcePath, Charset charSet, @Nullable IValidator<String> lineValidator) { PreCon.notNull(cls); PreCon.notNullOrEmpty(resourcePath); PreCon.notNull(charSet); InputStream input = cls.getResourceAsStream(resourcePath); if (input == null) return null; String result = scanTextFile(input, charSet, 50, lineValidator); try { input.close(); } catch (IOException e) { e.printStackTrace(); } return result; } /** * Get text file contents as a string. * * @param file The file to scan. * @param charSet The encoding type used by the text file. * * @return Null if file not found. * * @throws java.lang.IllegalArgumentException */ @Nullable public static String scanTextFile(File file, Charset charSet) { PreCon.notNull(file); PreCon.notNull(charSet); return scanTextFile(file, charSet, null); } /** * Get text file contents as a string. * * @param file The file to scan. * @param charSet The encoding type used by the text file. * @param lineValidator Optional validator to use for each scanned line. * Returning false excludes the line from the result. * * @return Null if file not found. * * @throws java.lang.IllegalArgumentException */ @Nullable public static String scanTextFile(File file, Charset charSet, @Nullable IValidator<String> lineValidator) { PreCon.notNull(file); PreCon.notNull(charSet); InputStream input = null; try { input = new FileInputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } if (input == null) return null; String result = scanTextFile(input, charSet, (int)file.length(), lineValidator); try { input.close(); } catch (IOException e) { e.printStackTrace(); } return result; } /** * Get text file contents as a string from a file contained in a * zip file. * * @param zipFile The zip file that contains the text file. * @param fileName The name and path of the text file. * @param charSet The encoding type used by the text file. * * @return Null if file not found or error reading the file. * * @throws java.lang.IllegalArgumentException */ @Nullable public static String scanTextFile(ZipFile zipFile, String fileName, Charset charSet) { return scanTextFile(zipFile, fileName, charSet, null); } /** * Get text file contents as a string from a file contained in a * zip file. * * @param zipFile The zip file that contains the text file. * @param fileName The name and path of the text file. * @param charSet The encoding type used by the text file. * @param lineValidator Optional validator to use for each scanned line. * Returning false excludes the line from the result. * * @return Null if file not found or error reading the file. * * @throws java.lang.IllegalArgumentException */ @Nullable public static String scanTextFile(ZipFile zipFile, String fileName, Charset charSet, @Nullable IValidator<String> lineValidator) { PreCon.notNull(zipFile); PreCon.notNull(fileName); PreCon.notNull(charSet); ZipEntry entry = zipFile.getEntry(fileName); InputStream input = null; try { input = zipFile.getInputStream(entry); } catch (IOException e) { e.printStackTrace(); } if (input == null) return null; String result = scanTextFile(input, charSet, (int)entry.getSize(), lineValidator); try { input.close(); } catch (IOException e) { e.printStackTrace(); } return result; } /** * Get text file contents from a stream. * * @param input The input stream to scan * @param charSet The encoding type used by the text file. * @param initialBufferSize The initial size of the buffer. * * @throws java.lang.IllegalArgumentException */ public static String scanTextFile(InputStream input, Charset charSet, int initialBufferSize) { PreCon.notNull(input); PreCon.notNull(charSet); return scanTextFile(input, charSet, initialBufferSize, null); } /** * Get text file contents from a stream. * * @param input The input stream to scan * @param charSet The encoding type used by the text file. * @param initialBufferSize The initial size of the buffer. * @param lineValidator Optional validator to use for each scanned line. * Returning false excludes the line from the result. * * @throws java.lang.IllegalArgumentException */ public static String scanTextFile(InputStream input, Charset charSet, int initialBufferSize, @Nullable IValidator<String> lineValidator) { PreCon.notNull(input); PreCon.notNull(charSet); StringBuilder result = new StringBuilder(initialBufferSize); Scanner scanner = new Scanner(input, charSet.name()); while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (lineValidator != null && !lineValidator.isValid(line)) continue; if (result.length() > 0) result.append('\n'); result.append(line); } return result.toString(); } /** * Writes a text file. * * <p>Intended for writing a single line at a time. The line producer * is used to retrieve each line and to know when to stop.</p> * * <p>The method finishes when the line producer returns null</p> * * @param file The file to write. * @param charset The encoding to use. * @param lineProducer The line producer. * * @return The number of lines written. */ public static int writeTextFile(File file, Charset charset, ITextLineProducer lineProducer) { PreCon.notNull(file); PreCon.notNull(charset); return writeTextFile(file, charset, -1, lineProducer); } /** * Writes a text file. * * <p>Intended for writing a single line at a time. The line producer * is used to retrieve each line.</p> * * <p>The method finishes when the line producer returns null or the total number * of lines written matches the specified total.</p> * * @param file The file to write. * @param charset The encoding to use. * @param totalLines The total number of lines to write. * @param lineProducer The line producer. * * @return The number of lines written. */ public static int writeTextFile(File file, Charset charset, int totalLines, ITextLineProducer lineProducer) { PreCon.notNull(file); PreCon.notNull(charset); OutputStreamWriter writer = null; int written = 0; try { FileOutputStream fileStream = new FileOutputStream(file); writer = new OutputStreamWriter(fileStream, charset.name()); while (written < totalLines || totalLines == -1) { String line = lineProducer.nextLine(); if (line == null) break; writer.write(line); writer.write('\n'); written++; } } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } return written; } /** * Writes a text file. * * @param file The file to write. * @param charset The encoding to use. * @param text The text to write to the file. * * @return The number of lines written or -1 if failed to write. */ public static int writeTextFile(File file, Charset charset, String text) { PreCon.notNull(file); PreCon.notNull(charset); PreCon.notNull(text); OutputStreamWriter writer = null; String[] lines = TextUtils.PATTERN_NEW_LINE.split(text); int written = -1; try { FileOutputStream fileStream = new FileOutputStream(file); writer = new OutputStreamWriter(fileStream, charset.name()); int i = 0; for (; i < lines.length; i++) { writer.write(lines[i]); writer.write('\n'); } written = i; } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } return written; } /** * Extract a class resource into a file. * * @param cls The class to get a resource stream from. * @param resourcePath The path of the file within the class jar file. * @param outDir The output directory. * * @throws java.lang.RuntimeException */ @Nullable public static File extractResource(Class<?> cls, String resourcePath, File outDir) { PreCon.notNull(cls); PreCon.notNull(resourcePath); PreCon.notNull(outDir); if (!outDir.exists() && !outDir.mkdirs()) throw new RuntimeException("Failed to create output folder(s)."); File outFile = new File(outDir, getFilename(resourcePath)); if (outFile.exists()) return outFile; InputStream input = cls.getResourceAsStream("/res/" + resourcePath); if (input == null) return null; FileOutputStream output = null; try { if (!outFile.createNewFile()) { return null; } output = new FileOutputStream(outFile); byte[] buffer = new byte[4096]; int read; while ((read = input.read(buffer)) > 0) { output.write(buffer, 0, read); } } catch (IOException e) { e.printStackTrace(); } finally { try { input.close(); } catch (IOException e) { e.printStackTrace(); } try { if (output != null) output.close(); } catch (IOException e) { e.printStackTrace(); } } return outFile; } private static String getFilename(String resource) { String[] components = TextUtils.PATTERN_FILEPATH_SLASH.split(resource); return components[components.length - 1]; } /** * Interface for getting the next line to write to a text file. */ public interface ITextLineProducer { /** * Get the next line. * * @return Null to stop writing. */ @Nullable String nextLine(); } }