diff options
Diffstat (limited to 'util/src')
-rw-r--r-- | util/src/org/aspectj/util/FileUtil.java | 2600 | ||||
-rw-r--r-- | util/src/org/aspectj/util/LangUtil.java | 2759 |
2 files changed, 2653 insertions, 2706 deletions
diff --git a/util/src/org/aspectj/util/FileUtil.java b/util/src/org/aspectj/util/FileUtil.java index cfea7e533..b0cd73a3b 100644 --- a/util/src/org/aspectj/util/FileUtil.java +++ b/util/src/org/aspectj/util/FileUtil.java @@ -13,980 +13,978 @@ package org.aspectj.util; -import java.io.*; -import java.net.*; -import java.util.*; -import java.util.zip.*; - +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.InputStreamReader; +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.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; +import java.util.zip.ZipInputStream; /** * */ 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 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 { + /** 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 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 iter = SOURCE_SUFFIXES.iterator(); iter.hasNext();) { + } + + /** @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 iter = SOURCE_SUFFIXES.iterator(); iter.hasNext();) { String suffix = (String) 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 (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 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 = (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++) { + result[i] = files[i].getPath(); + } + } + return result; + } + + /** @return array same length as input, with String paths */ + public static String[] getPaths(List 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 = (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 ((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); } - 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++) { + 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 (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(); - 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); - if (deleteEmptyDirs && (0 == file.list().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 result = new ArrayList(); - if ((null != srcDir) && srcDir.canRead()) { - listFiles(srcDir, null, result); - } - return (String[]) result.toArray(new String[0]); - } - - public static final FileFilter aspectjSourceFileFilter = new FileFilter() { + 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(); + 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); + if (deleteEmptyDirs && (0 == file.list().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 result = new ArrayList(); + if ((null != srcDir) && srcDir.canRead()) { + listFiles(srcDir, null, result); + } + return (String[]) 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 result = new ArrayList(); - if ((null != srcDir) && srcDir.canRead()) { - listFiles(srcDir, result, fileFilter); - } - return (File[]) result.toArray(new File[result.size()]); - } - - /** - * 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 list = new ArrayList(); - 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; - } + }; + + /** + * 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 result = new ArrayList(); + if ((null != srcDir) && srcDir.canRead()) { + listFiles(srcDir, result, fileFilter); + } + return (File[]) result.toArray(new File[result.size()]); + } + + /** + * 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 list = new ArrayList(); + 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 = (File[]) 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 */ - 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 = (File[]) 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 */ + 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) - */ - public static URL getFileURL(File file) { + 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) + */ + 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); - } + 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 - } - } - } + /** + * 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 @@ -994,54 +992,53 @@ public class FileUtil { 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(); + 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]); + 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(); + 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]); + 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(); + for (int i = 0; i < len; i++) + ret[i] = s.readUTF(); return ret; } - /** * Writes an int array with our encoding */ @@ -1052,10 +1049,10 @@ public class FileUtil { } int len = a.length; s.writeInt(len); - for (int i=0; i < len; i++) s.writeUTF(a[i]); + for (int i = 0; i < len; i++) + s.writeUTF(a[i]); } - /** * Returns the contents of this file as a String */ @@ -1064,13 +1061,14 @@ public class FileUtil { StringBuffer b = new StringBuffer(); while (true) { int ch = r.read(); - if (ch == -1) break; - b.append((char)ch); + if (ch == -1) + break; + b.append((char) ch); } r.close(); return b.toString(); } - + /** * Returns the contents of this stream as a String */ @@ -1079,14 +1077,14 @@ public class FileUtil { StringBuffer b = new StringBuffer(); while (true) { int ch = r.read(); - if (ch == -1) break; - b.append((char)ch); + if (ch == -1) + break; + b.append((char) ch); } in.close(); r.close(); return b.toString(); } - /** * Returns the contents of this file as a byte[] @@ -1098,7 +1096,6 @@ public class FileUtil { return ret; } - /** * Reads this input stream and returns contents as a byte[] */ @@ -1106,10 +1103,11 @@ public class FileUtil { 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; + int nRead = inStream.read(ba, readSoFar, size - readSoFar); + if (nRead == -1) + break; readSoFar += nRead; if (readSoFar == size) { int newSize = size * 2; @@ -1119,54 +1117,55 @@ public class FileUtil { 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)); + 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); - } + 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); + ZipEntry entry = zf.getEntry(name); + return zf.getInputStream(entry); } finally { - //??? is it safe not to close this zf.close(); + // ??? 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; + 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()); + // 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); @@ -1177,157 +1176,157 @@ public class FileUtil { } /** - * Do line-based search for literal text in source files, - * returning file:line where found. + * 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) + * @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 lineSeek(String sought, List sources, boolean listAll, - PrintStream errorSink) { - if (LangUtil.isEmpty(sought) || LangUtil.isEmpty(sources)) { - return Collections.EMPTY_LIST; - } - ArrayList result = new ArrayList(); - for (Iterator iter = sources.iterator(); iter.hasNext();) { + public static List lineSeek(String sought, List sources, boolean listAll, PrintStream errorSink) { + if (LangUtil.isEmpty(sought) || LangUtil.isEmpty(sources)) { + return Collections.EMPTY_LIST; + } + ArrayList result = new ArrayList(); + for (Iterator iter = sources.iterator(); iter.hasNext();) { String path = (String) 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 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; - } + 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 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(); + 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 listFiles(final File baseDir, ArrayList 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 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() { throw new Error("utility class"); } - + /** + * 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 listFiles(final File baseDir, ArrayList 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 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() { + throw new Error("utility class"); + } + public static List makeClasspath(URL[] urls) { List ret = new LinkedList(); if (urls != null) { @@ -1338,175 +1337,162 @@ public class FileUtil { return ret; } - /** - * 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) { - } - } + /** + * 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) { + } + } } diff --git a/util/src/org/aspectj/util/LangUtil.java b/util/src/org/aspectj/util/LangUtil.java index 4bcac4198..c17ceaa40 100644 --- a/util/src/org/aspectj/util/LangUtil.java +++ b/util/src/org/aspectj/util/LangUtil.java @@ -26,7 +26,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; @@ -34,270 +33,282 @@ import java.util.StringTokenizer; /** * */ -public class LangUtil { - -// /** map from String version to String class implemented in that version or later */ -// private static final Map VM_CLASSES; - - public static final String EOL; - static { - StringWriter buf = new StringWriter(); - PrintWriter writer = new PrintWriter(buf); - writer.println(""); - String eol = "\n"; - try { - buf.close(); - StringBuffer sb = buf.getBuffer(); - if (sb!=null) { - eol = buf.toString(); - } - } catch (Throwable t) { } - EOL = eol; - -// HashMap map = new HashMap(); -// map.put("1.2", "java.lang.ref.Reference"); -// map.put("1.3", "java.lang.reflect.Proxy"); -// map.put("1.4", "java.nio.Buffer"); -// map.put("1.5", "java.lang.annotation.Annotation"); -// -// VM_CLASSES = Collections.unmodifiableMap(map); - } - -// /** -// * Detect whether Java version is supported. -// * @param version String "1.2" or "1.3" or "1.4" -// * @return true if the currently-running VM supports the version -// * @throws IllegalArgumentException if version is not known -// */ -// public static final boolean supportsJava(String version) { -// LangUtil.throwIaxIfNull(version, "version"); -// String className = (String) VM_CLASSES.get(version); -// if (null == className) { -// throw new IllegalArgumentException("unknown version: " + version); -// } -// try { -// Class.forName(className); -// return true; -// } catch (Throwable t) { -// return false; -// } -// } - +public class LangUtil { + + // /** map from String version to String class implemented in that version or later */ + // private static final Map VM_CLASSES; + + public static final String EOL; + static { + StringWriter buf = new StringWriter(); + PrintWriter writer = new PrintWriter(buf); + writer.println(""); + String eol = "\n"; + try { + buf.close(); + StringBuffer sb = buf.getBuffer(); + if (sb != null) { + eol = buf.toString(); + } + } catch (Throwable t) { + } + EOL = eol; + + // HashMap map = new HashMap(); + // map.put("1.2", "java.lang.ref.Reference"); + // map.put("1.3", "java.lang.reflect.Proxy"); + // map.put("1.4", "java.nio.Buffer"); + // map.put("1.5", "java.lang.annotation.Annotation"); + // + // VM_CLASSES = Collections.unmodifiableMap(map); + } + + // /** + // * Detect whether Java version is supported. + // * @param version String "1.2" or "1.3" or "1.4" + // * @return true if the currently-running VM supports the version + // * @throws IllegalArgumentException if version is not known + // */ + // public static final boolean supportsJava(String version) { + // LangUtil.throwIaxIfNull(version, "version"); + // String className = (String) VM_CLASSES.get(version); + // if (null == className) { + // throw new IllegalArgumentException("unknown version: " + version); + // } + // try { + // Class.forName(className); + // return true; + // } catch (Throwable t) { + // return false; + // } + // } + private static boolean is13VMOrGreater = true; private static boolean is14VMOrGreater = true; private static boolean is15VMOrGreater = false; private static boolean is16VMOrGreater = false; - + static { - String vm = System.getProperty("java.version"); // JLS 20.18.7 - if (vm==null) vm = System.getProperty("java.runtime.version"); - if (vm==null) vm = System.getProperty("java.vm.version"); + String vm = System.getProperty("java.version"); // JLS 20.18.7 + if (vm == null) + vm = System.getProperty("java.runtime.version"); + if (vm == null) + vm = System.getProperty("java.vm.version"); if (vm.startsWith("1.3")) { is14VMOrGreater = false; - } else if (vm.startsWith("1.5") || vm.startsWith("1.6")) { + } else if (vm.startsWith("1.5") || vm.startsWith("1.6")) { is15VMOrGreater = true; is16VMOrGreater = true; } } - - public static boolean is13VMOrGreater() { return is13VMOrGreater;} - public static boolean is14VMOrGreater() { return is14VMOrGreater;} - public static boolean is15VMOrGreater() { return is15VMOrGreater;} - public static boolean is16VMOrGreater() { return is16VMOrGreater;} - - /** - * Shorthand for "if null, throw IllegalArgumentException" - * @throws IllegalArgumentException "null {name}" if o is null - */ - public static final void throwIaxIfNull(final Object o, final String name) { - if (null == o) { - String message = "null " + (null == name ? "input" : name); - throw new IllegalArgumentException(message); - } - } - - /** - * Shorthand for "if not null or not assignable, throw IllegalArgumentException" - * @param c the Class to check - use null to ignore type check - * @throws IllegalArgumentException "null {name}" if o is null - */ - public static final void throwIaxIfNotAssignable(final Object ra[], final Class c, final String name) { - throwIaxIfNull(ra, name); - String label = (null == name ? "input" : name); - for (int i = 0; i < ra.length; i++) { - if (null == ra[i]) { - String m = " null " + label + "[" + i + "]"; - throw new IllegalArgumentException(m); - } else if (null != c) { - Class actualClass = ra[i].getClass(); - if (!c.isAssignableFrom(actualClass)) { - String message = label + " not assignable to " + c.getName(); - throw new IllegalArgumentException(message); - } - } - } - } - /** - * Shorthand for "if not null or not assignable, throw IllegalArgumentException" - * @throws IllegalArgumentException "null {name}" if o is null - */ - public static final void throwIaxIfNotAssignable(final Object o, final Class c, final String name) { - throwIaxIfNull(o, name); - if (null != c) { - Class actualClass = o.getClass(); - if (!c.isAssignableFrom(actualClass)) { - String message = name + " not assignable to " + c.getName(); - throw new IllegalArgumentException(message); - } - } - } - -// /** -// * Shorthand for "if any not null or not assignable, throw IllegalArgumentException" -// * @throws IllegalArgumentException "{name} is not assignable to {c}" -// */ -// public static final void throwIaxIfNotAllAssignable(final Collection collection, -// final Class c, final String name) { -// throwIaxIfNull(collection, name); -// if (null != c) { -// for (Iterator iter = collection.iterator(); iter.hasNext();) { -// throwIaxIfNotAssignable(iter.next(), c, name); -// -// } -// } -// } - /** - * Shorthand for "if false, throw IllegalArgumentException" - * @throws IllegalArgumentException "{message}" if test is false - */ - public static final void throwIaxIfFalse(final boolean test, final String message) { - if (!test) { - throw new IllegalArgumentException(message); - } - } - -// /** @return ((null == s) || (0 == s.trim().length())); */ -// public static boolean isEmptyTrimmed(String s) { -// return ((null == s) || (0 == s.length()) -// || (0 == s.trim().length())); -// } - - /** @return ((null == s) || (0 == s.length())); */ - public static boolean isEmpty(String s) { - return ((null == s) || (0 == s.length())); - } - - /** @return ((null == ra) || (0 == ra.length)) */ - public static boolean isEmpty(Object[] ra) { - return ((null == ra) || (0 == ra.length)); - } - - /** @return ((null == collection) || (0 == collection.size())) */ - public static boolean isEmpty(Collection collection) { - return ((null == collection) || (0 == collection.size())); - } - - /** + + public static boolean is13VMOrGreater() { + return is13VMOrGreater; + } + + public static boolean is14VMOrGreater() { + return is14VMOrGreater; + } + + public static boolean is15VMOrGreater() { + return is15VMOrGreater; + } + + public static boolean is16VMOrGreater() { + return is16VMOrGreater; + } + + /** + * Shorthand for "if null, throw IllegalArgumentException" + * + * @throws IllegalArgumentException "null {name}" if o is null + */ + public static final void throwIaxIfNull(final Object o, final String name) { + if (null == o) { + String message = "null " + (null == name ? "input" : name); + throw new IllegalArgumentException(message); + } + } + + /** + * Shorthand for "if not null or not assignable, throw IllegalArgumentException" + * + * @param c the Class to check - use null to ignore type check + * @throws IllegalArgumentException "null {name}" if o is null + */ + public static final void throwIaxIfNotAssignable(final Object ra[], final Class c, final String name) { + throwIaxIfNull(ra, name); + String label = (null == name ? "input" : name); + for (int i = 0; i < ra.length; i++) { + if (null == ra[i]) { + String m = " null " + label + "[" + i + "]"; + throw new IllegalArgumentException(m); + } else if (null != c) { + Class actualClass = ra[i].getClass(); + if (!c.isAssignableFrom(actualClass)) { + String message = label + " not assignable to " + c.getName(); + throw new IllegalArgumentException(message); + } + } + } + } + + /** + * Shorthand for "if not null or not assignable, throw IllegalArgumentException" + * + * @throws IllegalArgumentException "null {name}" if o is null + */ + public static final void throwIaxIfNotAssignable(final Object o, final Class c, final String name) { + throwIaxIfNull(o, name); + if (null != c) { + Class actualClass = o.getClass(); + if (!c.isAssignableFrom(actualClass)) { + String message = name + " not assignable to " + c.getName(); + throw new IllegalArgumentException(message); + } + } + } + + // /** + // * Shorthand for "if any not null or not assignable, throw IllegalArgumentException" + // * @throws IllegalArgumentException "{name} is not assignable to {c}" + // */ + // public static final void throwIaxIfNotAllAssignable(final Collection collection, + // final Class c, final String name) { + // throwIaxIfNull(collection, name); + // if (null != c) { + // for (Iterator iter = collection.iterator(); iter.hasNext();) { + // throwIaxIfNotAssignable(iter.next(), c, name); + // + // } + // } + // } + /** + * Shorthand for "if false, throw IllegalArgumentException" + * + * @throws IllegalArgumentException "{message}" if test is false + */ + public static final void throwIaxIfFalse(final boolean test, final String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + // /** @return ((null == s) || (0 == s.trim().length())); */ + // public static boolean isEmptyTrimmed(String s) { + // return ((null == s) || (0 == s.length()) + // || (0 == s.trim().length())); + // } + + /** @return ((null == s) || (0 == s.length())); */ + public static boolean isEmpty(String s) { + return ((null == s) || (0 == s.length())); + } + + /** @return ((null == ra) || (0 == ra.length)) */ + public static boolean isEmpty(Object[] ra) { + return ((null == ra) || (0 == ra.length)); + } + + /** @return ((null == collection) || (0 == collection.size())) */ + public static boolean isEmpty(Collection collection) { + return ((null == collection) || (0 == collection.size())); + } + + /** * Splits <code>text</code> at whitespace. - * + * * @param text <code>String</code> to split. */ public static String[] split(String text) { return (String[]) strings(text).toArray(new String[0]); } - - /** - * Splits <code>input</code> at commas, - * trimming any white space. - * - * @param input <code>String</code> to split. - * @return List of String of elements. - */ - public static List commaSplit(String input) { - return anySplit(input, ","); - } - - /** - * Split string as classpath, delimited at File.pathSeparator. - * Entries are not trimmed, but empty entries are ignored. - * @param classpath the String to split - may be null or empty - * @return String[] of classpath entries - */ - public static String[] splitClasspath(String classpath) { - if (LangUtil.isEmpty(classpath)) { - return new String[0]; - } - StringTokenizer st = new StringTokenizer(classpath, File.pathSeparator); - ArrayList result = new ArrayList(st.countTokens()); - while (st.hasMoreTokens()) { - String entry = st.nextToken(); - if (!LangUtil.isEmpty(entry)) { - result.add(entry); - } - } - return (String[]) result.toArray(new String[0]); - } - - /** - * Get System property as boolean, - * but use default value where the system property is not set. - * @return true if value is set to true, false otherwise - */ - public static boolean getBoolean(String propertyName, boolean defaultValue) { - if (null != propertyName) { - try { - String value = System.getProperty(propertyName); - if (null != value) { - return Boolean.valueOf(value).booleanValue(); - } - } catch (Throwable t) { - // default below - } - } - return defaultValue; - } - - /** - * Splits <code>input</code>, removing delimiter and - * trimming any white space. - * Returns an empty collection if the input is null. - * If delimiter is null or empty or if the input contains - * no delimiters, the input itself is returned - * after trimming white space. - * - * @param input <code>String</code> to split. - * @param delim <code>String</code> separators for input. - * @return List of String of elements. - */ - public static List anySplit(String input, String delim) { - if (null == input) { - return Collections.EMPTY_LIST; - } - ArrayList result = new ArrayList(); - - if (LangUtil.isEmpty(delim) - || (-1 == input.indexOf(delim))) { - result.add(input.trim()); - } else { - StringTokenizer st = new StringTokenizer(input, delim); - while (st.hasMoreTokens()) { - result.add(st.nextToken().trim()); - } - } - return result; - } - /** - * Splits strings into a <code>List</code> using a - * <code>StringTokenizer</code>. - * + * Splits <code>input</code> at commas, trimming any white space. + * + * @param input <code>String</code> to split. + * @return List of String of elements. + */ + public static List commaSplit(String input) { + return anySplit(input, ","); + } + + /** + * Split string as classpath, delimited at File.pathSeparator. Entries are not trimmed, but empty entries are ignored. + * + * @param classpath the String to split - may be null or empty + * @return String[] of classpath entries + */ + public static String[] splitClasspath(String classpath) { + if (LangUtil.isEmpty(classpath)) { + return new String[0]; + } + StringTokenizer st = new StringTokenizer(classpath, File.pathSeparator); + ArrayList result = new ArrayList(st.countTokens()); + while (st.hasMoreTokens()) { + String entry = st.nextToken(); + if (!LangUtil.isEmpty(entry)) { + result.add(entry); + } + } + return (String[]) result.toArray(new String[0]); + } + + /** + * Get System property as boolean, but use default value where the system property is not set. + * + * @return true if value is set to true, false otherwise + */ + public static boolean getBoolean(String propertyName, boolean defaultValue) { + if (null != propertyName) { + try { + String value = System.getProperty(propertyName); + if (null != value) { + return Boolean.valueOf(value).booleanValue(); + } + } catch (Throwable t) { + // default below + } + } + return defaultValue; + } + + /** + * Splits <code>input</code>, removing delimiter and trimming any white space. Returns an empty collection if the input is null. + * If delimiter is null or empty or if the input contains no delimiters, the input itself is returned after trimming white + * space. + * + * @param input <code>String</code> to split. + * @param delim <code>String</code> separators for input. + * @return List of String of elements. + */ + public static List anySplit(String input, String delim) { + if (null == input) { + return Collections.EMPTY_LIST; + } + ArrayList result = new ArrayList(); + + if (LangUtil.isEmpty(delim) || (-1 == input.indexOf(delim))) { + result.add(input.trim()); + } else { + StringTokenizer st = new StringTokenizer(input, delim); + while (st.hasMoreTokens()) { + result.add(st.nextToken().trim()); + } + } + return result; + } + + /** + * Splits strings into a <code>List</code> using a <code>StringTokenizer</code>. + * * @param text <code>String</code> to split. */ public static List strings(String text) { - if (LangUtil.isEmpty(text)) { - return Collections.EMPTY_LIST; - } + if (LangUtil.isEmpty(text)) { + return Collections.EMPTY_LIST; + } List strings = new ArrayList(); StringTokenizer tok = new StringTokenizer(text); while (tok.hasMoreTokens()) { @@ -308,1169 +319,1119 @@ public class LangUtil { /** @return a non-null unmodifiable List */ public static List safeList(List list) { - return ( - null == list - ? Collections.EMPTY_LIST - : Collections.unmodifiableList(list)); + return (null == list ? Collections.EMPTY_LIST : Collections.unmodifiableList(list)); + } + + // /** + // * Select from input String[] based on suffix-matching + // * @param inputs String[] of input - null ignored + // * @param suffixes String[] of suffix selectors - null ignored + // * @param ignoreCase if true, ignore case + // * @return String[] of input that end with any input + // */ + // public static String[] endsWith(String[] inputs, String[] suffixes, boolean ignoreCase) { + // if (LangUtil.isEmpty(inputs) || LangUtil.isEmpty(suffixes)) { + // return new String[0]; + // } + // if (ignoreCase) { + // String[] temp = new String[suffixes.length]; + // for (int i = 0; i < temp.length; i++) { + // String suff = suffixes[i]; + // temp[i] = (null == suff ? null : suff.toLowerCase()); + // } + // suffixes = temp; + // } + // ArrayList result = new ArrayList(); + // for (int i = 0; i < inputs.length; i++) { + // String input = inputs[i]; + // if (null == input) { + // continue; + // } + // if (!ignoreCase) { + // input = input.toLowerCase(); + // } + // for (int j = 0; j < suffixes.length; j++) { + // String suffix = suffixes[j]; + // if (null == suffix) { + // continue; + // } + // if (input.endsWith(suffix)) { + // result.add(input); + // break; + // } + // } + // } + // return (String[]) result.toArray(new String[0]); + // } + // + // /** + // * Select from input String[] if readable directories + // * @param inputs String[] of input - null ignored + // * @param baseDir the base directory of the input + // * @return String[] of input that end with any input + // */ + // public static String[] selectDirectories(String[] inputs, File baseDir) { + // if (LangUtil.isEmpty(inputs)) { + // return new String[0]; + // } + // ArrayList result = new ArrayList(); + // for (int i = 0; i < inputs.length; i++) { + // String input = inputs[i]; + // if (null == input) { + // continue; + // } + // File inputFile = new File(baseDir, input); + // if (inputFile.canRead() && inputFile.isDirectory()) { + // result.add(input); + // } + // } + // return (String[]) result.toArray(new String[0]); + // } + + /** + * copy non-null two-dimensional String[][] + * + * @see extractOptions(String[], String[][]) + */ + public static String[][] copyStrings(String[][] in) { + String[][] out = new String[in.length][]; + for (int i = 0; i < out.length; i++) { + out[i] = new String[in[i].length]; + System.arraycopy(in[i], 0, out[i], 0, out[i].length); + } + return out; } - -// /** -// * Select from input String[] based on suffix-matching -// * @param inputs String[] of input - null ignored -// * @param suffixes String[] of suffix selectors - null ignored -// * @param ignoreCase if true, ignore case -// * @return String[] of input that end with any input -// */ -// public static String[] endsWith(String[] inputs, String[] suffixes, boolean ignoreCase) { -// if (LangUtil.isEmpty(inputs) || LangUtil.isEmpty(suffixes)) { -// return new String[0]; -// } -// if (ignoreCase) { -// String[] temp = new String[suffixes.length]; -// for (int i = 0; i < temp.length; i++) { -// String suff = suffixes[i]; -// temp[i] = (null == suff ? null : suff.toLowerCase()); -// } -// suffixes = temp; -// } -// ArrayList result = new ArrayList(); -// for (int i = 0; i < inputs.length; i++) { -// String input = inputs[i]; -// if (null == input) { -// continue; -// } -// if (!ignoreCase) { -// input = input.toLowerCase(); -// } -// for (int j = 0; j < suffixes.length; j++) { -// String suffix = suffixes[j]; -// if (null == suffix) { -// continue; -// } -// if (input.endsWith(suffix)) { -// result.add(input); -// break; -// } -// } -// } -// return (String[]) result.toArray(new String[0]); -// } -// -// /** -// * Select from input String[] if readable directories -// * @param inputs String[] of input - null ignored -// * @param baseDir the base directory of the input -// * @return String[] of input that end with any input -// */ -// public static String[] selectDirectories(String[] inputs, File baseDir) { -// if (LangUtil.isEmpty(inputs)) { -// return new String[0]; -// } -// ArrayList result = new ArrayList(); -// for (int i = 0; i < inputs.length; i++) { -// String input = inputs[i]; -// if (null == input) { -// continue; -// } -// File inputFile = new File(baseDir, input); -// if (inputFile.canRead() && inputFile.isDirectory()) { -// result.add(input); -// } -// } -// return (String[]) result.toArray(new String[0]); -// } - - /** - * copy non-null two-dimensional String[][] - * @see extractOptions(String[], String[][]) - */ - public static String[][] copyStrings(String[][] in) { - String[][] out = new String[in.length][]; - for (int i = 0; i < out.length; i++) { - out[i] = new String[in[i].length]; - System.arraycopy(in[i], 0, out[i], 0, out[i].length); - } - return out; - } - - /** - * Extract options and arguments to input option list, returning remainder. - * The input options will be nullified if not found. e.g., - * <pre>String[] options = new String[][] { new String[] { "-verbose" }, - * new String[] { "-classpath", null } }; - * String[] args = extractOptions(args, options); - * boolean verbose = null != options[0][0]; - * boolean classpath = options[1][1];</pre> - * @param args the String[] input options - * @param options the String[][]options to find in the input args - not null - * for each String[] component the first subcomponent is the option itself, - * and there is one String subcomponent for each additional argument. - * @return String[] of args remaining after extracting options to extracted - */ - public static String[] extractOptions(String[] args, String[][] options) { - if (LangUtil.isEmpty(args) || LangUtil.isEmpty(options) ) { - return args; - } - BitSet foundSet = new BitSet(); - String[] result = new String[args.length]; - int resultIndex = 0; - for (int j = 0; j < args.length; j++) { - boolean found = false; - for (int i = 0; !found && (i < options.length); i++) { - String[] option = options[i]; - LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(option), "options"); - String sought = option[0]; - found = sought.equals(args[j]); - if (found) { - foundSet.set(i); - int doMore = option.length-1; - if (0 < doMore) { - final int MAX = j + doMore; - if (MAX >= args.length) { - String s = "expecting " + doMore + " args after "; - throw new IllegalArgumentException(s + args[j]); - } - for (int k = 1; k < option.length; k++) { + + /** + * Extract options and arguments to input option list, returning remainder. The input options will be nullified if not found. + * e.g., + * + * <pre> + * String[] options = new String[][] { new String[] { "-verbose" }, new String[] { "-classpath", null } }; + * String[] args = extractOptions(args, options); + * boolean verbose = null != options[0][0]; + * boolean classpath = options[1][1]; + * </pre> + * + * @param args the String[] input options + * @param options the String[][]options to find in the input args - not null for each String[] component the first subcomponent + * is the option itself, and there is one String subcomponent for each additional argument. + * @return String[] of args remaining after extracting options to extracted + */ + public static String[] extractOptions(String[] args, String[][] options) { + if (LangUtil.isEmpty(args) || LangUtil.isEmpty(options)) { + return args; + } + BitSet foundSet = new BitSet(); + String[] result = new String[args.length]; + int resultIndex = 0; + for (int j = 0; j < args.length; j++) { + boolean found = false; + for (int i = 0; !found && (i < options.length); i++) { + String[] option = options[i]; + LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(option), "options"); + String sought = option[0]; + found = sought.equals(args[j]); + if (found) { + foundSet.set(i); + int doMore = option.length - 1; + if (0 < doMore) { + final int MAX = j + doMore; + if (MAX >= args.length) { + String s = "expecting " + doMore + " args after "; + throw new IllegalArgumentException(s + args[j]); + } + for (int k = 1; k < option.length; k++) { option[k] = args[++j]; } - } - } - } - if (!found) { - result[resultIndex++] = args[j]; - } - } - - // unset any not found - for (int i = 0; i < options.length; i++) { - if (!foundSet.get(i)) { - options[i][0] = null; - } - } - // fixup remainder - if (resultIndex < args.length) { - String[] temp = new String[resultIndex]; - System.arraycopy(result, 0, temp, 0, resultIndex); - args = temp; - } - - return args; - } -// -// /** -// * Extract options and arguments to input parameter list, returning remainder. -// * @param args the String[] input options -// * @param validOptions the String[] options to find in the input args - not null -// * @param optionArgs the int[] number of arguments for each option in validOptions -// * (if null, then no arguments for any option) -// * @param extracted the List for the matched options -// * @return String[] of args remaining after extracting options to extracted -// */ -// public static String[] extractOptions(String[] args, String[] validOptions, -// int[] optionArgs, List extracted) { -// if (LangUtil.isEmpty(args) -// || LangUtil.isEmpty(validOptions) ) { -// return args; -// } -// if (null != optionArgs) { -// if (optionArgs.length != validOptions.length) { -// throw new IllegalArgumentException("args must match options"); -// } -// } -// String[] result = new String[args.length]; -// int resultIndex = 0; -// for (int j = 0; j < args.length; j++) { -// boolean found = false; -// for (int i = 0; !found && (i < validOptions.length); i++) { -// String sought = validOptions[i]; -// int doMore = (null == optionArgs ? 0 : optionArgs[i]); -// if (LangUtil.isEmpty(sought)) { -// continue; -// } -// found = sought.equals(args[j]); -// if (found) { -// if (null != extracted) { -// extracted.add(sought); -// } -// if (0 < doMore) { -// final int MAX = j + doMore; -// if (MAX >= args.length) { -// String s = "expecting " + doMore + " args after "; -// throw new IllegalArgumentException(s + args[j]); -// } -// if (null != extracted) { -// while (j < MAX) { -// extracted.add(args[++j]); -// } -// } else { -// j = MAX; -// } -// } -// break; -// } -// } -// if (!found) { -// result[resultIndex++] = args[j]; -// } -// } -// if (resultIndex < args.length) { -// String[] temp = new String[resultIndex]; -// System.arraycopy(result, 0, temp, 0, resultIndex); -// args = temp; -// } -// return args; -// } - -// /** @return String[] of entries in validOptions found in args */ -// public static String[] selectOptions(String[] args, String[] validOptions) { -// if (LangUtil.isEmpty(args) || LangUtil.isEmpty(validOptions)) { -// return new String[0]; -// } -// ArrayList result = new ArrayList(); -// for (int i = 0; i < validOptions.length; i++) { -// String sought = validOptions[i]; -// if (LangUtil.isEmpty(sought)) { -// continue; -// } -// for (int j = 0; j < args.length; j++) { -// if (sought.equals(args[j])) { -// result.add(sought); -// break; -// } -// } -// } -// return (String[]) result.toArray(new String[0]); -// } - -// /** @return String[] of entries in validOptions found in args */ -// public static String[] selectOptions(List args, String[] validOptions) { -// if (LangUtil.isEmpty(args) || LangUtil.isEmpty(validOptions)) { -// return new String[0]; -// } -// ArrayList result = new ArrayList(); -// for (int i = 0; i < validOptions.length; i++) { -// String sought = validOptions[i]; -// if (LangUtil.isEmpty(sought)) { -// continue; -// } -// for (Iterator iter = args.iterator(); iter.hasNext();) { -// String arg = (String) iter.next(); -// if (sought.equals(arg)) { -// result.add(sought); -// break; -// } -// } -// } -// return (String[]) result.toArray(new String[0]); -// } - -// /** -// * Generate variants of String[] options by creating an extra set for -// * each option that ends with "-". If none end with "-", then an -// * array equal to <code>new String[][] { options }</code> is returned; -// * if one ends with "-", then two sets are returned, -// * three causes eight sets, etc. -// * @return String[][] with each option set. -// * @throws IllegalArgumentException if any option is null or empty. -// */ -// public static String[][] optionVariants(String[] options) { -// if ((null == options) || (0 == options.length)) { -// return new String[][] { new String[0]}; -// } -// // be nice, don't stomp input -// String[] temp = new String[options.length]; -// System.arraycopy(options, 0, temp, 0, temp.length); -// options = temp; -// boolean[] dup = new boolean[options.length]; -// int numDups = 0; -// -// for (int i = 0; i < options.length; i++) { -// String option = options[i]; -// if (LangUtil.isEmpty(option)) { -// throw new IllegalArgumentException("empty option at " + i); -// } -// if (option.endsWith("-")) { -// options[i] = option.substring(0, option.length()-1); -// dup[i] = true; -// numDups++; -// } -// } -// final String[] NONE = new String[0]; -// final int variants = exp(2, numDups); -// final String[][] result = new String[variants][]; -// // variant is a bitmap wrt doing extra value when dup[k]=true -// for (int variant = 0; variant < variants; variant++) { -// ArrayList next = new ArrayList(); -// int nextOption = 0; -// for (int k = 0; k < options.length; k++) { -// if (!dup[k] || (0 != (variant & (1 << (nextOption++))))) { -// next.add(options[k]); -// } -// } -// result[variant] = (String[]) next.toArray(NONE); -// } -// return result; -// } -// -// private static int exp(int base, int power) { // not in Math? -// if (0 > power) { -// throw new IllegalArgumentException("negative power: " + power); -// } -// int result = 1; -// while (0 < power--) { -// result *= base; -// } -// return result; -// } - -// /** -// * Make a copy of the array. -// * @return an array with the same component type as source -// * containing same elements, even if null. -// * @throws IllegalArgumentException if source is null -// */ -// public static final Object[] copy(Object[] source) { -// LangUtil.throwIaxIfNull(source, "source"); -// final Class c = source.getClass().getComponentType(); -// Object[] result = (Object[]) Array.newInstance(c, source.length); -// System.arraycopy(source, 0, result, 0, result.length); -// return result; -// } - - - /** - * Convert arrays safely. The number of elements in the result - * will be 1 smaller for each element that is null or not assignable. - * This will use sink if it has exactly the right size. - * The result will always have the same component type as sink. - * @return an array with the same component type as sink - * containing any assignable elements in source (in the same order). - * @throws IllegalArgumentException if either is null - */ - public static Object[] safeCopy(Object[] source, Object[] sink) { - final Class sinkType = (null == sink - ? Object.class - : sink.getClass().getComponentType()); - final int sourceLength = (null == source ? 0 : source.length); - final int sinkLength = (null == sink ? 0 : sink.length); - - final int resultSize; - ArrayList result = null; - if (0 == sourceLength) { - resultSize = 0; - } else { - result = new ArrayList(sourceLength); - for (int i = 0; i < sourceLength; i++) { - if ((null != source[i]) - && (sinkType.isAssignableFrom(source[i].getClass()))) { - result.add(source[i]); - } - } - resultSize = result.size(); - } - if (resultSize != sinkLength) { - sink = (Object[]) Array.newInstance(sinkType, result.size()); - } - if (0 < resultSize) { - sink = result.toArray(sink); - } - return sink; - } - - /** - * @return a String with the unqualified class name of the class (or "null") - */ - public static String unqualifiedClassName(Class c) { - if (null == c) { - return "null"; - } - String name = c.getName(); - int loc = name.lastIndexOf("."); - if (-1 != loc) { - name = name.substring(1 + loc); - } - return name; - } - - /** - * @return a String with the unqualified class name of the object (or "null") - */ - public static String unqualifiedClassName(Object o) { - return LangUtil.unqualifiedClassName(null == o ? null : o.getClass()); - } - - /** inefficient way to replace all instances of sought with replace */ - public static String replace(String in, String sought, String replace) { - if (LangUtil.isEmpty(in) || LangUtil.isEmpty(sought)) { - return in; - } - StringBuffer result = new StringBuffer(); - final int len = sought.length(); - int start = 0; - int loc; - while (-1 != (loc = in.indexOf(sought, start))) { - result.append(in.substring(start, loc)); - if (!LangUtil.isEmpty(replace)) { - result.append(replace); - } - start = loc + len; - } - result.append(in.substring(start)); - return result.toString(); - } - - /** render i right-justified with a given width less than about 40 */ - public static String toSizedString(long i, int width) { - String result = "" + i; - int size = result.length(); - if (width > size) { - final String pad = " "; - final int padLength = pad.length(); - if (width > padLength) { - width = padLength; - } - int topad = width-size; - result = pad.substring(0, topad) + result; - } - return result; - } - -// /** clip StringBuffer to maximum number of lines */ -// static String clipBuffer(StringBuffer buffer, int maxLines) { -// if ((null == buffer) || (1 > buffer.length())) return ""; -// StringBuffer result = new StringBuffer(); -// int j = 0; -// final int MAX = maxLines; -// final int N = buffer.length(); -// for (int i = 0, srcBegin = 0; i < MAX; srcBegin += j) { -// // todo: replace with String variant if/since getting char? -// char[] chars = new char[128]; -// int srcEnd = srcBegin+chars.length; -// if (srcEnd >= N) { -// srcEnd = N-1; -// } -// if (srcBegin == srcEnd) break; -// //log("srcBegin:" + srcBegin + ":srcEnd:" + srcEnd); -// buffer.getChars(srcBegin, srcEnd, chars, 0); -// for (j = 0; j < srcEnd-srcBegin/*chars.length*/; j++) { -// char c = chars[j]; -// if (c == '\n') { -// i++; -// j++; -// break; -// } -// } -// try { result.append(chars, 0, j); } -// catch (Throwable t) { } -// } -// return result.toString(); -// } - - /** - * @return "({UnqualifiedExceptionClass}) {message}" - */ - public static String renderExceptionShort(Throwable e) { - if (null == e) - return "(Throwable) null"; - return "(" + LangUtil.unqualifiedClassName(e) + ") " + e.getMessage(); - } - - /** - * Renders exception <code>t</code> after unwrapping and - * eliding any test packages. - * @param t <code>Throwable</code> to print. - * @see #maxStackTrace - */ - public static String renderException(Throwable t) { - return renderException(t, true); - } - - /** - * Renders exception <code>t</code>, unwrapping, - * optionally eliding and limiting total number of lines. - * @param t <code>Throwable</code> to print. - * @param elide true to limit to 100 lines and elide test packages - * @see StringChecker#TEST_PACKAGES - */ - public static String renderException(Throwable t, boolean elide) { - if (null == t) return "null throwable"; - t = unwrapException(t); - StringBuffer stack = stackToString(t, false); - if (elide) { - elideEndingLines(StringChecker.TEST_PACKAGES, stack, 100); - } - return stack.toString(); - } - - /** - * Trim ending lines from a StringBuffer, - * clipping to maxLines and further removing any number of - * trailing lines accepted by checker. - * @param checker returns true if trailing line should be elided. - * @param stack StringBuffer with lines to elide - * @param maxLines int for maximum number of resulting lines - */ - static void elideEndingLines(StringChecker checker, StringBuffer stack, int maxLines) { - if (null == checker || (null == stack) || (0 == stack.length())) { - return; - } - final LinkedList lines = new LinkedList(); - StringTokenizer st = new StringTokenizer(stack.toString(),"\n\r"); - while (st.hasMoreTokens() && (0 < --maxLines)) { - lines.add(st.nextToken()); - } - st = null; - - String line; - int elided = 0; - while (!lines.isEmpty()) { - line = (String) lines.getLast(); - if (!checker.acceptString(line)) { - break; - } else { - elided++; - lines.removeLast(); - } - } - if ((elided > 0) || (maxLines < 1)) { - final int EOL_LEN = EOL.length(); - int totalLength = 0; - while (!lines.isEmpty()) { - totalLength += EOL_LEN + ((String) lines.getFirst()).length(); - lines.removeFirst(); - } - if (stack.length() > totalLength) { - stack.setLength(totalLength); - if (elided > 0) { - stack.append(" (... " + elided + " lines...)"); - } - } - } - } - - - /** Dump message and stack to StringBuffer. */ - public static StringBuffer stackToString(Throwable throwable, boolean skipMessage) { - if (null == throwable) { - return new StringBuffer(); - } - StringWriter buf = new StringWriter(); - PrintWriter writer = new PrintWriter(buf); - if (!skipMessage) { - writer.println(throwable.getMessage()); - } - throwable.printStackTrace(writer); - try { buf.close(); } - catch (IOException ioe) {} // ignored - return buf.getBuffer(); - } - - - /** @return Throwable input or tail of any wrapped exception chain */ - public static Throwable unwrapException(Throwable t) { - Throwable current = t; - Throwable next = null; - while (current != null) { - // Java 1.2 exceptions that carry exceptions - if (current instanceof InvocationTargetException) { - next = ((InvocationTargetException) current).getTargetException(); - } else if (current instanceof ClassNotFoundException) { - next = ((ClassNotFoundException) current).getException(); - } else if (current instanceof ExceptionInInitializerError) { - next = ((ExceptionInInitializerError) current).getException(); - } else if (current instanceof PrivilegedActionException) { - next = ((PrivilegedActionException) current).getException(); - } else if (current instanceof SQLException) { - next = ((SQLException) current).getNextException(); - } - // ...getException(): - // javax.naming.event.NamingExceptionEvent - // javax.naming.ldap.UnsolicitedNotification - // javax.xml.parsers.FactoryConfigurationError - // javax.xml.transform.TransformerFactoryConfigurationError - // javax.xml.transform.TransformerException - // org.xml.sax.SAXException - // 1.4: Throwable.getCause - // java.util.logging.LogRecord.getThrown() - if (null == next) { - break; - } else { - current = next; - next = null; - } - } - return current; - } + } + } + } + if (!found) { + result[resultIndex++] = args[j]; + } + } + + // unset any not found + for (int i = 0; i < options.length; i++) { + if (!foundSet.get(i)) { + options[i][0] = null; + } + } + // fixup remainder + if (resultIndex < args.length) { + String[] temp = new String[resultIndex]; + System.arraycopy(result, 0, temp, 0, resultIndex); + args = temp; + } + + return args; + } + + // + // /** + // * Extract options and arguments to input parameter list, returning remainder. + // * @param args the String[] input options + // * @param validOptions the String[] options to find in the input args - not null + // * @param optionArgs the int[] number of arguments for each option in validOptions + // * (if null, then no arguments for any option) + // * @param extracted the List for the matched options + // * @return String[] of args remaining after extracting options to extracted + // */ + // public static String[] extractOptions(String[] args, String[] validOptions, + // int[] optionArgs, List extracted) { + // if (LangUtil.isEmpty(args) + // || LangUtil.isEmpty(validOptions) ) { + // return args; + // } + // if (null != optionArgs) { + // if (optionArgs.length != validOptions.length) { + // throw new IllegalArgumentException("args must match options"); + // } + // } + // String[] result = new String[args.length]; + // int resultIndex = 0; + // for (int j = 0; j < args.length; j++) { + // boolean found = false; + // for (int i = 0; !found && (i < validOptions.length); i++) { + // String sought = validOptions[i]; + // int doMore = (null == optionArgs ? 0 : optionArgs[i]); + // if (LangUtil.isEmpty(sought)) { + // continue; + // } + // found = sought.equals(args[j]); + // if (found) { + // if (null != extracted) { + // extracted.add(sought); + // } + // if (0 < doMore) { + // final int MAX = j + doMore; + // if (MAX >= args.length) { + // String s = "expecting " + doMore + " args after "; + // throw new IllegalArgumentException(s + args[j]); + // } + // if (null != extracted) { + // while (j < MAX) { + // extracted.add(args[++j]); + // } + // } else { + // j = MAX; + // } + // } + // break; + // } + // } + // if (!found) { + // result[resultIndex++] = args[j]; + // } + // } + // if (resultIndex < args.length) { + // String[] temp = new String[resultIndex]; + // System.arraycopy(result, 0, temp, 0, resultIndex); + // args = temp; + // } + // return args; + // } + + // /** @return String[] of entries in validOptions found in args */ + // public static String[] selectOptions(String[] args, String[] validOptions) { + // if (LangUtil.isEmpty(args) || LangUtil.isEmpty(validOptions)) { + // return new String[0]; + // } + // ArrayList result = new ArrayList(); + // for (int i = 0; i < validOptions.length; i++) { + // String sought = validOptions[i]; + // if (LangUtil.isEmpty(sought)) { + // continue; + // } + // for (int j = 0; j < args.length; j++) { + // if (sought.equals(args[j])) { + // result.add(sought); + // break; + // } + // } + // } + // return (String[]) result.toArray(new String[0]); + // } + + // /** @return String[] of entries in validOptions found in args */ + // public static String[] selectOptions(List args, String[] validOptions) { + // if (LangUtil.isEmpty(args) || LangUtil.isEmpty(validOptions)) { + // return new String[0]; + // } + // ArrayList result = new ArrayList(); + // for (int i = 0; i < validOptions.length; i++) { + // String sought = validOptions[i]; + // if (LangUtil.isEmpty(sought)) { + // continue; + // } + // for (Iterator iter = args.iterator(); iter.hasNext();) { + // String arg = (String) iter.next(); + // if (sought.equals(arg)) { + // result.add(sought); + // break; + // } + // } + // } + // return (String[]) result.toArray(new String[0]); + // } + + // /** + // * Generate variants of String[] options by creating an extra set for + // * each option that ends with "-". If none end with "-", then an + // * array equal to <code>new String[][] { options }</code> is returned; + // * if one ends with "-", then two sets are returned, + // * three causes eight sets, etc. + // * @return String[][] with each option set. + // * @throws IllegalArgumentException if any option is null or empty. + // */ + // public static String[][] optionVariants(String[] options) { + // if ((null == options) || (0 == options.length)) { + // return new String[][] { new String[0]}; + // } + // // be nice, don't stomp input + // String[] temp = new String[options.length]; + // System.arraycopy(options, 0, temp, 0, temp.length); + // options = temp; + // boolean[] dup = new boolean[options.length]; + // int numDups = 0; + // + // for (int i = 0; i < options.length; i++) { + // String option = options[i]; + // if (LangUtil.isEmpty(option)) { + // throw new IllegalArgumentException("empty option at " + i); + // } + // if (option.endsWith("-")) { + // options[i] = option.substring(0, option.length()-1); + // dup[i] = true; + // numDups++; + // } + // } + // final String[] NONE = new String[0]; + // final int variants = exp(2, numDups); + // final String[][] result = new String[variants][]; + // // variant is a bitmap wrt doing extra value when dup[k]=true + // for (int variant = 0; variant < variants; variant++) { + // ArrayList next = new ArrayList(); + // int nextOption = 0; + // for (int k = 0; k < options.length; k++) { + // if (!dup[k] || (0 != (variant & (1 << (nextOption++))))) { + // next.add(options[k]); + // } + // } + // result[variant] = (String[]) next.toArray(NONE); + // } + // return result; + // } + // + // private static int exp(int base, int power) { // not in Math? + // if (0 > power) { + // throw new IllegalArgumentException("negative power: " + power); + // } + // int result = 1; + // while (0 < power--) { + // result *= base; + // } + // return result; + // } + + // /** + // * Make a copy of the array. + // * @return an array with the same component type as source + // * containing same elements, even if null. + // * @throws IllegalArgumentException if source is null + // */ + // public static final Object[] copy(Object[] source) { + // LangUtil.throwIaxIfNull(source, "source"); + // final Class c = source.getClass().getComponentType(); + // Object[] result = (Object[]) Array.newInstance(c, source.length); + // System.arraycopy(source, 0, result, 0, result.length); + // return result; + // } + + /** + * Convert arrays safely. The number of elements in the result will be 1 smaller for each element that is null or not + * assignable. This will use sink if it has exactly the right size. The result will always have the same component type as sink. + * + * @return an array with the same component type as sink containing any assignable elements in source (in the same order). + * @throws IllegalArgumentException if either is null + */ + public static Object[] safeCopy(Object[] source, Object[] sink) { + final Class sinkType = (null == sink ? Object.class : sink.getClass().getComponentType()); + final int sourceLength = (null == source ? 0 : source.length); + final int sinkLength = (null == sink ? 0 : sink.length); + + final int resultSize; + ArrayList result = null; + if (0 == sourceLength) { + resultSize = 0; + } else { + result = new ArrayList(sourceLength); + for (int i = 0; i < sourceLength; i++) { + if ((null != source[i]) && (sinkType.isAssignableFrom(source[i].getClass()))) { + result.add(source[i]); + } + } + resultSize = result.size(); + } + if (resultSize != sinkLength) { + sink = (Object[]) Array.newInstance(sinkType, result.size()); + } + if (0 < resultSize) { + sink = result.toArray(sink); + } + return sink; + } + + /** + * @return a String with the unqualified class name of the class (or "null") + */ + public static String unqualifiedClassName(Class c) { + if (null == c) { + return "null"; + } + String name = c.getName(); + int loc = name.lastIndexOf("."); + if (-1 != loc) { + name = name.substring(1 + loc); + } + return name; + } + + /** + * @return a String with the unqualified class name of the object (or "null") + */ + public static String unqualifiedClassName(Object o) { + return LangUtil.unqualifiedClassName(null == o ? null : o.getClass()); + } + + /** inefficient way to replace all instances of sought with replace */ + public static String replace(String in, String sought, String replace) { + if (LangUtil.isEmpty(in) || LangUtil.isEmpty(sought)) { + return in; + } + StringBuffer result = new StringBuffer(); + final int len = sought.length(); + int start = 0; + int loc; + while (-1 != (loc = in.indexOf(sought, start))) { + result.append(in.substring(start, loc)); + if (!LangUtil.isEmpty(replace)) { + result.append(replace); + } + start = loc + len; + } + result.append(in.substring(start)); + return result.toString(); + } + + /** render i right-justified with a given width less than about 40 */ + public static String toSizedString(long i, int width) { + String result = "" + i; + int size = result.length(); + if (width > size) { + final String pad = " "; + final int padLength = pad.length(); + if (width > padLength) { + width = padLength; + } + int topad = width - size; + result = pad.substring(0, topad) + result; + } + return result; + } + + // /** clip StringBuffer to maximum number of lines */ + // static String clipBuffer(StringBuffer buffer, int maxLines) { + // if ((null == buffer) || (1 > buffer.length())) return ""; + // StringBuffer result = new StringBuffer(); + // int j = 0; + // final int MAX = maxLines; + // final int N = buffer.length(); + // for (int i = 0, srcBegin = 0; i < MAX; srcBegin += j) { + // // todo: replace with String variant if/since getting char? + // char[] chars = new char[128]; + // int srcEnd = srcBegin+chars.length; + // if (srcEnd >= N) { + // srcEnd = N-1; + // } + // if (srcBegin == srcEnd) break; + // //log("srcBegin:" + srcBegin + ":srcEnd:" + srcEnd); + // buffer.getChars(srcBegin, srcEnd, chars, 0); + // for (j = 0; j < srcEnd-srcBegin/*chars.length*/; j++) { + // char c = chars[j]; + // if (c == '\n') { + // i++; + // j++; + // break; + // } + // } + // try { result.append(chars, 0, j); } + // catch (Throwable t) { } + // } + // return result.toString(); + // } + + /** + * @return "({UnqualifiedExceptionClass}) {message}" + */ + public static String renderExceptionShort(Throwable e) { + if (null == e) + return "(Throwable) null"; + return "(" + LangUtil.unqualifiedClassName(e) + ") " + e.getMessage(); + } + + /** + * Renders exception <code>t</code> after unwrapping and eliding any test packages. + * + * @param t <code>Throwable</code> to print. + * @see #maxStackTrace + */ + public static String renderException(Throwable t) { + return renderException(t, true); + } + + /** + * Renders exception <code>t</code>, unwrapping, optionally eliding and limiting total number of lines. + * + * @param t <code>Throwable</code> to print. + * @param elide true to limit to 100 lines and elide test packages + * @see StringChecker#TEST_PACKAGES + */ + public static String renderException(Throwable t, boolean elide) { + if (null == t) + return "null throwable"; + t = unwrapException(t); + StringBuffer stack = stackToString(t, false); + if (elide) { + elideEndingLines(StringChecker.TEST_PACKAGES, stack, 100); + } + return stack.toString(); + } + + /** + * Trim ending lines from a StringBuffer, clipping to maxLines and further removing any number of trailing lines accepted by + * checker. + * + * @param checker returns true if trailing line should be elided. + * @param stack StringBuffer with lines to elide + * @param maxLines int for maximum number of resulting lines + */ + static void elideEndingLines(StringChecker checker, StringBuffer stack, int maxLines) { + if (null == checker || (null == stack) || (0 == stack.length())) { + return; + } + final LinkedList lines = new LinkedList(); + StringTokenizer st = new StringTokenizer(stack.toString(), "\n\r"); + while (st.hasMoreTokens() && (0 < --maxLines)) { + lines.add(st.nextToken()); + } + st = null; + + String line; + int elided = 0; + while (!lines.isEmpty()) { + line = (String) lines.getLast(); + if (!checker.acceptString(line)) { + break; + } else { + elided++; + lines.removeLast(); + } + } + if ((elided > 0) || (maxLines < 1)) { + final int EOL_LEN = EOL.length(); + int totalLength = 0; + while (!lines.isEmpty()) { + totalLength += EOL_LEN + ((String) lines.getFirst()).length(); + lines.removeFirst(); + } + if (stack.length() > totalLength) { + stack.setLength(totalLength); + if (elided > 0) { + stack.append(" (... " + elided + " lines...)"); + } + } + } + } + + /** Dump message and stack to StringBuffer. */ + public static StringBuffer stackToString(Throwable throwable, boolean skipMessage) { + if (null == throwable) { + return new StringBuffer(); + } + StringWriter buf = new StringWriter(); + PrintWriter writer = new PrintWriter(buf); + if (!skipMessage) { + writer.println(throwable.getMessage()); + } + throwable.printStackTrace(writer); + try { + buf.close(); + } catch (IOException ioe) { + } // ignored + return buf.getBuffer(); + } + + /** @return Throwable input or tail of any wrapped exception chain */ + public static Throwable unwrapException(Throwable t) { + Throwable current = t; + Throwable next = null; + while (current != null) { + // Java 1.2 exceptions that carry exceptions + if (current instanceof InvocationTargetException) { + next = ((InvocationTargetException) current).getTargetException(); + } else if (current instanceof ClassNotFoundException) { + next = ((ClassNotFoundException) current).getException(); + } else if (current instanceof ExceptionInInitializerError) { + next = ((ExceptionInInitializerError) current).getException(); + } else if (current instanceof PrivilegedActionException) { + next = ((PrivilegedActionException) current).getException(); + } else if (current instanceof SQLException) { + next = ((SQLException) current).getNextException(); + } + // ...getException(): + // javax.naming.event.NamingExceptionEvent + // javax.naming.ldap.UnsolicitedNotification + // javax.xml.parsers.FactoryConfigurationError + // javax.xml.transform.TransformerFactoryConfigurationError + // javax.xml.transform.TransformerException + // org.xml.sax.SAXException + // 1.4: Throwable.getCause + // java.util.logging.LogRecord.getThrown() + if (null == next) { + break; + } else { + current = next; + next = null; + } + } + return current; + } /** - * Replacement for Arrays.asList(..) which gacks on null - * and returns a List in which remove is an unsupported operation. - * @param array the Object[] to convert (may be null) - * @return the List corresponding to array (never null) - */ - public static List arrayAsList(Object[] array) { - if ((null == array) || (1 > array.length)) { - return Collections.EMPTY_LIST; - } - ArrayList list = new ArrayList(); - list.addAll(Arrays.asList(array)); - return list; - } - - - - - /** check if input contains any packages to elide. */ - public static class StringChecker { - static StringChecker TEST_PACKAGES = new StringChecker(new String[] - { "org.aspectj.testing", - "org.eclipse.jdt.internal.junit", - "junit.framework.", - "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" - }); - - String[] infixes; - - /** @param infixes adopted */ - StringChecker(String[] infixes) { - this.infixes = infixes; - } - - /** @return true if input contains infixes */ + * Replacement for Arrays.asList(..) which gacks on null and returns a List in which remove is an unsupported operation. + * + * @param array the Object[] to convert (may be null) + * @return the List corresponding to array (never null) + */ + public static List arrayAsList(Object[] array) { + if ((null == array) || (1 > array.length)) { + return Collections.EMPTY_LIST; + } + ArrayList list = new ArrayList(); + list.addAll(Arrays.asList(array)); + return list; + } + + /** check if input contains any packages to elide. */ + public static class StringChecker { + static StringChecker TEST_PACKAGES = new StringChecker(new String[] { "org.aspectj.testing", + "org.eclipse.jdt.internal.junit", "junit.framework.", + "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" }); + + String[] infixes; + + /** @param infixes adopted */ + StringChecker(String[] infixes) { + this.infixes = infixes; + } + + /** @return true if input contains infixes */ public boolean acceptString(String input) { - boolean result = false; + boolean result = false; if (!LangUtil.isEmpty(input)) { - for (int i = 0; !result && (i < infixes.length); i++) { - result = (-1 != input.indexOf(infixes[i])); - } - } - return result; - } - } - - /** - * Gen classpath. - * @param bootclasspath - * @param classpath - * @param classesDir - * @param outputJar - * @return String combining classpath elements - */ - public static String makeClasspath( // XXX dumb implementation - String bootclasspath, - String classpath, - String classesDir, - String outputJar) { - StringBuffer sb = new StringBuffer(); - addIfNotEmpty(bootclasspath, sb, File.pathSeparator); - addIfNotEmpty(classpath, sb, File.pathSeparator); - if (!addIfNotEmpty(classesDir, sb, File.pathSeparator)) { - addIfNotEmpty(outputJar, sb, File.pathSeparator); - } - return sb.toString(); - } - - /** - * @param input ignored if null - * @param sink the StringBuffer to add input to - return false if null - * @param delimiter the String to append to input when added - ignored if empty - * @return true if input + delimiter added to sink - */ - private static boolean addIfNotEmpty(String input, StringBuffer sink, String delimiter) { - if (LangUtil.isEmpty(input) || (null == sink)) { - return false; - } - sink.append(input); - if (!LangUtil.isEmpty(delimiter)) { - sink.append(delimiter); - } - return true; - } - - - /** - * Create or initialize a process controller to run - * a process in another VM asynchronously. - * @param controller the ProcessController to initialize, if not null - * @param classpath - * @param mainClass - * @param args - * @return initialized ProcessController - */ - public static ProcessController makeProcess( - ProcessController controller, - String classpath, - String mainClass, - String[] args) { - File java = LangUtil.getJavaExecutable(); - ArrayList cmd = new ArrayList(); - cmd.add(java.getAbsolutePath()); - cmd.add("-classpath"); - cmd.add(classpath); - cmd.add(mainClass); - if (!LangUtil.isEmpty(args)) { - cmd.addAll(Arrays.asList(args)); - } - String[] command = (String[]) cmd.toArray(new String[0]); - if (null == controller) { - controller = new ProcessController(); - } - controller.init(command, mainClass); - return controller; - } - -// /** -// * Create a process to run asynchronously. -// * @param controller if not null, initialize this one -// * @param command the String[] command to run -// * @param controller the ProcessControl for streams and results -// */ -// public static ProcessController makeProcess( // not needed? -// ProcessController controller, -// String[] command, -// String label) { -// if (null == controller) { -// controller = new ProcessController(); -// } -// controller.init(command, label); -// return controller; -// } - - /** - * Find java executable File path from java.home system property. - * @return File associated with the java command, or null if not found. - */ - public static File getJavaExecutable() { - String javaHome = null; - File result = null; - //java.home - // java.class.path - // java.ext.dirs - try { - javaHome = System.getProperty("java.home"); - } catch (Throwable t) { - // ignore - } - if (null != javaHome) { - File binDir = new File(javaHome, "bin"); - if (binDir.isDirectory() && binDir.canRead()) { - String[] execs = new String[] { "java", "java.exe" }; - for (int i = 0; i < execs.length; i++) { - result = new File(binDir, execs[i]); - if (result.canRead()) { - break; - } - } - } - } - return result; - } - - /** - * Sleep for a particular period (in milliseconds). - * @param time the long time in milliseconds to sleep - * @return true if delay succeeded, false if interrupted 100 times - */ - public static boolean sleep(long milliseconds) { - if (milliseconds == 0) { - return true; - } else if (milliseconds < 0) { - throw new IllegalArgumentException("negative: " + milliseconds); - } - return sleepUntil(milliseconds + System.currentTimeMillis()); - } - - /** - * Sleep until a particular time. - * @param time the long time in milliseconds to sleep until - * @return true if delay succeeded, false if interrupted 100 times - */ - public static boolean sleepUntil(long time) { - if (time == 0) { - return true; - } else if (time < 0) { - throw new IllegalArgumentException("negative: " + time); - } -// final Thread thread = Thread.currentThread(); - long curTime = System.currentTimeMillis(); - for (int i = 0; (i < 100) && (curTime < time); i++) { - try { - Thread.sleep(time-curTime); - } catch (InterruptedException e) { - // ignore - } - curTime = System.currentTimeMillis(); - } - return (curTime >= time); - } - - /** - * Handle an external process asynchrously. - * <code>start()</code> launches a main thread to wait for the process - * and pipes streams (in child threads) through to the corresponding - * streams (e.g., the process System.err to this System.err). - * This can complete normally, by exception, or on demand by a client. - * Clients can implement <code>doCompleting(..)</code> to get notice - * when the process completes. - * <p>The following sample code creates a process with a completion - * callback starts it, and some time later retries the process. - * <pre> - * LangUtil.ProcessController controller - * = new LangUtil.ProcessController() { - * protected void doCompleting(LangUtil.ProcessController.Thrown thrown, - * int result) { - * // signal result - * } - * }; - * controller.init(new String[] { "java", "-version" }, "java version"); - * controller.start(); - * // some time later... - * // retry... - * if (!controller.completed()) { - * controller.stop(); - * controller.reinit(); - * controller.start(); - * } - * </pre> - * <u>warning</u>: Currently this does not close the input or output - * streams, since doing so prevents their use later. - */ - public static class ProcessController { - /* - * XXX not verified thread-safe, but should be. Known problems: - * - user stops (completed = true) then exception thrown - * from destroying process (stop() expects !completed) - * ... - */ - private String[] command; - private String[] envp; - private String label; - - private boolean init; - private boolean started; - private boolean completed; - /** if true, stopped by user when not completed */ - private boolean userStopped; - - private Process process; - private FileUtil.Pipe errStream; - private FileUtil.Pipe outStream; - private FileUtil.Pipe inStream; - private ByteArrayOutputStream errSnoop; - private ByteArrayOutputStream outSnoop; - - private int result; - private Thrown thrown; - - public ProcessController() { - } - - /** - * Permit re-running using the same command - * if this is not started or if completed. - * Can also call this when done with results to release - * references associated with results - * (e.g., stack traces). - */ - public final void reinit() { - if (!init) { - throw new IllegalStateException("must init(..) before reinit()"); - } - if (started && !completed) { - throw new IllegalStateException("not completed - do stop()"); - } - // init everything but command and label - started = false; - completed = false; - result = Integer.MIN_VALUE; - thrown = null; - process = null; - errStream = null; - outStream = null; - inStream = null; - } - - public final void init( - String classpath, - String mainClass, - String[] args) { - init(LangUtil.getJavaExecutable(), - classpath, mainClass, args); - } - - public final void init(File java, - String classpath, - String mainClass, - String[] args) { - LangUtil.throwIaxIfNull(java, "java"); - LangUtil.throwIaxIfNull(mainClass, "mainClass"); - LangUtil.throwIaxIfNull(args, "args"); - ArrayList cmd = new ArrayList(); - cmd.add(java.getAbsolutePath()); - cmd.add("-classpath"); - cmd.add(classpath); - cmd.add(mainClass); - if (!LangUtil.isEmpty(args)) { - cmd.addAll(Arrays.asList(args)); - } - init((String[]) cmd.toArray(new String[0]), mainClass); - } - - public final void init(String[] command, String label) { - this.command = (String[]) LangUtil.safeCopy(command, new String[0]); - if (1 > this.command.length) { - throw new IllegalArgumentException("empty command"); - } - this.label = LangUtil.isEmpty(label) ? command[0] : label; - this.init = true; - reinit(); - } - - public final void setEnvp(String[] envp) { - this.envp = (String[]) LangUtil.safeCopy(envp, new String[0]); - if (1 > this.envp.length) { - throw new IllegalArgumentException("empty envp"); - } - } - - public final void setErrSnoop(ByteArrayOutputStream snoop) { - errSnoop = snoop; - if (null != errStream) { - errStream.setSnoop(errSnoop); - } - } - - public final void setOutSnoop(ByteArrayOutputStream snoop) { - outSnoop = snoop; - if (null != outStream) { - outStream.setSnoop(outSnoop); - } - } - - /** - * Start running the process and pipes asynchronously. - * @return Thread started or null if unable to start thread - * (results available via <code>getThrown()</code>, etc.) - */ - public final Thread start() { - if (!init) { - throw new IllegalStateException("not initialized"); - } - synchronized (this) { - if (started) { - throw new IllegalStateException("already started"); - } - started = true; - } - try { - process = Runtime.getRuntime().exec(command); - } catch (IOException e) { - stop(e, Integer.MIN_VALUE); - return null; - } - errStream = new FileUtil.Pipe(process.getErrorStream(), System.err); - if (null != errSnoop) { - errStream.setSnoop(errSnoop); - } - outStream = new FileUtil.Pipe(process.getInputStream(), System.out); - if (null != outSnoop) { - outStream.setSnoop(outSnoop); - } - inStream = new FileUtil.Pipe(System.in, process.getOutputStream()); - // start 4 threads, process & pipes for in, err, out - Runnable processRunner = new Runnable() { - public void run() { - Throwable thrown = null; - int result = Integer.MIN_VALUE; - try { - // pipe threads are children - new Thread(errStream).start(); - new Thread(outStream).start(); - new Thread(inStream).start(); - process.waitFor(); - result = process.exitValue(); - } catch (Throwable e) { - thrown = e; - } finally { - stop(thrown, result); - } - } - }; - Thread result = new Thread(processRunner, label); - result.start(); - return result; - } - - /** - * Destroy any process, stop any pipes. - * This waits for the pipes to clear (reading until - * no more input is available), but does not wait - * for the input stream for the pipe to close - * (i.e., not waiting for end-of-file on input stream). - */ - public final synchronized void stop() { - if (completed) { - return; - } - userStopped = true; - stop(null, Integer.MIN_VALUE); - } - - public final String[] getCommand() { - String[] toCopy = command; - if (LangUtil.isEmpty(toCopy)) { - return new String[0]; - } - String[] result = new String[toCopy.length]; - System.arraycopy(toCopy, 0, result, 0, result.length); - return result; - } - - public final boolean completed() { - return completed; - } - - public final boolean started() { - return started; - } - - public final boolean userStopped() { - return userStopped; - } - - /** - * Get any Throwable thrown. - * Note that the process can complete normally (with a valid return - * value), at the same time the pipes throw exceptions, - * and that this may return some exceptions even if the process - * is not complete. - * @return null if not complete or - * Thrown containing exceptions thrown by the process and streams. - */ - public final Thrown getThrown() { // cache this - return makeThrown(null); - } - - public final int getResult() { - return result; - } - - /** - * Subclasses implement this to get synchronous notice of completion. - * All pipes and processes should be complete at this time. - * To get the exceptions thrown for the pipes, use - * <code>getThrown()</code>. - * If there is an exception, the process completed abruptly - * (including side-effects of the user halting the process). - * If <code>userStopped()</code> is true, then some client asked - * that the process be destroyed using <code>stop()</code>. - * Otherwise, the result code should be the result value - * returned by the process. - * @param thrown same as <code>getThrown().fromProcess</code>. - * @param result same as <code>getResult()</code> - * @see getThrown() - * @see getResult() - * @see stop() - */ - protected void doCompleting(Thrown thrown, int result) { - } - - /** - * Handle termination (on-demand, abrupt, or normal) - * by destroying and/or halting process and pipes. - * @param thrown ignored if null - * @param result ignored if Integer.MIN_VALUE - */ - private final synchronized void stop(Throwable thrown, int result) { - if (completed) { - throw new IllegalStateException("already completed"); - } else if (null != this.thrown) { - throw new IllegalStateException("already set thrown: " + thrown); - } - // assert null == this.thrown - this.thrown = makeThrown(thrown); - if (null != process) { - process.destroy(); - } - if (null != inStream) { - inStream.halt(false, true); // this will block if waiting - inStream = null; - } - if (null != outStream) { - outStream.halt(true, true); - outStream = null; - } - if (null != errStream) { - errStream.halt(true, true); - errStream = null; - } - if (Integer.MIN_VALUE != result) { - this.result = result; - } - completed = true; - doCompleting(this.thrown, result); - } - - /** - * Create snapshot of Throwable's thrown. - * @param thrown ignored if null or if this.thrown is not null - */ - private final synchronized Thrown makeThrown(Throwable processThrown) { - if (null != thrown) { - return thrown; - } - return new Thrown( - processThrown, - (null == outStream ? null : outStream.getThrown()), - (null == errStream ? null : errStream.getThrown()), - (null == inStream ? null : inStream.getThrown()) - ); - } - - public static class Thrown { - public final Throwable fromProcess; - public final Throwable fromErrPipe; - public final Throwable fromOutPipe; - public final Throwable fromInPipe; - /** true only if some Throwable is not null */ - public final boolean thrown; - private Thrown( - Throwable fromProcess, - Throwable fromOutPipe, - Throwable fromErrPipe, - Throwable fromInPipe) { - this.fromProcess = fromProcess; - this.fromErrPipe = fromErrPipe; - this.fromOutPipe = fromOutPipe; - this.fromInPipe = fromInPipe; - thrown = ((null != fromProcess) - || (null != fromInPipe) - || (null != fromOutPipe) - || (null != fromErrPipe)); - } - - public String toString() { - StringBuffer sb = new StringBuffer(); - append(sb, fromProcess, "process"); - append(sb, fromOutPipe, " stdout"); - append(sb, fromErrPipe, " stderr"); - append(sb, fromInPipe, " stdin"); - if (0 == sb.length()) { - return "Thrown (none)"; - } else { - return sb.toString(); - } - } - private void append(StringBuffer sb, Throwable thrown, String label) { - if (null != thrown) { - sb.append("from " + label + ": "); - sb.append(LangUtil.renderExceptionShort(thrown)); - sb.append(LangUtil.EOL); - } - } - } // class Thrown - } + for (int i = 0; !result && (i < infixes.length); i++) { + result = (-1 != input.indexOf(infixes[i])); + } + } + return result; + } + } -} + /** + * Gen classpath. + * + * @param bootclasspath + * @param classpath + * @param classesDir + * @param outputJar + * @return String combining classpath elements + */ + public static String makeClasspath( // XXX dumb implementation + String bootclasspath, String classpath, String classesDir, String outputJar) { + StringBuffer sb = new StringBuffer(); + addIfNotEmpty(bootclasspath, sb, File.pathSeparator); + addIfNotEmpty(classpath, sb, File.pathSeparator); + if (!addIfNotEmpty(classesDir, sb, File.pathSeparator)) { + addIfNotEmpty(outputJar, sb, File.pathSeparator); + } + return sb.toString(); + } + + /** + * @param input ignored if null + * @param sink the StringBuffer to add input to - return false if null + * @param delimiter the String to append to input when added - ignored if empty + * @return true if input + delimiter added to sink + */ + private static boolean addIfNotEmpty(String input, StringBuffer sink, String delimiter) { + if (LangUtil.isEmpty(input) || (null == sink)) { + return false; + } + sink.append(input); + if (!LangUtil.isEmpty(delimiter)) { + sink.append(delimiter); + } + return true; + } + + /** + * Create or initialize a process controller to run a process in another VM asynchronously. + * + * @param controller the ProcessController to initialize, if not null + * @param classpath + * @param mainClass + * @param args + * @return initialized ProcessController + */ + public static ProcessController makeProcess(ProcessController controller, String classpath, String mainClass, String[] args) { + File java = LangUtil.getJavaExecutable(); + ArrayList cmd = new ArrayList(); + cmd.add(java.getAbsolutePath()); + cmd.add("-classpath"); + cmd.add(classpath); + cmd.add(mainClass); + if (!LangUtil.isEmpty(args)) { + cmd.addAll(Arrays.asList(args)); + } + String[] command = (String[]) cmd.toArray(new String[0]); + if (null == controller) { + controller = new ProcessController(); + } + controller.init(command, mainClass); + return controller; + } + + // /** + // * Create a process to run asynchronously. + // * @param controller if not null, initialize this one + // * @param command the String[] command to run + // * @param controller the ProcessControl for streams and results + // */ + // public static ProcessController makeProcess( // not needed? + // ProcessController controller, + // String[] command, + // String label) { + // if (null == controller) { + // controller = new ProcessController(); + // } + // controller.init(command, label); + // return controller; + // } + + /** + * Find java executable File path from java.home system property. + * + * @return File associated with the java command, or null if not found. + */ + public static File getJavaExecutable() { + String javaHome = null; + File result = null; + // java.home + // java.class.path + // java.ext.dirs + try { + javaHome = System.getProperty("java.home"); + } catch (Throwable t) { + // ignore + } + if (null != javaHome) { + File binDir = new File(javaHome, "bin"); + if (binDir.isDirectory() && binDir.canRead()) { + String[] execs = new String[] { "java", "java.exe" }; + for (int i = 0; i < execs.length; i++) { + result = new File(binDir, execs[i]); + if (result.canRead()) { + break; + } + } + } + } + return result; + } + /** + * Sleep for a particular period (in milliseconds). + * + * @param time the long time in milliseconds to sleep + * @return true if delay succeeded, false if interrupted 100 times + */ + public static boolean sleep(long milliseconds) { + if (milliseconds == 0) { + return true; + } else if (milliseconds < 0) { + throw new IllegalArgumentException("negative: " + milliseconds); + } + return sleepUntil(milliseconds + System.currentTimeMillis()); + } + + /** + * Sleep until a particular time. + * + * @param time the long time in milliseconds to sleep until + * @return true if delay succeeded, false if interrupted 100 times + */ + public static boolean sleepUntil(long time) { + if (time == 0) { + return true; + } else if (time < 0) { + throw new IllegalArgumentException("negative: " + time); + } + // final Thread thread = Thread.currentThread(); + long curTime = System.currentTimeMillis(); + for (int i = 0; (i < 100) && (curTime < time); i++) { + try { + Thread.sleep(time - curTime); + } catch (InterruptedException e) { + // ignore + } + curTime = System.currentTimeMillis(); + } + return (curTime >= time); + } + + /** + * Handle an external process asynchrously. <code>start()</code> launches a main thread to wait for the process and pipes + * streams (in child threads) through to the corresponding streams (e.g., the process System.err to this System.err). This can + * complete normally, by exception, or on demand by a client. Clients can implement <code>doCompleting(..)</code> to get notice + * when the process completes. + * <p> + * The following sample code creates a process with a completion callback starts it, and some time later retries the process. + * + * <pre> + * LangUtil.ProcessController controller = new LangUtil.ProcessController() { + * protected void doCompleting(LangUtil.ProcessController.Thrown thrown, int result) { + * // signal result + * } + * }; + * controller.init(new String[] { "java", "-version" }, "java version"); + * controller.start(); + * // some time later... + * // retry... + * if (!controller.completed()) { + * controller.stop(); + * controller.reinit(); + * controller.start(); + * } + * </pre> + * + * <u>warning</u>: Currently this does not close the input or output streams, since doing so prevents their use later. + */ + public static class ProcessController { + /* + * XXX not verified thread-safe, but should be. Known problems: - user stops (completed = true) then exception thrown from + * destroying process (stop() expects !completed) ... + */ + private String[] command; + private String[] envp; + private String label; + + private boolean init; + private boolean started; + private boolean completed; + /** if true, stopped by user when not completed */ + private boolean userStopped; + + private Process process; + private FileUtil.Pipe errStream; + private FileUtil.Pipe outStream; + private FileUtil.Pipe inStream; + private ByteArrayOutputStream errSnoop; + private ByteArrayOutputStream outSnoop; + + private int result; + private Thrown thrown; + + public ProcessController() { + } + + /** + * Permit re-running using the same command if this is not started or if completed. Can also call this when done with + * results to release references associated with results (e.g., stack traces). + */ + public final void reinit() { + if (!init) { + throw new IllegalStateException("must init(..) before reinit()"); + } + if (started && !completed) { + throw new IllegalStateException("not completed - do stop()"); + } + // init everything but command and label + started = false; + completed = false; + result = Integer.MIN_VALUE; + thrown = null; + process = null; + errStream = null; + outStream = null; + inStream = null; + } + + public final void init(String classpath, String mainClass, String[] args) { + init(LangUtil.getJavaExecutable(), classpath, mainClass, args); + } + + public final void init(File java, String classpath, String mainClass, String[] args) { + LangUtil.throwIaxIfNull(java, "java"); + LangUtil.throwIaxIfNull(mainClass, "mainClass"); + LangUtil.throwIaxIfNull(args, "args"); + ArrayList cmd = new ArrayList(); + cmd.add(java.getAbsolutePath()); + cmd.add("-classpath"); + cmd.add(classpath); + cmd.add(mainClass); + if (!LangUtil.isEmpty(args)) { + cmd.addAll(Arrays.asList(args)); + } + init((String[]) cmd.toArray(new String[0]), mainClass); + } + + public final void init(String[] command, String label) { + this.command = (String[]) LangUtil.safeCopy(command, new String[0]); + if (1 > this.command.length) { + throw new IllegalArgumentException("empty command"); + } + this.label = LangUtil.isEmpty(label) ? command[0] : label; + this.init = true; + reinit(); + } + + public final void setEnvp(String[] envp) { + this.envp = (String[]) LangUtil.safeCopy(envp, new String[0]); + if (1 > this.envp.length) { + throw new IllegalArgumentException("empty envp"); + } + } + + public final void setErrSnoop(ByteArrayOutputStream snoop) { + errSnoop = snoop; + if (null != errStream) { + errStream.setSnoop(errSnoop); + } + } + public final void setOutSnoop(ByteArrayOutputStream snoop) { + outSnoop = snoop; + if (null != outStream) { + outStream.setSnoop(outSnoop); + } + } + + /** + * Start running the process and pipes asynchronously. + * + * @return Thread started or null if unable to start thread (results available via <code>getThrown()</code>, etc.) + */ + public final Thread start() { + if (!init) { + throw new IllegalStateException("not initialized"); + } + synchronized (this) { + if (started) { + throw new IllegalStateException("already started"); + } + started = true; + } + try { + process = Runtime.getRuntime().exec(command); + } catch (IOException e) { + stop(e, Integer.MIN_VALUE); + return null; + } + errStream = new FileUtil.Pipe(process.getErrorStream(), System.err); + if (null != errSnoop) { + errStream.setSnoop(errSnoop); + } + outStream = new FileUtil.Pipe(process.getInputStream(), System.out); + if (null != outSnoop) { + outStream.setSnoop(outSnoop); + } + inStream = new FileUtil.Pipe(System.in, process.getOutputStream()); + // start 4 threads, process & pipes for in, err, out + Runnable processRunner = new Runnable() { + public void run() { + Throwable thrown = null; + int result = Integer.MIN_VALUE; + try { + // pipe threads are children + new Thread(errStream).start(); + new Thread(outStream).start(); + new Thread(inStream).start(); + process.waitFor(); + result = process.exitValue(); + } catch (Throwable e) { + thrown = e; + } finally { + stop(thrown, result); + } + } + }; + Thread result = new Thread(processRunner, label); + result.start(); + return result; + } + + /** + * Destroy any process, stop any pipes. This waits for the pipes to clear (reading until no more input is available), but + * does not wait for the input stream for the pipe to close (i.e., not waiting for end-of-file on input stream). + */ + public final synchronized void stop() { + if (completed) { + return; + } + userStopped = true; + stop(null, Integer.MIN_VALUE); + } + + public final String[] getCommand() { + String[] toCopy = command; + if (LangUtil.isEmpty(toCopy)) { + return new String[0]; + } + String[] result = new String[toCopy.length]; + System.arraycopy(toCopy, 0, result, 0, result.length); + return result; + } + + public final boolean completed() { + return completed; + } + + public final boolean started() { + return started; + } + + public final boolean userStopped() { + return userStopped; + } + + /** + * Get any Throwable thrown. Note that the process can complete normally (with a valid return value), at the same time the + * pipes throw exceptions, and that this may return some exceptions even if the process is not complete. + * + * @return null if not complete or Thrown containing exceptions thrown by the process and streams. + */ + public final Thrown getThrown() { // cache this + return makeThrown(null); + } + + public final int getResult() { + return result; + } + + /** + * Subclasses implement this to get synchronous notice of completion. All pipes and processes should be complete at this + * time. To get the exceptions thrown for the pipes, use <code>getThrown()</code>. If there is an exception, the process + * completed abruptly (including side-effects of the user halting the process). If <code>userStopped()</code> is true, then + * some client asked that the process be destroyed using <code>stop()</code>. Otherwise, the result code should be the + * result value returned by the process. + * + * @param thrown same as <code>getThrown().fromProcess</code>. + * @param result same as <code>getResult()</code> + * @see getThrown() + * @see getResult() + * @see stop() + */ + protected void doCompleting(Thrown thrown, int result) { + } + + /** + * Handle termination (on-demand, abrupt, or normal) by destroying and/or halting process and pipes. + * + * @param thrown ignored if null + * @param result ignored if Integer.MIN_VALUE + */ + private final synchronized void stop(Throwable thrown, int result) { + if (completed) { + throw new IllegalStateException("already completed"); + } else if (null != this.thrown) { + throw new IllegalStateException("already set thrown: " + thrown); + } + // assert null == this.thrown + this.thrown = makeThrown(thrown); + if (null != process) { + process.destroy(); + } + if (null != inStream) { + inStream.halt(false, true); // this will block if waiting + inStream = null; + } + if (null != outStream) { + outStream.halt(true, true); + outStream = null; + } + if (null != errStream) { + errStream.halt(true, true); + errStream = null; + } + if (Integer.MIN_VALUE != result) { + this.result = result; + } + completed = true; + doCompleting(this.thrown, result); + } + + /** + * Create snapshot of Throwable's thrown. + * + * @param thrown ignored if null or if this.thrown is not null + */ + private final synchronized Thrown makeThrown(Throwable processThrown) { + if (null != thrown) { + return thrown; + } + return new Thrown(processThrown, (null == outStream ? null : outStream.getThrown()), (null == errStream ? null + : errStream.getThrown()), (null == inStream ? null : inStream.getThrown())); + } + + public static class Thrown { + public final Throwable fromProcess; + public final Throwable fromErrPipe; + public final Throwable fromOutPipe; + public final Throwable fromInPipe; + /** true only if some Throwable is not null */ + public final boolean thrown; + + private Thrown(Throwable fromProcess, Throwable fromOutPipe, Throwable fromErrPipe, Throwable fromInPipe) { + this.fromProcess = fromProcess; + this.fromErrPipe = fromErrPipe; + this.fromOutPipe = fromOutPipe; + this.fromInPipe = fromInPipe; + thrown = ((null != fromProcess) || (null != fromInPipe) || (null != fromOutPipe) || (null != fromErrPipe)); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + append(sb, fromProcess, "process"); + append(sb, fromOutPipe, " stdout"); + append(sb, fromErrPipe, " stderr"); + append(sb, fromInPipe, " stdin"); + if (0 == sb.length()) { + return "Thrown (none)"; + } else { + return sb.toString(); + } + } + + private void append(StringBuffer sb, Throwable thrown, String label) { + if (null != thrown) { + sb.append("from " + label + ": "); + sb.append(LangUtil.renderExceptionShort(thrown)); + sb.append(LangUtil.EOL); + } + } + } // class Thrown + } + +} |