LangUtil.java
/* *******************************************************************
* Copyright (c) 1999-2000 Xerox Corporation.
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v 2.0
* which accompanies this distribution and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
*
* Contributors:
* Xerox/PARC initial implementation
* ******************************************************************/
package org.aspectj.testing.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import org.aspectj.bridge.AbortException;
import org.aspectj.bridge.IMessage;
/**
* misc lang utilities
*/
public class LangUtil {
/** Delimiter used by split(String) (and ArrayList.toString()?) */
public static final String SPLIT_DELIM = ", ";
/** prefix used by split(String) (and ArrayList.toString()?) */
public static final String SPLIT_START = "[";
/** suffix used by split(String) (and ArrayList.toString()?) */
public static final String SPLIT_END = "]";
/** system-dependent classpath separator */
public static final String CLASSPATH_SEP;
private static final String[] NONE = new String[0];
/** bad: hard-wired unix, windows, mac path separators */
private static final char[] SEPS = new char[] { '/', '\\', ':' };
static {
// XXX this has to be the wrong way to get system-dependent classpath separator
String ps = ";";
try {
ps = System.getProperty("path.separator");
if (null == ps) {
ps = ";";
String cp = System.getProperty("java.class.path");
if (null != cp) {
if (cp.contains(";")) {
ps = ";";
} else if (cp.contains(":")) {
ps = ":";
}
// else warn?
}
}
} catch (Throwable t) { // ignore
} finally {
CLASSPATH_SEP = ps;
}
}
/**
* @return input if any are empty or no target in input,
* or input with escape prefixing all original target
*/
public static String escape(String input, String target, String escape) {
if (isEmpty(input) || isEmpty(target) || isEmpty(escape)) {
return input;
}
StringBuffer sink = new StringBuffer();
escape(input, target, escape, sink);
return sink.toString();
}
/**
* Append escaped input to sink.
* Cheap form of arbitrary escaping does not escape the escape String
* itself, but unflatten treats it as significant only before the target.
* (so this fails with input that ends with target).
*/
public static void escape(String input, String target, String escape, StringBuffer sink) {
if ((null == sink) || isEmpty(input) || isEmpty(target) || isEmpty(escape)) {
return;
} else if (!input.contains(target)) { // avoid StringTokenizer construction
sink.append(input);
return;
}
throw new Error("unimplemented");
}
/** flatten list per spec to sink */
public static void flatten(List list, FlattenSpec spec, StringBuffer sink) {
throwIaxIfNull(spec, "spec");
final FlattenSpec s = spec;
flatten(list, s.prefix, s.nullFlattened, s.escape, s.delim, s.suffix, sink);
}
/**
* Flatten a List to String by first converting to String[]
* (using toString() if the elements are not already String)
* and calling flatten(String[]...).
*/
public static void flatten(
List list,
String prefix,
String nullFlattened,
String escape,
String delim,
String suffix,
StringBuffer sink) {
throwIaxIfNull(list, "list");
Object[] ra = list.toArray();
String[] result;
if (String.class == ra.getClass().getComponentType()) {
result = (String[]) ra;
} else {
result = new String[ra.length];
for (int i = 0; i < result.length; i++) {
if (null != ra[i]) {
result[i] = ra[i].toString();
}
}
}
flatten(result, prefix, nullFlattened, escape, delim, suffix, sink);
}
/** flatten String[] per spec to sink */
public static void flatten(String[] input, FlattenSpec spec, StringBuffer sink) {
throwIaxIfNull(spec, "spec");
final FlattenSpec s = spec;
flatten(input, s.prefix, s.nullFlattened, s.escape, s.delim,s.suffix, sink);
}
/**
* Flatten a String[] to String by writing strings to sink,
* prefixing with leader (if not null),
* using nullRendering for null entries (skipped if null),
* escaping any delim in entry by prefixing with escape (if not null),
* separating entries with delim (if not null),
* and suffixing with trailer (if not null).
* Note that nullFlattened is not processed for internal delim,
* and strings is not copied before processing.
* @param strings the String[] input - not null
* @param prefix the output starts with this if not null
* @param nullFlattened the output of a null entry - entry is skipped (no delim) if null
* @param escape any delim in an item will be prefixed by escape if not null
* @param delim two items in the output will be separated by delim if not null
* @param suffix the output ends with this if not null
* @param sink the StringBuffer to use for output
* @return null if sink is not null (results added to sink) or rendering otherwise
*/
public static void flatten(
String[] strings,
String prefix,
String nullFlattened,
String escape,
String delim,
String suffix,
StringBuffer sink) {
throwIaxIfNull(strings, "strings");
if (null == sink) {
return;
}
final boolean haveDelim = (!isEmpty(delim));
final boolean haveNullFlattened = (null != nullFlattened);
final boolean escaping = (haveDelim && (null != escape));
final int numStrings = (null == strings ? 0 : strings.length);
if (null != prefix) {
sink.append(prefix);
}
for (int i = 0; i < numStrings; i++) {
String s = strings[i];
if (null == s) {
if (!haveNullFlattened) {
continue;
}
if (haveDelim && (i > 0)) {
sink.append(delim);
}
sink.append(nullFlattened);
} else {
if (haveDelim && (i > 0)) {
sink.append(delim);
}
if (escaping) {
escape(s, delim, escape, sink);
} else {
sink.append(s);
}
}
}
if (null != suffix) {
sink.append(suffix);
}
}
/**
* Get indexes of any invalid entries in array.
* @param ra the Object[] entries to check
* (if null, this returns new int[] { -1 })
* @param superType the Class, if any, to verify that
* any entries are assignable.
* @return null if all entries are non-null, assignable to superType
* or comma-delimited error String, with components
* <code>"[#] {null || not {superType}"</code>,
* e.g., "[3] null, [5] not String"
*/
public static String invalidComponents(Object[] ra, Class superType) {
if (null == ra) {
return "null input array";
} else if (0 == ra.length) {
return null;
}
StringBuilder result = new StringBuilder();
final String cname = LangUtil.unqualifiedClassName(superType);
// int index = 0;
for (int i = 0; i < ra.length; i++) {
if (null == ra[i]) {
result.append(", [" + i + "] null");
} else if ((null != superType)
&& !superType.isAssignableFrom(ra[i].getClass())) {
result.append(", [" + i + "] not " + cname);
}
}
if (0 == result.length()) {
return null;
} else {
return result.toString().substring(2);
}
}
/** @return ((null == ra) || (0 == ra.length)) */
public static boolean isEmpty(Object[] ra) {
return ((null == ra) || (0 == ra.length));
}
/** @return ((null == s) || (0 == s.length())); */
public static boolean isEmpty(String s) {
return ((null == s) || (0 == s.length()));
}
/**
* Throw IllegalArgumentException if any component in input array
* is null or (if superType is not null) not assignable to superType.
* The exception message takes the form
* <code>{name} invalid entries: {invalidEntriesResult}</code>
* @throws IllegalArgumentException if any components bad
* @see #invalidComponents(Object[], Class)
*/
public static final void throwIaxIfComponentsBad(
final Object[] input,
final String name,
final Class superType) {
String errs = invalidComponents(input, superType);
if (null != errs) {
String err = name + " invalid entries: " + errs;
throw new IllegalArgumentException(err);
}
}
/**
* 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);
}
}
/**
* 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);
}
}
public static ArrayList unflatten(String input, FlattenSpec spec) {
throwIaxIfNull(spec, "spec");
final FlattenSpec s = spec;
return unflatten(input,s.prefix, s.nullFlattened, s.escape, s.delim, s.suffix, s.emptyUnflattened);
}
/**
* Unflatten a String to String[] by separating elements at delim,
* handling prefixes, suffixes, escapes, etc.
* Any prefix or suffix is stripped from the input
* (or, if not found, an IllegalArgumentException is thrown).
* If delim is null or empty or input contains no delim,
* then return new String[] {stripped input}.
*
* XXX fix comments
* prefixing with leader (if not null),
* using nullRendering for null entries (skipped if null),
* escaping any delim in entry by prefixing with escape (if not null),
* separating entries with delim (if not null),
* and suffixing with trailer (if not null).
* Note that nullRendering is not processed for internal delim,
* and strings is not copied before processing.
* @param strings the String[] input - not null
* @param prefix the output starts with this if not null
* @param nullRendering the output of a null entry - entry is skipped (no delim) if null
* @param escape any delim in an item will be prefixed by escape if not null
* @param delim two items in the output will be separated by delim if not null
* @param suffix the output ends with this if not null
* @param sink the StringBuffer to use for output
* @return null if sink is not null (results added to sink) or rendering otherwise
* @throws IllegalArgumentException if input is null
* or if any prefix does not start the input
* or if any suffix does not end the input
*/
public static ArrayList unflatten(
String input,
String prefix,
String nullFlattened,
String escape,
String delim,
String suffix,
String emptyUnflattened) {
throwIaxIfNull(input, "input");
final boolean haveDelim = (!isEmpty(delim));
// final boolean haveNullFlattened = (null != nullFlattened);
// final boolean escaping = (haveDelim && (null != escape));
if (!isEmpty(prefix)) {
if (input.startsWith(prefix)) {
input = input.substring(prefix.length());
} else {
String s = "expecting \"" + prefix + "\" at start of " + input + "\"";
throw new IllegalArgumentException(s);
}
}
if (!isEmpty(suffix)) {
if (input.endsWith(suffix)) {
input = input.substring(0, input.length() - suffix.length());
} else {
String s = "expecting \"" + suffix + "\" at end of " + input + "\"";
throw new IllegalArgumentException(s);
}
}
final ArrayList result = new ArrayList();
if (isEmpty(input)) {
return result;
}
if ((!haveDelim) || (!input.contains(delim))) {
result.add(input);
return result;
}
StringTokenizer st = new StringTokenizer(input, delim, true);
// StringBuffer cur = new StringBuffer();
// boolean lastEndedWithEscape = false;
// boolean lastWasDelim = false;
while (st.hasMoreTokens()) {
String token = st.nextToken();
System.out.println("reading " + token);
if (delim.equals(token)) {
} else {
result.add(token);
}
}
return result;
}
/** combine two string arrays, removing null and duplicates
* @return concatenation of both arrays, less null in either or dups in two
* @see Util#combine(Object[], Object[])
*/
public static String[] combine(String[] one, String[] two) {
List twoList = new ArrayList(org.aspectj.util.LangUtil.arrayAsList(two));
ArrayList result = new ArrayList();
if (null != one) {
for (String s : one) {
if (null != s) {
twoList.remove(s);
result.add(s);
}
}
}
for (Object o : twoList) {
String element = (String) o;
if (null != element) {
result.add(element);
}
}
return (String[]) result.toArray(NONE);
}
public static Properties combine(Properties dest, Properties add, boolean respectExisting) { // XXX
if (null == add) return dest;
if (null == dest) return add;
for (Object o : add.keySet()) {
String key = (String) o;
if (null == key) {
continue;
}
String value = add.getProperty(key);
if (null == value) {
continue;
}
if (!respectExisting || (null == dest.getProperty(key))) {
dest.setProperty(key, value);
}
}
return dest;
}
public static List arrayAsList(Object[] ra) {
return org.aspectj.util.LangUtil.arrayAsList(ra);
}
/**
* return the fully-qualified class names
* inferred from the file names in dir
* assuming dir is the root of the source tree
* and class files end with ".class".
* @throws Error if dir is not properly named as prefix
* of class files found in dir.
*/
public static String[] classesIn(File dir) {
boolean alwaysTrue = true;
FileFilter filter = ValidFileFilter.CLASS_FILE;
CollectorFileFilter collector = new CollectorFileFilter(filter, alwaysTrue);
FileUtil.descendFileTree(dir, collector);
List list = collector.getFiles();
String[] result = new String[list.size()];
Iterator it = list.iterator();
String dirPrefix = dir.getPath();
for (int i = 0; i < result.length; i++) {
if (!it.hasNext()) {
throw new Error("unexpected end of list at " + i);
}
result[i] = fileToClassname((File) it.next(), dirPrefix);
}
return result;
}
/**
* Convert String[] to String by using conventions for
* split. Will ignore any entries containing SPLIT_DELIM
* (and write as such to errs if not null).
* @param input the String[] to convert
* @param errs the StringBuffer for error messages (if any)
*/
public static String unsplit(String[] input, StringBuffer errs) {
StringBuilder sb = new StringBuilder();
sb.append(SPLIT_START);
for (int i = 0; i < input.length; i++) {
if (input[i].contains(SPLIT_DELIM)) {
if (null != errs) {
errs.append("\nLangUtil.unsplit(..) - item " + i + ": \"" + input[i]
+ " contains \"" + SPLIT_DELIM + "\"");
}
} else {
sb.append(input[i]);
if (1+i < input.length) {
sb.append(SPLIT_DELIM);
}
}
}
sb.append(SPLIT_END);
return sb.toString();
}
/**
* Split input into substrings on the assumption that it is
* either only one string or it was generated using List.toString(),
* with tokens
* <pre>SPLIT_START {string} { SPLIT_DELIM {string}} SPLIT_END<pre>
* (e.g., <code>"[one, two, three]"</code>).
*/
public static String[] split(String s) {
if (null == s) {
return null;
}
if ((!s.startsWith(SPLIT_START)) || (!s.endsWith(SPLIT_END))) {
return new String[] { s };
}
s = s.substring(SPLIT_START.length(),s.length()-SPLIT_END.length());
final int LEN = s.length();
int start = 0;
final ArrayList result = new ArrayList();
final String DELIM = ", ";
int loc = s.indexOf(SPLIT_DELIM, start);
while ((start < LEN) && (-1 != loc)) {
result.add(s.substring(start, loc));
start = DELIM.length() + loc;
loc = s.indexOf(SPLIT_DELIM, start);
}
result.add(s.substring(start));
return (String[]) result.toArray(new String[0]);
}
public static String[] strip(String[] src, String[] toStrip) {
if (null == toStrip) {
return strip(src, NONE);
} else if (null == src) {
return strip(NONE, toStrip);
}
List slist = org.aspectj.util.LangUtil.arrayAsList(src);
List tlist = org.aspectj.util.LangUtil.arrayAsList(toStrip);
slist.removeAll(tlist);
return (String[]) slist.toArray(NONE);
}
/**
* Load all classes specified by args, logging success to out
* and fail to err.
*/
public static void loadClasses(String[] args, StringBuffer out,
StringBuffer err) {
if (null != args) {
for (String arg : args) {
try {
Class c = Class.forName(arg);
if (null != out) {
out.append("\n");
out.append(arg);
out.append(": ");
out.append(c.getName());
}
} catch (Throwable t) {
if (null != err) {
err.append("\n");
FileUtil.render(t, err);
}
}
}
}
}
private static String fileToClassname(File f, String prefix) {
// this can safely assume file exists, starts at base, ends with .class
// this WILL FAIL if full path with drive letter on windows
String path = f.getPath();
if (!path.startsWith(prefix)) {
String err = "!\"" + path + "\".startsWith(\"" + prefix + "\")";
throw new IllegalArgumentException(err);
}
int length = path.length() - ".class".length();
path = path.substring(prefix.length()+1, length);
for (char sep : SEPS) {
path = path.replace(sep, '.');
}
return path;
}
public static void main (String[] args) { // todo remove as testing
StringBuffer err = new StringBuffer();
StringBuffer out = new StringBuffer();
for (String arg : args) {
String[] names = classesIn(new File(arg));
System.err.println(arg + " -> " + render(names));
loadClasses(names, out, err);
}
if (0 < err.length()) {
System.err.println(err.toString());
}
if (0 < out.length()) {
System.out.println(out.toString());
}
}
public static String render (String[] args) { // todo move as testing
if ((null == args) || (1 > args.length)) {
return "[]";
}
boolean longFormat = (args.length < 10);
String sep = (longFormat ? ", " : "\n\t");
StringBuilder sb = new StringBuilder();
if (!longFormat) sb.append("[");
for (int i = 0; i < args.length; i++) {
if (0 < i) sb.append(sep);
sb.append(args[i]);
}
sb.append(longFormat ? "\n" : "]");
return sb.toString();
}
/**
* @param thrown the Throwable to render
*/
public static String debugStr(Throwable thrown) {
if (null == thrown) {
return "((Throwable) null)";
} else if (thrown instanceof InvocationTargetException) {
return debugStr(((InvocationTargetException)thrown).getTargetException());
} else if (thrown instanceof AbortException) {
IMessage m = ((AbortException) thrown).getIMessage();
if (null != m) {
return "" + m;
}
}
StringWriter buf = new StringWriter();
PrintWriter writer = new PrintWriter(buf);
writer.println(thrown.getMessage());
thrown.printStackTrace(writer);
try { buf.close(); }
catch (IOException ioe) {}
return buf.toString();
}
/**
* <code>debugStr(o, false);</code>
* @param source the Object to render
*/
public static String debugStr(Object o) {
return debugStr(o, false);
}
/**
* Render standard debug string for an object in normal, default form.
* @param source the Object to render
* @param recurse if true, then recurse on all non-primitives unless rendered
*/
public static String debugStr(Object o, boolean recurse) {
if (null == o) {
return "null";
} else if (recurse) {
ArrayList rendering = new ArrayList();
rendering.add(o);
return debugStr(o, rendering);
} else {
Class c = o.getClass();
Field[] fields = c.getDeclaredFields();
Object[] values = new Object[fields.length];
String[] names = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
names[i] = field.getName();
try {
values[i] = field.get(o);
if (field.getType().isArray()) {
List list = org.aspectj.util.LangUtil.arrayAsList((Object[]) values[i]);
values[i] = list.toString();
}
} catch (IllegalAccessException e) {
values[i] = "<IllegalAccessException>";
}
}
return debugStr(c, names, values);
}
}
/**
* recursive variant avoids cycles.
* o added to rendering before call.
*/
private static String debugStr(Object o, ArrayList rendering) {
if (null == o) {
return "null";
} else if (!rendering.contains(o)) {
throw new Error("o not in rendering");
}
Class c = o.getClass();
if (c.isArray()) {
Object[] ra = (Object[]) o;
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < ra.length; i++) {
if (i > 0) {
sb.append(", ");
}
rendering.add(ra[i]);
sb.append(debugStr(ra[i], rendering));
}
sb.append("]");
return sb.toString();
}
Field[] fields = nonStaticFields(c.getFields());
Object[] values = new Object[fields.length];
String[] names = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
names[i] = field.getName();
// collapse to String
Object value = privilegedGetField(field,o);
if (null == value) {
values[i] = "null";
} else if (rendering.contains(value)) {
values[i] = "<recursion>";
} else {
rendering.add(value);
values[i] = debugStr(value, rendering);
}
}
return debugStr(c, names, values);
}
/** incomplete - need protection domain */
private static Object privilegedGetField(final Field field, final Object o) {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() {
try {
return field.get(o);
} catch(IllegalAccessException e) {
return "<IllegalAccessException>";
}
}
});
} catch (PrivilegedActionException e) {
return "<IllegalAccessException>";
}
}
private static Field[] nonStaticFields(Field[] fields) {
if (null == fields) {
return new Field[0];
}
int to = 0;
int from = 0;
while (from < fields.length) {
if (!Modifier.isStatic(fields[from].getModifiers())) {
if (to != from) {
fields[to] = fields[from];
}
to++;
}
from++;
}
if (to < from) {
Field[] result = new Field[to];
if (to > 0) {
System.arraycopy(fields, 0, result, 0, to);
}
fields = result;
}
return fields;
}
/** <code> debugStr(source, names, items, null, null, null, null)<code> */
public static String debugStr(Class source, String[] names, Object[] items) {
return debugStr(source, null, names, null, items, null, null);
}
/**
* Render standard debug string for an object.
* This is the normal form and an example with the default values:<pre>
* {className}{prefix}{{name}{infix}{value}{delimiter}}..{suffix}
* Structure[head=root, tail=leaf]</pre>
* Passing null for the formatting entries provokes the default values,
* so to print nothing, you should pass "". Default values:<pre>
* prefix: "[" SPLIT_START
* infix: "="
* delimiter: ", " SPLIT_DELIM
* suffix: "]" SPLIT_END
* @param source the Class prefix to render unqualified - omitted if null
* @param names the String[] (field) names of the items - omitted if null
* @param items the Object[] (field) values
* @param prefix the String to separate classname and start of name/values
* @param delimiter the String to separate name/value instances
* @param infix the String to separate name and value
* used only if both name and value exist
* @param suffix the String to delimit the end of the name/value instances
* used only if classname exists
*/
public static String debugStr(Class source, String prefix, String[] names,
String infix, Object[] items, String delimiter, String suffix) {
if (null == delimiter) {
delimiter = SPLIT_DELIM;
}
if (null == prefix) {
prefix = SPLIT_START;
}
if (null == infix) {
infix = "=";
}
if (null == suffix) {
suffix = SPLIT_END;
}
StringBuilder sb = new StringBuilder();
if (null != source) {
sb.append(org.aspectj.util.LangUtil.unqualifiedClassName(source));
}
sb.append(prefix);
if (null == names) {
names = NONE;
}
if (null == items) {
items = NONE;
}
final int MAX
= (names.length > items.length ? names.length : items.length);
for (int i = 0; i < MAX; i++) {
if (i > 0) {
sb.append(delimiter);
}
if (i < names.length) {
sb.append(names[i]);
}
if (i < items.length) {
if (i < names.length) {
sb.append(infix);
}
sb.append(items[i] + "");
}
}
sb.append(suffix);
return sb.toString();
}
/**
* @return a String with the unqualified class name of the object (or "null")
*/
public static String unqualifiedClassName(Object o) {
return unqualifiedClassName(null == o ? null : o.getClass());
}
/**
* @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;
}
/**
* Calculate exact diffs and report missing and extra items.
* This assumes the input List are not modified concurrently.
* @param expectedListIn the List of expected results - treated as empty if null
* @param actualListIn the List of actual results - treated as empty if null
* @param extraListOut the List for any actual results not expected - ignored if null
* @param missingListOut the List for any expected results not found - ignored if null
* */
public static void makeDiffs(
List expectedListIn,
List actualListIn,
List missingListOut,
List extraListOut) {
if ((null == missingListOut) && (null == extraListOut)) {
return;
}
if (null == expectedListIn) {
expectedListIn = Collections.EMPTY_LIST;
}
if (null == actualListIn) {
actualListIn = Collections.EMPTY_LIST;
}
if ((0 == actualListIn.size()) && (0 == expectedListIn.size()) ) {
return;
}
BitSet actualExpected = new BitSet();
for (Object expect : expectedListIn) {
int loc = actualListIn.indexOf(expect);
if (-1 == loc) {
if (null != missingListOut) {
missingListOut.add(expect);
}
} else {
actualExpected.set(loc);
}
}
if (null != extraListOut) {
for (int i = 0; i < actualListIn.size(); i++) {
if (!actualExpected.get(i)) {
extraListOut.add(actualListIn.get(i));
}
}
}
}
// XXX unit test for makeSoftDiffs
/**
* Calculate potentially "soft" diffs using
* Comparator.compare(expected, actual).
* This shallow-copies and sorts the input Lists.
* @param expectedListIn the List of expected results - treated as empty if null
* @param actualListIn the List of actual results - treated as empty if null
* @param extraListOut the List for any actual results not expected - ignored if null
* @param missingListOut the List for any expected results not found - ignored if null
* @param comparator the Comparator for comparisons - not null
* @throws IllegalArgumentException if comp is null
*/
public static void makeSoftDiffs( // XXX no intersect or union on collections???
List expectedListIn,
List actualListIn,
List missingListOut,
List extraListOut,
Comparator comparator) {
if ((null == missingListOut) && (null == extraListOut)) {
return;
}
if (null == comparator) {
throw new IllegalArgumentException("null comparator");
}
if (null == expectedListIn) {
expectedListIn = Collections.EMPTY_LIST;
}
if (null == actualListIn) {
actualListIn = Collections.EMPTY_LIST;
}
if ((0 == actualListIn.size()) && (0 == expectedListIn.size()) ) {
return;
}
List expected = new ArrayList(expectedListIn);
expected.sort(comparator);
List actual = new ArrayList(actualListIn);
actual.sort(comparator);
Iterator actualIter = actual.iterator();
Object act = null;
if (missingListOut != null) {
missingListOut.addAll(expectedListIn);
}
if (extraListOut != null) {
extraListOut.addAll(actualListIn);
}
// AMC: less efficient, but simplified implementation. Needed since messages can
// now match on text content too, and the old algorithm did not cope with two expected
// messages on the same line, but with different text content.
while (actualIter.hasNext()) {
act = actualIter.next();
for (Object exp : expected) {
// if actual matches expected remove actual from extraListOut, and
// remove expected from missingListOut
int diff = comparator.compare(exp, act);
if (diff == 0) {
extraListOut.remove(act);
missingListOut.remove(exp);
} else if (diff > 0) {
// since list is sorted, there can be no more matches...
break;
}
}
}
// while (((null != act) || actualIter.hasNext())
// && ((null != exp) || expectedIter.hasNext())) {
// if (null == act) {
// act = actualIter.next();
// }
// if (null == exp) {
// exp = expectedIter.next();
// }
// int diff = comparator.compare(exp, act);
// if (0 > diff) { // exp < act
// if (null != missingListOut) {
// missingListOut.add(exp);
// exp = null;
// }
// } else if (0 < diff) { // exp > act
// if (null != extraListOut) {
// extraListOut.add(act);
// act = null;
// }
// } else { // got match of actual to expected
// // absorb all actual matching expected (duplicates)
// while ((0 == diff) && actualIter.hasNext()) {
// act = actualIter.next();
// diff = comparator.compare(exp, act);
// }
// if (0 == diff) {
// act = null;
// }
// exp = null;
// }
// }
// if (null != missingListOut) {
// if (null != exp) {
// missingListOut.add(exp);
// }
// while (expectedIter.hasNext()) {
// missingListOut.add(expectedIter.next());
// }
// }
// if (null != extraListOut) {
// if (null != act) {
// extraListOut.add(act);
// }
// while (actualIter.hasNext()) {
// extraListOut.add(actualIter.next());
// }
// }
}
public static class FlattenSpec {
/**
* This tells unflatten(..) to throw IllegalArgumentException
* if it finds two contiguous delimiters.
*/
public static final String UNFLATTEN_EMPTY_ERROR
= "empty items not permitted when unflattening";
/**
* This tells unflatten(..) to skip empty items when unflattening
* (since null means "use null")
*/
public static final String UNFLATTEN_EMPTY_AS_NULL
= "unflatten empty items as null";
/**
* This tells unflatten(..) to skip empty items when unflattening
* (since null means "use null")
*/
public static final String SKIP_EMPTY_IN_UNFLATTEN
= "skip empty items when unflattening";
/**
* For Ant-style attributes: "item,item" (with escaped commas).
* There is no way when unflattening to distinguish
* values which were empty from those which were null,
* so all are unflattened as empty.
*/
public static final FlattenSpec COMMA
= new FlattenSpec(null, "", "\\", ",", null, "") {
public String toString() { return "FlattenSpec.COMMA"; }
};
/** this attempts to mimic ((List)l).toString() */
public static final FlattenSpec LIST
= new FlattenSpec("[", "", null, ", ", "]", UNFLATTEN_EMPTY_ERROR) {
public String toString() { return "FlattenSpec.LIST"; }
};
/** how toString renders null values */
public static final String NULL = "<null>";
private static String r(String s) {
return (null == s ? NULL : s);
}
public final String prefix;
public final String nullFlattened;
public final String escape;
public final String delim;
public final String suffix;
public final String emptyUnflattened;
private transient String toString;
public FlattenSpec(
String prefix,
String nullRendering,
String escape,
String delim,
String suffix,
String emptyUnflattened) {
this.prefix = prefix;
this.nullFlattened = nullRendering;
this.escape = escape;
this.delim = delim;
this.suffix = suffix;
this.emptyUnflattened = emptyUnflattened;
throwIaxIfNull(emptyUnflattened, "use UNFLATTEN_EMPTY_AS_NULL");
}
public String toString() {
if (null == toString) {
toString = "FlattenSpec("
+ "prefix=" + r(prefix)
+ ", nullRendering=" + r(nullFlattened)
+ ", escape=" + r(escape)
+ ", delim=" + r(delim)
+ ", suffix=" + r(suffix)
+ ", emptyUnflattened=" + r(emptyUnflattened)
+ ")";
}
return toString;
}
}
} // class LangUtil
// --------- java runs using Ant
// /**
// * Run a Java command separately.
// * @param className the fully-qualified String name of the class
// * with the main method to run
// * @param classpathFiles the File to put on the classpath
// * @param args to the main method of the class
// * @param outSink the PrintStream for the output stream - may be null
// */
// public static void oldexecuteJava(
// String className,
// File[] classpathFiles,
// String[] args,
// PrintStream outSink) {
// Project project = new Project();
// project.setName("LangUtil.executeJava(" + className + ")");
// Path classpath = new Path(project, classpathFiles[0].getAbsolutePath());
// for (int i = 1; i < classpathFiles.length; i++) {
// classpath.addExisting(new Path(project, classpathFiles[i].getAbsolutePath()));
// }
//
// Commandline cmds = new Commandline();
// cmds.addArguments(new String[] {className});
// cmds.addArguments(args);
//
// ExecuteJava runner = new ExecuteJava();
// runner.setClasspath(classpath);
// runner.setJavaCommand(cmds);
// if (null != outSink) {
// runner.setOutput(outSink); // XXX todo
// }
// runner.execute(project);
// }
// public static void executeJava(
// String className,
// File dir,
// File[] classpathFiles,
// String[] args,
// PrintStream outSink) {
// StringBuffer sb = new StringBuffer();
//
// sb.append("c:/apps/jdk1.3.1/bin/java.exe -classpath \"");
// for (int i = 0; i < classpathFiles.length; i++) {
// if (i < 0) {
// sb.append(";");
// }
// sb.append(classpathFiles[i].getAbsolutePath());
// }
// sb.append("\" -verbose " + className);
// for (int i = 0; i < args.length; i++) {
// sb.append(" " + args[i]);
// }
// Exec exec = new Exec();
// Project project = new Project();
// project.setProperty("ant.home", "c:/home/wes/aj/aspectj/modules/lib/ant");
// System.setProperty("ant.home", "c:/home/wes/aj/aspectj/modules/lib/ant");
// exec.setProject(new Project());
// exec.setCommand(sb.toString());
// exec.setDir(dir.getAbsolutePath());
// exec.execute();
// }
// public static void execJavaProcess(
// String className,
// File dir,
// File[] classpathFiles,
// String[] args,
// PrintStream outSink) throws Throwable {
// StringBuffer sb = new StringBuffer();
//
// sb.append("c:\\apps\\jdk1.3.1\\bin\\java.exe -classpath \"");
// for (int i = 0; i < classpathFiles.length; i++) {
// if (i > 0) {
// sb.append(";");
// }
// sb.append(classpathFiles[i].getAbsolutePath());
// }
// sb.append("\" -verbose " + className);
// for (int i = 0; i < args.length; i++) {
// sb.append(" " + args[i]);
// }
// String command = sb.toString();
// System.err.println("launching process: " + command);
// Process process = Runtime.getRuntime().exec(command);
// // huh? err/out
// InputStream errStream = null;
// InputStream outStream = null;
// Throwable toThrow = null;
// int result = -1;
// try {
// System.err.println("waiting for process: " + command);
// errStream = null; // process.getErrorStream();
// outStream = null; // process.getInputStream(); // misnamed - out
// result = process.waitFor();
// System.err.println("Done waiting for process: " + command);
// process.destroy();
// } catch (Throwable t) {
// toThrow = t;
// } finally {
// if (null != outStream) {
// FileUtil.copy(outStream, System.out, false);
// try { outStream.close(); }
// catch (IOException e) {}
// }
// if (null != errStream) {
// FileUtil.copy(errStream, System.err, false);
// try { errStream.close(); }
// catch (IOException e) {}
// }
// }
// if (null != toThrow) {
// throw toThrow;
// }
// }
// try {
// // show the command
// log(command, Project.MSG_VERBOSE);
//
// // exec command on system runtime
// Process proc = Runtime.getRuntime().exec(command);
//
// if (out != null) {
// fos = new PrintWriter(new FileWriter(out));
// log("Output redirected to " + out, Project.MSG_VERBOSE);
// }
//
// // copy input and error to the output stream
// StreamPumper inputPumper =
// new StreamPumper(proc.getInputStream(), Project.MSG_INFO);
// StreamPumper errorPumper =
// new StreamPumper(proc.getErrorStream(), Project.MSG_WARN);
//
// // starts pumping away the generated output/error
// inputPumper.start();
// errorPumper.start();
//
// // Wait for everything to finish
// proc.waitFor();
// inputPumper.join();
// errorPumper.join();
// proc.destroy();
//
// // close the output file if required
// logFlush();
//
// // check its exit value
// err = proc.exitValue();
// if (err != 0) {
// if (failOnError) {
// throw new BuildException("Exec returned: " + err, getLocation());
// } else {
// log("Result: " + err, Project.MSG_ERR);
// }
// }
// } catch (IOException ioe) {
// throw new BuildException("Error exec: " + command, ioe, getLocation());
// } catch (InterruptedException ex) {}
//