package com.bergerkiller.bukkit.common.utils; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; /** * Stream and File-related utility methods */ public class StreamUtil { public static UUID readUUID(DataInputStream stream) throws IOException { return new UUID(stream.readLong(), stream.readLong()); } public static void writeUUID(DataOutputStream stream, UUID uuid) throws IOException { stream.writeLong(uuid.getMostSignificantBits()); stream.writeLong(uuid.getLeastSignificantBits()); } public static void writeIndent(BufferedWriter writer, int indent) throws IOException { for (int i = 0; i < indent; i++) { writer.write(' '); } } /** * Returns an iterable that can be used to navigate all the files in a directory. * The listed files include sub-directories.<br> * <br> * <b>Implementers note:</b><br> * Might be used to optimize file listing for Java 1.7. * Alternatively, perhaps some native calls will do the job. * The goal is providing faster directory listing than the default * {@link File#listFiles()} method can offer. * * @param directory to list the file contents of * @return Iterable of Files available in the directory */ public static Iterable<File> listFiles(File directory) { return Arrays.asList(directory.listFiles()); } /** * Gets the amount of bytes of data stored on disk by a specific file or in a specific folder * * @param file to get the size of * @return File/folder size in bytes */ public static long getFileSize(File file) { if (!file.exists()) { return 0L; } else if (file.isDirectory()) { long size = 0L; for (File subfile : listFiles(file)) { size += getFileSize(subfile); } return size; } else { return file.length(); } } /** * Deletes a file or directory. * This method will attempt to delete all files in a directory * if a directory is specified. * All files it could not delete will be returned. * If the returned list is empty then deletion was successful. * The returned list is unmodifiable. * * @param file to delete * @return a list of files it could not delete */ public static List<File> deleteFile(File file) { if (file.isDirectory()) { List<File> failFiles = new ArrayList<File>(); deleteFileList(file, failFiles); return Collections.unmodifiableList(failFiles); } else if (file.delete()) { return Collections.emptyList(); } else { return Collections.unmodifiableList(Arrays.asList(file)); } } private static boolean deleteFileList(File file, List<File> failFiles) { if (file.isDirectory()) { boolean success = true; for (File subFile : listFiles(file)) { success &= deleteFileList(subFile, failFiles); } if (success) { file.delete(); } return success; } else if (file.delete()) { return true; } else { failFiles.add(file); return false; } } /** * Creates a new FileOutputStream to a file. * If the file does not yet exist a new file is created. * The contents of existing files will be overwritten. * * @param file to open * @return a new FileOutputStream for the (created) file * @throws IOException in case opening the file failed * @throws SecurityException in case the Security Manager (if assigned) denies access */ public static FileOutputStream createOutputStream(File file) throws IOException, SecurityException { return createOutputStream(file, false); } /** * Creates a new FileOutputStream to a file. * If the file does not yet exist a new file is created. * * @param file to open * @param append whether to append to existing files or not * @return a new FileOutputStream for the (created) file * @throws IOException in case opening the file failed * @throws SecurityException in case the Security Manager (if assigned) denies access */ public static FileOutputStream createOutputStream(File file, boolean append) throws IOException, SecurityException { File directory = file.getAbsoluteFile().getParentFile(); if (!directory.exists() && !directory.mkdirs()) { throw new IOException("Failed to create the parent directory of the file"); } if (!file.exists() && !file.createNewFile()) { throw new IOException("Failed to create the new file to write to"); } return new FileOutputStream(file, append); } /** * Tries to copy a file or directory from one place to the other. * If copying fails along the way the error is printed and false is returned. * Note that when copying directories it may result in an incomplete copy. * * @param sourceLocation * @param targetLocation * @return True if copying succeeded, False if not */ public static boolean tryCopyFile(File sourceLocation, File targetLocation) { try { copyFile(sourceLocation, targetLocation); return true; } catch (IOException ex) { ex.printStackTrace(); return false; } } /** * Copies a file or directory from one place to the other. * If copying fails along the way an IOException is thrown. * Note that when copying directories it may result in an incomplete copy. * * @param sourceLocation * @param targetLocation * @throws IOException */ public static void copyFile(File sourceLocation, File targetLocation) throws IOException { if (sourceLocation.isDirectory()) { if (!targetLocation.exists()) { targetLocation.mkdirs(); } for (File subFile : listFiles(sourceLocation)) { String subFileName = subFile.getName(); copyFile(new File(sourceLocation, subFileName), new File(targetLocation, subFileName)); } } else { // Start a new stream FileInputStream input = null; FileOutputStream output = null; FileChannel inputChannel = null; FileChannel outputChannel = null; try { // Initialize file streams input = new FileInputStream(sourceLocation); inputChannel = input.getChannel(); output = createOutputStream(targetLocation); outputChannel = output.getChannel(); // Start transferring long transfered = 0; long bytes = inputChannel.size(); while (transfered < bytes) { transfered += outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); outputChannel.position(transfered); } } finally { // Close input stream if (inputChannel != null) { inputChannel.close(); } else if (input != null) { input.close(); } // Close output stream if (outputChannel != null) { outputChannel.close(); } else if (output != null) { output.close(); } } } } /** * Obtains a file found in a parent directory. * Child name casing is ignored. If the child file is found * in the parent directory with the same casing, this is returned. * If this is not the case, but a file with the same name but different * casing is available, then this is returned instead. If none could be found, * a file with the casing of the child is returned alternatively.<br> * <br> * The returned File will point to an existing file <b>on the current OS</b>, if * system-independent file paths are needed or paths with accurate File System casing, * {@link File#getCanonicalFile()} should be used on the resulting File. * * @param parent directory * @param child file name * @return File pointing to the child file found in the parent directory */ public static File getFileIgnoreCase(File parent, String child) { if (LogicUtil.nullOrEmpty(child)) { return parent; } File childFile = new File(parent, child); if (!childFile.exists() && parent.exists()) { int firstFolderIdx = child.indexOf(File.separator); if (firstFolderIdx != -1) { // Process folder structure recursively File newParent = getFileIgnoreCase(parent, child.substring(0, firstFolderIdx)); String newChild = child.substring(firstFolderIdx + 1); return getFileIgnoreCase(newParent, newChild); } else { // No folder specified in child - find the accurate file name for (String childName : parent.list()) { if (childName.equalsIgnoreCase(child)) { return new File(parent, childName); } } } } return childFile; } }