// If you edit this file, you must also edit its tests.
// For tests of this and the entire plume package, see class TestPlume.
package plume;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.lang.reflect.*;
// import Assert;
/** Utility functions that do not belong elsewhere in the plume package. */
public final class UtilMDE {
private UtilMDE() { throw new Error("do not instantiate"); }
@SuppressWarnings("nullness") // line.separator property always exists
private static final String lineSep = System.getProperty("line.separator");
///////////////////////////////////////////////////////////////////////////
/// Array
///
// For arrays, see ArraysMDE.java.
///////////////////////////////////////////////////////////////////////////
/// BitSet
///
/**
* Returns true if the cardinality of the intersection of the two
* BitSets is at least the given value.
**/
public static boolean intersectionCardinalityAtLeast(BitSet a, BitSet b, int i) {
// Here are three implementation strategies to determine the
// cardinality of the intersection:
// 1. a.clone().and(b).cardinality()
// 2. do the above, but copy only a subset of the bits initially -- enough
// that it should exceed the given number -- and if that fails, do the
// whole thing. Unfortunately, bits.get(int, int) isn't optimized
// for the case where the indices line up, so I'm not sure at what
// point this approach begins to dominate #1.
// 3. iterate through both sets with nextSetBit()
int size = Math.min(a.length(), b.length());
if (size > 10*i) {
// The size is more than 10 times the limit. So first try processing
// just a subset of the bits (4 times the limit).
BitSet intersection = a.get(0, 4*i);
intersection.and(b);
if (intersection.cardinality() >= i) {
return true;
}
}
return (intersectionCardinality(a, b) >= i);
}
/**
* Returns true if the cardinality of the intersection of the two
* BitSets is at least the given value.
**/
public static boolean intersectionCardinalityAtLeast(BitSet a, BitSet b, BitSet c, int i) {
// See comments in intersectionCardinalityAtLeast(BitSet, BitSet, int).
// This is a copy of that.
int size = Math.min(a.length(), b.length());
size = Math.min(size, c.length());
if (size > 10*i) {
// The size is more than 10 times the limit. So first try processing
// just a subset of the bits (4 times the limit).
BitSet intersection = a.get(0, 4*i);
intersection.and(b);
intersection.and(c);
if (intersection.cardinality() >= i) {
return true;
}
}
return (intersectionCardinality(a, b, c) >= i);
}
/** Returns the cardinality of the intersection of the two BitSets. **/
public static int intersectionCardinality(BitSet a, BitSet b) {
BitSet intersection = (BitSet) a.clone();
intersection.and(b);
return intersection.cardinality();
}
/** Returns the cardinality of the intersection of the three BitSets. **/
public static int intersectionCardinality(BitSet a, BitSet b, BitSet c) {
BitSet intersection = (BitSet) a.clone();
intersection.and(b);
intersection.and(c);
return intersection.cardinality();
}
///////////////////////////////////////////////////////////////////////////
/// BufferedFileReader
///
// Convenience methods for creating InputStreams, Readers, BufferedReaders, and LineNumberReaders.
/**
* Returns an InputStream for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
*/
public static InputStream fileInputStream(File file) throws IOException {
InputStream in;
if (file.getName().endsWith(".gz")) {
in = new GZIPInputStream(new FileInputStream(file));
} else {
in = new FileInputStream(file);
}
return in;
}
/**
* Returns a Reader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static InputStreamReader fileReader(String filename) throws FileNotFoundException, IOException {
// return fileReader(filename, "ISO-8859-1");
return fileReader(new File(filename), null);
}
/**
* Returns a Reader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static InputStreamReader fileReader(File file) throws FileNotFoundException, IOException {
return fileReader(file, null);
}
/**
* Returns a Reader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* @param charsetName may be null, or the name of a Charset
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static InputStreamReader fileReader(File file, /*@Nullable*/ String charsetName) throws FileNotFoundException, IOException {
InputStream in = new FileInputStream(file);
InputStreamReader file_reader;
if (charsetName == null) {
file_reader = new InputStreamReader(in);
} else {
file_reader = new InputStreamReader(in, charsetName);
}
return file_reader;
}
/**
* Returns a BufferedReader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static BufferedReader bufferedFileReader(String filename) throws FileNotFoundException, IOException {
return bufferedFileReader(new File(filename));
}
/**
* Returns a BufferedReader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static BufferedReader bufferedFileReader(File file) throws FileNotFoundException, IOException {
return(bufferedFileReader(file, null));
}
/**
* Returns a BufferedReader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static BufferedReader bufferedFileReader(String filename, /*@Nullable*/ String charsetName) throws FileNotFoundException, IOException {
return bufferedFileReader(new File(filename), charsetName);
}
/**
* Returns a BufferedReader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static BufferedReader bufferedFileReader(File file, /*@Nullable*/ String charsetName) throws FileNotFoundException, IOException {
Reader file_reader = fileReader(file, charsetName);
return new BufferedReader(file_reader);
}
/**
* Returns a LineNumberReader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static LineNumberReader lineNumberFileReader(String filename) throws FileNotFoundException, IOException {
return lineNumberFileReader(new File(filename));
}
/**
* Returns a LineNumberReader for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static LineNumberReader lineNumberFileReader(File file) throws FileNotFoundException, IOException {
Reader file_reader;
if (file.getName().endsWith(".gz")) {
file_reader = new InputStreamReader(new GZIPInputStream(new FileInputStream(file)),
"ISO-8859-1");
} else {
file_reader = new InputStreamReader(new FileInputStream(file),
"ISO-8859-1");
}
return new LineNumberReader(file_reader);
}
/**
* Returns a BufferedWriter for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
**/
public static BufferedWriter bufferedFileWriter(String filename) throws IOException {
return bufferedFileWriter (filename, false);
}
/**
* Returns a BufferedWriter for the file, accounting for the possibility
* that the file is compressed.
* (A file whose name ends with ".gz" is treated as compressed.)
* <p>
* Warning: The "gzip" program writes and reads files containing
* concatenated gzip files. As of Java 1.4, Java reads
* just the first one: it silently discards all characters (including
* gzipped files) after the first gzipped file.
* @param append if true, the resulting BufferedWriter appends to the end
* of the file instead of the beginning.
**/
public static BufferedWriter bufferedFileWriter(String filename, boolean append) throws IOException {
Writer file_writer;
if (filename.endsWith(".gz")) {
file_writer = new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(filename, append)));
} else {
file_writer = new FileWriter(filename, append);
}
return new BufferedWriter(file_writer);
}
/** @deprecated use bufferedFileReader (note lowercase first letter) */
@Deprecated // since June 2005
public static BufferedReader BufferedFileReader(String filename) throws FileNotFoundException, IOException {
return bufferedFileReader(filename);
}
/** @deprecated use lineNumberFileReader (note lowercase first letter) */
@Deprecated // since June 2005
public static LineNumberReader LineNumberFileReader(String filename) throws FileNotFoundException, IOException {
return lineNumberFileReader(filename);
}
/** @deprecated use lineNumberFileReader (note lowercase first letter) */
@Deprecated // since June 2005
public static LineNumberReader LineNumberFileReader(File file) throws FileNotFoundException, IOException {
return lineNumberFileReader(file);
}
/** @deprecated use bufferedFileWriter (note lowercase first letter) */
@Deprecated // since June 2005
public static BufferedWriter BufferedFileWriter(String filename) throws IOException {
return bufferedFileWriter(filename);
}
/** @deprecated use bufferedFileWriter (note lowercase first letter) */
@Deprecated // since June 2005
public static BufferedWriter BufferedFileWriter(String filename, boolean append) throws IOException {
return bufferedFileWriter(filename, append);
}
///////////////////////////////////////////////////////////////////////////
/// Class
///
private static HashMap<String,Class<?>> primitiveClasses = new HashMap<String,Class<?>>(8);
static {
primitiveClasses.put("boolean", Boolean.TYPE);
primitiveClasses.put("byte", Byte.TYPE);
primitiveClasses.put("char", Character.TYPE);
primitiveClasses.put("double", Double.TYPE);
primitiveClasses.put("float", Float.TYPE);
primitiveClasses.put("int", Integer.TYPE);
primitiveClasses.put("long", Long.TYPE);
primitiveClasses.put("short", Short.TYPE);
}
/**
* Like @link{Class.forName(String)}, but works when the string
* represents a primitive type, too. If the original name can't
* be found, it also tries looking for the name with the last '.'
* changed to a dollar sign ($). This accounts for inner classes
* which are sometimes separated with '.'.
**/
public static Class<?> classForName(String className) throws ClassNotFoundException {
Class<?> result = primitiveClasses.get(className);
if (result != null)
return result;
else {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
int pos = className.lastIndexOf('.');
String inner_name = className.substring (0, pos) + "$"
+ className.substring (pos+1);
try {
return Class.forName (inner_name);
} catch (ClassNotFoundException ee) {
throw e;
}
}
}
}
private static HashMap<String,String> primitiveClassesJvm = new HashMap<String,String>(8);
static {
primitiveClassesJvm.put("boolean", "Z");
primitiveClassesJvm.put("byte", "B");
primitiveClassesJvm.put("char", "C");
primitiveClassesJvm.put("double", "D");
primitiveClassesJvm.put("float", "F");
primitiveClassesJvm.put("int", "I");
primitiveClassesJvm.put("long", "J");
primitiveClassesJvm.put("short", "S");
}
/**
* Convert a fully-qualified classname from Java format to JVML format.
* For example, convert "java.lang.Object[]" to "[Ljava/lang/Object;".
**/
public static String classnameToJvm(String classname) {
int dims = 0;
while (classname.endsWith("[]")) {
dims++;
classname = classname.substring(0, classname.length()-2);
}
String result = primitiveClassesJvm.get(classname);
if (result == null) {
result = "L" + classname + ";";
}
for (int i=0; i<dims; i++) {
result = "[" + result;
}
return result.replace('.', '/');
}
/**
* Convert a primitive java type name (e.g., "int", "double", etc.) to
* the single character JVM name (e.g., "I", "D", etc.).
* Throws IllegalArgumentException if primitive_name is not a valid name.
*/
public static String primitive_name_to_jvm (String primitive_name) {
String result = primitiveClassesJvm.get (primitive_name);
if (result == null) {
throw new IllegalArgumentException("Not the name of a primitive type: " + primitive_name);
}
return result;
}
/**
* Convert a fully-qualified argument list from Java format to JVML format.
* For example, convert "(java.lang.Integer[], int, java.lang.Integer[][])"
* to "([Ljava/lang/Integer;I[[Ljava/lang/Integer;)".
**/
public static String arglistToJvm(String arglist) {
if (! (arglist.startsWith("(") && arglist.endsWith(")"))) {
throw new Error("Malformed arglist: " + arglist);
}
String result = "(";
String comma_sep_args = arglist.substring(1, arglist.length()-1);
StringTokenizer args_tokenizer
= new StringTokenizer(comma_sep_args, ",", false);
for ( ; args_tokenizer.hasMoreTokens(); ) {
String arg = args_tokenizer.nextToken().trim();
result += classnameToJvm(arg);
}
result += ")";
// System.out.println("arglistToJvm: " + arglist + " => " + result);
return result;
}
private static HashMap<String,String> primitiveClassesFromJvm = new HashMap<String,String>(8);
static {
primitiveClassesFromJvm.put("Z", "boolean");
primitiveClassesFromJvm.put("B", "byte");
primitiveClassesFromJvm.put("C", "char");
primitiveClassesFromJvm.put("D", "double");
primitiveClassesFromJvm.put("F", "float");
primitiveClassesFromJvm.put("I", "int");
primitiveClassesFromJvm.put("J", "long");
primitiveClassesFromJvm.put("S", "short");
}
// does not convert "V" to "void". Should it?
/**
* Convert a classname from JVML format to Java format.
* For example, convert "[Ljava/lang/Object;" to "java.lang.Object[]".
**/
public static String classnameFromJvm(String classname) {
int dims = 0;
while (classname.startsWith("[")) {
dims++;
classname = classname.substring(1);
}
String result;
if (classname.startsWith("L") && classname.endsWith(";")) {
result = classname.substring(1, classname.length() - 1);
} else {
result = primitiveClassesFromJvm.get(classname);
if (result == null) {
throw new Error("Malformed base class: " + classname);
}
}
for (int i=0; i<dims; i++) {
result += "[]";
}
return result.replace('/', '.');
}
/**
* Convert an argument list from JVML format to Java format.
* For example, convert "([Ljava/lang/Integer;I[[Ljava/lang/Integer;)"
* to "(java.lang.Integer[], int, java.lang.Integer[][])".
**/
public static String arglistFromJvm(String arglist) {
if (! (arglist.startsWith("(") && arglist.endsWith(")"))) {
throw new Error("Malformed arglist: " + arglist);
}
String result = "(";
int pos = 1;
while (pos < arglist.length()-1) {
if (pos > 1)
result += ", ";
int nonarray_pos = pos;
while (arglist.charAt(nonarray_pos) == '[') {
nonarray_pos++;
}
char c = arglist.charAt(nonarray_pos);
if (c == 'L') {
int semi_pos = arglist.indexOf(";", nonarray_pos);
result += classnameFromJvm(arglist.substring(pos, semi_pos+1));
pos = semi_pos + 1;
} else {
String maybe = classnameFromJvm(arglist.substring(pos, nonarray_pos+1));
if (maybe == null) {
// return null;
throw new Error("Malformed arglist: " + arglist);
}
result += maybe;
pos = nonarray_pos+1;
}
}
return result + ")";
}
///////////////////////////////////////////////////////////////////////////
/// ClassLoader
///
/**
* This static nested class has no purpose but to define loadClassFromFile.
* ClassLoader.defineClass is protected, so I subclass ClassLoader in
* order to call defineClass.
**/
private static class PromiscuousLoader extends ClassLoader {
/** Load a class from a .class file, and return it. */
public Class<?> loadClassFromFile(String className, String pathname) throws FileNotFoundException, IOException {
FileInputStream fi = new FileInputStream(pathname);
int numbytes = fi.available();
byte[] classBytes = new byte[numbytes];
fi.read(classBytes);
fi.close();
Class<?> return_class = defineClass(className, classBytes, 0, numbytes);
resolveClass(return_class);
return return_class;
}
}
private static PromiscuousLoader thePromiscuousLoader = new PromiscuousLoader();
/**
* @param pathname the pathname of a .class file
* @return a Java Object corresponding to the Class defined in the .class file
**/
public static Class<?> loadClassFromFile(String className, String pathname) throws FileNotFoundException, IOException {
return thePromiscuousLoader.loadClassFromFile(className, pathname);
}
///////////////////////////////////////////////////////////////////////////
/// Classpath
///
// Perhaps abstract out the simpler addToPath from this
/** Add the directory to the system classpath. */
public static void addToClasspath(String dir) {
// If the dir isn't on CLASSPATH, add it.
String pathSep = System.getProperty("path.separator");
// what is the point of the "replace()" call?
String cp = System.getProperty("java.class.path",".").replace('\\', '/');
StringTokenizer tokenizer = new StringTokenizer(cp, pathSep, false);
boolean found = false;
while (tokenizer.hasMoreTokens() && !found) {
found = tokenizer.nextToken().equals(dir);
}
if (!found) {
System.setProperty("java.class.path", dir + pathSep + cp);
}
}
///////////////////////////////////////////////////////////////////////////
/// File
///
/** Count the number of lines in the specified file **/
public static long count_lines(String filename) throws IOException {
LineNumberReader reader = UtilMDE.lineNumberFileReader(filename);
long count = 0;
while (reader.readLine() != null)
count++;
return count;
}
/** Tries to infer the line separator used in a file. **/
public static String inferLineSeparator(String filename) throws IOException {
return inferLineSeparator(new File(filename));
}
/** Tries to infer the line separator used in a file. **/
public static String inferLineSeparator(File filename) throws IOException {
BufferedReader r = UtilMDE.bufferedFileReader(filename);
int unix = 0;
int dos = 0;
int mac = 0;
while (true) {
String s = r.readLine();
if (s == null) {
break;
}
if (s.endsWith("\r\n")) {
dos++;
} else if (s.endsWith("\r")) {
mac++;
} else if (s.endsWith("\n")) {
unix++;
} else {
// This can happen only if the last line is not terminated.
}
}
if ((dos > mac && dos > unix)
|| (lineSep.equals("\r\n") && dos >= unix && dos >= mac)) {
return "\r\n";
}
if ((mac > dos && mac > unix)
|| (lineSep.equals("\r") && mac >= dos && mac >= unix)) {
return "\n";
}
if ((unix > dos && unix > mac)
|| (lineSep.equals("\n") && unix >= dos && unix >= mac)) {
return "\n";
}
// The two non-preferred line endings are tied and have more votes than
// the preferred line ending. Give up.
return lineSep;
}
/**
* Returns true iff files have the same contents.
*/
public static boolean equalFiles(String file1, String file2) {
return equalFiles(file1, file2, false);
}
/**
* Returns true iff files have the same contents.
* @param trimLines if true, call String.trim on each line before comparing
*/
public static boolean equalFiles(String file1, String file2, boolean trimLines) {
try {
LineNumberReader reader1 = UtilMDE.lineNumberFileReader(file1);
LineNumberReader reader2 = UtilMDE.lineNumberFileReader(file2);
String line1 = reader1.readLine();
String line2 = reader2.readLine();
while (line1 != null && line2 != null) {
if (trimLines) {
line1 = line1.trim();
line2 = line2.trim();
}
if (! (line1.equals(line2))) {
return false;
}
line1 = reader1.readLine();
line2 = reader2.readLine();
}
if (line1 == null && line2 == null) {
return true;
}
return false;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Returns true
* if the file exists and is writable, or
* if the file can be created.
**/
public static boolean canCreateAndWrite(File file) {
if (file.exists()) {
return file.canWrite();
} else {
File directory = file.getParentFile();
if (directory == null) {
directory = new File(".");
}
// Does this test need "directory.canRead()" also?
return directory.canWrite();
}
/// Old implementation; is this equivalent to the new one, above??
// try {
// if (file.exists()) {
// return file.canWrite();
// } else {
// file.createNewFile();
// file.delete();
// return true;
// }
// } catch (IOException e) {
// return false;
// }
}
///
/// Directories
///
/**
* Creates an empty directory in the default temporary-file directory,
* using the given prefix and suffix to generate its name. For example
* calling createTempDir("myPrefix", "mySuffix") will create the following
* directory: temporaryFileDirectory/myUserName/myPrefix_someString_suffix.
* someString is internally generated to ensure no temporary files of the
* same name are generated.
* @param prefix The prefix string to be used in generating the file's
* name; must be at least three characters long
* @param suffix The suffix string to be used in generating the file's
* name; may be null, in which case the suffix ".tmp" will be used Returns:
* An abstract pathname denoting a newly-created empty file
* @throws IllegalArgumentException If the prefix argument contains fewer
* than three characters
* @throws IOException If a file could not be created
* @throws SecurityException If a security manager exists and its
* SecurityManager.checkWrite(java.lang.String) method does not allow a
* file to be created
* @see java.io.File#createTempFile(String, String, File)
**/
public static File createTempDir(String prefix, String suffix)
throws IOException {
String fs = File.separator;
String path = System.getProperty("java.io.tmpdir") + fs +
System.getProperty("user.name") + fs;
File pathFile = new File(path);
pathFile.mkdirs();
File tmpfile = File.createTempFile(prefix + "_", "_", pathFile);
String tmpDirPath = tmpfile.getPath() + suffix;
tmpfile.delete();
File tmpDir = new File(tmpDirPath);
tmpDir.mkdirs();
return tmpDir;
}
/**
* Deletes the directory at dirName and all its files.
* Fails if dirName has any subdirectories.
*/
public static void deleteDir(String dirName) {
deleteDir(new File(dirName));
}
/**
* Deletes the directory at dirName and all its files.
* Fails if dirName has any subdirectories.
*/
public static void deleteDir(File dir) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (int i = 0; i < files.length; i++) {
files[i].delete();
}
dir.delete();
}
///
/// File names (aka filenames)
///
// Someone must have already written this. Right?
/**
* A FilenameFilter that accepts files whose name matches the given wildcard.
* The wildcard may contain exactly one "*".
*/
public static final class WildcardFilter implements FilenameFilter {
String prefix;
String suffix;
public WildcardFilter(String filename) {
int astloc = filename.indexOf("*");
if (astloc == -1)
throw new Error("No asterisk in wildcard argument: " + filename);
prefix = filename.substring(0, astloc);
suffix = filename.substring(astloc+1);
if (filename.indexOf("*") != -1)
throw new Error("Multiple asterisks in wildcard argument: " + filename);
}
public boolean accept(File dir, String name) {
return name.startsWith(prefix) && name.endsWith(suffix);
}
}
@SuppressWarnings("nullness") // user.home property always exists
static final /*@NonNull*/ String userHome = System.getProperty ("user.home");
/**
* Does tilde expansion on a file name (to the user's home directory).
*/
public static File expandFilename (File name) {
String path = name.getPath();
String newname = expandFilename (path);
@SuppressWarnings("interning")
boolean changed = (newname != path);
if (changed)
return new File (newname);
else
return name;
}
/**
* Does tilde expansion on a file name (to the user's home directory).
*/
public static String expandFilename (String name) {
if (name.contains ("~"))
return (name.replace ("~", userHome));
else
return name;
}
/**
* Returns a string version of the name that can be used in Java source.
* On Windows, the file will return a backslash separated string. Since
* backslash is an escape character, it must be quoted itself inside
* the string.
*
* The current implementation presumes that backslashes don't appear
* in filenames except as windows path separators. That seems like a
* reasonable assumption.
*/
public static String java_source (File name) {
return name.getPath().replace ("\\", "\\\\");
}
///
/// Reading and writing
///
/**
* Writes an Object to a File.
**/
public static void writeObject(Object o, File file) throws IOException {
// 8192 is the buffer size in BufferedReader
OutputStream bytes =
new BufferedOutputStream(new FileOutputStream(file), 8192);
if (file.getName().endsWith(".gz")) {
bytes = new GZIPOutputStream(bytes);
}
ObjectOutputStream objs = new ObjectOutputStream(bytes);
objs.writeObject(o);
objs.close();
}
/**
* Reads an Object from a File.
**/
public static Object readObject(File file) throws
IOException, ClassNotFoundException {
// 8192 is the buffer size in BufferedReader
InputStream istream =
new BufferedInputStream(new FileInputStream(file), 8192);
if (file.getName().endsWith(".gz")) {
istream = new GZIPInputStream(istream);
}
ObjectInputStream objs = new ObjectInputStream(istream);
return objs.readObject();
}
/**
* Reads the entire contents of the reader and returns it as a string.
* Any IOException encountered will be turned into an Error.
*/
public static String readerContents(Reader r) {
try {
StringBuilder contents = new StringBuilder();
int ch;
while ((ch = r.read()) != -1) {
contents.append((char) ch);
}
r.close();
return contents.toString();
} catch (Exception e) {
throw new Error ("Unexpected error in readerContents(" + r + ")", e);
}
}
// an alternate name would be "fileContents".
/**
* Reads the entire contents of the file and returns it as a string.
* Any IOException encountered will be turned into an Error.
*/
public static String readFile (File file) {
try {
BufferedReader reader = UtilMDE.bufferedFileReader (file);
StringBuilder contents = new StringBuilder();
String line = reader.readLine();
while (line != null) {
contents.append (line);
// Note that this converts line terminators!
contents.append (lineSep);
line = reader.readLine();
}
reader.close();
return contents.toString();
} catch (Exception e) {
throw new Error ("Unexpected error in readFile(" + file + ")", e);
}
}
/**
* Creates a file with the given name and writes the specified string
* to it. If the file currently exists (and is writable) it is overwritten
* Any IOException encountered will be turned into an Error.
*/
public static void writeFile (File file, String contents) {
try {
FileWriter writer = new FileWriter (file);
writer.write (contents, 0, contents.length());
writer.close();
} catch (Exception e) {
throw new Error ("Unexpected error in writeFile(" + file + ")", e);
}
}
///////////////////////////////////////////////////////////////////////////
/// Hashing
///
// In hashing, there are two separate issues. First, one must convert
// the input datum into an integer. Then, one must transform the
// resulting integer in a pseudorandom way so as to result in a number
// that is far separated from other values that may have been near it to
// begin with. Often these two steps are combined, particularly if
// one wishes to avoid creating too large an integer (losing information
// off the top bits).
// http://burtleburtle.net/bob/hash/hashfaq.html says (of combined methods):
// * for (h=0, i=0; i<len; ++i) { h += key[i]; h += (h<<10); h ^= (h>>6); }
// h += (h<<3); h ^= (h>>11); h += (h<<15);
// is good.
// * for (h=0, i=0; i<len; ++i) h = tab[(h^key[i])&0xff]; may be good.
// * for (h=0, i=0; i<len; ++i) h = (h>>8)^tab[(key[i]+h)&0xff]; may be good.
// In this part of the file, perhaps I will eventually write good hash
// functions. For now, write cheesy ones that primarily deal with the
// first issue, transforming a data structure into a single number. This
// is also known as fingerprinting.
/**
* Return a hash of the arguments.
* Note that this differs from the result of {@link Double#hashCode()}.
*/
public static final int hash(double x) {
return hash(Double.doubleToLongBits(x));
}
/** Return a hash of the arguments. */
public static final int hash(double a, double b) {
double result = 17;
result = result * 37 + a;
result = result * 37 + b;
return hash(result);
}
/** Return a hash of the arguments. */
public static final int hash(double a, double b, double c) {
double result = 17;
result = result * 37 + a;
result = result * 37 + b;
result = result * 37 + c;
return hash(result);
}
/** Return a hash of the arguments. */
public static final int hash(double /*@Nullable*/ [] a) {
double result = 17;
if (a != null) {
result = result * 37 + a.length;
for (int i = 0; i < a.length; i++) {
result = result * 37 + a[i];
}
}
return hash(result);
}
/** Return a hash of the arguments. */
public static final int hash(double /*@Nullable*/ [] a, double /*@Nullable*/ [] b) {
return hash(hash(a), hash(b));
}
/// Don't define hash with int args; use the long versions instead.
/**
* Return a hash of the arguments.
* Note that this differs from the result of {@link Long#hashCode()}.
* But it doesn't map -1 and 0 to the same value.
*/
public static final int hash(long l) {
// If possible, use the value itself.
if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
return (int) l;
}
int result = 17;
int hibits = (int) (l >> 32);
int lobits = (int) l;
result = result * 37 + hibits;
result = result * 37 + lobits;
return result;
}
/** Return a hash of the arguments. */
public static final int hash(long a, long b) {
long result = 17;
result = result * 37 + a;
result = result * 37 + b;
return hash(result);
}
/** Return a hash of the arguments. */
public static final int hash(long a, long b, long c) {
long result = 17;
result = result * 37 + a;
result = result * 37 + b;
result = result * 37 + c;
return hash(result);
}
/** Return a hash of the arguments. */
public static final int hash(long /*@Nullable*/ [] a) {
long result = 17;
if (a != null) {
result = result * 37 + a.length;
for (int i = 0; i < a.length; i++) {
result = result * 37 + a[i];
}
}
return hash(result);
}
/** Return a hash of the arguments. */
public static final int hash(long /*@Nullable*/ [] a, long /*@Nullable*/ [] b) {
return hash(hash(a), hash(b));
}
/** Return a hash of the arguments. */
public static final int hash(/*@Nullable*/ String a) {
return (a == null) ? 0 : a.hashCode();
}
/** Return a hash of the arguments. */
public static final int hash(/*@Nullable*/ String a, /*@Nullable*/ String b) {
long result = 17;
result = result * 37 + hash(a);
result = result * 37 + hash(b);
return hash(result);
}
/** Return a hash of the arguments. */
public static final int hash(/*@Nullable*/ String a, /*@Nullable*/ String b, /*@Nullable*/ String c) {
long result = 17;
result = result * 37 + hash(a);
result = result * 37 + hash(b);
result = result * 37 + hash(c);
return hash(result);
}
/** Return a hash of the arguments. */
public static final int hash(/*@Nullable*/ String /*@Nullable*/ [] a) {
long result = 17;
if (a != null) {
result = result * 37 + a.length;
for (int i = 0; i < a.length; i++) {
result = result * 37 + hash(a[i]);
}
}
return hash(result);
}
///////////////////////////////////////////////////////////////////////////
/// Iterator
///
// Making these classes into functions didn't work because I couldn't get
// their arguments into a scope that Java was happy with.
/** Converts an Enumeration into an Iterator. */
public static final class EnumerationIterator<T> implements Iterator<T> {
Enumeration<T> e;
public EnumerationIterator(Enumeration<T> e) { this.e = e; }
public boolean hasNext() { return e.hasMoreElements(); }
public T next() { return e.nextElement(); }
public void remove() { throw new UnsupportedOperationException(); }
}
/** Converts an Iterator into an Enumeration. */
public static final class IteratorEnumeration<T> implements Enumeration<T> {
Iterator<T> itor;
public IteratorEnumeration(Iterator<T> itor) { this.itor = itor; }
public boolean hasMoreElements() { return itor.hasNext(); }
public T nextElement() { return itor.next(); }
}
// This must already be implemented someplace else. Right??
/**
* An Iterator that returns first the elements returned by its first
* argument, then the elements returned by its second argument.
* Like MergedIterator, but specialized for the case of two arguments.
**/
public static final class MergedIterator2<T> implements Iterator<T> {
Iterator<T> itor1, itor2;
public MergedIterator2(Iterator<T> itor1_, Iterator<T> itor2_) {
this.itor1 = itor1_; this.itor2 = itor2_;
}
public boolean hasNext() {
return (itor1.hasNext() || itor2.hasNext());
}
public T next() {
if (itor1.hasNext())
return itor1.next();
else if (itor2.hasNext())
return itor2.next();
else
throw new NoSuchElementException();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
// This must already be implemented someplace else. Right??
/**
* An Iterator that returns the elements in each of its argument
* Iterators, in turn. The argument is an Iterator of Iterators.
* Like MergedIterator2, but generalized to arbitrary number of iterators.
**/
public static final class MergedIterator<T> implements Iterator<T> {
Iterator<Iterator<T>> itorOfItors;
public MergedIterator(Iterator<Iterator<T>> itorOfItors) { this.itorOfItors = itorOfItors; }
// an empty iterator to prime the pump
Iterator<T> current = new ArrayList<T>().iterator();
public boolean hasNext() {
while ((!current.hasNext()) && (itorOfItors.hasNext())) {
current = itorOfItors.next();
}
return current.hasNext();
}
public T next() {
hasNext(); // for side effect
return current.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
/** An iterator that only returns elements that match the given Filter. */
public static final class FilteredIterator<T> implements Iterator<T> {
Iterator<T> itor;
Filter<T> filter;
public FilteredIterator(Iterator<T> itor, Filter<T> filter) {
this.itor = itor; this.filter = filter;
}
@SuppressWarnings("unchecked")
T invalid_t = (T) new Object();
T current = invalid_t;
boolean current_valid = false;
public boolean hasNext() {
while ((!current_valid) && itor.hasNext()) {
current = itor.next();
current_valid = filter.accept(current);
}
return current_valid;
}
public T next() {
if (hasNext()) {
current_valid = false;
@SuppressWarnings("interning")
boolean ok = (current != invalid_t);
assert ok;
return current;
} else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Returns an iterator just like its argument, except that the first and
* last elements are removed. They can be accessed via the getFirst and
* getLast methods.
**/
public static final class RemoveFirstAndLastIterator<T> implements Iterator<T> {
Iterator<T> itor;
// I don't think this works, because the iterator might itself return null
// /*@Nullable*/ T nothing = (/*@Nullable*/ T) null;
@SuppressWarnings("unchecked")
T nothing = (T) new Object();
T first = nothing;
T current = nothing;
public RemoveFirstAndLastIterator(Iterator<T> itor) {
this.itor = itor;
if (itor.hasNext()) {
first = itor.next();
}
if (itor.hasNext()) {
current = itor.next();
}
}
public boolean hasNext() {
return itor.hasNext();
}
public T next() {
if (! itor.hasNext()) {
throw new NoSuchElementException();
}
T tmp = current;
current = itor.next();
return tmp;
}
public T getFirst() {
@SuppressWarnings("interning")
boolean invalid = (first == nothing);
if (invalid) {
throw new NoSuchElementException();
}
return first;
}
// Throws an error unless the RemoveFirstAndLastIterator has already
// been iterated all the way to its end (so the delegate is pointing to
// the last element). Also, this is buggy when the delegate is empty.
public T getLast() {
if (itor.hasNext()) {
throw new Error();
}
return current;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Return an List containing num_elts randomly chosen
* elements from the iterator, or all the elements of the iterator if
* there are fewer. It examines every element of the iterator, but does
* not keep them all in memory.
**/
public static <T> List<T> randomElements(Iterator<T> itor, int num_elts) {
return randomElements(itor, num_elts, r);
}
private static Random r = new Random();
/**
* Return an List containing num_elts randomly chosen
* elements from the iterator, or all the elements of the iterator if
* there are fewer. It examines every element of the iterator, but does
* not keep them all in memory.
**/
public static <T> List<T> randomElements(Iterator<T> itor, int num_elts, Random random) {
// The elements are chosen with the following probabilities,
// where n == num_elts:
// n n/2 n/3 n/4 n/5 ...
RandomSelector<T> rs = new RandomSelector<T> (num_elts, random);
while (itor.hasNext()) {
rs.accept (itor.next());
}
return rs.getValues();
/*
ArrayList<T> result = new ArrayList<T>(num_elts);
int i=1;
for (int n=0; n<num_elts && itor.hasNext(); n++, i++) {
result.add(itor.next());
}
for (; itor.hasNext(); i++) {
T o = itor.next();
// test random < num_elts/i
if (random.nextDouble() * i < num_elts) {
// This element will replace one of the existing elements.
result.set(random.nextInt(num_elts), o);
}
}
return result;
*/
}
///////////////////////////////////////////////////////////////////////////
/// Map
///
// In Python, inlining this gave a 10x speed improvement.
// Will the same be true for Java?
/**
* Increment the Integer which is indexed by key in the Map.
* If the key isn't in the Map, it is added.
* Throws an error if the key is in the Map but maps to a non-Integer.
**/
public static <T> /*@Nullable*/ Integer incrementMap(Map<T,Integer> m, T key, int count) {
Integer old = m.get(key);
int new_total;
if (old == null) {
new_total = count;
} else {
new_total = old.intValue() + count;
}
return m.put(key, new Integer(new_total));
}
/** Returns a multi-line string representation of a map. */
public static <K,V> String mapToString(Map<K,V> m) {
StringBuilder sb = new StringBuilder();
mapToString(sb, m, "");
return sb.toString();
}
/**
* Write a multi-line representation of the map into the given Appendable
* (e.g., a StringBuilder).
*/
public static <K,V> void mapToString(Appendable sb, Map<K,V> m, String linePrefix) {
try {
for (Map.Entry<K, V> entry : m.entrySet()) {
sb.append(linePrefix);
sb.append(entry.getKey().toString());
sb.append(" => ");
sb.append(entry.getValue().toString());
sb.append(lineSep);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/** Returns a sorted version of m.keySet(). */
public static <K extends Comparable<? super K>,V> Collection</*@KeyFor("#0")*/ K> sortedKeySet(Map<K,V> m) {
ArrayList<K> theKeys = new ArrayList<K> (m.keySet());
Collections.sort (theKeys);
return theKeys;
}
/** Returns a sorted version of m.keySet(). */
public static <K,V> Collection</*@KeyFor("#0")*/ K> sortedKeySet(Map<K,V> m, Comparator<K> comparator) {
ArrayList<K> theKeys = new ArrayList<K> (m.keySet());
Collections.sort (theKeys, comparator);
return theKeys;
}
///////////////////////////////////////////////////////////////////////////
/// Method
///
/** Maps from a string of arg names to an array of Class objects. */
static HashMap<String,Class<?>[]> args_seen = new HashMap<String,Class<?>[]>();
/** Given a method name, return the method. */
public static Method methodForName(String method)
throws ClassNotFoundException, NoSuchMethodException, SecurityException {
int oparenpos = method.indexOf('(');
int dotpos = method.lastIndexOf('.', oparenpos);
int cparenpos = method.indexOf(')', oparenpos);
if ((dotpos == -1) || (oparenpos == -1) || (cparenpos == -1)) {
throw new Error("malformed method name should contain a period, open paren, and close paren: " + method + " <<" + dotpos + "," + oparenpos + "," + cparenpos + ">>");
}
for (int i=cparenpos+1; i<method.length(); i++) {
if (! Character.isWhitespace(method.charAt(i))) {
throw new Error("malformed method name should contain only whitespace following close paren");
}
}
String classname = method.substring(0,dotpos);
String methodname = method.substring(dotpos+1, oparenpos);
String all_argnames = method.substring(oparenpos+1, cparenpos).trim();
Class<?>[] argclasses = args_seen.get(all_argnames);
if (argclasses == null) {
String[] argnames;
if (all_argnames.equals("")) {
argnames = new String[0];
} else {
argnames = split(all_argnames, ',');
}
argclasses = new Class<?>[argnames.length];
for (int i=0; i<argnames.length; i++) {
String argname = argnames[i].trim();
int numbrackets = 0;
while (argname.endsWith("[]")) {
argname = argname.substring(0, argname.length()-2);
numbrackets++;
}
if (numbrackets > 0) {
argname = "L" + argname + ";";
while (numbrackets>0) {
argname = "[" + argname;
numbrackets--;
}
}
// System.out.println("argname " + i + " = " + argname + " for method " + method);
argclasses[i] = classForName(argname);
}
args_seen.put(all_argnames, argclasses);
}
return methodForName(classname, methodname, argclasses);
}
/** Given a class name and a method name in that class, return the method. */
public static Method methodForName(String classname, String methodname, Class<?>[] params)
throws ClassNotFoundException, NoSuchMethodException, SecurityException {
Class<?> c = Class.forName(classname);
Method m = c.getDeclaredMethod(methodname, params);
return m;
}
///////////////////////////////////////////////////////////////////////////
/// ProcessBuilder
///
/**
* Execute the given command, and return all its output as a string.
*/
public static String backticks(String... command) {
return backticks(Arrays.asList(command));
}
/**
* Execute the given command, and return all its output as a string.
*/
public static String backticks(List<String> command) {
ProcessBuilder pb = new ProcessBuilder(command);
pb.redirectErrorStream(true);
// TimeLimitProcess p = new TimeLimitProcess(pb.start(), TIMEOUT_SEC * 1000);
try {
Process p = pb.start();
String output = UtilMDE.streamString(p.getInputStream());
return output;
} catch (IOException e) {
return "IOException: " + e.getMessage();
}
}
///////////////////////////////////////////////////////////////////////////
/// Properties
///
/**
* Determines whether a property has value "true", "yes", or "1".
* @see Properties#getProperty
**/
public static boolean propertyIsTrue(Properties p, String key) {
String pvalue = p.getProperty(key);
if (pvalue == null) {
return false;
}
pvalue = pvalue.toLowerCase();
return (pvalue.equals("true") || pvalue.equals("yes") || pvalue.equals("1"));
}
/**
* Set the property to its previous value concatenated to the given value.
* Returns the previous value.
* @see Properties#getProperty
* @see Properties#setProperty
**/
public static /*@Nullable*/ String appendProperty(Properties p, String key, String value) {
return (String)p.setProperty(key, p.getProperty(key, "") + value);
}
/**
* Set the property only if it was not previously set.
* @deprecated use setDefaultMaybe
* @see Properties#getProperty
* @see Properties#setProperty
**/
@Deprecated
public static /*@Nullable*/ String setDefault(Properties p, String key, String value) {
String currentValue = p.getProperty(key);
if (currentValue == null) {
p.setProperty(key, value);
}
return currentValue;
}
/**
* Set the property only if it was not previously set.
* @see Properties#getProperty
* @see Properties#setProperty
**/
public static /*@Nullable*/ String setDefaultMaybe(Properties p, String key, String value) {
String currentValue = p.getProperty(key);
if (currentValue == null) {
p.setProperty(key, value);
}
return currentValue;
}
///////////////////////////////////////////////////////////////////////////
/// Regexp (regular expression)
///
// Stolen from JDK 1.5. Intended for use (and viewing) only by JRL
// licensees (if you are not one, proceed no further). To be removed
// as soon as we migrate to Java 1.5.
/**
* Returns a literal pattern <code>String</code> for the specified
* <code>String</code>.
*
* <p>This method produces a <code>String</code> that can be used to
* create a <code>Pattern</code> that would match the string
* <code>s</code> as if it were a literal pattern.</p> Metacharacters
* or escape sequences in the input sequence will be given no special
* meaning.
*
* @param s The string to be literalized
* @return A literal string replacement
* @since 1.5
*/
public static String patternQuote(String s) {
int slashEIndex = s.indexOf("\\E");
if (slashEIndex == -1)
return "\\Q" + s + "\\E";
StringBuffer sb = new StringBuffer(s.length() * 2);
sb.append("\\Q");
slashEIndex = 0;
int current = 0;
while ((slashEIndex = s.indexOf("\\E", current)) != -1) {
sb.append(s.substring(current, slashEIndex));
current = slashEIndex + 2;
sb.append("\\E\\\\E\\Q");
}
sb.append(s.substring(current, s.length()));
sb.append("\\E");
return sb.toString();
}
///////////////////////////////////////////////////////////////////////////
/// Reflection
///
/**
* Sets the given field, which may be final and/or private.
* Leaves the field accessible.
* Intended for use in readObject and nowhere else!
*/
public static void setFinalField(Object o, String fieldName, /*@Nullable*/ Object value)
throws NoSuchFieldException {
Class<?> c = o.getClass();
while (c != Object.class) { // Class is interned
// System.out.printf ("Setting field %s in %s%n", fieldName, c);
try {
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(o, value);
return;
} catch (NoSuchFieldException e) {
if (c.getSuperclass() == Object.class) // Class is interned
throw e;
} catch (IllegalAccessException e) {
throw new Error("This can't happen: " + e);
}
c = c.getSuperclass();
assert c != null : "@SuppressWarnings(nullness): c was not Object, so is not null now";
}
throw new NoSuchFieldException (fieldName);
}
/**
* Reads the given field, which may be private.
* Leaves the field accessible.
* Use with care!
*/
public static /*@Nullable*/ Object getPrivateField(Object o, String fieldName)
throws NoSuchFieldException {
Class<?> c = o.getClass();
while (c != Object.class) { // Class is interned
// System.out.printf ("Setting field %s in %s%n", fieldName, c);
try {
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(o);
} catch (NoSuchFieldException e) {
if (c.getSuperclass() == Object.class) // Class is interned
throw e;
} catch (IllegalAccessException e) {
throw new Error("This can't happen: " + e);
}
c = c.getSuperclass();
assert c != null : "@SuppressWarnings(nullness): c was not Object, so is not null now";
}
throw new NoSuchFieldException (fieldName);
}
///////////////////////////////////////////////////////////////////////////
/// Set
///
/**
* Returns the object in this set that is equal to key.
* The Set abstraction doesn't provide this; it only provides "contains".
* Returns null if the argument is null, or if it isn't in the set.
**/
public static /*@Nullable*/ Object getFromSet(Set<?> set, Object key) {
if (key == null) {
return null;
}
for (Object elt : set) {
if (key.equals(elt)) {
return elt;
}
}
return null;
}
///////////////////////////////////////////////////////////////////////////
/// Stream
///
/** Copy the contents of the input stream to the output stream. */
public static void streamCopy(java.io.InputStream from, java.io.OutputStream to) {
byte[] buffer = new byte[1024];
int bytes;
try {
while (true) {
bytes = from.read(buffer);
if (bytes == -1) {
return;
}
to.write(buffer, 0, bytes);
}
} catch (java.io.IOException e) {
e.printStackTrace();
throw new Error(e);
}
}
/** Return a String containing all the characters from the input stream. **/
public static String streamString(java.io.InputStream is) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
streamCopy(is, baos);
return baos.toString();
}
///////////////////////////////////////////////////////////////////////////
/// String
///
/**
* Return a new string which is the text of target with all instances of
* oldStr replaced by newStr.
**/
public static String replaceString(String target, String oldStr, String newStr) {
if (oldStr.equals("")) throw new IllegalArgumentException();
StringBuffer result = new StringBuffer();
int lastend = 0;
int pos;
while ((pos = target.indexOf(oldStr, lastend)) != -1) {
result.append(target.substring(lastend, pos));
result.append(newStr);
lastend = pos + oldStr.length();
}
result.append(target.substring(lastend));
return result.toString();
}
/**
* Return an array of Strings representing the characters between
* successive instances of the delimiter character.
* Always returns an array of length at least 1 (it might contain only the
* empty string).
* @see #split(String s, String delim)
**/
public static String[] split(String s, char delim) {
Vector<String> result = new Vector<String>();
for (int delimpos = s.indexOf(delim); delimpos != -1; delimpos = s.indexOf(delim)) {
result.add(s.substring(0, delimpos));
s = s.substring(delimpos+1);
}
result.add(s);
String[] result_array = new String[result.size()];
result.copyInto(result_array);
return result_array;
}
/**
* Return an array of Strings representing the characters between
* successive instances of the delimiter String.
* Always returns an array of length at least 1 (it might contain only the
* empty string).
* @see #split(String s, char delim)
**/
public static String[] split(String s, String delim) {
int delimlen = delim.length();
if (delimlen == 0) {
throw new Error("Second argument to split was empty.");
}
Vector<String> result = new Vector<String>();
for (int delimpos = s.indexOf(delim); delimpos != -1; delimpos = s.indexOf(delim)) {
result.add(s.substring(0, delimpos));
s = s.substring(delimpos+delimlen);
}
result.add(s);
String[] result_array = new String[result.size()];
result.copyInto(result_array);
return result_array;
}
/**
* Return an array of Strings, one for each line in the argument.
* Always returns an array of length at least 1 (it might contain only the
* empty string). All common line separators (cr, lf, cr-lf, or lf-cr)
* are supported. Note that a string that ends with a line separator
* will return an empty string as the last element of the array.
* @see #split(String s, char delim)
**/
public static String[] splitLines(String s) {
return s.split ("\r\n?|\n\r?", -1);
}
/**
* Concatenate the string representations of the objects, placing the
* delimiter between them.
* @see plume.ArraysMDE#toString(int[])
**/
public static String join(Object[] a, String delim) {
if (a.length == 0) return "";
if (a.length == 1) return String.valueOf(a[0]);
StringBuffer sb = new StringBuffer(String.valueOf(a[0]));
for (int i=1; i<a.length; i++)
sb.append(delim).append(a[i]);
return sb.toString();
}
/**
* Concatenate the string representations of the objects, placing the
* system-specific line separator between them.
* @see plume.ArraysMDE#toString(int[])
**/
public static String joinLines(Object... a) {
return join(a, lineSep);
}
/**
* Concatenate the string representations of the objects, placing the
* delimiter between them.
* @see java.util.AbstractCollection#toString()
**/
public static String join(List<?> v, String delim) {
if (v.size() == 0) return "";
if (v.size() == 1) return v.get(0).toString();
// This should perhaps use an iterator rather than get().
StringBuffer sb = new StringBuffer(v.get(0).toString());
for (int i=1; i<v.size(); i++)
sb.append(delim).append(v.get(i));
return sb.toString();
}
/**
* Concatenate the string representations of the objects, placing the
* system-specific line separator between them.
* @see java.util.AbstractCollection#toString()
**/
public static String joinLines(List<String> v, String delim) {
return join(v, lineSep);
}
/**
* Escape \, ", newline, and carriage-return characters in the
* target as \\, \", \n, and \r; return a new string if any
* modifications were necessary. The intent is that by surrounding
* the return value with double quote marks, the result will be a
* Java string literal denoting the original string.
**/
public static String escapeNonJava(String orig) {
StringBuffer sb = new StringBuffer();
// The previous escape character was seen right before this position.
int post_esc = 0;
int orig_len = orig.length();
for (int i=0; i<orig_len; i++) {
char c = orig.charAt(i);
switch (c) {
case '\"':
case '\\':
if (post_esc < i) {
sb.append(orig.substring(post_esc, i));
}
sb.append('\\');
post_esc = i;
break;
case '\n': // not lineSep
if (post_esc < i) {
sb.append(orig.substring(post_esc, i));
}
sb.append("\\n"); // not lineSep
post_esc = i+1;
break;
case '\r':
if (post_esc < i) {
sb.append(orig.substring(post_esc, i));
}
sb.append("\\r");
post_esc = i+1;
break;
default:
// Nothing to do: i gets incremented
}
}
if (sb.length() == 0)
return orig;
sb.append(orig.substring(post_esc));
return sb.toString();
}
// The overhead of this is too high to call in escapeNonJava(String), so
// it is inlined there.
/** Like {@link #escapeNonJava(String)}, but for a single character. */
public static String escapeNonJava(Character ch) {
char c = ch.charValue();
switch (c) {
case '\"':
return "\\\"";
case '\\':
return "\\\\";
case '\n': // not lineSep
return "\\n"; // not lineSep
case '\r':
return "\\r";
default:
return new String(new char[] { c });
}
}
/**
* Escape unprintable characters in the target, following the usual
* Java backslash conventions, so that the result is sure to be
* printable ASCII. Returns a new string.
**/
public static String escapeNonASCII(String orig) {
StringBuffer sb = new StringBuffer();
int orig_len = orig.length();
for (int i=0; i<orig_len; i++) {
char c = orig.charAt(i);
sb.append(escapeNonASCII(c));
}
return sb.toString();
}
/**
* Like escapeNonJava(), but quote more characters so that the
* result is sure to be printable ASCII. Not particularly optimized.
**/
private static String escapeNonASCII(char c) {
if (c == '"') {
return "\\\"";
} else if (c == '\\') {
return "\\\\";
} else if (c == '\n') { // not lineSep
return "\\n"; // not lineSep
} else if (c == '\r') {
return "\\r";
} else if (c == '\t') {
return "\\t";
} else if (c >= ' ' && c <= '~') {
return new String(new char[] { c });
} else if (c < 256) {
String octal = Integer.toOctalString(c);
while (octal.length() < 3)
octal = '0' + octal;
return "\\" + octal;
} else {
String hex = Integer.toHexString(c);
while (hex.length() < 4)
hex = "0" + hex;
return "\\u" + hex;
}
}
/**
* Replace "\\", "\"", "\n", and "\r" sequences by their
* one-character equivalents. All other backslashes are removed
* (for instance, octal/hex escape sequences are not turned into
* their respective characters). This is the inverse operation of
* escapeNonJava(). Previously known as unquote().
**/
public static String unescapeNonJava(String orig) {
StringBuffer sb = new StringBuffer();
// The previous escape character was seen just before this position.
int post_esc = 0;
int this_esc = orig.indexOf('\\');
while (this_esc != -1) {
if (this_esc == orig.length()-1) {
sb.append(orig.substring(post_esc, this_esc+1));
post_esc = this_esc+1;
break;
}
switch (orig.charAt(this_esc+1)) {
case 'n':
sb.append(orig.substring(post_esc, this_esc));
sb.append('\n'); // not lineSep
post_esc = this_esc+2;
break;
case 'r':
sb.append(orig.substring(post_esc, this_esc));
sb.append('\r');
post_esc = this_esc+2;
break;
case '\\':
// This is not in the default case because the search would find
// the quoted backslash. Here we incluce the first backslash in
// the output, but not the first.
sb.append(orig.substring(post_esc, this_esc+1));
post_esc = this_esc+2;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
sb.append(orig.substring(post_esc, this_esc));
char octal_char = 0;
int ii = this_esc+1;
while (ii < orig.length()) {
char ch = orig.charAt(ii++);
if ((ch < '0') || (ch > '8'))
break;
octal_char = (char) ((octal_char * 8)+ Character.digit (ch, 8));
}
sb.append (octal_char);
post_esc = ii-1;
break;
default:
// In the default case, retain the character following the backslash,
// but discard the backslash itself. "\*" is just a one-character string.
sb.append(orig.substring(post_esc, this_esc));
post_esc = this_esc+1;
break;
}
this_esc = orig.indexOf('\\', post_esc);
}
if (post_esc == 0)
return orig;
sb.append(orig.substring(post_esc));
return sb.toString();
}
// Use the built-in String.trim()!
// /** Return the string with all leading and trailing whitespace stripped. */
// public static String trimWhitespace(String s) {
// int len = s.length();
// if (len == 0)
// return s;
// int first_non_ws = 0;
// int last_non_ws = len-1;
// while ((first_non_ws < len) && Character.isWhitespace(s.charAt(first_non_ws)))
// first_non_ws++;
// if (first_non_ws == len)
// return "";
// while (Character.isWhitespace(s.charAt(last_non_ws)))
// last_non_ws--;
// if ((first_non_ws == 0) && (last_non_ws == len))
// return s;
// else
// return s.substring(first_non_ws, last_non_ws+1);
// }
// // // Testing:
// // assert(UtilMDE.trimWhitespace("foo").equals("foo"));
// // assert(UtilMDE.trimWhitespace(" foo").equals("foo"));
// // assert(UtilMDE.trimWhitespace(" foo").equals("foo"));
// // assert(UtilMDE.trimWhitespace("foo ").equals("foo"));
// // assert(UtilMDE.trimWhitespace("foo ").equals("foo"));
// // assert(UtilMDE.trimWhitespace(" foo ").equals("foo"));
// // assert(UtilMDE.trimWhitespace(" foo bar ").equals("foo bar"));
// // assert(UtilMDE.trimWhitespace("").equals(""));
// // assert(UtilMDE.trimWhitespace(" ").equals(""));
/** Remove all whitespace before or after instances of delimiter. **/
public static String removeWhitespaceAround(String arg, String delimiter) {
arg = removeWhitespaceBefore(arg, delimiter);
arg = removeWhitespaceAfter(arg, delimiter);
return arg;
}
/** Remove all whitespace after instances of delimiter. **/
public static String removeWhitespaceAfter(String arg, String delimiter) {
// String orig = arg;
int delim_len = delimiter.length();
int delim_index = arg.indexOf(delimiter);
while (delim_index > -1) {
int non_ws_index = delim_index+delim_len;
while ((non_ws_index < arg.length())
&& (Character.isWhitespace(arg.charAt(non_ws_index)))) {
non_ws_index++;
}
// if (non_ws_index == arg.length()) {
// System.out.println("No nonspace character at end of: " + arg);
// } else {
// System.out.println("'" + arg.charAt(non_ws_index) + "' not a space character at " + non_ws_index + " in: " + arg);
// }
if (non_ws_index != delim_index+delim_len) {
arg = arg.substring(0, delim_index + delim_len) + arg.substring(non_ws_index);
}
delim_index = arg.indexOf(delimiter, delim_index+1);
}
return arg;
}
/** Remove all whitespace before instances of delimiter. **/
public static String removeWhitespaceBefore(String arg, String delimiter) {
// System.out.println("removeWhitespaceBefore(\"" + arg + "\", \"" + delimiter + "\")");
// String orig = arg;
int delim_len = delimiter.length();
int delim_index = arg.indexOf(delimiter);
while (delim_index > -1) {
int non_ws_index = delim_index-1;
while ((non_ws_index >= 0)
&& (Character.isWhitespace(arg.charAt(non_ws_index)))) {
non_ws_index--;
}
// if (non_ws_index == -1) {
// System.out.println("No nonspace character at front of: " + arg);
// } else {
// System.out.println("'" + arg.charAt(non_ws_index) + "' not a space character at " + non_ws_index + " in: " + arg);
// }
if (non_ws_index != delim_index-1) {
arg = arg.substring(0, non_ws_index + 1) + arg.substring(delim_index);
}
delim_index = arg.indexOf(delimiter, non_ws_index+2);
}
return arg;
}
/**
* Returns either "n <em>noun</em>" or "n <em>noun</em>s" depending on n.
* Adds "es" to words ending with "ch", "s", "sh", or "x".
*/
public static String nplural(int n, String noun) {
if (n == 1)
return n + " " + noun;
else if (noun.endsWith("ch") || noun.endsWith("s") ||
noun.endsWith("sh") || noun.endsWith("x"))
return n + " " + noun + "es";
else
return n + " " + noun + "s";
}
/**
* Returns a string of the specified length, truncated if necessary,
* and padded with spaces to the left if necessary.
*/
public static String lpad(String s, int length) {
if (s.length() < length) {
StringBuffer buf = new StringBuffer();
for (int i = s.length(); i < length; i++) {
buf.append(' ');
}
return buf.toString() + s;
} else {
return s.substring(0, length);
}
}
/**
* Returns a string of the specified length, truncated if necessary,
* and padded with spaces to the right if necessary.
*/
public static String rpad(String s, int length) {
if (s.length() < length) {
StringBuffer buf = new StringBuffer(s);
for (int i = s.length(); i < length; i++) {
buf.append(' ');
}
return buf.toString();
} else {
return s.substring(0, length);
}
}
/** Converts the int to a String, then formats it using {@link #rpad(String,int)}. */
public static String rpad(int num, int length) {
return rpad(String.valueOf(num), length);
}
/** Converts the double to a String, then formats it using {@link #rpad(String,int)}. */
public static String rpad(double num, int length) {
return rpad(String.valueOf(num), length);
}
/**
* Same as built-in String comparison, but accept null arguments,
* and place them at the beginning.
*/
public static class NullableStringComparator
implements Comparator<String>
{
public int compare(String s1, String s2) {
if (s1 == null && s2 == null) return 0;
if (s1 == null && s2 != null) return 1;
if (s1 != null && s2 == null) return -1;
return s1.compareTo(s2);
}
}
/** Return the number of times the character appears in the string. **/
public static int count(String s, int ch) {
int result = 0;
int pos = s.indexOf(ch);
while (pos > -1) {
result++;
pos = s.indexOf(ch, pos+1);
}
return result;
}
/** Return the number of times the second string appears in the first. **/
public static int count(String s, String sub) {
int result = 0;
int pos = s.indexOf(sub);
while (pos > -1) {
result++;
pos = s.indexOf(sub, pos+1);
}
return result;
}
///////////////////////////////////////////////////////////////////////////
/// StringTokenizer
///
/**
* Return a Vector of the Strings returned by
* {@link java.util.StringTokenizer#StringTokenizer(String,String,boolean)} with the given arguments.
* <p>
* The static type is Vector<Object> because StringTokenizer extends
* Enumeration<Object> instead of Enumeration<String> as it should
* (probably due to backward-compatibility).
**/
public static Vector<Object> tokens(String str, String delim, boolean returnTokens) {
return makeVector(new StringTokenizer(str, delim, returnTokens));
}
/**
* Return a Vector of the Strings returned by
* {@link java.util.StringTokenizer#StringTokenizer(String,String)} with the given arguments.
**/
public static Vector<Object> tokens(String str, String delim) {
return makeVector(new StringTokenizer(str, delim));
}
/**
* Return a Vector of the Strings returned by
* {@link java.util.StringTokenizer#StringTokenizer(String)} with the given arguments.
**/
public static Vector<Object> tokens(String str) {
return makeVector(new StringTokenizer(str));
}
///////////////////////////////////////////////////////////////////////////
/// Throwable
///
/** For the current backtrace, do "backtrace(new Throwable())". **/
public static String backTrace(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.close();
String result = sw.toString();
return result;
}
// Deprecated as of 2/1/2004.
/**
* @deprecated use "backtrace(new Throwable())" instead, to avoid
* spurious "at plume.UtilMDE.backTrace(UtilMDE.java:1491)" in output.
* @see #backTrace(Throwable)
**/
@Deprecated
public static String backTrace() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
new Throwable().printStackTrace(pw);
pw.close();
String result = sw.toString();
// TODO: should remove "at plume.UtilMDE.backTrace(UtilMDE.java:1491)"
return result;
}
///////////////////////////////////////////////////////////////////////////
/// Collections
///
/**
* Returns the sorted version of the list. Does not alter the list.
* Simply calls Collections.sort(List<T>, Comparator<? super T>).
**/
public static <T> List<T> sortList (List<T> l, Comparator<? super T> c) {
List<T> result = new ArrayList<T>(l);
Collections.sort(result, c);
return result;
}
/**
* Returns a copy of the list with duplicates removed.
* Retains the original order.
**/
public static <T> List<T> removeDuplicates(List<T> l) {
// There are shorter solutions that do not maintain order.
HashSet<T> hs = new HashSet<T>(l.size());
List<T> result = new ArrayList<T>();
for (T t : l) {
if (hs.add(t)) {
result.add(t);
}
}
return result;
}
///////////////////////////////////////////////////////////////////////////
/// Vector
///
/** Returns a vector containing the elements of the enumeration. */
public static <T> Vector<T> makeVector(Enumeration<T> e) {
Vector<T> result = new Vector<T>();
while (e.hasMoreElements()) {
result.addElement(e.nextElement());
}
return result;
}
// Rather than writing something like VectorToStringArray, use
// v.toArray(new String[0])
/**
* Returns a list of lists of each combination (with repetition, but
* not permutations) of the specified objects starting at index
* start over dims dimensions, for dims > 0.
*
* For example, create_combinations (1, 0, {a, b, c}) returns:
* {a}, {b}, {c}
*
* And create_combinations (2, 0, {a, b, c}) returns:
*
* {a, a}, {a, b}, {a, c}
* {b, b}, {b, c},
* {c, c}
*/
public static <T> List<List<T>> create_combinations (int dims, int start, List<T> objs) {
if (dims < 1) throw new IllegalArgumentException();
List<List<T>> results = new ArrayList<List<T>>();
for (int i = start; i < objs.size(); i++) {
if (dims == 1) {
List<T> simple = new ArrayList<T>();
simple.add (objs.get(i));
results.add (simple);
} else {
List<List<T>> combos = create_combinations (dims-1, i, objs);
for (Iterator<List<T>> j = combos.iterator(); j.hasNext(); ) {
List<T> simple = new ArrayList<T>();
simple.add (objs.get(i));
simple.addAll (j.next());
results.add (simple);
}
}
}
return (results);
}
/**
* Returns a list of lists of each combination (with repetition, but
* not permutations) of integers from start to cnt (inclusive) over
* arity dimensions.
*
* For example, create_combinations (1, 0, 2) returns:
* {0}, {1}, {2}
*
* And create_combinations (2, 0, 2) returns:
*
* {0, 0}, {0, 1}, {0, 2}
* {1, 1} {1, 2},
* {2, 2}
*/
public static ArrayList<ArrayList<Integer>> create_combinations (int arity, int start, int cnt) {
ArrayList<ArrayList<Integer>> results = new ArrayList<ArrayList<Integer>>();
// Return a list with one zero length element if arity is zero
if (arity == 0) {
results.add (new ArrayList<Integer>());
return (results);
}
for (int i = start; i <= cnt; i++) {
ArrayList<ArrayList<Integer>> combos = create_combinations (arity-1, i, cnt);
for (Iterator<ArrayList<Integer>> j = combos.iterator(); j.hasNext(); ) {
ArrayList<Integer> simple = new ArrayList<Integer>();
simple.add (new Integer(i));
simple.addAll (j.next());
results.add (simple);
}
}
return (results);
}
/**
* Returns the simple unqualified class name that corresponds to the
* specified fully qualified name. For example if qualified name
* is java.lang.String, String will be returned.
**/
public static String unqualified_name (String qualified_name) {
int offset = qualified_name.lastIndexOf ('.');
if (offset == -1)
return (qualified_name);
return (qualified_name.substring (offset+1));
}
/**
* Returns the simple unqualified class name that corresponds to the
* specified class. For example if qualified name of the class
* is java.lang.String, String will be returned.
**/
public static String unqualified_name (Class<?> cls) {
return (unqualified_name (cls.getName()));
}
// This name "human_readable" is terrible.
/**
* Convert a number into an abbreviation such as "5.00K" for 5000 or
* "65.0M" for 65000000. K stands for 1000, not 1024; M stands for
* 1000000, not 1048576, etc. There are always exactly 3 decimal digits
* of precision in the result (counting both sides of the decimal point).
*/
public static String human_readable (long val) {
double dval = (double) val;
String mag = "";
if (val < 1000)
;
else if (val < 1000000) {
dval = val / 1000.0;
mag = "K";
} else if (val < 1000000000) {
dval = val / 1000000.0;
mag = "M";
} else {
dval = val / 1000000000.0;
mag = "G";
}
String precision = "0";
if (dval < 10)
precision = "2";
else if (dval < 100)
precision = "1";
return String.format ("%,1." + precision + "f" + mag, dval);
}
}