| /* ******************************************************************* |
| * Copyright (c) 1999-2001 Xerox Corporation, |
| * 2002 Palo Alto Research Center, Incorporated (PARC). |
| * All rights reserved. |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Xerox/PARC initial implementation |
| * ******************************************************************/ |
| |
| package org.aspectj.util; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.BufferedReader; |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.io.Writer; |
| import java.net.MalformedURLException; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| |
| /** |
| * @author Andy Clement |
| * @author Kris De Volder |
| */ |
| public class FileUtil { |
| /** default parent directory File when a file has a null parent */ |
| public static final File DEFAULT_PARENT = new File("."); // XXX user.dir? |
| |
| /** unmodifiable List of String source file suffixes (including leading ".") */ |
| public static final List<String> SOURCE_SUFFIXES = Collections.unmodifiableList(Arrays.asList(new String[] { ".java", ".aj" })); |
| |
| public static final FileFilter ZIP_FILTER = new FileFilter() { |
| public boolean accept(File file) { |
| return isZipFile(file); |
| } |
| |
| public String toString() { |
| return "ZIP_FILTER"; |
| } |
| }; |
| |
| // public static final FileFilter SOURCE_FILTER = new FileFilter() { |
| // public boolean accept(File file) { |
| // return hasSourceSuffix(file); |
| // } |
| // |
| // public String toString() { |
| // return "SOURCE_FILTER"; |
| // } |
| // }; |
| |
| final static int[] INT_RA = new int[0]; |
| |
| /** accept all files */ |
| public static final FileFilter ALL = new FileFilter() { |
| public boolean accept(File f) { |
| return true; |
| } |
| }; |
| public static final FileFilter DIRS_AND_WRITABLE_CLASSES = new FileFilter() { |
| public boolean accept(File file) { |
| return ((null != file) && (file.isDirectory() || (file.canWrite() && file.getName().toLowerCase().endsWith(".class")))); |
| } |
| }; |
| private static final boolean PERMIT_CVS; |
| static { |
| String name = FileUtil.class.getName() + ".PERMIT_CVS"; |
| PERMIT_CVS = LangUtil.getBoolean(name, false); |
| } |
| |
| /** @return true if file exists and is a zip file */ |
| public static boolean isZipFile(File file) { |
| try { |
| return (null != file) && new ZipFile(file) != null; |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| |
| /** @return true if path ends with .zip or .jar */ |
| // public static boolean hasZipSuffix(String path) { |
| // return ((null != path) && (0 != zipSuffixLength(path))); |
| // } |
| /** @return 0 if file has no zip/jar suffix or 4 otherwise */ |
| public static int zipSuffixLength(File file) { |
| return (null == file ? 0 : zipSuffixLength(file.getPath())); |
| } |
| |
| /** @return 0 if no zip/jar suffix or 4 otherwise */ |
| public static int zipSuffixLength(String path) { |
| if ((null != path) && (4 < path.length())) { |
| String test = path.substring(path.length() - 4).toLowerCase(); |
| if (".zip".equals(test) || ".jar".equals(test)) { |
| return 4; |
| } |
| } |
| return 0; |
| } |
| |
| /** @return true if file path has a source suffix */ |
| public static boolean hasSourceSuffix(File file) { |
| return ((null != file) && hasSourceSuffix(file.getPath())); |
| } |
| |
| /** @return true if path ends with .java or .aj */ |
| public static boolean hasSourceSuffix(String path) { |
| return ((null != path) && (0 != sourceSuffixLength(path))); |
| } |
| |
| /** |
| * @return 0 if file has no source suffix or the length of the suffix otherwise |
| */ |
| public static int sourceSuffixLength(File file) { |
| return (null == file ? 0 : sourceSuffixLength(file.getPath())); |
| } |
| |
| /** @return 0 if no source suffix or the length of the suffix otherwise */ |
| public static int sourceSuffixLength(String path) { |
| if (LangUtil.isEmpty(path)) { |
| return 0; |
| } |
| |
| for (Iterator<String> iter = SOURCE_SUFFIXES.iterator(); iter.hasNext();) { |
| String suffix = iter.next(); |
| if (path.endsWith(suffix) || path.toLowerCase().endsWith(suffix)) { |
| return suffix.length(); |
| } |
| } |
| return 0; |
| } |
| |
| /** @return true if this is a readable directory */ |
| public static boolean canReadDir(File dir) { |
| return ((null != dir) && dir.canRead() && dir.isDirectory()); |
| } |
| |
| /** @return true if this is a readable file */ |
| public static boolean canReadFile(File file) { |
| return ((null != file) && file.canRead() && file.isFile()); |
| } |
| |
| /** @return true if dir is a writable directory */ |
| public static boolean canWriteDir(File dir) { |
| return ((null != dir) && dir.canWrite() && dir.isDirectory()); |
| } |
| |
| /** @return true if this is a writable file */ |
| public static boolean canWriteFile(File file) { |
| return ((null != file) && file.canWrite() && file.isFile()); |
| } |
| |
| // /** |
| // * @throws IllegalArgumentException unless file is readable and not a |
| // * directory |
| // */ |
| // public static void throwIaxUnlessCanReadFile(File file, String label) { |
| // if (!canReadFile(file)) { |
| // throw new IllegalArgumentException(label + " not readable file: " + |
| // file); |
| // } |
| // } |
| |
| /** |
| * @throws IllegalArgumentException unless dir is a readable directory |
| */ |
| public static void throwIaxUnlessCanReadDir(File dir, String label) { |
| if (!canReadDir(dir)) { |
| throw new IllegalArgumentException(label + " not readable dir: " + dir); |
| } |
| } |
| |
| /** |
| * @throws IllegalArgumentException unless file is readable and not a directory |
| */ |
| public static void throwIaxUnlessCanWriteFile(File file, String label) { |
| if (!canWriteFile(file)) { |
| throw new IllegalArgumentException(label + " not writable file: " + file); |
| } |
| } |
| |
| /** @throws IllegalArgumentException unless dir is a readable directory */ |
| public static void throwIaxUnlessCanWriteDir(File dir, String label) { |
| if (!canWriteDir(dir)) { |
| throw new IllegalArgumentException(label + " not writable dir: " + dir); |
| } |
| } |
| |
| /** @return array same length as input, with String paths */ |
| public static String[] getPaths(File[] files) { |
| if ((null == files) || (0 == files.length)) { |
| return new String[0]; |
| } |
| String[] result = new String[files.length]; |
| for (int i = 0; i < result.length; i++) { |
| if (null != files[i]) { |
| result[i] = files[i].getPath(); |
| } |
| } |
| return result; |
| } |
| |
| /** @return array same length as input, with String paths */ |
| public static String[] getPaths(List<File> files) { |
| final int size = (null == files ? 0 : files.size()); |
| if (0 == size) { |
| return new String[0]; |
| } |
| String[] result = new String[size]; |
| for (int i = 0; i < size; i++) { |
| File file = files.get(i); |
| if (null != file) { |
| result[i] = file.getPath(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Extract the name of a class from the path to its file. If the basedir is null, then the class is assumed to be in the default |
| * package unless the classFile has one of the top-level suffixes { com, org, java, javax } as a parent directory. |
| * |
| * @param basedir the File of the base directory (prefix of classFile) |
| * @param classFile the File of the class to extract the name for |
| * @throws IllegalArgumentException if classFile is null or does not end with ".class" or a non-null basedir is not a prefix of |
| * classFile |
| */ |
| public static String fileToClassName(File basedir, File classFile) { |
| LangUtil.throwIaxIfNull(classFile, "classFile"); |
| String classFilePath = normalizedPath(classFile); |
| if (!classFilePath.endsWith(".class")) { |
| String m = classFile + " does not end with .class"; |
| throw new IllegalArgumentException(m); |
| } |
| classFilePath = classFilePath.substring(0, classFilePath.length() - 6); |
| if (null != basedir) { |
| String basePath = normalizedPath(basedir); |
| if (!classFilePath.startsWith(basePath)) { |
| String m = classFile + " does not start with " + basedir; |
| throw new IllegalArgumentException(m); |
| } |
| classFilePath = classFilePath.substring(basePath.length() + 1); |
| } else { |
| final String[] suffixes = new String[] { "com", "org", "java", "javax" }; |
| boolean found = false; |
| for (int i = 0; !found && (i < suffixes.length); i++) { |
| int loc = classFilePath.indexOf(suffixes[i] + "/"); |
| if ((0 == loc) || ((-1 != loc) && ('/' == classFilePath.charAt(loc - 1)))) { |
| classFilePath = classFilePath.substring(loc); |
| found = true; |
| } |
| } |
| if (!found) { |
| int loc = classFilePath.lastIndexOf("/"); |
| if (-1 != loc) { // treat as default package |
| classFilePath = classFilePath.substring(loc + 1); |
| } |
| } |
| } |
| return classFilePath.replace('/', '.'); |
| } |
| |
| /** |
| * Normalize path for comparisons by rendering absolute, clipping basedir prefix, trimming and changing '\\' to '/' |
| * |
| * @param file the File with the path to normalize |
| * @param basedir the File for the prefix of the file to normalize - ignored if null |
| * @return "" if null or normalized path otherwise |
| * @throws IllegalArgumentException if basedir is not a prefix of file |
| */ |
| public static String normalizedPath(File file, File basedir) { |
| String filePath = normalizedPath(file); |
| if (null != basedir) { |
| String basePath = normalizedPath(basedir); |
| if (filePath.startsWith(basePath)) { |
| filePath = filePath.substring(basePath.length()); |
| if (filePath.startsWith("/")) { |
| filePath = filePath.substring(1); |
| } |
| } |
| } |
| return filePath; |
| } |
| |
| /** |
| * Render a set of files to String as a path by getting absolute paths of each and delimiting with infix. |
| * |
| * @param files the File[] to flatten - may be null or empty |
| * @param infix the String delimiter internally between entries (if null, then use File.pathSeparator). (alias to |
| * <code>flatten(getAbsolutePaths(files), infix)</code> |
| * @return String with absolute paths to entries in order, delimited with infix |
| */ |
| public static String flatten(File[] files, String infix) { |
| if (LangUtil.isEmpty(files)) { |
| return ""; |
| } |
| return flatten(getPaths(files), infix); |
| } |
| |
| /** |
| * Flatten File[] to String. |
| * |
| * @param files the File[] of paths to flatten - null ignored |
| * @param infix the String infix to use - null treated as File.pathSeparator |
| */ |
| public static String flatten(String[] paths, String infix) { |
| if (null == infix) { |
| infix = File.pathSeparator; |
| } |
| StringBuffer result = new StringBuffer(); |
| boolean first = true; |
| for (int i = 0; i < paths.length; i++) { |
| String path = paths[i]; |
| if (null == path) { |
| continue; |
| } |
| if (first) { |
| first = false; |
| } else { |
| result.append(infix); |
| } |
| result.append(path); |
| } |
| return result.toString(); |
| } |
| |
| /** |
| * Normalize path for comparisons by rendering absolute trimming and changing '\\' to '/' |
| * |
| * @return "" if null or normalized path otherwise |
| */ |
| public static String normalizedPath(File file) { |
| return (null == file ? "" : weakNormalize(file.getAbsolutePath())); |
| } |
| |
| /** |
| * Weakly normalize path for comparisons by trimming and changing '\\' to '/' |
| */ |
| public static String weakNormalize(String path) { |
| if (null != path) { |
| path = path.replace('\\', '/').trim(); |
| } |
| return path; |
| } |
| |
| /** |
| * Get best File for the first-readable path in input paths, treating entries prefixed "sp:" as system property keys. Safe to |
| * call in static initializers. |
| * |
| * @param paths the String[] of paths to check. |
| * @return null if not found, or valid File otherwise |
| */ |
| public static File getBestFile(String[] paths) { |
| if (null == paths) { |
| return null; |
| } |
| File result = null; |
| for (int i = 0; (null == result) && (i < paths.length); i++) { |
| String path = paths[i]; |
| if (null == path) { |
| continue; |
| } |
| if (path.startsWith("sp:")) { |
| try { |
| path = System.getProperty(path.substring(3)); |
| } catch (Throwable t) { |
| path = null; |
| } |
| if (null == path) { |
| continue; |
| } |
| } |
| try { |
| File f = new File(path); |
| if (f.exists() && f.canRead()) { |
| result = FileUtil.getBestFile(f); |
| } |
| } catch (Throwable t) { |
| // swallow |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Render as best file, canonical or absolute. |
| * |
| * @param file the File to get the best File for (not null) |
| * @return File of the best-available path |
| * @throws IllegalArgumentException if file is null |
| */ |
| public static File getBestFile(File file) { |
| LangUtil.throwIaxIfNull(file, "file"); |
| if (file.exists()) { |
| try { |
| return file.getCanonicalFile(); |
| } catch (IOException e) { |
| return file.getAbsoluteFile(); |
| } |
| } else { |
| return file; |
| } |
| } |
| |
| /** |
| * Render as best path, canonical or absolute. |
| * |
| * @param file the File to get the path for (not null) |
| * @return String of the best-available path |
| * @throws IllegalArgumentException if file is null |
| */ |
| public static String getBestPath(File file) { |
| LangUtil.throwIaxIfNull(file, "file"); |
| if (file.exists()) { |
| try { |
| return file.getCanonicalPath(); |
| } catch (IOException e) { |
| return file.getAbsolutePath(); |
| } |
| } else { |
| return file.getPath(); |
| } |
| } |
| |
| /** @return array same length as input, with String absolute paths */ |
| public static String[] getAbsolutePaths(File[] files) { |
| if ((null == files) || (0 == files.length)) { |
| return new String[0]; |
| } |
| String[] result = new String[files.length]; |
| for (int i = 0; i < result.length; i++) { |
| if (null != files[i]) { |
| result[i] = files[i].getAbsolutePath(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Recursively delete the contents of dir, but not the dir itself |
| * |
| * @return the total number of files deleted |
| */ |
| public static int deleteContents(File dir) { |
| return deleteContents(dir, ALL); |
| } |
| |
| /** |
| * Recursively delete some contents of dir, but not the dir itself. This deletes any subdirectory which is empty after its files |
| * are deleted. |
| * |
| * @return the total number of files deleted |
| */ |
| public static int deleteContents(File dir, FileFilter filter) { |
| return deleteContents(dir, filter, true); |
| } |
| |
| /** |
| * Recursively delete some contents of dir, but not the dir itself. If deleteEmptyDirs is true, this deletes any subdirectory |
| * which is empty after its files are deleted. |
| * |
| * @param dir the File directory (if a file, the the file is deleted) |
| * @return the total number of files deleted |
| */ |
| public static int deleteContents(File dir, FileFilter filter, |
| boolean deleteEmptyDirs) { |
| if (null == dir) { |
| throw new IllegalArgumentException("null dir"); |
| } |
| if ((!dir.exists()) || (!dir.canWrite())) { |
| return 0; |
| } |
| if (!dir.isDirectory()) { |
| dir.delete(); |
| return 1; |
| } |
| String[] fromFiles = dir.list(); |
| if (fromFiles == null) { |
| return 0; |
| } |
| int result = 0; |
| for (int i = 0; i < fromFiles.length; i++) { |
| String string = fromFiles[i]; |
| File file = new File(dir, string); |
| if ((null == filter) || filter.accept(file)) { |
| if (file.isDirectory()) { |
| result += deleteContents(file, filter, deleteEmptyDirs); |
| String[] fileContent = file.list(); |
| if (deleteEmptyDirs && fileContent != null |
| && 0 == fileContent.length) { |
| file.delete(); |
| } |
| } else { |
| /* boolean ret = */ |
| file.delete(); |
| result++; |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Copy contents of fromDir into toDir |
| * |
| * @param fromDir must exist and be readable |
| * @param toDir must exist or be creatable and be writable |
| * @return the total number of files copied |
| */ |
| public static int copyDir(File fromDir, File toDir) throws IOException { |
| return copyDir(fromDir, toDir, null, null); |
| } |
| |
| /** |
| * Recursively copy files in fromDir (with any fromSuffix) to toDir, replacing fromSuffix with toSuffix if any. This silently |
| * ignores dirs and files that are not readable but throw IOException for directories that are not writable. This does not clean |
| * out the original contents of toDir. (subdirectories are not renamed per directory rules) |
| * |
| * @param fromSuffix select files with this suffix - select all if null or empty |
| * @param toSuffix replace fromSuffix with toSuffix in the destination file name - ignored if null or empty, appended to name if |
| * fromSuffix is null or empty |
| * @return the total number of files copied |
| */ |
| public static int copyDir(File fromDir, File toDir, final String fromSuffix, String toSuffix) throws IOException { |
| return copyDir(fromDir, toDir, fromSuffix, toSuffix, (FileFilter) null); |
| } |
| |
| // /** |
| // * Recursively copy files in fromDir (with any fromSuffix) to toDir, |
| // * replacing fromSuffix with toSuffix if any, and adding the destination |
| // * file to any collector. This silently ignores dirs and files that are |
| // not |
| // * readable but throw IOException for directories that are not writable. |
| // * This does not clean out the original contents of toDir. (subdirectories |
| // * are not renamed per directory rules) This calls any delegate |
| // * FilenameFilter to collect any selected file. |
| // * |
| // * @param fromSuffix select files with this suffix - select all if null or |
| // * empty |
| // * @param toSuffix replace fromSuffix with toSuffix in the destination |
| // file |
| // * name - ignored if null or empty, appended to name if |
| // * fromSuffix is null or empty |
| // * @param collector the List sink for destination files - ignored if null |
| // * @return the total number of files copied |
| // */ |
| // public static int copyDir(File fromDir, File toDir, final String |
| // fromSuffix, final String toSuffix, final List collector) |
| // throws IOException { |
| // // int before = collector.size(); |
| // if (null == collector) { |
| // return copyDir(fromDir, toDir, fromSuffix, toSuffix); |
| // } else { |
| // FileFilter collect = new FileFilter() { |
| // public boolean accept(File pathname) { |
| // return collector.add(pathname); |
| // } |
| // }; |
| // return copyDir(fromDir, toDir, fromSuffix, toSuffix, collect); |
| // } |
| // } |
| |
| /** |
| * Recursively copy files in fromDir (with any fromSuffix) to toDir, replacing fromSuffix with toSuffix if any. This silently |
| * ignores dirs and files that are not readable but throw IOException for directories that are not writable. This does not clean |
| * out the original contents of toDir. (subdirectories are not renamed per directory rules) This calls any delegate |
| * FilenameFilter to collect any selected file. |
| * |
| * @param fromSuffix select files with this suffix - select all if null or empty |
| * @param toSuffix replace fromSuffix with toSuffix in the destination file name - ignored if null or empty, appended to name if |
| * fromSuffix is null or empty |
| * @return the total number of files copied |
| */ |
| public static int copyDir(File fromDir, File toDir, final String fromSuffix, final String toSuffix, final FileFilter delegate) |
| throws IOException { |
| |
| if ((null == fromDir) || (!fromDir.canRead())) { |
| return 0; |
| } |
| final boolean haveSuffix = ((null != fromSuffix) && (0 < fromSuffix.length())); |
| final int slen = (!haveSuffix ? 0 : fromSuffix.length()); |
| |
| if (!toDir.exists()) { |
| toDir.mkdirs(); |
| } |
| final String[] fromFiles; |
| if (!haveSuffix) { |
| fromFiles = fromDir.list(); |
| } else { |
| FilenameFilter filter = new FilenameFilter() { |
| public boolean accept(File dir, String name) { |
| return (new File(dir, name).isDirectory() || (name.endsWith(fromSuffix))); |
| } |
| }; |
| fromFiles = fromDir.list(filter); |
| } |
| int result = 0; |
| final int MAX = (null == fromFiles ? 0 : fromFiles.length); |
| for (int i = 0; i < MAX; i++) { |
| String filename = fromFiles[i]; |
| File fromFile = new File(fromDir, filename); |
| if (fromFile.canRead()) { |
| if (fromFile.isDirectory()) { |
| result += copyDir(fromFile, new File(toDir, filename), fromSuffix, toSuffix, delegate); |
| } else if (fromFile.isFile()) { |
| if (haveSuffix) { |
| filename = filename.substring(0, filename.length() - slen); |
| } |
| if (null != toSuffix) { |
| filename = filename + toSuffix; |
| } |
| File targetFile = new File(toDir, filename); |
| if ((null == delegate) || delegate.accept(targetFile)) { |
| copyFile(fromFile, targetFile); |
| } |
| result++; |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Recursively list files in srcDir. |
| * |
| * @return ArrayList with String paths of File under srcDir (relative to srcDir) |
| */ |
| public static String[] listFiles(File srcDir) { |
| ArrayList<String> result = new ArrayList<String>(); |
| if ((null != srcDir) && srcDir.canRead()) { |
| listFiles(srcDir, null, result); |
| } |
| return result.toArray(new String[0]); |
| } |
| |
| public static final FileFilter aspectjSourceFileFilter = new FileFilter() { |
| public boolean accept(File pathname) { |
| String name = pathname.getName().toLowerCase(); |
| return name.endsWith(".java") || name.endsWith(".aj"); |
| } |
| }; |
| |
| /** |
| * Recursively list files in srcDir. |
| * |
| * @return ArrayList with String paths of File under srcDir (relative to srcDir) |
| */ |
| public static File[] listFiles(File srcDir, FileFilter fileFilter) { |
| ArrayList<File> result = new ArrayList<File>(); |
| if ((null != srcDir) && srcDir.canRead()) { |
| listFiles(srcDir, result, fileFilter); |
| } |
| return result.toArray(new File[result.size()]); |
| } |
| |
| /** |
| * Recursively list .class files in specified directory |
| * |
| * @return List of File objects |
| */ |
| public static List<File> listClassFiles(File dir) { |
| ArrayList<File> result = new ArrayList<File>(); |
| if ((null != dir) && dir.canRead()) { |
| listClassFiles(dir, result); |
| } |
| return result; |
| } |
| |
| /** |
| * Convert String[] paths to File[] as offset of base directory |
| * |
| * @param basedir the non-null File base directory for File to create with paths |
| * @param paths the String[] of paths to create |
| * @return File[] with same length as paths |
| */ |
| public static File[] getBaseDirFiles(File basedir, String[] paths) { |
| return getBaseDirFiles(basedir, paths, (String[]) null); |
| } |
| |
| /** |
| * Convert String[] paths to File[] as offset of base directory |
| * |
| * @param basedir the non-null File base directory for File to create with paths |
| * @param paths the String[] of paths to create |
| * @param suffixes the String[] of suffixes to limit sources to - ignored if null |
| * @return File[] with same length as paths |
| */ |
| public static File[] getBaseDirFiles(File basedir, String[] paths, String[] suffixes) { |
| LangUtil.throwIaxIfNull(basedir, "basedir"); |
| LangUtil.throwIaxIfNull(paths, "paths"); |
| File[] result = null; |
| if (!LangUtil.isEmpty(suffixes)) { |
| ArrayList<File> list = new ArrayList<File>(); |
| for (int i = 0; i < paths.length; i++) { |
| String path = paths[i]; |
| for (int j = 0; j < suffixes.length; j++) { |
| if (path.endsWith(suffixes[j])) { |
| list.add(new File(basedir, paths[i])); |
| break; |
| } |
| } |
| } |
| result = list.toArray(new File[0]); |
| } else { |
| result = new File[paths.length]; |
| for (int i = 0; i < result.length; i++) { |
| result[i] = newFile(basedir, paths[i]); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Create a new File, resolving paths ".." and "." specially. |
| * |
| * @param dir the File for the parent directory of the file |
| * @param path the path in the parent directory (filename only?) |
| * @return File for the new file. |
| */ |
| private static File newFile(File dir, String path) { |
| if (".".equals(path)) { |
| return dir; |
| } else if ("..".equals(path)) { |
| File parentDir = dir.getParentFile(); |
| if (null != parentDir) { |
| return parentDir; |
| } else { |
| return new File(dir, ".."); |
| } |
| } else { |
| return new File(dir, path); |
| } |
| } |
| |
| /** |
| * Copy files from source dir into destination directory, creating any needed directories. This differs from copyDir in not |
| * being recursive; each input with the source dir creates a full path. However, if the source is a directory, it is copied as |
| * such. |
| * |
| * @param srcDir an existing, readable directory containing relativePaths files |
| * @param relativePaths a set of paths relative to srcDir to readable File to copy |
| * @param destDir an existing, writable directory to copy files to |
| * @throws IllegalArgumentException if input invalid, IOException if operations fail |
| */ |
| public static File[] copyFiles(File srcDir, String[] relativePaths, File destDir) throws IllegalArgumentException, IOException { |
| final String[] paths = relativePaths; |
| throwIaxUnlessCanReadDir(srcDir, "srcDir"); |
| throwIaxUnlessCanWriteDir(destDir, "destDir"); |
| LangUtil.throwIaxIfNull(paths, "relativePaths"); |
| File[] result = new File[paths.length]; |
| for (int i = 0; i < paths.length; i++) { |
| String path = paths[i]; |
| LangUtil.throwIaxIfNull(path, "relativePaths-entry"); |
| File src = newFile(srcDir, paths[i]); |
| File dest = newFile(destDir, path); |
| File destParent = dest.getParentFile(); |
| if (!destParent.exists()) { |
| destParent.mkdirs(); |
| } |
| LangUtil.throwIaxIfFalse(canWriteDir(destParent), "dest-entry-parent"); |
| copyFile(src, dest); // both file-dir and dir-dir copies |
| result[i] = dest; |
| } |
| return result; |
| } |
| |
| /** |
| * Copy fromFile to toFile, handling file-file, dir-dir, and file-dir copies. |
| * |
| * @param fromFile the File path of the file or directory to copy - must be readable |
| * @param toFile the File path of the target file or directory - must be writable (will be created if it does not exist) |
| */ |
| public static void copyFile(File fromFile, File toFile) throws IOException { |
| LangUtil.throwIaxIfNull(fromFile, "fromFile"); |
| LangUtil.throwIaxIfNull(toFile, "toFile"); |
| LangUtil.throwIaxIfFalse(!toFile.equals(fromFile), "same file"); |
| if (toFile.isDirectory()) { // existing directory |
| throwIaxUnlessCanWriteDir(toFile, "toFile"); |
| if (fromFile.isFile()) { // file-dir |
| File targFile = new File(toFile, fromFile.getName()); |
| copyValidFiles(fromFile, targFile); |
| } else if (fromFile.isDirectory()) { // dir-dir |
| copyDir(fromFile, toFile); |
| } else { |
| LangUtil.throwIaxIfFalse(false, "not dir or file: " + fromFile); |
| } |
| } else if (toFile.isFile()) { // target file exists |
| if (fromFile.isDirectory()) { |
| LangUtil.throwIaxIfFalse(false, "can't copy to file dir: " + fromFile); |
| } |
| copyValidFiles(fromFile, toFile); // file-file |
| } else { // target file is a non-existent path -- could be file or dir |
| /* File toFileParent = */ensureParentWritable(toFile); |
| if (fromFile.isFile()) { |
| copyValidFiles(fromFile, toFile); |
| } else if (fromFile.isDirectory()) { |
| toFile.mkdirs(); |
| throwIaxUnlessCanWriteDir(toFile, "toFile"); |
| copyDir(fromFile, toFile); |
| } else { |
| LangUtil.throwIaxIfFalse(false, "not dir or file: " + fromFile); |
| } |
| } |
| } |
| |
| /** |
| * Ensure that the parent directory to path can be written. If the path has a null parent, DEFAULT_PARENT is tested. If the path |
| * parent does not exist, this tries to create it. |
| * |
| * @param path the File path whose parent should be writable |
| * @return the File path of the writable parent directory |
| * @throws IllegalArgumentException if parent cannot be written or path is null. |
| */ |
| public static File ensureParentWritable(File path) { |
| LangUtil.throwIaxIfNull(path, "path"); |
| File pathParent = path.getParentFile(); |
| if (null == pathParent) { |
| pathParent = DEFAULT_PARENT; |
| } |
| if (!pathParent.canWrite()) { |
| pathParent.mkdirs(); |
| } |
| throwIaxUnlessCanWriteDir(pathParent, "pathParent"); |
| return pathParent; |
| } |
| |
| /** |
| * Copy file to file. |
| * |
| * @param fromFile the File to copy (readable, non-null file) |
| * @param toFile the File to copy to (non-null, parent dir exists) |
| * @throws IOException |
| */ |
| public static void copyValidFiles(File fromFile, File toFile) throws IOException { |
| FileInputStream in = null; |
| FileOutputStream out = null; |
| try { |
| in = new FileInputStream(fromFile); |
| out = new FileOutputStream(toFile); |
| copyStream(in, out); |
| } finally { |
| if (out != null) { |
| out.close(); |
| } |
| if (in != null) { |
| in.close(); |
| } |
| } |
| } |
| |
| /** do line-based copying */ |
| @SuppressWarnings("deprecation") |
| public static void copyStream(DataInputStream in, PrintStream out) throws IOException { |
| LangUtil.throwIaxIfNull(in, "in"); |
| LangUtil.throwIaxIfNull(in, "out"); |
| String s; |
| while (null != (s = in.readLine())) { |
| out.println(s); |
| } |
| } |
| |
| public static void copyStream(InputStream in, OutputStream out) throws IOException { |
| final int MAX = 4096; |
| byte[] buf = new byte[MAX]; |
| for (int bytesRead = in.read(buf, 0, MAX); bytesRead != -1; bytesRead = in.read(buf, 0, MAX)) { |
| out.write(buf, 0, bytesRead); |
| } |
| } |
| |
| public static void copyStream(Reader in, Writer out) throws IOException { |
| final int MAX = 4096; |
| char[] buf = new char[MAX]; |
| for (int bytesRead = in.read(buf, 0, MAX); bytesRead != -1; bytesRead = in.read(buf, 0, MAX)) { |
| out.write(buf, 0, bytesRead); |
| } |
| } |
| |
| /** |
| * Make a new child directory of parent |
| * |
| * @param parent a File for the parent (writable) |
| * @param child a prefix for the child directory |
| * @return a File dir that exists with parentDir as the parent file or null |
| */ |
| public static File makeNewChildDir(File parent, String child) { |
| if (null == parent || !parent.canWrite() || !parent.isDirectory()) { |
| throw new IllegalArgumentException("bad parent: " + parent); |
| } else if (null == child) { |
| child = "makeNewChildDir"; |
| } else if (!isValidFileName(child)) { |
| throw new IllegalArgumentException("bad child: " + child); |
| } |
| File result = new File(parent, child); |
| int safety = 1000; |
| for (String suffix = FileUtil.randomFileString(); ((0 < --safety) && result.exists()); suffix = FileUtil.randomFileString()) { |
| result = new File(parent, child + suffix); |
| } |
| if (result.exists()) { |
| System.err.println("exhausted files for child dir in " + parent); |
| return null; |
| } |
| return ((result.mkdirs() && result.exists()) ? result : null); |
| } |
| |
| /** |
| * Make a new temporary directory in the same directory that the system uses for temporary files, or if that files, in the |
| * current directory. |
| * |
| * @param name the preferred (simple) name of the directory - may be null. |
| * @return File of an existing new temp dir, or null if unable to create |
| */ |
| public static File getTempDir(String name) { |
| if (null == name) { |
| name = "FileUtil_getTempDir"; |
| } else if (!isValidFileName(name)) { |
| throw new IllegalArgumentException(" invalid: " + name); |
| } |
| File result = null; |
| File tempFile = null; |
| try { |
| tempFile = File.createTempFile("ignoreMe", ".txt"); |
| File tempParent = tempFile.getParentFile(); |
| result = makeNewChildDir(tempParent, name); |
| } catch (IOException t) { |
| result = makeNewChildDir(new File("."), name); |
| } finally { |
| if (null != tempFile) { |
| tempFile.delete(); |
| } |
| } |
| return result; |
| } |
| |
| public static URL[] getFileURLs(File[] files) { |
| if ((null == files) || (0 == files.length)) { |
| return new URL[0]; |
| } |
| URL[] result = new URL[files.length]; // XXX dangerous non-copy... |
| for (int i = 0; i < result.length; i++) { |
| result[i] = getFileURL(files[i]); |
| } |
| return result; |
| } |
| |
| /** |
| * Get URL for a File. This appends "/" for directories. prints errors to System.err |
| * |
| * @param file the File to convert to URL (not null) |
| */ |
| @SuppressWarnings("deprecation") |
| public static URL getFileURL(File file) { |
| LangUtil.throwIaxIfNull(file, "file"); |
| URL result = null; |
| try { |
| result = file.toURL();// TODO AV - was toURI.toURL that does not |
| // works on Java 1.3 |
| if (null != result) { |
| return result; |
| } |
| String url = "file:" + file.getAbsolutePath().replace('\\', '/'); |
| result = new URL(url + (file.isDirectory() ? "/" : "")); |
| } catch (MalformedURLException e) { |
| String m = "Util.makeURL(\"" + file.getPath() + "\" MUE " + e.getMessage(); |
| System.err.println(m); |
| } |
| return result; |
| } |
| |
| /** |
| * Write contents to file, returning null on success or error message otherwise. This tries to make any necessary parent |
| * directories first. |
| * |
| * @param file the File to write (not null) |
| * @param contents the String to write (use "" if null) |
| * @return String null on no error, error otherwise |
| */ |
| public static String writeAsString(File file, String contents) { |
| LangUtil.throwIaxIfNull(file, "file"); |
| if (null == contents) { |
| contents = ""; |
| } |
| Writer out = null; |
| try { |
| File parentDir = file.getParentFile(); |
| if (!parentDir.exists() && !parentDir.mkdirs()) { |
| return "unable to make parent dir for " + file; |
| } |
| Reader in = new StringReader(contents); |
| out = new FileWriter(file); |
| FileUtil.copyStream(in, out); |
| return null; |
| } catch (IOException e) { |
| return LangUtil.unqualifiedClassName(e) + " writing " + file + ": " + e.getMessage(); |
| } finally { |
| if (null != out) { |
| try { |
| out.close(); |
| } catch (IOException e) { |
| } // ignored |
| } |
| } |
| } |
| |
| /** |
| * Reads a boolean array with our encoding |
| */ |
| public static boolean[] readBooleanArray(DataInputStream s) throws IOException { |
| int len = s.readInt(); |
| boolean[] ret = new boolean[len]; |
| for (int i = 0; i < len; i++) { |
| ret[i] = s.readBoolean(); |
| } |
| return ret; |
| } |
| |
| /** |
| * Writes a boolean array with our encoding |
| */ |
| public static void writeBooleanArray(boolean[] a, DataOutputStream s) throws IOException { |
| int len = a.length; |
| s.writeInt(len); |
| for (int i = 0; i < len; i++) { |
| s.writeBoolean(a[i]); |
| } |
| } |
| |
| /** |
| * Reads an int array with our encoding |
| */ |
| public static int[] readIntArray(DataInputStream s) throws IOException { |
| int len = s.readInt(); |
| int[] ret = new int[len]; |
| for (int i = 0; i < len; i++) { |
| ret[i] = s.readInt(); |
| } |
| return ret; |
| } |
| |
| /** |
| * Writes an int array with our encoding |
| */ |
| public static void writeIntArray(int[] a, DataOutputStream s) throws IOException { |
| int len = a.length; |
| s.writeInt(len); |
| for (int i = 0; i < len; i++) { |
| s.writeInt(a[i]); |
| } |
| } |
| |
| /** |
| * Reads an int array with our encoding |
| */ |
| public static String[] readStringArray(DataInputStream s) throws IOException { |
| int len = s.readInt(); |
| String[] ret = new String[len]; |
| for (int i = 0; i < len; i++) { |
| ret[i] = s.readUTF(); |
| } |
| return ret; |
| } |
| |
| /** |
| * Writes an int array with our encoding |
| */ |
| public static void writeStringArray(String[] a, DataOutputStream s) throws IOException { |
| if (a == null) { |
| s.writeInt(0); |
| return; |
| } |
| int len = a.length; |
| s.writeInt(len); |
| for (int i = 0; i < len; i++) { |
| s.writeUTF(a[i]); |
| } |
| } |
| |
| /** |
| * Returns the contents of this file as a String |
| */ |
| public static String readAsString(File file) throws IOException { |
| BufferedReader r = new BufferedReader(new FileReader(file)); |
| StringBuffer b = new StringBuffer(); |
| while (true) { |
| int ch = r.read(); |
| if (ch == -1) { |
| break; |
| } |
| b.append((char) ch); |
| } |
| r.close(); |
| return b.toString(); |
| } |
| |
| // /** |
| // * Returns the contents of this stream as a String |
| // */ |
| // public static String readAsString(InputStream in) throws IOException { |
| // BufferedReader r = new BufferedReader(new InputStreamReader(in)); |
| // StringBuffer b = new StringBuffer(); |
| // while (true) { |
| // int ch = r.read(); |
| // if (ch == -1) |
| // break; |
| // b.append((char) ch); |
| // } |
| // in.close(); |
| // r.close(); |
| // return b.toString(); |
| // } |
| |
| /** |
| * Returns the contents of this file as a byte[] |
| */ |
| public static byte[] readAsByteArray(File file) throws IOException { |
| FileInputStream in = new FileInputStream(file); |
| byte[] ret = FileUtil.readAsByteArray(in); |
| in.close(); |
| return ret; |
| } |
| |
| /** |
| * Reads this input stream and returns contents as a byte[] |
| */ |
| public static byte[] readAsByteArray(InputStream inStream) throws IOException { |
| int size = 1024; |
| byte[] ba = new byte[size]; |
| int readSoFar = 0; |
| |
| while (true) { |
| int nRead = inStream.read(ba, readSoFar, size - readSoFar); |
| if (nRead == -1) { |
| break; |
| } |
| readSoFar += nRead; |
| if (readSoFar == size) { |
| int newSize = size * 2; |
| byte[] newBa = new byte[newSize]; |
| System.arraycopy(ba, 0, newBa, 0, size); |
| ba = newBa; |
| size = newSize; |
| } |
| } |
| |
| byte[] newBa = new byte[readSoFar]; |
| System.arraycopy(ba, 0, newBa, 0, readSoFar); |
| return newBa; |
| } |
| |
| final static String FILECHARS = "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; |
| |
| /** @return semi-random String of length 6 usable as filename suffix */ |
| static String randomFileString() { |
| final double FILECHARS_length = FILECHARS.length(); |
| final int LEN = 6; |
| final char[] result = new char[LEN]; |
| int index = (int) (Math.random() * 6d); |
| for (int i = 0; i < LEN; i++) { |
| if (index >= LEN) { |
| index = 0; |
| } |
| result[index++] = FILECHARS.charAt((int) (Math.random() * FILECHARS_length)); |
| } |
| return new String(result); |
| } |
| |
| public static InputStream getStreamFromZip(String zipFile, String name) { |
| try { |
| ZipFile zf = new ZipFile(zipFile); |
| try { |
| ZipEntry entry = zf.getEntry(name); |
| return zf.getInputStream(entry); |
| } finally { |
| // ??? is it safe not to close this zf.close(); |
| } |
| } catch (IOException ioe) { |
| return null; |
| } |
| } |
| |
| // |
| // public static void extractJar(String zipFile, String outDir) throws |
| // IOException { |
| // ZipInputStream zs = new ZipInputStream(new FileInputStream(zipFile)); |
| // ZipEntry entry; |
| // while ((entry = zs.getNextEntry()) != null) { |
| // if (entry.isDirectory()) |
| // continue; |
| // byte[] in = readAsByteArray(zs); |
| // |
| // File outFile = new File(outDir + "/" + entry.getName()); |
| // // if (!outFile.getParentFile().exists()) |
| // // System.err.println("parent: " + outFile.getParentFile()); |
| // // System.err.println("parent: " + outFile.getParentFile()); |
| // outFile.getParentFile().mkdirs(); |
| // FileOutputStream os = new FileOutputStream(outFile); |
| // os.write(in); |
| // os.close(); |
| // zs.closeEntry(); |
| // } |
| // zs.close(); |
| // } |
| |
| /** |
| * Do line-based search for literal text in source files, returning file:line where found. |
| * |
| * @param sought the String text to seek in the file |
| * @param sources the List of String paths to the source files |
| * @param listAll if false, only list first match in file |
| * @param errorSink the PrintStream to print any errors to (one per line) (use null to silently ignore errors) |
| * @return List of String of the form file:line for each found entry (never null, might be empty) |
| */ |
| // OPTIMIZE only used by tests? move it out |
| public static List<String> lineSeek(String sought, List<String> sources, boolean listAll, PrintStream errorSink) { |
| if (LangUtil.isEmpty(sought) || LangUtil.isEmpty(sources)) { |
| return Collections.emptyList(); |
| } |
| ArrayList<String> result = new ArrayList<String>(); |
| for (Iterator<String> iter = sources.iterator(); iter.hasNext();) { |
| String path = iter.next(); |
| String error = lineSeek(sought, path, listAll, result); |
| if ((null != error) && (null != errorSink)) { |
| errorSink.println(error); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Do line-based search for literal text in source file, returning line where found as a String in the form |
| * {sourcePath}:line:column submitted to the collecting parameter sink. Any error is rendered to String and returned as the |
| * result. |
| * |
| * @param sought the String text to seek in the file |
| * @param sources the List of String paths to the source files |
| * @param listAll if false, only list first match in file |
| * @param List sink the List for String entries of the form {sourcePath}:line:column |
| * @return String error if any, or add String entries to sink |
| */ |
| public static String lineSeek(String sought, String sourcePath, boolean listAll, ArrayList<String> sink) { |
| if (LangUtil.isEmpty(sought) || LangUtil.isEmpty(sourcePath)) { |
| return "nothing sought"; |
| } |
| if (LangUtil.isEmpty(sourcePath)) { |
| return "no sourcePath"; |
| } |
| final File file = new File(sourcePath); |
| if (!file.canRead() || !file.isFile()) { |
| return "sourcePath not a readable file"; |
| } |
| int lineNum = 0; |
| FileReader fin = null; |
| try { |
| fin = new FileReader(file); |
| BufferedReader reader = new BufferedReader(fin); |
| String line; |
| while (null != (line = reader.readLine())) { |
| lineNum++; |
| int loc = line.indexOf(sought); |
| if (-1 != loc) { |
| sink.add(sourcePath + ":" + lineNum + ":" + loc); |
| if (!listAll) { |
| break; |
| } |
| } |
| } |
| } catch (IOException e) { |
| return LangUtil.unqualifiedClassName(e) + " reading " + sourcePath + ":" + lineNum; |
| } finally { |
| try { |
| if (null != fin) { |
| fin.close(); |
| } |
| } catch (IOException e) { |
| } // ignore |
| } |
| return null; |
| } |
| |
| public static BufferedOutputStream makeOutputStream(File file) throws FileNotFoundException { |
| File parent = file.getParentFile(); |
| if (parent != null) { |
| parent.mkdirs(); |
| } |
| return new BufferedOutputStream(new FileOutputStream(file)); |
| } |
| |
| /** |
| * Sleep until after the last last-modified stamp from the files. |
| * |
| * @param files the File[] of files to inspect for last modified times (this ignores null or empty files array and null or |
| * non-existing components of files array) |
| * @return true if succeeded without 100 interrupts |
| */ |
| public static boolean sleepPastFinalModifiedTime(File[] files) { |
| if ((null == files) || (0 == files.length)) { |
| return true; |
| } |
| long delayUntil = System.currentTimeMillis(); |
| for (int i = 0; i < files.length; i++) { |
| File file = files[i]; |
| if ((null == file) || !file.exists()) { |
| continue; |
| } |
| long nextModTime = file.lastModified(); |
| if (nextModTime > delayUntil) { |
| delayUntil = nextModTime; |
| } |
| } |
| return LangUtil.sleepUntil(++delayUntil); |
| } |
| |
| private static void listClassFiles(final File baseDir, ArrayList<File> result) { |
| File[] files = baseDir.listFiles(); |
| for (int i = 0; i < files.length; i++) { |
| File f = files[i]; |
| if (f.isDirectory()) { |
| listClassFiles(f, result); |
| } else { |
| if (f.getName().endsWith(".class")) { |
| result.add(f); |
| } |
| } |
| } |
| } |
| |
| private static void listFiles(final File baseDir, ArrayList<File> result, FileFilter filter) { |
| File[] files = baseDir.listFiles(); |
| // hack https://bugs.eclipse.org/bugs/show_bug.cgi?id=48650 |
| final boolean skipCVS = (!PERMIT_CVS && (filter == aspectjSourceFileFilter)); |
| for (int i = 0; i < files.length; i++) { |
| File f = files[i]; |
| if (f.isDirectory()) { |
| if (skipCVS) { |
| String name = f.getName().toLowerCase(); |
| if ("cvs".equals(name) || "sccs".equals(name)) { |
| continue; |
| } |
| } |
| listFiles(f, result, filter); |
| } else { |
| if (filter.accept(f)) { |
| result.add(f); |
| } |
| } |
| } |
| } |
| |
| /** @return true if input is not null and contains no path separator */ |
| private static boolean isValidFileName(String input) { |
| return ((null != input) && (-1 == input.indexOf(File.pathSeparator))); |
| } |
| |
| private static void listFiles(final File baseDir, String dir, ArrayList<String> result) { |
| final String dirPrefix = (null == dir ? "" : dir + "/"); |
| final File dirFile = (null == dir ? baseDir : new File(baseDir.getPath() + "/" + dir)); |
| final String[] files = dirFile.list(); |
| for (int i = 0; i < files.length; i++) { |
| File f = new File(dirFile, files[i]); |
| String path = dirPrefix + files[i]; |
| if (f.isDirectory()) { |
| listFiles(baseDir, path, result); |
| } else { |
| result.add(path); |
| } |
| } |
| } |
| |
| private FileUtil() { |
| } |
| |
| public static List<String> makeClasspath(URL[] urls) { |
| List<String> ret = new LinkedList<String>(); |
| if (urls != null) { |
| for (int i = 0; i < urls.length; i++) { |
| ret.add(toPathString(urls[i])); |
| } |
| } |
| return ret; |
| } |
| |
| private static String toPathString(URL url) { |
| try { |
| return url.toURI().getPath(); |
| } catch (URISyntaxException e) { |
| System.err.println("Warning!! Malformed URL may cause problems: "+url); // TODO: Better way to report this? |
| // In this case it was likely not using properly escaped |
| // characters so we just use the 'bad' method that doesn't decode |
| // special chars |
| return url.getPath(); |
| } |
| } |
| |
| /** |
| * A pipe when run reads from an input stream to an output stream, optionally sleeping between reads. |
| * |
| * @see #copyStream(InputStream, OutputStream) |
| */ |
| public static class Pipe implements Runnable { |
| private final InputStream in; |
| private final OutputStream out; |
| private final long sleep; |
| private ByteArrayOutputStream snoop; |
| private long totalWritten; |
| private Throwable thrown; |
| private boolean halt; |
| /** |
| * Seem to be unable to detect erroroneous closing of System.out... |
| */ |
| private final boolean closeInput; |
| private final boolean closeOutput; |
| |
| /** |
| * If true, then continue processing stream until no characters are returned when halting. |
| */ |
| private boolean finishStream; |
| |
| private boolean done; // true after completing() completes |
| |
| /** |
| * alias for <code>Pipe(in, out, 100l, false, false)</code> |
| * |
| * @param in the InputStream source to read |
| * @param out the OutputStream sink to write |
| */ |
| Pipe(InputStream in, OutputStream out) { |
| this(in, out, 100l, false, false); |
| } |
| |
| /** |
| * @param in the InputStream source to read |
| * @param out the OutputStream sink to write |
| * @param tryClosingStreams if true, then try closing both streams when done |
| * @param sleep milliseconds to delay between reads (pinned to 0..1 minute) |
| */ |
| Pipe(InputStream in, OutputStream out, long sleep, boolean closeInput, boolean closeOutput) { |
| LangUtil.throwIaxIfNull(in, "in"); |
| LangUtil.throwIaxIfNull(out, "out"); |
| this.in = in; |
| this.out = out; |
| this.closeInput = closeInput; |
| this.closeOutput = closeOutput; |
| this.sleep = Math.min(0l, Math.max(60l * 1000l, sleep)); |
| } |
| |
| public void setSnoop(ByteArrayOutputStream snoop) { |
| this.snoop = snoop; |
| } |
| |
| /** |
| * Run the pipe. This halts on the first Throwable thrown or when a read returns -1 (for end-of-file) or on demand. |
| */ |
| public void run() { |
| totalWritten = 0; |
| if (halt) { |
| return; |
| } |
| try { |
| final int MAX = 4096; |
| byte[] buf = new byte[MAX]; |
| // TODO this blocks, hanging the harness |
| int count = in.read(buf, 0, MAX); |
| ByteArrayOutputStream mySnoop; |
| while ((halt && finishStream && (0 < count)) || (!halt && (-1 != count))) { |
| out.write(buf, 0, count); |
| mySnoop = snoop; |
| if (null != mySnoop) { |
| mySnoop.write(buf, 0, count); |
| } |
| totalWritten += count; |
| if (halt && !finishStream) { |
| break; |
| } |
| if (!halt && (0 < sleep)) { |
| Thread.sleep(sleep); |
| } |
| if (halt && !finishStream) { |
| break; |
| } |
| count = in.read(buf, 0, MAX); |
| } |
| } catch (Throwable e) { |
| thrown = e; |
| } finally { |
| halt = true; |
| if (closeInput) { |
| try { |
| in.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| if (closeOutput) { |
| try { |
| out.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| done = true; |
| completing(totalWritten, thrown); |
| } |
| |
| } |
| |
| /** |
| * Tell the pipe to halt the next time it gains control. |
| * |
| * @param wait if true, this waits synchronously until pipe is done |
| * @param finishStream if true, then continue until a read from the input stream returns no bytes, then halt. |
| * @return true if <code>run()</code> will return the next time it gains control |
| */ |
| public boolean halt(boolean wait, boolean finishStream) { |
| if (!halt) { |
| halt = true; |
| } |
| if (wait) { |
| while (!done) { |
| synchronized (this) { |
| notifyAll(); |
| } |
| if (!done) { |
| try { |
| Thread.sleep(5l); |
| } catch (InterruptedException e) { |
| break; |
| } |
| } |
| } |
| } |
| return halt; |
| } |
| |
| /** @return the total number of bytes written */ |
| public long totalWritten() { |
| return totalWritten; |
| } |
| |
| /** @return any exception thrown when reading/writing */ |
| public Throwable getThrown() { |
| return thrown; |
| } |
| |
| /** |
| * This is called when the pipe is completing. This implementation does nothing. Subclasses implement this to get notice. |
| * Note that halt(true, true) might or might not have completed before this method is called. |
| */ |
| protected void completing(long totalWritten, Throwable thrown) { |
| } |
| } |
| |
| } |