package com.cloudhopper.commons.util; /* * #%L * ch-commons-util * %% * Copyright (C) 2012 Cloudhopper by Twitter * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; /** * Utility class for handling Files. * * NOTE: Some code copied from the JXL project. * * @author joelauer (twitter: @jjlauer or <a href="http://twitter.com/jjlauer" target=window>http://twitter.com/jjlauer</a>) */ public class FileUtil { private FileUtil() { // do nothing } private static boolean equals(InputStream is1, InputStream is2) throws IOException { int BUFFSIZE = 1024; byte buf1[] = new byte[BUFFSIZE]; byte buf2[] = new byte[BUFFSIZE]; if (is1 == is2) { return true; } if (is1 == null && is2 == null) { return true; } if (is1 == null || is2 == null) { return false; } int read1 = -1; int read2 = -1; do { int offset1 = 0; while (offset1 < BUFFSIZE && (read1 = is1.read(buf1, offset1, BUFFSIZE-offset1)) >= 0) { offset1 += read1; } int offset2 = 0; while (offset2 < BUFFSIZE && (read2 = is2.read(buf2, offset2, BUFFSIZE-offset2)) >= 0) { offset2 += read2; } if (offset1 != offset2) { return false; } if (offset1 != BUFFSIZE) { Arrays.fill(buf1, offset1, BUFFSIZE, (byte)0); Arrays.fill(buf2, offset2, BUFFSIZE, (byte)0); } if (!Arrays.equals(buf1, buf2)) { return false; } } while (read1 >= 0 && read2 >= 0); if (read1 < 0 && read2 < 0) { return true; // both at EOF } return false; } /** * Tests whether the contents of two files equals each other by performing * a byte-by-byte comparison. Each byte must match each other in both files. * @param file1 The file to compare * @param file2 The other file to compare * @return True if file contents are equal, otherwise false. * @throws IOException Thrown if there is an underlying IO error while * attempt to compare the bytes. */ public static boolean equals(File file1, File file2) throws IOException { // file lengths must match if (file1.length() != file2.length()) { return false; } InputStream is1 = null; InputStream is2 = null; try { is1 = new FileInputStream(file1); is2 = new FileInputStream(file2); return equals(is1, is2); } finally { // make sure input streams are closed if (is1 != null) { try { is1.close(); } catch (Exception e) { } } if (is2 != null) { try { is2.close(); } catch (Exception e) { } } } } /** * Checks if the extension is valid. This method only permits letters, digits, * and an underscore character. * @param extension The file extension to validate * @return True if its valid, otherwise false */ public static boolean isValidFileExtension(String extension) { for (int i = 0; i < extension.length(); i++) { char c = extension.charAt(i); if (!(Character.isDigit(c) || Character.isLetter(c) || c == '_')) { return false; } } return true; } /** * Parse the filename and return the file extension. For example, if the * file is "app.2006-10-10.log", then this method will return "log". Will * only return the last file extension. For example, if the filename ends * with ".log.gz", then this method will return "gz" * @param String to process containing the filename * @return The file extension (without leading period) such as "gz" or "txt" * or null if none exists. */ public static String parseFileExtension(String filename) { // if null, return null if (filename == null) { return null; } // find position of last period int pos = filename.lastIndexOf('.'); // did one exist or have any length? if (pos < 0 || (pos+1) >= filename.length()) { return null; } // parse extension return filename.substring(pos+1); } /** * Finds all files (non-recursively) in a directory based on the FileFilter. * This method is slightly different than the JDK File.listFiles() version * since it throws an error if the directory does not exist or is not a * directory. Also, this method only finds files and will skip including * directories. */ public static File[] findFiles(File dir, FileFilter filter) throws FileNotFoundException { if (!dir.exists()) { throw new FileNotFoundException("Directory " + dir + " does not exist."); } if (!dir.isDirectory()) { throw new FileNotFoundException("File " + dir + " is not a directory."); } // being matching process, create array for returning results ArrayList<File> files = new ArrayList<File>(); // get all files in this directory File[] allFiles = dir.listFiles(); // were any files returned? if (allFiles != null && allFiles.length > 0) { // loop thru every file in the dir for (File f : allFiles) { // only match files, not a directory if (f.isFile()) { // delegate matching to provided file matcher if (filter.accept(f)) { files.add(f); } } } } // based on filesystem, order of files not guaranteed -- sort now File[] r = files.toArray(new File[0]); Arrays.sort(r); return r; } /** * Get all of the files (not dirs) under <CODE>dir</CODE> * @param dir Directory to search. * @return all the files under <CODE>dir</CODE> */ /** public static Set<File> getRecursiveFiles(File dir) throws IOException { if (!dir.isDirectory()) { HashSet<File> one = new HashSet<File>(); one.add(dir); return one; } else { Set<File> ret = recurseDir(dir); return ret; } } private static Set<File> recurseDir(File dir) throws IOException { HashSet<File> c = new HashSet<File>(); File[] files = dir.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { c.addAll(recurseDir(files[i])); } else { c.add(files[i]); } } return c; } */ /** public static boolean rmdir(File dir, boolean recursive) throws IOException { // make sure this is a directory if (!dir.isDirectory()) { throw new IOException("File " + dir + " is not a directory"); } File[] files = dir.listFiles(); // are there files? if (files != null && files.length > 0 && !recursive) { throw new IOException("Directory " + dir + " is not empty, cannot be deleted"); } for (File file : files) { if (file.isDirectory()) { rmdir(file); } else { file.delete(); } } // finally remove this directory return dir.delete(); } private static boolean rmdir(File dir) throws IOException { // make sure this is a directory if (!dir.isDirectory()) { throw new IOException("File " + dir + " is not a directory"); } File[] files = dir.listFiles(); // are there files? if (files != null && files.length > 0 && !recursive) { throw new IOException("Directory " + dir + " is not empty, cannot be deleted"); } boolean success = true; for (File file : files) { if (file.isDirectory()) { success |= rmdir(file); } else { success |= file.delete(); } } // was the recursive part okay? // finally remove this directory return dir.delete(); } */ /** * Copy dest.length bytes from the inputstream into the dest bytearray. * @param is * @param dest * @throws IOException */ /** public static void copy(InputStream is, byte[] dest) throws IOException { int len = dest.length; int ofs = 0; while (len > 0) { int size = is.read(dest, ofs, len); ofs += size; len -= size; } } */ /** * Copy the source file to the target file. * @param sourceFile The source file to copy from * @param targetFile The target file to copy to * @throws FileAlreadyExistsException Thrown if the target file already * exists. This exception is a subclass of IOException, so catching * an IOException is enough if you don't care about this specific reason. * @throws IOException Thrown if an error during the copy */ public static void copy(File sourceFile, File targetFile) throws FileAlreadyExistsException, IOException { copy(sourceFile, targetFile, false); } /** * Copy the source file to the target file while optionally permitting an * overwrite to occur in case the target file already exists. * @param sourceFile The source file to copy from * @param targetFile The target file to copy to * @return True if an overwrite occurred, otherwise false. * @throws FileAlreadyExistsException Thrown if the target file already * exists and an overwrite is not permitted. This exception is a * subclass of IOException, so catching an IOException is enough if you * don't care about this specific reason. * @throws IOException Thrown if an error during the copy */ public static boolean copy(File sourceFile, File targetFile, boolean overwrite) throws FileAlreadyExistsException, IOException { boolean overwriteOccurred = false; // check if the targetFile already exists if (targetFile.exists()) { // if overwrite is not allowed, throw an exception if (!overwrite) { throw new FileAlreadyExistsException("Target file " + targetFile + " already exists"); } else { // set the flag that it occurred overwriteOccurred = true; } } // proceed with copy FileInputStream fis = new FileInputStream(sourceFile); FileOutputStream fos = new FileOutputStream(targetFile); fis.getChannel().transferTo(0, sourceFile.length(), fos.getChannel()); fis.close(); fos.flush(); fos.close(); return overwriteOccurred; } /** public static boolean copy(Set<File> sources, File toDir) throws IOException { boolean completeSuccess = true; int index = 0; for (File source : sources) { File target = new File(toDir, source.getName()); try { copy(source, target); } catch (IOException ioe) { completeSuccess = false; ioe.printStackTrace(); } index++; } return completeSuccess; } */ /** * Copy the contents of is to os. * @param is * @param os * @param buf Can be null * @param close If true, is is closed after the copy. * @throws IOException */ /** public static final void copy(InputStream is, OutputStream os, byte[] buf, boolean close) throws IOException { int len; if (buf == null) { buf = new byte[4096]; } while ((len = is.read(buf)) > 0) { os.write(buf, 0, len); } os.flush(); if (close) { is.close(); } } public static void flush(byte[] data, File toFile) throws IOException { FileOutputStream fos = new FileOutputStream(toFile); fos.write(data); fos.flush(); fos.close(); } */ /** * Read <CODE>f</CODE> and return as byte[] * @param f * @throws IOException * @return bytes from <CODE>f</CODE> */ /** public static final byte[] load(File f) throws IOException { FileInputStream fis = new FileInputStream(f); return load(fis, true); } */ /** * Copy the contents of is to the returned byte array. * @param is * @param close If true, is is closed after the copy. * @throws IOException */ /** public static final byte[] load(InputStream is, boolean close) throws IOException { final ByteArrayOutputStream os = new ByteArrayOutputStream(); copy(is, os, null, close); return os.toByteArray(); } */ /** * Class to compare Files by their embedded DateTimes. */ public static class FileNameDateTimeComparator implements Comparator<File> { private String pattern; private DateTimeZone zone; /** * Creates a default instance where the pattern is "yyyy-MM-dd" and the * default timezone of UTC. */ public FileNameDateTimeComparator() { this(null, null); } public FileNameDateTimeComparator(String pattern, DateTimeZone zone) { if (pattern == null) { this.pattern = "yyyy-MM-dd"; } else { this.pattern = pattern; } if (zone == null) { this.zone = DateTimeZone.UTC; } else { this.zone = zone; } } public int compare(File f1, File f2) { // extract datetimes from both files DateTime dt1 = DateTimeUtil.parseEmbedded(f1.getName(), pattern, zone); DateTime dt2 = DateTimeUtil.parseEmbedded(f2.getName(), pattern, zone); // compare these two return dt1.compareTo(dt2); } } }