]> gitweb.fperrin.net Git - Dictionary.git/blobdiff - jars/icu4j-4_2_1-src/src/com/ibm/icu/dev/test/TestFmwk.java
go
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / dev / test / TestFmwk.java
old mode 100755 (executable)
new mode 100644 (file)
index deddc9f..b54a361
-//##header\r
-/*\r
- *******************************************************************************\r
- * Copyright (C) 1996-2009, International Business Machines Corporation and    *\r
- * others. All Rights Reserved.                                                *\r
- *******************************************************************************\r
- */\r
-package com.ibm.icu.dev.test;\r
-\r
-import com.ibm.icu.text.UTF16;\r
-import com.ibm.icu.text.DecimalFormat;\r
-import com.ibm.icu.text.NumberFormat;\r
-import com.ibm.icu.util.TimeZone;\r
-import com.ibm.icu.util.ULocale;\r
-import java.io.ByteArrayOutputStream;\r
-import java.io.IOException;\r
-import java.io.OutputStream;\r
-import java.io.PrintStream;\r
-import java.io.PrintWriter;\r
-import java.io.Writer;\r
-import java.lang.reflect.Field;\r
-import java.lang.reflect.InvocationTargetException;\r
-import java.lang.reflect.Method;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Comparator;\r
-import java.util.HashMap;\r
-import java.util.Locale;\r
-import java.util.MissingResourceException;\r
-import java.util.Random;\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//## import com.ibm.icu.impl.Utility;\r
-//#endif\r
-/**\r
- * TestFmwk is a base class for tests that can be run conveniently from the\r
- * command line as well as under the Java test harness.\r
- * <p>\r
- * Sub-classes implement a set of methods named Test <something>. Each of these\r
- * methods performs some test. Test methods should indicate errors by calling\r
- * either err or errln. This will increment the errorCount field and may\r
- * optionally print a message to the log. Debugging information may also be\r
- * added to the log via the log and logln methods. These methods will add their\r
- * arguments to the log only if the test is being run in verbose mode.\r
- */\r
-public class TestFmwk extends AbstractTestLog {\r
-    /**\r
-     * The default time zone for all of our tests. Used in Target.run();\r
-     */\r
-    private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("PST");\r
-\r
-    /**\r
-     * The default locale used for all of our tests. Used in Target.run();\r
-     */\r
-    private final static Locale defaultLocale = Locale.US;\r
-\r
-    public static final class TestFmwkException extends Exception {\r
-        /**\r
-         * For serialization\r
-         */\r
-        private static final long serialVersionUID = -3051148210247229194L;\r
-\r
-        TestFmwkException(String msg) {\r
-            super(msg);\r
-        }\r
-    }\r
-    protected void handleException(Throwable e){\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//##    Throwable ex = null;\r
-//#else\r
-        Throwable ex = e.getCause();\r
-//#endif\r
-        if(ex==null){\r
-            ex = e;\r
-        }\r
-        if(ex instanceof ExceptionInInitializerError){\r
-            ex = ((ExceptionInInitializerError)ex).getException();\r
-        }\r
-        String msg = ex.getMessage();\r
-        if(msg==null){\r
-            msg = "";\r
-        }\r
-        //System.err.println("TF handleException msg: " + msg);\r
-        if (ex instanceof MissingResourceException || ex instanceof NoClassDefFoundError || msg.indexOf("java.util.MissingResourceException")>=0) {\r
-            if (params.warnings || params.nodata) {\r
-                warnln(msg);\r
-            } else if (params.nothrow) {\r
-                errln(msg);\r
-                ex.printStackTrace();\r
-            } else {\r
-                ex.printStackTrace();\r
-                throw new RuntimeException(msg);\r
-            }\r
-        } else {\r
-            if (params.nothrow) {\r
-                errln(msg);\r
-                ex.printStackTrace();\r
-            } else {\r
-                errln(msg);\r
-                ex.printStackTrace();\r
-                throw new RuntimeException(msg);\r
-            }\r
-        }\r
-    }\r
-    // use this instead of new random so we get a consistent seed\r
-    // for our tests\r
-    protected Random createRandom() {\r
-        return new Random(params.seed);\r
-    }\r
-\r
-    /**\r
-     * A test that has no test methods itself, but instead runs other tests.\r
-     * \r
-     * This overrides methods are getTargets and getSubtest from TestFmwk.\r
-     * \r
-     * If you want the default behavior, pass an array of class names and an\r
-     * optional description to the constructor. The named classes must extend\r
-     * TestFmwk. If a provided name doesn't include a ".", package name is\r
-     * prefixed to it (the package of the current test is used if none was\r
-     * provided in the constructor). The resulting full name is used to\r
-     * instantiate an instance of the class using the default constructor.\r
-     * \r
-     * Class names are resolved to classes when getTargets or getSubtest is\r
-     * called. This allows instances of TestGroup to be compiled and run without\r
-     * all the targets they would normally invoke being available.\r
-     */\r
-    public static abstract class TestGroup extends TestFmwk {\r
-        private String defaultPackage;\r
-        private String[] names;\r
-        private String description;\r
-\r
-        private Class[] tests; // deferred init\r
-\r
-        /**\r
-         * Constructor that takes a default package name and a list of class\r
-         * names. Adopts and modifies the classname list\r
-         */\r
-        protected TestGroup(String defaultPackage, String[] classnames,\r
-                String description) {\r
-            if (classnames == null) {\r
-                throw new IllegalStateException("classnames must not be null");\r
-            }\r
-\r
-            if (defaultPackage == null) {\r
-                defaultPackage = getClass().getPackage().getName();\r
-            }\r
-            defaultPackage = defaultPackage + ".";\r
-\r
-            this.defaultPackage = defaultPackage;\r
-            this.names = classnames;\r
-            this.description = description;\r
-        }\r
-\r
-        /**\r
-         * Constructor that takes a list of class names and a description, and\r
-         * uses the package for this class as the default package.\r
-         */\r
-        protected TestGroup(String[] classnames, String description) {\r
-            this(null, classnames, description);\r
-        }\r
-\r
-        /**\r
-         * Constructor that takes a list of class names, and uses the package\r
-         * for this class as the default package.\r
-         */\r
-        protected TestGroup(String[] classnames) {\r
-            this(null, classnames, null);\r
-        }\r
-\r
-        protected String getDescription() {\r
-            return description;\r
-        }\r
-\r
-        protected Target getTargets(String targetName) {\r
-            Target target = null;\r
-            if (targetName != null) {\r
-                finishInit(); // hmmm, want to get subtest without initializing\r
-                              // all tests\r
-\r
-                try {\r
-                    TestFmwk test = getSubtest(targetName);\r
-                    if (test != null) {\r
-                        target = test.new ClassTarget();\r
-                    } else {\r
-                        target = this.new Target(targetName);\r
-                    }\r
-                } catch (TestFmwkException e) {\r
-                    target = this.new Target(targetName);\r
-                }\r
-            } else if (params.doRecurse()) {\r
-                finishInit();\r
-                boolean groupOnly = params.doRecurseGroupsOnly();\r
-                for (int i = names.length; --i >= 0;) {\r
-                    Target newTarget = null;\r
-                    Class cls = tests[i];\r
-                    if (cls == null) { // hack no warning for missing tests\r
-                        if (params.warnings) {\r
-                            continue;\r
-                        }\r
-                        newTarget = this.new Target(names[i]);\r
-                    } else {\r
-                        TestFmwk test = getSubtest(i, groupOnly);\r
-                        if (test != null) {\r
-                            newTarget = test.new ClassTarget();\r
-                        } else {\r
-                            if (groupOnly) {\r
-                                newTarget = this.new EmptyTarget(names[i]);\r
-                            } else {\r
-                                newTarget = this.new Target(names[i]);\r
-                            }\r
-                        }\r
-                    }\r
-                    if (newTarget != null) {\r
-                        newTarget.setNext(target);\r
-                        target = newTarget;\r
-                    }\r
-                }\r
-            }\r
-\r
-            return target;\r
-        }\r
-        protected TestFmwk getSubtest(String testName) throws TestFmwkException {\r
-            finishInit();\r
-\r
-            for (int i = 0; i < names.length; ++i) {\r
-                if (names[i].equalsIgnoreCase(testName)) { // allow\r
-                                                           // case-insensitive\r
-                                                           // matching\r
-                    return getSubtest(i, false);\r
-                }\r
-            }\r
-            throw new TestFmwkException(testName);\r
-        }\r
-\r
-        private TestFmwk getSubtest(int i, boolean groupOnly) {\r
-            Class cls = tests[i];\r
-            if (cls != null) {\r
-                if (groupOnly && !TestGroup.class.isAssignableFrom(cls)) {\r
-                    return null;\r
-                }\r
-\r
-                try {\r
-                    TestFmwk subtest = (TestFmwk) cls.newInstance();\r
-                    subtest.params = params;\r
-                    return subtest;\r
-                } catch (InstantiationException e) {\r
-                    throw new IllegalStateException(e.getMessage());\r
-                } catch (IllegalAccessException e) {\r
-                    throw new IllegalStateException(e.getMessage());\r
-                }\r
-            }\r
-            return null;\r
-        }\r
-\r
-        private void finishInit() {\r
-            if (tests == null) {\r
-                tests = new Class[names.length];\r
-\r
-                for (int i = 0; i < names.length; ++i) {\r
-                    String name = names[i];\r
-                    if (name.indexOf('.') == -1) {\r
-                        name = defaultPackage + name;\r
-                    }\r
-                    try {\r
-                        Class cls = Class.forName(name);\r
-                        if (!TestFmwk.class.isAssignableFrom(cls)) {\r
-                            throw new IllegalStateException("class " + name\r
-                                    + " does not extend TestFmwk");\r
-                        }\r
-\r
-                        tests[i] = cls;\r
-                        names[i] = getClassTargetName(cls);\r
-                    } catch (ClassNotFoundException e) {\r
-                        // leave tests[i] null and name as classname\r
-                    }\r
-                }\r
-            }\r
-        }\r
-    }\r
-\r
-    /**\r
-     * The default target is invalid.\r
-     */\r
-    public class Target {\r
-        private Target next;\r
-        public final String name;\r
-\r
-        public Target(String name) {\r
-            this.name = name;\r
-        }\r
-\r
-        public Target setNext(Target next) {\r
-            this.next = next;\r
-            return this;\r
-        }\r
-\r
-        public Target getNext() {\r
-            return next;\r
-        }\r
-\r
-        public Target append(Target targets) {\r
-            Target t = this;\r
-            while(t.next != null) {\r
-                t = t.next;\r
-            }\r
-            t.next = targets;\r
-            return this;\r
-        }\r
-\r
-        public void run() throws Exception {\r
-            int f = filter();\r
-            if (f == -1) {\r
-                ++params.invalidCount;\r
-            } else {\r
-                Locale.setDefault(defaultLocale);\r
-                TimeZone.setDefault(defaultTimeZone);\r
-\r
-                if (!validate()) {\r
-                    params.writeTestInvalid(name, params.nodata);\r
-                } else {\r
-                    params.push(name, getDescription(), f == 1);\r
-                    execute();\r
-                    params.pop();\r
-                }\r
-            }\r
-        }\r
-\r
-        protected int filter() {\r
-            return params.filter(name);\r
-        }\r
-\r
-        protected boolean validate() {\r
-            return false;\r
-        }\r
-\r
-        protected String getDescription() {\r
-            return null;\r
-        }\r
-\r
-        protected void execute() throws Exception{\r
-        }\r
-    }\r
-\r
-    public class EmptyTarget extends Target {\r
-        public EmptyTarget(String name) {\r
-            super(name);\r
-        }\r
-\r
-        protected boolean validate() {\r
-            return true;\r
-        }\r
-    }\r
-\r
-    public class MethodTarget extends Target {\r
-        private Method testMethod;\r
-\r
-        public MethodTarget(String name, Method method) {\r
-            super(name);\r
-            testMethod = method;\r
-        }\r
-\r
-        protected boolean validate() {\r
-            return testMethod != null && validateMethod(name);\r
-        }\r
-\r
-        protected String getDescription() {\r
-            return getMethodDescription(name);\r
-        }\r
-\r
-        protected void execute() throws Exception{\r
-            if (params.inDocMode()) {\r
-                // nothing to execute\r
-            } else if (!params.stack.included) {\r
-                ++params.invalidCount;\r
-            } else {\r
-                final Object[] NO_ARGS = new Object[0];\r
-                try {\r
-                    ++params.testCount;\r
-                    init();\r
-                    testMethod.invoke(TestFmwk.this, NO_ARGS);\r
-                } catch (IllegalAccessException e) {\r
-                    errln("Can't access test method " + testMethod.getName());\r
-                }catch (ExceptionInInitializerError e){\r
-                    handleException(e);\r
-                } catch (InvocationTargetException e) {\r
-                    //e.printStackTrace();\r
-                    handleException(e);\r
-                }catch (MissingResourceException e) {\r
-                    handleException(e);\r
-                }catch (NoClassDefFoundError e) {\r
-                    handleException(e);\r
-                }catch (Exception e){\r
-                    /*errln("Encountered: "+ e.toString());\r
-                    e.printStackTrace(System.err);\r
-                    */\r
-                    handleException(e);\r
-                }\r
-            }\r
-            // If non-exhaustive, check if the method target\r
-            // takes excessive time.\r
-            if (params.inclusion <= 5) {\r
-                double deltaSec = (double)(System.currentTimeMillis() - params.stack.millis)/1000;\r
-                if (deltaSec > params.maxTargetSec) {\r
-                    if (params.timeLog == null) {\r
-                        params.timeLog = new StringBuffer();\r
-                    }\r
-                    params.stack.appendPath(params.timeLog);\r
-                    params.timeLog.append(" (" + deltaSec + "s" + ")\n");\r
-                }\r
-            }\r
-        }\r
-\r
-        protected String getStackTrace(InvocationTargetException e) {\r
-            ByteArrayOutputStream bs = new ByteArrayOutputStream();\r
-            PrintStream ps = new PrintStream(bs);\r
-            e.getTargetException().printStackTrace(ps);\r
-            return bs.toString();\r
-        }\r
-    }\r
-\r
-    public class ClassTarget extends Target {\r
-        String targetName;\r
-\r
-        public ClassTarget() {\r
-            this(null);\r
-        }\r
-\r
-        public ClassTarget(String targetName) {\r
-            super(getClassTargetName(TestFmwk.this.getClass()));\r
-            this.targetName = targetName;\r
-        }\r
-\r
-        protected boolean validate() {\r
-            return TestFmwk.this.validate();\r
-        }\r
-\r
-        protected String getDescription() {\r
-            return TestFmwk.this.getDescription();\r
-        }\r
-\r
-        protected void execute() throws Exception {\r
-            params.indentLevel++;\r
-            Target target = randomize(getTargets(targetName));\r
-            while (target != null) {\r
-                target.run();\r
-                target = target.next;\r
-            }\r
-            params.indentLevel--;\r
-        }\r
-\r
-        private Target randomize(Target t) {\r
-            if (t != null && t.getNext() != null) {\r
-                ArrayList list = new ArrayList();\r
-                while (t != null) {\r
-                    list.add(t);\r
-                    t = t.getNext();\r
-                }\r
-\r
-                Target[] arr = (Target[]) list.toArray(new Target[list.size()]);\r
-\r
-                if (true) { // todo - add to params?\r
-                    // different jvms return class methods in different orders,\r
-                    // so we sort them (always, and then randomize them, so that\r
-                    // forcing a seed will also work across jvms).\r
-                    Arrays.sort(arr, new Comparator() {\r
-                        public int compare(Object lhs, Object rhs) {\r
-                            // sort in reverse order, later we link up in\r
-                            // forward order\r
-                            return ((Target) rhs).name\r
-                                    .compareTo(((Target) lhs).name);\r
-                        }\r
-                    });\r
-\r
-                    // t is null to start, ends up as first element\r
-                    // (arr[arr.length-1])\r
-                    for (int i = 0; i < arr.length; ++i) {\r
-                        t = arr[i].setNext(t); // relink in forward order\r
-                    }\r
-                }\r
-\r
-                if (params.random != null) {\r
-                    t = null; // reset t to null\r
-                    Random r = params.random;\r
-                    for (int i = arr.length; --i >= 1;) {\r
-                        int x = r.nextInt(i + 1);\r
-                        t = arr[x].setNext(t);\r
-                        arr[x] = arr[i];\r
-                    }\r
-\r
-                    t = arr[0].setNext(t); // new first element\r
-                }\r
-            }\r
-\r
-            return t;\r
-        }\r
-    }\r
-\r
-    //------------------------------------------------------------------------\r
-    // Everything below here is boilerplate code that makes it possible\r
-    // to add a new test by simply adding a function to an existing class\r
-    //------------------------------------------------------------------------\r
-\r
-    protected TestFmwk() {\r
-    }\r
-    \r
-    protected void init() throws Exception{\r
-    }\r
-    \r
-    /**\r
-     * Parse arguments into a TestParams object and a collection of target\r
-     * paths. If there was an error parsing the TestParams, print usage and exit\r
-     * with -1. Otherwise, call resolveTarget(TestParams, String) for each path,\r
-     * and run the returned target. After the last test returns, if prompt is\r
-     * set, prompt and wait for input from stdin. Finally, exit with number of\r
-     * errors.\r
-     * \r
-     * This method never returns, since it always exits with System.exit();\r
-     */\r
-    public void run(String[] args) {\r
-        System.exit(run(args, new PrintWriter(System.out)));\r
-     }\r
-    \r
-    /**\r
-     * Like run(String[]) except this allows you to specify the error log.\r
-     * Unlike run(String[]) this returns the error code as a result instead of\r
-     * calling System.exit().\r
-     */\r
-    public int run(String[] args, PrintWriter log) {\r
-        boolean prompt = false;\r
-        int wx = 0;\r
-        for (int i = 0; i < args.length; ++i) {\r
-            String arg = args[i];\r
-            if (arg.equals("-p") || arg.equals("-prompt")) {\r
-                prompt = true;\r
-            } else {\r
-                if (wx < i) {\r
-                    args[wx] = arg;\r
-                }\r
-                wx++;\r
-            }\r
-        }\r
-        while (wx < args.length) {\r
-            args[wx++] = null;\r
-        }\r
-        \r
-        TestParams localParams = TestParams.create(args, log);\r
-        if (localParams == null) {\r
-            return -1;\r
-        }\r
-        \r
-        int errorCount = runTests(localParams, args);\r
-        \r
-        if (localParams.seed != 0) {\r
-            localParams.log.println("-random:" + localParams.seed);\r
-            localParams.log.flush();\r
-        }\r
-\r
-        if (localParams.errorSummary != null && localParams.errorSummary.length() > 0) {\r
-            localParams.log.println("\nError summary:");\r
-            localParams.log.println(localParams.errorSummary.toString());\r
-        }\r
-\r
-        if (localParams.timeLog != null && localParams.timeLog.length() > 0) {\r
-            localParams.log.println("\nTest cases taking excessive time (>" +\r
-                    localParams.maxTargetSec + "s):");\r
-            localParams.log.println(localParams.timeLog.toString());\r
-        }\r
-\r
-        if (prompt) {\r
-            System.out.println("Hit RETURN to exit...");\r
-            System.out.flush();\r
-            try {\r
-                System.in.read();\r
-            } catch (IOException e) {\r
-                localParams.log.println("Exception: " + e.toString() + e.getMessage());\r
-            }\r
-        }\r
-\r
-        return errorCount;\r
-    }\r
-\r
-    public int runTests(TestParams _params, String[] tests) {\r
-        int ec = 0;\r
-        \r
-        StringBuffer summary = null;\r
-        try {\r
-            if (tests.length == 0 || tests[0] == null) { // no args\r
-                _params.init();\r
-                resolveTarget(_params).run();\r
-                ec = _params.errorCount;\r
-            } else {\r
-                for (int i = 0; i < tests.length ; ++i) {\r
-                    if (tests[i] == null) continue;\r
-                    \r
-                    if (i > 0) {\r
-                        _params.log.println();\r
-                    }\r
-\r
-                    _params.init();\r
-                    resolveTarget(_params, tests[i]).run();\r
-                    ec += _params.errorCount;\r
-                    \r
-                    if (_params.errorSummary != null && _params.errorSummary.length() > 0) {\r
-                        if (summary == null) {\r
-                            summary = new StringBuffer();\r
-                        }\r
-                        summary.append("\nTest Root: " + tests[i] + "\n");\r
-                        summary.append(_params.errorSummary());\r
-                    }\r
-                }\r
-                _params.errorSummary = summary;\r
-            }\r
-        } catch (Exception e) {\r
-            e.printStackTrace(_params.log);\r
-            _params.log.println(e.getMessage());\r
-            _params.log.println("encountered exception, exiting");\r
-        }\r
-        \r
-        return ec;\r
-    }\r
-    \r
-    /**\r
-     * Return a ClassTarget for this test. Params is set on this test.\r
-     */\r
-    public Target resolveTarget(TestParams paramsArg) {\r
-        this.params = paramsArg;\r
-        return new ClassTarget();\r
-    }\r
-\r
-    /**\r
-     * Resolve a path from this test to a target. If this test has subtests, and\r
-     * the path contains '/', the portion before the '/' is resolved to a\r
-     * subtest, until the path is consumed or the test has no subtests. Returns\r
-     * a ClassTarget created using the resolved test and remaining path (which\r
-     * ought to be null or a method name). Params is set on the target's test.\r
-     */\r
-    public Target resolveTarget(TestParams paramsArg, String targetPath) {\r
-        TestFmwk test = this;\r
-        test.params = paramsArg;\r
-\r
-        if (targetPath != null) {\r
-            if (targetPath.length() == 0) {\r
-                targetPath = null;\r
-            } else {\r
-                int p = 0;\r
-                int e = targetPath.length();\r
-\r
-                // trim all leading and trailing '/'\r
-                while (targetPath.charAt(p) == '/') {\r
-                    ++p;\r
-                }\r
-                while (e > p && targetPath.charAt(e - 1) == '/') {\r
-                    --e;\r
-                }\r
-                if (p > 0 || e < targetPath.length()) {\r
-                    targetPath = targetPath.substring(p, e - p);\r
-                    p = 0;\r
-                    e = targetPath.length();\r
-                }\r
-\r
-                try {\r
-                    for (;;) {\r
-                        int n = targetPath.indexOf('/');\r
-                        String prefix = n == -1 ? targetPath : targetPath\r
-                                .substring(0, n);\r
-                        TestFmwk subtest = test.getSubtest(prefix);\r
-\r
-                        if (subtest == null) {\r
-                            break;\r
-                        }\r
-\r
-                        test = subtest;\r
-\r
-                        if (n == -1) {\r
-                            targetPath = null;\r
-                            break;\r
-                        }\r
-\r
-                        targetPath = targetPath.substring(n + 1);\r
-                    }\r
-                } catch (TestFmwkException ex) {\r
-                    return test.new Target(targetPath);\r
-                }\r
-            }\r
-        }\r
-\r
-        return test.new ClassTarget(targetPath);\r
-    }\r
-\r
-    /**\r
-     * Return true if we can run this test (allows test to inspect jvm,\r
-     * environment, params before running)\r
-     */\r
-    protected boolean validate() {\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * Return the targets for this test. If targetName is null, return all\r
-     * targets, otherwise return a target for just that name. The returned\r
-     * target can be null.\r
-     * \r
-     * The default implementation returns a MethodTarget for each public method\r
-     * of the object's class whose name starts with "Test" or "test".\r
-     */\r
-    protected Target getTargets(String targetName) {\r
-        return getClassTargets(getClass(), targetName);\r
-    }\r
-\r
-    protected Target getClassTargets(Class cls, String targetName) {\r
-        if (cls == null) {\r
-            return null;\r
-        }\r
-\r
-        Target target = null;\r
-        if (targetName != null) {\r
-            try {\r
-                Method method = cls.getMethod(targetName, (Class[])null);\r
-                target = new MethodTarget(targetName, method);\r
-            } catch (NoSuchMethodException e) {\r
-        if (!inheritTargets()) {\r
-            return new Target(targetName); // invalid target\r
-        }\r
-            } catch (SecurityException e) {\r
-                return null;\r
-            }\r
-        } else {\r
-            if (params.doMethods()) {\r
-                Method[] methods = cls.getDeclaredMethods();\r
-                for (int i = methods.length; --i >= 0;) {\r
-                    String name = methods[i].getName();\r
-                    if (name.startsWith("Test") || name.startsWith("test")) {\r
-                        target = new MethodTarget(name, methods[i])\r
-                                .setNext(target);\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        if (inheritTargets()) {\r
-          Target parentTarget = getClassTargets(cls.getSuperclass(), targetName);\r
-          if (parentTarget == null) {\r
-            return target;\r
-          }\r
-          if (target == null) {\r
-            return parentTarget;\r
-          }\r
-          return parentTarget.append(target);\r
-        }\r
-\r
-        return target;\r
-    }\r
-\r
-    protected boolean inheritTargets() {\r
-        return false;\r
-    }\r
-\r
-    protected String getDescription() {\r
-        return null;\r
-    }\r
-\r
-    protected boolean validateMethod(String name) {\r
-        return true;\r
-    }\r
-\r
-    protected String getMethodDescription(String name) {\r
-        return null;\r
-    }\r
-\r
-    // method tests have no subtests, group tests override\r
-    protected TestFmwk getSubtest(String prefix) throws TestFmwkException {\r
-        return null;\r
-    }\r
-\r
-    public boolean isVerbose() {\r
-        return params.verbose;\r
-    }\r
-\r
-    public boolean noData() {\r
-        return params.nodata;\r
-    }\r
-\r
-    public boolean isTiming() {\r
-        return params.timing < Long.MAX_VALUE;\r
-    }\r
-\r
-    public boolean isMemTracking() {\r
-        return params.memusage;\r
-    }\r
-\r
-    /**\r
-     * 0 = fewest tests, 5 is normal build, 10 is most tests\r
-     */\r
-    public int getInclusion() {\r
-        return params.inclusion;\r
-    }\r
-\r
-    public boolean isModularBuild() {\r
-        return params.warnings;\r
-    }\r
-\r
-    public boolean isQuick() {\r
-        return params.inclusion == 0;\r
-    }\r
-\r
-    public void msg(String message, int level, boolean incCount, boolean newln) {\r
-        params.msg(message, level, incCount, newln);\r
-    }\r
-\r
-    protected int getErrorCount() {\r
-        return params.errorCount;\r
-    }\r
-\r
-    public String getProperty(String key) {\r
-        String val = null;\r
-        if (key != null && key.length() > 0 && params.props != null) {\r
-            val = (String)params.props.get(key.toLowerCase());\r
-        }\r
-        return val;\r
-    }\r
-\r
-    protected TimeZone safeGetTimeZone(String id) {\r
-        TimeZone tz = TimeZone.getTimeZone(id);\r
-        if (tz == null) {\r
-            // should never happen\r
-            errln("FAIL: TimeZone.getTimeZone(" + id + ") => null");\r
-        }\r
-        if (!tz.getID().equals(id)) {\r
-            warnln("FAIL: TimeZone.getTimeZone(" + id + ") => " + tz.getID());\r
-        }\r
-        return tz;\r
-    }\r
-\r
-    /**\r
-     * Print a usage message for this test class.\r
-     */\r
-    public void usage() {\r
-        usage(new PrintWriter(System.out), getClass().getName());\r
-    }\r
-    \r
-    public static void usage(PrintWriter pw, String className) {\r
-        pw.println("Usage: " + className + " option* target*");\r
-        pw.println();\r
-        pw.println("Options:");\r
-        pw.println(" -d[escribe] Print a short descriptive string for this test and all");\r
-        pw.println("       listed targets.");\r
-        pw.println(" -e<n> Set exhaustiveness from 0..10.  Default is 0, fewest tests.\n"\r
-                 + "       To run all tests, specify -e10.  Giving -e with no <n> is\n"\r
-                 + "       the same as -e5.");\r
-        pw.println(" -filter:<str> Only tests matching filter will be run or listed.\n"\r
-                 + "       <str> is of the form ['^']text[','['^']text].\n"\r
-                 + "       Each string delimited by ',' is a separate filter argument.\n"\r
-                 + "       If '^' is prepended to an argument, its matches are excluded.\n"\r
-                 + "       Filtering operates on test groups as well as tests, if a test\n"\r
-                 + "       group is included, all its subtests that are not excluded will\n"\r
-                 + "       be run.  Examples:\n"\r
-                 + "    -filter:A -- only tests matching A are run.  If A matches a group,\n"\r
-                 + "       all subtests of this group are run.\n"\r
-                 + "    -filter:^A -- all tests except those matching A are run.  If A matches\n"\r
-                 + "        a group, no subtest of that group will be run.\n"\r
-                 + "    -filter:A,B,^C,^D -- tests matching A or B and not C and not D are run\n"\r
-                 + "       Note: Filters are case insensitive.");\r
-        pw.println(" -h[elp] Print this help text and exit.");\r
-        pw.println(" -l[ist] List immediate targets of this test");\r
-        pw.println("   -la, -listAll List immediate targets of this test, and all subtests");\r
-        pw.println("   -le, -listExaustive List all subtests and targets");\r
-        // don't know how to get useful numbers for memory usage using java API\r
-        // calls\r
-        //      pw.println(" -m[emory] print memory usage and force gc for\r
-        // each test");\r
-        pw.println(" -n[othrow] Message on test failure rather than exception");\r
-        pw.println(" -p[rompt] Prompt before exiting");\r
-        pw.println(" -prop:<key>=<value> Set optional property used by this test");\r
-        pw.println(" -q[uiet] Do not show warnings");\r
-        pw.println(" -r[andom][:<n>] If present, randomize targets.  If n is present,\n"\r
-                        + "       use it as the seed.  If random is not set, targets will\n"\r
-                        + "       be in alphabetical order to ensure cross-platform consistency.");\r
-        pw.println(" -s[ilent] No output except error summary or exceptions.");\r
-        pw.println(" -tfilter:<str> Transliterator Test filter of ids.");\r
-        pw.println(" -t[ime][:<n>] Print elapsed time for each test.  if n is present\n"\r
-                        + "       only print times >= n milliseconds.");\r
-        pw.println(" -v[erbose] Show log messages");\r
-        pw.println(" -u[nicode] Don't escape error or log messages");\r
-        pw.println(" -w[arning] Continue in presence of warnings, and disable missing test warnings.");\r
-        pw.println(" -nodata | -nd Do not warn if resource data is not present.");\r
-        pw.println();\r
-        pw.println(" If a list or describe option is provided, no tests are run.");\r
-        pw.println();\r
-        pw.println("Targets:");\r
-        pw.println(" If no target is specified, all targets for this test are run.");\r
-        pw.println(" If a target contains no '/' characters, and matches a target");\r
-        pw.println(" of this test, the target is run.  Otherwise, the part before the");\r
-        pw.println(" '/' is used to match a subtest, which then evaluates the");\r
-        pw.println(" remainder of the target as above.  Target matching is case-insensitive.");\r
-        pw.println();\r
-        pw.println(" If multiple targets are provided, each is executed in order.");\r
-        pw.flush();\r
-    }\r
-    public static String hex(char[] s){\r
-        StringBuffer result = new StringBuffer();\r
-        for (int i = 0; i < s.length; ++i) {\r
-            if (i != 0) result.append(',');\r
-            result.append(hex(s[i]));\r
-        }\r
-        return result.toString();\r
-    }\r
-    public static String hex(byte[] s){\r
-        StringBuffer result = new StringBuffer();\r
-        for (int i = 0; i < s.length; ++i) {\r
-            if (i != 0) result.append(',');\r
-            result.append(hex(s[i]));\r
-        }\r
-        return result.toString();\r
-    }\r
-    public static String hex(char ch) {\r
-        StringBuffer result = new StringBuffer();\r
-        String foo = Integer.toString(ch, 16).toUpperCase();\r
-        for (int i = foo.length(); i < 4; ++i) {\r
-            result.append('0');\r
-        }\r
-        return result + foo;\r
-    }\r
-\r
-    public static String hex(int ch) {\r
-        StringBuffer result = new StringBuffer();\r
-        String foo = Integer.toString(ch, 16).toUpperCase();\r
-        for (int i = foo.length(); i < 4; ++i) {\r
-            result.append('0');\r
-        }\r
-        return result + foo;\r
-    }\r
-\r
-    public static String hex(String s) {\r
-        StringBuffer result = new StringBuffer();\r
-        for (int i = 0; i < s.length(); ++i) {\r
-            if (i != 0)\r
-                result.append(',');\r
-            result.append(hex(s.charAt(i)));\r
-        }\r
-        return result.toString();\r
-    }\r
-\r
-    public static String hex(StringBuffer s) {\r
-        return hex(s.toString());\r
-    }\r
-    public static String prettify(String s) {\r
-        StringBuffer result = new StringBuffer();\r
-        int ch;\r
-        for (int i = 0; i < s.length(); i += UTF16.getCharCount(ch)) {\r
-            ch = UTF16.charAt(s, i);\r
-            if (ch > 0xfffff) {\r
-                result.append("\\U00");\r
-                result.append(hex(ch));\r
-            } else if (ch > 0xffff) {\r
-                result.append("\\U000");\r
-                result.append(hex(ch));\r
-            } else if (ch > 0x7f) {\r
-                result.append("\\u");\r
-                result.append(hex(ch));\r
-            } else {\r
-                result.append((char) ch);\r
-            }\r
-\r
-        }\r
-        return result.toString();\r
-    }\r
-    public static String prettify(StringBuffer s) {\r
-        return prettify(s.toString());\r
-    }\r
-\r
-    private static java.util.GregorianCalendar cal;\r
-\r
-    /**\r
-     * Return a Date given a year, month, and day of month. This is similar to\r
-     * new Date(y-1900, m, d). It uses the default time zone at the time this\r
-     * method is first called.\r
-     * \r
-     * @param year\r
-     *            use 2000 for 2000, unlike new Date()\r
-     * @param month\r
-     *            use Calendar.JANUARY etc.\r
-     * @param dom\r
-     *            day of month, 1-based\r
-     * @return a Date object for the given y/m/d\r
-     */\r
-    protected static synchronized java.util.Date getDate(int year, int month,\r
-            int dom) {\r
-        if (cal == null) {\r
-            cal = new java.util.GregorianCalendar();\r
-        }\r
-        cal.clear();\r
-        cal.set(year, month, dom);\r
-        return cal.getTime();\r
-    }\r
-\r
-    public static class NullWriter extends PrintWriter {\r
-        public NullWriter() {\r
-            super(System.out, false);\r
-        }\r
-        public void write(int c) {\r
-        }\r
-        public void write(char[] buf, int off, int len) {\r
-        }\r
-        public void write(String s, int off, int len) {\r
-        }\r
-        public void println() {\r
-        }\r
-    }\r
-\r
-    public static class ASCIIWriter extends PrintWriter {\r
-        private StringBuffer buffer = new StringBuffer();\r
-\r
-        // Characters that we think are printable but that escapeUnprintable\r
-        // doesn't\r
-        private static final String PRINTABLES = "\t\n\r";\r
-\r
-        public ASCIIWriter(Writer w, boolean autoFlush) {\r
-            super(w, autoFlush);\r
-        }\r
-\r
-        public ASCIIWriter(OutputStream os, boolean autoFlush) {\r
-            super(os, autoFlush);\r
-        }\r
-\r
-        public void write(int c) {\r
-            synchronized (lock) {\r
-                buffer.setLength(0);\r
-                if (PRINTABLES.indexOf(c) < 0\r
-                        && TestUtil.escapeUnprintable(buffer, c)) {\r
-                    super.write(buffer.toString());\r
-                } else {\r
-                    super.write(c);\r
-                }\r
-            }\r
-        }\r
-\r
-        public void write(char[] buf, int off, int len) {\r
-            synchronized (lock) {\r
-                buffer.setLength(0);\r
-                int limit = off + len;\r
-                while (off < limit) {\r
-                    int c = UTF16Util.charAt(buf, 0, buf.length, off);\r
-                    off += UTF16Util.getCharCount(c);\r
-                    if (PRINTABLES.indexOf(c) < 0\r
-                            && TestUtil.escapeUnprintable(buffer, c)) {\r
-                        super.write(buffer.toString());\r
-                        buffer.setLength(0);\r
-                    } else {\r
-                        super.write(c);\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        public void write(String s, int off, int len) {\r
-            write(s.substring(off, off + len).toCharArray(), 0, len);\r
-        }\r
-    }\r
-\r
-    // filters\r
-    // match against the entire hierarchy\r
-    // A;B;!C;!D --> (A ||B) && (!C && !D)\r
-    // positive, negative, unknown matches\r
-    // positive -- known to be included, negative- known to be excluded\r
-    // positive only if no excludes, and matches at least one include, if any\r
-    // negative only if matches at least one exclude\r
-    // otherwise, we wait\r
-\r
-    public static class TestParams {\r
-        public boolean prompt;\r
-        public boolean nothrow;\r
-        public boolean verbose;\r
-        public boolean quiet;\r
-        public int listlevel;\r
-        public boolean describe;\r
-        public boolean warnings;\r
-        public boolean nodata;\r
-        public long timing = Long.MAX_VALUE;\r
-        public boolean memusage;\r
-        public int inclusion;\r
-        public String filter;\r
-        public long seed;\r
-        public String tfilter; // for transliterator tests\r
-\r
-        public State stack;\r
-\r
-        public StringBuffer errorSummary;\r
-        private StringBuffer timeLog;\r
-\r
-        public PrintWriter log;\r
-        public int indentLevel;\r
-        private boolean needLineFeed;\r
-        private boolean suppressIndent;\r
-        public int errorCount;\r
-        public int warnCount;\r
-        public int invalidCount;\r
-        public int testCount;\r
-        private NumberFormat tformat;\r
-        public Random random;\r
-        public int maxTargetSec = 10;\r
-        public HashMap props;\r
-\r
-        private TestParams() {\r
-        }\r
-        \r
-        public static TestParams create(String arglist, PrintWriter log) {\r
-            String[] args = null;\r
-            if (arglist != null && arglist.length() > 0) {\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//##            args = Utility.split(arglist, '\u0020');\r
-//#else\r
-                args = arglist.split("\\s");\r
-//#endif\r
-            }\r
-            return create(args, log);\r
-        }\r
-        \r
-        /**\r
-         * Create a TestParams from a list of arguments.  If successful, return the params object,\r
-         * else return null.  Error messages will be reported on errlog if it is not null.\r
-         * Arguments and values understood by this method will be removed from the args array\r
-         * and existing args will be shifted down, to be filled by nulls at the end.\r
-         * @param args the list of arguments\r
-         * @param log the error log, or null if no error log is desired\r
-         * @return the new TestParams object, or null if error\r
-         */\r
-        public static TestParams create(String[] args, PrintWriter log) {\r
-            TestParams params = new TestParams();\r
-            \r
-            if(log == null){\r
-                params.log = new NullWriter();\r
-            }else{\r
-                params.log =  new ASCIIWriter(log, true);\r
-            }\r
-            \r
-            boolean usageError = false;\r
-            String filter = null;\r
-            int wx = 0; // write argets.\r
-            if (args != null) {\r
-                for (int i = 0; i < args.length; i++) {\r
-                    String arg = args[i];\r
-                    if (arg == null || arg.length() == 0) {\r
-                        continue;\r
-                    }\r
-                    if (arg.charAt(0) == '-') {\r
-                        arg = arg.toLowerCase();\r
-                        if (arg.equals("-verbose") || arg.equals("-v")) {\r
-                            params.verbose = true;\r
-                            params.quiet = false;\r
-                        } else if (arg.equals("-quiet") || arg.equals("-q")) {\r
-                            params.quiet = true;\r
-                            params.verbose = false;\r
-                        } else if (arg.equals("-help") || arg.equals("-h")) {\r
-                            usageError = true;\r
-                        } else if (arg.equals("-warning") || arg.equals("-w")) {\r
-                            params.warnings = true;\r
-                        } else if (arg.equals("-nodata") || arg.equals("-nd")) {\r
-                            params.nodata = true;\r
-                        } else if (arg.equals("-list") || arg.equals("-l")) {\r
-                            params.listlevel = 1;\r
-                        } else if (arg.equals("-listall") || arg.equals("-la")) {\r
-                            params.listlevel = 2;\r
-                        } else if (arg.equals("-listexaustive") || arg.equals("-le")) {\r
-                            params.listlevel = 3;\r
-                        } else if (arg.equals("-memory") || arg.equals("-m")) {\r
-                            params.memusage = true;\r
-                        } else if (arg.equals("-nothrow") || arg.equals("-n")) {\r
-                            params.nothrow = true;\r
-                            params.errorSummary = new StringBuffer();\r
-                        } else if (arg.equals("-describe") || arg.equals("-d")) {\r
-                            params.describe = true;\r
-                        } else if (arg.startsWith("-r")) {\r
-                            String s = null;\r
-                            int n = arg.indexOf(':');\r
-                            if (n != -1) {\r
-                                s = arg.substring(n + 1);\r
-                                arg = arg.substring(0, n);\r
-                            }\r
-\r
-                            if (arg.equals("-r") || arg.equals("-random")) {\r
-                                if (s == null) {\r
-                                    params.seed = System.currentTimeMillis();\r
-                                } else {\r
-                                    params.seed = Long.parseLong(s);\r
-                                }\r
-                            } else {\r
-                                log.println("*** Error: unrecognized argument: " + arg);\r
-                                usageError = true;\r
-                                break;\r
-                            }\r
-                        } else if (arg.startsWith("-e")) {\r
-                            // see above\r
-                            params.inclusion = (arg.length() == 2) \r
-                                ? 5 \r
-                                : Integer.parseInt(arg.substring(2));\r
-                            if (params.inclusion < 0 || params.inclusion > 10) {\r
-                                usageError = true;\r
-                                break;\r
-                            }\r
-                        } else if (arg.startsWith("-tfilter:")) {\r
-                            params.tfilter = arg.substring(8);\r
-                        } else if (arg.startsWith("-time") || arg.startsWith("-t")) {\r
-                            long val = 0;\r
-                            int inx = arg.indexOf(':');\r
-                            if (inx > 0) {\r
-                                String num = arg.substring(inx + 1);\r
-                                try {\r
-                                    val = Long.parseLong(num);\r
-                                } catch (Exception e) {\r
-                                    log.println("*** Error: could not parse time threshold '"\r
-                                                + num + "'");\r
-                                    usageError = true;\r
-                                    break;\r
-                                }\r
-                            }\r
-                            params.timing = val;\r
-                            String fmt = "#,00s";\r
-                            if (val <= 10) {\r
-                                fmt = "#,##0.000s";\r
-                            } else if (val <= 100) {\r
-                                fmt = "#,##0.00s";\r
-                            } else if (val <= 1000) {\r
-                                fmt = "#,##0.0s";\r
-                            }\r
-                            params.tformat = new DecimalFormat(fmt);\r
-                        } else if (arg.startsWith("-filter:")) {\r
-                            String temp = arg.substring(8).toLowerCase();\r
-                            filter = filter == null ? temp : filter + "," + temp;\r
-                        } else if (arg.startsWith("-f:")) {\r
-                            String temp = arg.substring(3).toLowerCase();\r
-                            filter = filter == null ? temp : filter + "," + temp;\r
-                        } else if (arg.startsWith("-s")) {\r
-                            params.log = new NullWriter();\r
-                        } else if (arg.startsWith("-u")) {\r
-                            if (params.log instanceof ASCIIWriter) {\r
-                                params.log = log;\r
-                            }\r
-                        } else if (arg.startsWith("-prop:")) {\r
-                            String temp = arg.substring(6);\r
-                            int eql = temp.indexOf('=');\r
-                            if (eql <= 0) {\r
-                                log.println("*** Error: could not parse custom property '" + arg + "'");\r
-                                usageError = true;\r
-                                break;\r
-                            }\r
-                            if (params.props == null) {\r
-                                params.props = new HashMap();\r
-                            }\r
-                            params.props.put(temp.substring(0, eql), temp.substring(eql+1));\r
-                        } else {\r
-                            log.println("*** Error: unrecognized argument: "\r
-                                        + args[i]);\r
-                            usageError = true;\r
-                            break;\r
-                        }\r
-                    } else {\r
-                        args[wx++] = arg; // shift down\r
-                    }\r
-                }\r
-\r
-                while (wx < args.length) {\r
-                    args[wx++] = null;\r
-                }\r
-            }\r
-            \r
-            if (usageError) {\r
-                usage(log, "TestAll");\r
-                return null;\r
-            }\r
-\r
-            if (filter != null) {\r
-                params.filter = filter.toLowerCase();\r
-            }\r
-\r
-            params.init();\r
-            \r
-            return params;\r
-        }\r
-        \r
-        public String errorSummary() {\r
-            return errorSummary == null ? "" : errorSummary.toString();\r
-        }\r
-\r
-        public void init() {\r
-            indentLevel = 0;\r
-            needLineFeed = false;\r
-            suppressIndent = false;\r
-            errorCount = 0;\r
-            warnCount = 0;\r
-            invalidCount = 0;\r
-            testCount = 0;\r
-            random = seed == 0 ? null : new Random(seed);\r
-        }\r
-\r
-        public class State {\r
-            State link;\r
-            String name;\r
-            StringBuffer buffer;\r
-            int level;\r
-            int ec;\r
-            int wc;\r
-            int ic;\r
-            int tc;\r
-            boolean flushed;\r
-            public boolean included;\r
-            long mem;\r
-            long millis;\r
-\r
-            public State(State link, String name, boolean included) {\r
-                this.link = link;\r
-                this.name = name;\r
-                if (link == null) {\r
-                    this.level = 0;\r
-                    this.included = included;\r
-                } else {\r
-                    this.level = link.level + 1;\r
-                    this.included = included || link.included;\r
-                }\r
-                this.ec = errorCount;\r
-                this.wc = warnCount;\r
-                this.ic = invalidCount;\r
-                this.tc = testCount;\r
-\r
-                if (link == null || this.included) {\r
-                    flush();\r
-                }\r
-\r
-                mem = getmem();\r
-                millis = System.currentTimeMillis();\r
-            }\r
-\r
-            void flush() {\r
-                if (!flushed) {\r
-                    if (link != null) {\r
-                        link.flush();\r
-                    }\r
-\r
-                    indent(level);\r
-                    log.print(name);\r
-                    log.flush();\r
-\r
-                    flushed = true;\r
-\r
-                    needLineFeed = true;\r
-                }\r
-            }\r
-\r
-            void appendPath(StringBuffer buf) {\r
-                if (this.link != null) {\r
-                    this.link.appendPath(buf);\r
-                    buf.append('/');\r
-                }\r
-                buf.append(name);\r
-            }\r
-        }\r
-\r
-        public void push(String name, String description, boolean included) {\r
-            if (inDocMode() && describe && description != null) {\r
-                name += ": " + description;\r
-            }\r
-            stack = new State(stack, name, included);\r
-        }\r
-\r
-        public void pop() {\r
-            if (stack != null) {\r
-                writeTestResult();\r
-                stack = stack.link;\r
-            }\r
-        }\r
-\r
-        public boolean inDocMode() {\r
-            return describe || listlevel != 0;\r
-        }\r
-\r
-        public boolean doMethods() {\r
-            return !inDocMode() || listlevel == 3\r
-                    || (indentLevel == 1 && listlevel > 0);\r
-        }\r
-\r
-        public boolean doRecurse() {\r
-            return !inDocMode() || listlevel > 1\r
-                    || (indentLevel == 1 && listlevel > 0);\r
-        }\r
-\r
-        public boolean doRecurseGroupsOnly() {\r
-            return inDocMode()\r
-                    && (listlevel == 2 || (indentLevel == 1 && listlevel > 0));\r
-        }\r
-\r
-        // return 0, -1, or 1\r
-        // 1: run this test\r
-        // 0: might run this test, no positive include or exclude on this group\r
-        // -1: exclude this test\r
-        public int filter(String testName) {\r
-            int result = 0;\r
-            if (filter == null) {\r
-                result = 1;\r
-            } else {\r
-                boolean noIncludes = true;\r
-                boolean noExcludes = filter.indexOf('^') == -1;\r
-                testName = testName.toLowerCase();\r
-                int ix = 0;\r
-                while (ix < filter.length()) {\r
-                    int nix = filter.indexOf(',', ix);\r
-                    if (nix == -1) {\r
-                        nix = filter.length();\r
-                    }\r
-                    if (filter.charAt(ix) == '^') {\r
-                        if (testName.indexOf(filter.substring(ix + 1, nix)) != -1) {\r
-                            result = -1;\r
-                            break;\r
-                        }\r
-                    } else {\r
-                        noIncludes = false;\r
-                        if (testName.indexOf(filter.substring(ix, nix)) != -1) {\r
-                            result = 1;\r
-                            if (noExcludes) {\r
-                                break;\r
-                            }\r
-                        }\r
-                    }\r
-\r
-                    ix = nix + 1;\r
-                }\r
-                if (result == 0 && noIncludes) {\r
-                    result = 1;\r
-                }\r
-            }\r
-            //              System.out.println("filter: " + testName + " returns: " +\r
-            // result);\r
-            return result;\r
-        }\r
-\r
-        /**\r
-         * Log access.\r
-         * @param msg The string message to write\r
-         */\r
-        public void write(String msg) {\r
-            write(msg, false);\r
-        }\r
-        \r
-        public void writeln(String msg) {\r
-            write(msg, true);\r
-        }\r
-        \r
-        private void write(String msg, boolean newln) {\r
-            if (!suppressIndent) {\r
-                if (needLineFeed) {\r
-                    log.println();\r
-                    needLineFeed = false;\r
-                }\r
-                log.print(spaces.substring(0, indentLevel * 2));\r
-            }\r
-            log.print(msg);\r
-            if (newln) {\r
-                log.println(); \r
-            }\r
-            log.flush();\r
-            suppressIndent = !newln;\r
-        }\r
-        \r
-        private void msg(String message, int level, boolean incCount,\r
-                boolean newln) {\r
-            if (level == WARN && (!warnings && !nodata)){\r
-                level = ERR;\r
-            }\r
-\r
-            if (incCount) {\r
-                if (level == WARN) {\r
-                    warnCount++;\r
-                    invalidCount++;\r
-                } else if (level == ERR) {\r
-                    errorCount++;\r
-                }\r
-            }\r
-\r
-            // should roll indentation stuff into log ???\r
-            if (verbose || level > (quiet ? WARN : LOG)) {\r
-                if (!suppressIndent) {\r
-                    indent(indentLevel + 1);\r
-                    final String[] MSGNAMES = {"", "Warning: ", "Error: "};\r
-                    log.print(MSGNAMES[level]);\r
-                }\r
-\r
-                log.print(message);\r
-                if (newln) {\r
-                    log.println();\r
-                }\r
-                log.flush();\r
-            }\r
-\r
-            if (level == ERR) {\r
-                if (!nothrow) {\r
-                    throw new RuntimeException(message);\r
-                }\r
-                if (!suppressIndent && errorSummary != null && stack !=null \r
-                        && (errorCount == stack.ec + 1)) {\r
-                    stack.appendPath(errorSummary);\r
-                    errorSummary.append("\n");\r
-                }\r
-            }\r
-\r
-            suppressIndent = !newln;\r
-        }\r
-\r
-        private void writeTestInvalid(String name, boolean nodataArg) {\r
-            //              msg("***" + name + "*** not found or not valid.", WARN, true,\r
-            // true);\r
-            if (inDocMode()) {\r
-                if (!warnings) {\r
-                    if (stack != null) {\r
-                        stack.flush();\r
-                    }\r
-                    log.println(" *** Target not found or not valid.");\r
-                    log.flush();\r
-                    needLineFeed = false;\r
-                }\r
-            } else {\r
-                if(!nodataArg){\r
-                    msg("Test " + name + " not found or not valid.", WARN, true,\r
-                        true);\r
-                }\r
-            }\r
-        }\r
-\r
-        long getmem() {\r
-            long newmem = 0;\r
-            if (memusage) {\r
-                Runtime rt = Runtime.getRuntime();\r
-                long lastmem = Long.MAX_VALUE;\r
-                do {\r
-                    rt.gc();\r
-                    rt.gc();\r
-                    try {\r
-                        Thread.sleep(50);\r
-                    } catch (Exception e) {\r
-                        break;\r
-                    }\r
-                    lastmem = newmem;\r
-                    newmem = rt.totalMemory() - rt.freeMemory();\r
-                } while (newmem < lastmem);\r
-            }\r
-            return newmem;\r
-        }\r
-\r
-        private void writeTestResult() {\r
-            if (inDocMode()) {\r
-                if (needLineFeed) {\r
-                    log.println();\r
-                    log.flush();\r
-                }\r
-                needLineFeed = false;\r
-                return;\r
-            }\r
-\r
-            long dmem = getmem() - stack.mem;\r
-            long dtime = System.currentTimeMillis() - stack.millis;\r
-\r
-            int testDelta = testCount - stack.tc;\r
-            if (testDelta == 0) {\r
-                return;\r
-            }\r
-\r
-            int errorDelta = errorCount - stack.ec;\r
-            int invalidDelta = invalidCount - stack.ic;\r
-\r
-            stack.flush();\r
-\r
-            if (!needLineFeed) {\r
-                indent(indentLevel);\r
-                log.print("}");\r
-            }\r
-            needLineFeed = false;\r
-\r
-            if (memusage || dtime >= timing) {\r
-                log.print(" (");\r
-                if (memusage) {\r
-                    log.print("dmem: " + dmem);\r
-                }\r
-                if (dtime >= timing) {\r
-                    if (memusage) {\r
-                        log.print(", ");\r
-                    }\r
-                    log.print(tformat.format(dtime / 1000f));\r
-                }\r
-                log.print(")");\r
-            }\r
-\r
-            if (errorDelta != 0) {\r
-                log.println(" FAILED ("\r
-                        + errorDelta\r
-                        + " failures"\r
-                        + ((invalidDelta != 0) ? ", " + invalidDelta\r
-                                + " tests skipped)" : ")"));\r
-            } else if (invalidDelta != 0) {\r
-                log.println(" Qualified (" + invalidDelta + " tests skipped)");\r
-            } else {\r
-                log.println(" Passed");\r
-            }\r
-        }\r
-\r
-        private final void indent(int distance) {\r
-            boolean idm = inDocMode();\r
-            if (needLineFeed) {\r
-                if (idm) {\r
-                    log.println();\r
-                } else {\r
-                    log.println(" {");\r
-                }\r
-                needLineFeed = false;\r
-            }\r
-\r
-            log.print(spaces.substring(0, distance * (idm ? 3 : 2)));\r
-\r
-            if (idm) {\r
-                log.print("-- ");\r
-            }\r
-        }\r
-    }\r
-\r
-    public String getTranslitTestFilter() {\r
-        return params.tfilter;\r
-    }\r
-\r
-    /**\r
-     * Return the target name for a test class. This is either the end of the\r
-     * class name, or if the class declares a public static field\r
-     * CLASS_TARGET_NAME, the value of that field.\r
-     */\r
-    private static String getClassTargetName(Class testClass) {\r
-        String name = testClass.getName();\r
-        try {\r
-            Field f = testClass.getField("CLASS_TARGET_NAME");\r
-            name = (String) f.get(null);\r
-        } catch (IllegalAccessException e) {\r
-            throw new IllegalStateException(\r
-                    "static field CLASS_TARGET_NAME must be accessible");\r
-        } catch (NoSuchFieldException e) {\r
-            int n = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));\r
-            if (n != -1) {\r
-                name = name.substring(n + 1);\r
-            }\r
-        }\r
-        return name;\r
-    }\r
-\r
-    /**\r
-     * Check the given array to see that all the strings in the expected array\r
-     * are present.\r
-     * \r
-     * @param msg\r
-     *            string message, for log output\r
-     * @param array\r
-     *            array of strings to check\r
-     * @param expected\r
-     *            array of strings we expect to see, or null\r
-     * @return the length of 'array', or -1 on error\r
-     */\r
-    protected int checkArray(String msg, String array[], String expected[]) {\r
-        int explen = (expected != null) ? expected.length : 0;\r
-        if (!(explen >= 0 && explen < 31)) { // [sic] 31 not 32\r
-            errln("Internal error");\r
-            return -1;\r
-        }\r
-        int i = 0;\r
-        StringBuffer buf = new StringBuffer();\r
-        int seenMask = 0;\r
-        for (; i < array.length; ++i) {\r
-            String s = array[i];\r
-            if (i != 0)\r
-                buf.append(", ");\r
-            buf.append(s);\r
-            // check expected list\r
-            for (int j = 0, bit = 1; j < explen; ++j, bit <<= 1) {\r
-                if ((seenMask & bit) == 0) {\r
-                    if (s.equals(expected[j])) {\r
-                        seenMask |= bit;\r
-                        logln("Ok: \"" + s + "\" seen");\r
-                    }\r
-                }\r
-            }\r
-        }\r
-        logln(msg + " = [" + buf + "] (" + i + ")");\r
-        // did we see all expected strings?\r
-        if (((1 << explen) - 1) != seenMask) {\r
-            for (int j = 0, bit = 1; j < expected.length; ++j, bit <<= 1) {\r
-                if ((seenMask & bit) == 0) {\r
-                    errln("\"" + expected[j] + "\" not seen");\r
-                }\r
-            }\r
-        }\r
-        return array.length;\r
-    }\r
-\r
-    /**\r
-     * Check the given array to see that all the locales in the expected array\r
-     * are present.\r
-     * \r
-     * @param msg\r
-     *            string message, for log output\r
-     * @param array\r
-     *            array of locales to check\r
-     * @param expected\r
-     *            array of locales names we expect to see, or null\r
-     * @return the length of 'array'\r
-     */\r
-    protected int checkArray(String msg, Locale array[], String expected[]) {\r
-        String strs[] = new String[array.length];\r
-        for (int i = 0; i < array.length; ++i)\r
-            strs[i] = array[i].toString();\r
-        return checkArray(msg, strs, expected);\r
-    }\r
-\r
-    /**\r
-     * Check the given array to see that all the locales in the expected array\r
-     * are present.\r
-     * \r
-     * @param msg\r
-     *            string message, for log output\r
-     * @param array\r
-     *            array of locales to check\r
-     * @param expected\r
-     *            array of locales names we expect to see, or null\r
-     * @return the length of 'array'\r
-     */\r
-    protected int checkArray(String msg, ULocale array[], String expected[]) {\r
-        String strs[] = new String[array.length];\r
-        for (int i = 0; i < array.length; ++i)\r
-            strs[i] = array[i].toString();\r
-        return checkArray(msg, strs, expected);\r
-    }\r
-\r
-    // JUnit-like assertions.\r
-\r
-    protected boolean assertTrue(String message, boolean condition) {\r
-        return handleAssert(condition, message, "true", null);\r
-    }\r
-\r
-    protected boolean assertFalse(String message, boolean condition) {\r
-        return handleAssert(!condition, message, "false", null);\r
-    }\r
-\r
-    protected boolean assertEquals(String message, boolean expected,\r
-            boolean actual) {\r
-        return handleAssert(expected == actual, message, String\r
-                .valueOf(expected), String.valueOf(actual));\r
-    }\r
-\r
-    protected boolean assertEquals(String message, long expected, long actual) {\r
-        return handleAssert(expected == actual, message, String\r
-                .valueOf(expected), String.valueOf(actual));\r
-    }\r
-\r
-    // do NaN and range calculations to precision of float, don't rely on\r
-    // promotion to double\r
-    protected boolean assertEquals(String message, float expected,\r
-            float actual, double error) {\r
-        boolean result = Float.isInfinite(expected)\r
-                ? expected == actual\r
-                : !(Math.abs(expected - actual) > error); // handles NaN\r
-        return handleAssert(result, message, String.valueOf(expected)\r
-                + (error == 0 ? "" : " (within " + error + ")"), String\r
-                .valueOf(actual));\r
-    }\r
-\r
-    protected boolean assertEquals(String message, double expected,\r
-            double actual, double error) {\r
-        boolean result = Double.isInfinite(expected)\r
-                ? expected == actual\r
-                : !(Math.abs(expected - actual) > error); // handles NaN\r
-        return handleAssert(result, message, String.valueOf(expected)\r
-                + (error == 0 ? "" : " (within " + error + ")"), String\r
-                .valueOf(actual));\r
-    }\r
-\r
-    protected boolean assertEquals(String message, Object expected,\r
-            Object actual) {\r
-        boolean result = expected == null ? actual == null : expected\r
-                .equals(actual);\r
-        return handleAssert(result, message, stringFor(expected),\r
-                stringFor(actual));\r
-    }\r
-\r
-    protected boolean assertNotEquals(String message, Object expected,\r
-            Object actual) {\r
-        boolean result = !(expected == null ? actual == null : expected\r
-                .equals(actual));\r
-        return handleAssert(result, message, stringFor(expected),\r
-                stringFor(actual), "not equal to", true);\r
-    }\r
-\r
-    protected boolean assertSame(String message, Object expected, Object actual) {\r
-        return handleAssert(expected == actual, message, stringFor(expected),\r
-                stringFor(actual), "==", false);\r
-    }\r
-\r
-    protected boolean assertNotSame(String message, Object expected,\r
-            Object actual) {\r
-        return handleAssert(expected != actual, message, stringFor(expected),\r
-                stringFor(actual), "!=", true);\r
-    }\r
-\r
-    protected boolean assertNull(String message, Object actual) {\r
-        return handleAssert(actual == null, message, null, stringFor(actual));\r
-    }\r
-\r
-    protected boolean assertNotNull(String message, Object actual) {\r
-        return handleAssert(actual != null, message, null, stringFor(actual),\r
-                "!=", true);\r
-    }\r
-\r
-    protected void fail(String message) {\r
-        errln(message);\r
-    }\r
-\r
-    private boolean handleAssert(boolean result, String message,\r
-            String expected, String actual) {\r
-        return handleAssert(result, message, expected, actual, null, false);\r
-    }\r
-\r
-    public boolean handleAssert(boolean result, String message,\r
-            Object expected, Object actual, String relation, boolean flip) {\r
-        if (!result || isVerbose()) {\r
-            message = message == null ? "" : " " + message;\r
-            relation = relation == null ? ", got " : " " + relation + " ";\r
-            if (result) {\r
-                logln("OK" + message + ": "\r
-                        + (flip ? expected + relation + actual : expected));\r
-            } else {\r
-                // assert must assume errors are true errors and not just warnings\r
-                // so cannot warnln here\r
-                errln(message\r
-                        + ": expected"\r
-                        + (flip ? relation + expected : " " + expected\r
-                                + (actual != null ? relation + actual : "")));\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    private final String stringFor(Object obj) {\r
-        if (obj == null)\r
-            return "null";\r
-        if (obj instanceof String)\r
-            return "\"" + obj + '"';\r
-        return obj.getClass().getName() + "<" + obj + ">";\r
-    }\r
-\r
-    // End JUnit-like assertions\r
-\r
-    // PrintWriter support\r
-\r
-    public PrintWriter getErrorLogPrintWriter() {\r
-        return new PrintWriter(new TestLogWriter(this, TestLog.ERR));\r
-    }\r
-\r
-    public PrintWriter getLogPrintWriter() {\r
-        return new PrintWriter(new TestLogWriter(this, TestLog.LOG));\r
-    }\r
-\r
-    // end PrintWriter support\r
-\r
-    protected TestParams params = null;\r
-\r
-    private final static String spaces = "                                          ";\r
-    \r
-}\r
+//##header J2SE15
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2009, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ */
+package com.ibm.icu.dev.test;
+
+import com.ibm.icu.text.UTF16;
+import com.ibm.icu.text.DecimalFormat;
+import com.ibm.icu.text.NumberFormat;
+import com.ibm.icu.util.TimeZone;
+import com.ibm.icu.util.ULocale;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.Random;
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//## import com.ibm.icu.impl.Utility;
+//#endif
+/**
+ * TestFmwk is a base class for tests that can be run conveniently from the
+ * command line as well as under the Java test harness.
+ * <p>
+ * Sub-classes implement a set of methods named Test <something>. Each of these
+ * methods performs some test. Test methods should indicate errors by calling
+ * either err or errln. This will increment the errorCount field and may
+ * optionally print a message to the log. Debugging information may also be
+ * added to the log via the log and logln methods. These methods will add their
+ * arguments to the log only if the test is being run in verbose mode.
+ */
+public class TestFmwk extends AbstractTestLog {
+    /**
+     * The default time zone for all of our tests. Used in Target.run();
+     */
+    private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("PST");
+
+    /**
+     * The default locale used for all of our tests. Used in Target.run();
+     */
+    private final static Locale defaultLocale = Locale.US;
+
+    public static final class TestFmwkException extends Exception {
+        /**
+         * For serialization
+         */
+        private static final long serialVersionUID = -3051148210247229194L;
+
+        TestFmwkException(String msg) {
+            super(msg);
+        }
+    }
+    protected void handleException(Throwable e){
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//##    Throwable ex = null;
+//#else
+        Throwable ex = e.getCause();
+//#endif
+        if(ex==null){
+            ex = e;
+        }
+        if(ex instanceof ExceptionInInitializerError){
+            ex = ((ExceptionInInitializerError)ex).getException();
+        }
+        String msg = ex.getMessage();
+        if(msg==null){
+            msg = "";
+        }
+        //System.err.println("TF handleException msg: " + msg);
+        if (ex instanceof MissingResourceException || ex instanceof NoClassDefFoundError || msg.indexOf("java.util.MissingResourceException")>=0) {
+            if (params.warnings || params.nodata) {
+                warnln(msg);
+            } else if (params.nothrow) {
+                errln(msg);
+                ex.printStackTrace();
+            } else {
+                ex.printStackTrace();
+                throw new RuntimeException(msg);
+            }
+        } else {
+            if (params.nothrow) {
+                errln(msg);
+                ex.printStackTrace();
+            } else {
+                errln(msg);
+                ex.printStackTrace();
+                throw new RuntimeException(msg);
+            }
+        }
+    }
+    // use this instead of new random so we get a consistent seed
+    // for our tests
+    protected Random createRandom() {
+        return new Random(params.seed);
+    }
+
+    /**
+     * A test that has no test methods itself, but instead runs other tests.
+     * 
+     * This overrides methods are getTargets and getSubtest from TestFmwk.
+     * 
+     * If you want the default behavior, pass an array of class names and an
+     * optional description to the constructor. The named classes must extend
+     * TestFmwk. If a provided name doesn't include a ".", package name is
+     * prefixed to it (the package of the current test is used if none was
+     * provided in the constructor). The resulting full name is used to
+     * instantiate an instance of the class using the default constructor.
+     * 
+     * Class names are resolved to classes when getTargets or getSubtest is
+     * called. This allows instances of TestGroup to be compiled and run without
+     * all the targets they would normally invoke being available.
+     */
+    public static abstract class TestGroup extends TestFmwk {
+        private String defaultPackage;
+        private String[] names;
+        private String description;
+
+        private Class[] tests; // deferred init
+
+        /**
+         * Constructor that takes a default package name and a list of class
+         * names. Adopts and modifies the classname list
+         */
+        protected TestGroup(String defaultPackage, String[] classnames,
+                String description) {
+            if (classnames == null) {
+                throw new IllegalStateException("classnames must not be null");
+            }
+
+            if (defaultPackage == null) {
+                defaultPackage = getClass().getPackage().getName();
+            }
+            defaultPackage = defaultPackage + ".";
+
+            this.defaultPackage = defaultPackage;
+            this.names = classnames;
+            this.description = description;
+        }
+
+        /**
+         * Constructor that takes a list of class names and a description, and
+         * uses the package for this class as the default package.
+         */
+        protected TestGroup(String[] classnames, String description) {
+            this(null, classnames, description);
+        }
+
+        /**
+         * Constructor that takes a list of class names, and uses the package
+         * for this class as the default package.
+         */
+        protected TestGroup(String[] classnames) {
+            this(null, classnames, null);
+        }
+
+        protected String getDescription() {
+            return description;
+        }
+
+        protected Target getTargets(String targetName) {
+            Target target = null;
+            if (targetName != null) {
+                finishInit(); // hmmm, want to get subtest without initializing
+                              // all tests
+
+                try {
+                    TestFmwk test = getSubtest(targetName);
+                    if (test != null) {
+                        target = test.new ClassTarget();
+                    } else {
+                        target = this.new Target(targetName);
+                    }
+                } catch (TestFmwkException e) {
+                    target = this.new Target(targetName);
+                }
+            } else if (params.doRecurse()) {
+                finishInit();
+                boolean groupOnly = params.doRecurseGroupsOnly();
+                for (int i = names.length; --i >= 0;) {
+                    Target newTarget = null;
+                    Class cls = tests[i];
+                    if (cls == null) { // hack no warning for missing tests
+                        if (params.warnings) {
+                            continue;
+                        }
+                        newTarget = this.new Target(names[i]);
+                    } else {
+                        TestFmwk test = getSubtest(i, groupOnly);
+                        if (test != null) {
+                            newTarget = test.new ClassTarget();
+                        } else {
+                            if (groupOnly) {
+                                newTarget = this.new EmptyTarget(names[i]);
+                            } else {
+                                newTarget = this.new Target(names[i]);
+                            }
+                        }
+                    }
+                    if (newTarget != null) {
+                        newTarget.setNext(target);
+                        target = newTarget;
+                    }
+                }
+            }
+
+            return target;
+        }
+        protected TestFmwk getSubtest(String testName) throws TestFmwkException {
+            finishInit();
+
+            for (int i = 0; i < names.length; ++i) {
+                if (names[i].equalsIgnoreCase(testName)) { // allow
+                                                           // case-insensitive
+                                                           // matching
+                    return getSubtest(i, false);
+                }
+            }
+            throw new TestFmwkException(testName);
+        }
+
+        private TestFmwk getSubtest(int i, boolean groupOnly) {
+            Class cls = tests[i];
+            if (cls != null) {
+                if (groupOnly && !TestGroup.class.isAssignableFrom(cls)) {
+                    return null;
+                }
+
+                try {
+                    TestFmwk subtest = (TestFmwk) cls.newInstance();
+                    subtest.params = params;
+                    return subtest;
+                } catch (InstantiationException e) {
+                    throw new IllegalStateException(e.getMessage());
+                } catch (IllegalAccessException e) {
+                    throw new IllegalStateException(e.getMessage());
+                }
+            }
+            return null;
+        }
+
+        private void finishInit() {
+            if (tests == null) {
+                tests = new Class[names.length];
+
+                for (int i = 0; i < names.length; ++i) {
+                    String name = names[i];
+                    if (name.indexOf('.') == -1) {
+                        name = defaultPackage + name;
+                    }
+                    try {
+                        Class cls = Class.forName(name);
+                        if (!TestFmwk.class.isAssignableFrom(cls)) {
+                            throw new IllegalStateException("class " + name
+                                    + " does not extend TestFmwk");
+                        }
+
+                        tests[i] = cls;
+                        names[i] = getClassTargetName(cls);
+                    } catch (ClassNotFoundException e) {
+                        // leave tests[i] null and name as classname
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * The default target is invalid.
+     */
+    public class Target {
+        private Target next;
+        public final String name;
+
+        public Target(String name) {
+            this.name = name;
+        }
+
+        public Target setNext(Target next) {
+            this.next = next;
+            return this;
+        }
+
+        public Target getNext() {
+            return next;
+        }
+
+        public Target append(Target targets) {
+            Target t = this;
+            while(t.next != null) {
+                t = t.next;
+            }
+            t.next = targets;
+            return this;
+        }
+
+        public void run() throws Exception {
+            int f = filter();
+            if (f == -1) {
+                ++params.invalidCount;
+            } else {
+                Locale.setDefault(defaultLocale);
+                TimeZone.setDefault(defaultTimeZone);
+
+                if (!validate()) {
+                    params.writeTestInvalid(name, params.nodata);
+                } else {
+                    params.push(name, getDescription(), f == 1);
+                    execute();
+                    params.pop();
+                }
+            }
+        }
+
+        protected int filter() {
+            return params.filter(name);
+        }
+
+        protected boolean validate() {
+            return false;
+        }
+
+        protected String getDescription() {
+            return null;
+        }
+
+        protected void execute() throws Exception{
+        }
+    }
+
+    public class EmptyTarget extends Target {
+        public EmptyTarget(String name) {
+            super(name);
+        }
+
+        protected boolean validate() {
+            return true;
+        }
+    }
+
+    public class MethodTarget extends Target {
+        private Method testMethod;
+
+        public MethodTarget(String name, Method method) {
+            super(name);
+            testMethod = method;
+        }
+
+        protected boolean validate() {
+            return testMethod != null && validateMethod(name);
+        }
+
+        protected String getDescription() {
+            return getMethodDescription(name);
+        }
+
+        protected void execute() throws Exception{
+            if (params.inDocMode()) {
+                // nothing to execute
+            } else if (!params.stack.included) {
+                ++params.invalidCount;
+            } else {
+                final Object[] NO_ARGS = new Object[0];
+                try {
+                    ++params.testCount;
+                    init();
+                    testMethod.invoke(TestFmwk.this, NO_ARGS);
+                } catch (IllegalAccessException e) {
+                    errln("Can't access test method " + testMethod.getName());
+                }catch (ExceptionInInitializerError e){
+                    handleException(e);
+                } catch (InvocationTargetException e) {
+                    //e.printStackTrace();
+                    handleException(e);
+                }catch (MissingResourceException e) {
+                    handleException(e);
+                }catch (NoClassDefFoundError e) {
+                    handleException(e);
+                }catch (Exception e){
+                    /*errln("Encountered: "+ e.toString());
+                    e.printStackTrace(System.err);
+                    */
+                    handleException(e);
+                }
+            }
+            // If non-exhaustive, check if the method target
+            // takes excessive time.
+            if (params.inclusion <= 5) {
+                double deltaSec = (double)(System.currentTimeMillis() - params.stack.millis)/1000;
+                if (deltaSec > params.maxTargetSec) {
+                    if (params.timeLog == null) {
+                        params.timeLog = new StringBuffer();
+                    }
+                    params.stack.appendPath(params.timeLog);
+                    params.timeLog.append(" (" + deltaSec + "s" + ")\n");
+                }
+            }
+        }
+
+        protected String getStackTrace(InvocationTargetException e) {
+            ByteArrayOutputStream bs = new ByteArrayOutputStream();
+            PrintStream ps = new PrintStream(bs);
+            e.getTargetException().printStackTrace(ps);
+            return bs.toString();
+        }
+    }
+
+    public class ClassTarget extends Target {
+        String targetName;
+
+        public ClassTarget() {
+            this(null);
+        }
+
+        public ClassTarget(String targetName) {
+            super(getClassTargetName(TestFmwk.this.getClass()));
+            this.targetName = targetName;
+        }
+
+        protected boolean validate() {
+            return TestFmwk.this.validate();
+        }
+
+        protected String getDescription() {
+            return TestFmwk.this.getDescription();
+        }
+
+        protected void execute() throws Exception {
+            params.indentLevel++;
+            Target target = randomize(getTargets(targetName));
+            while (target != null) {
+                target.run();
+                target = target.next;
+            }
+            params.indentLevel--;
+        }
+
+        private Target randomize(Target t) {
+            if (t != null && t.getNext() != null) {
+                ArrayList list = new ArrayList();
+                while (t != null) {
+                    list.add(t);
+                    t = t.getNext();
+                }
+
+                Target[] arr = (Target[]) list.toArray(new Target[list.size()]);
+
+                if (true) { // todo - add to params?
+                    // different jvms return class methods in different orders,
+                    // so we sort them (always, and then randomize them, so that
+                    // forcing a seed will also work across jvms).
+                    Arrays.sort(arr, new Comparator() {
+                        public int compare(Object lhs, Object rhs) {
+                            // sort in reverse order, later we link up in
+                            // forward order
+                            return ((Target) rhs).name
+                                    .compareTo(((Target) lhs).name);
+                        }
+                    });
+
+                    // t is null to start, ends up as first element
+                    // (arr[arr.length-1])
+                    for (int i = 0; i < arr.length; ++i) {
+                        t = arr[i].setNext(t); // relink in forward order
+                    }
+                }
+
+                if (params.random != null) {
+                    t = null; // reset t to null
+                    Random r = params.random;
+                    for (int i = arr.length; --i >= 1;) {
+                        int x = r.nextInt(i + 1);
+                        t = arr[x].setNext(t);
+                        arr[x] = arr[i];
+                    }
+
+                    t = arr[0].setNext(t); // new first element
+                }
+            }
+
+            return t;
+        }
+    }
+
+    //------------------------------------------------------------------------
+    // Everything below here is boilerplate code that makes it possible
+    // to add a new test by simply adding a function to an existing class
+    //------------------------------------------------------------------------
+
+    protected TestFmwk() {
+    }
+    
+    protected void init() throws Exception{
+    }
+    
+    /**
+     * Parse arguments into a TestParams object and a collection of target
+     * paths. If there was an error parsing the TestParams, print usage and exit
+     * with -1. Otherwise, call resolveTarget(TestParams, String) for each path,
+     * and run the returned target. After the last test returns, if prompt is
+     * set, prompt and wait for input from stdin. Finally, exit with number of
+     * errors.
+     * 
+     * This method never returns, since it always exits with System.exit();
+     */
+    public void run(String[] args) {
+        System.exit(run(args, new PrintWriter(System.out)));
+     }
+    
+    /**
+     * Like run(String[]) except this allows you to specify the error log.
+     * Unlike run(String[]) this returns the error code as a result instead of
+     * calling System.exit().
+     */
+    public int run(String[] args, PrintWriter log) {
+        boolean prompt = false;
+        int wx = 0;
+        for (int i = 0; i < args.length; ++i) {
+            String arg = args[i];
+            if (arg.equals("-p") || arg.equals("-prompt")) {
+                prompt = true;
+            } else {
+                if (wx < i) {
+                    args[wx] = arg;
+                }
+                wx++;
+            }
+        }
+        while (wx < args.length) {
+            args[wx++] = null;
+        }
+        
+        TestParams localParams = TestParams.create(args, log);
+        if (localParams == null) {
+            return -1;
+        }
+        
+        int errorCount = runTests(localParams, args);
+        
+        if (localParams.seed != 0) {
+            localParams.log.println("-random:" + localParams.seed);
+            localParams.log.flush();
+        }
+
+        if (localParams.errorSummary != null && localParams.errorSummary.length() > 0) {
+            localParams.log.println("\nError summary:");
+            localParams.log.println(localParams.errorSummary.toString());
+        }
+
+        if (localParams.timeLog != null && localParams.timeLog.length() > 0) {
+            localParams.log.println("\nTest cases taking excessive time (>" +
+                    localParams.maxTargetSec + "s):");
+            localParams.log.println(localParams.timeLog.toString());
+        }
+
+        if (prompt) {
+            System.out.println("Hit RETURN to exit...");
+            System.out.flush();
+            try {
+                System.in.read();
+            } catch (IOException e) {
+                localParams.log.println("Exception: " + e.toString() + e.getMessage());
+            }
+        }
+
+        return errorCount;
+    }
+
+    public int runTests(TestParams _params, String[] tests) {
+        int ec = 0;
+        
+        StringBuffer summary = null;
+        try {
+            if (tests.length == 0 || tests[0] == null) { // no args
+                _params.init();
+                resolveTarget(_params).run();
+                ec = _params.errorCount;
+            } else {
+                for (int i = 0; i < tests.length ; ++i) {
+                    if (tests[i] == null) continue;
+                    
+                    if (i > 0) {
+                        _params.log.println();
+                    }
+
+                    _params.init();
+                    resolveTarget(_params, tests[i]).run();
+                    ec += _params.errorCount;
+                    
+                    if (_params.errorSummary != null && _params.errorSummary.length() > 0) {
+                        if (summary == null) {
+                            summary = new StringBuffer();
+                        }
+                        summary.append("\nTest Root: " + tests[i] + "\n");
+                        summary.append(_params.errorSummary());
+                    }
+                }
+                _params.errorSummary = summary;
+            }
+        } catch (Exception e) {
+            e.printStackTrace(_params.log);
+            _params.log.println(e.getMessage());
+            _params.log.println("encountered exception, exiting");
+        }
+        
+        return ec;
+    }
+    
+    /**
+     * Return a ClassTarget for this test. Params is set on this test.
+     */
+    public Target resolveTarget(TestParams paramsArg) {
+        this.params = paramsArg;
+        return new ClassTarget();
+    }
+
+    /**
+     * Resolve a path from this test to a target. If this test has subtests, and
+     * the path contains '/', the portion before the '/' is resolved to a
+     * subtest, until the path is consumed or the test has no subtests. Returns
+     * a ClassTarget created using the resolved test and remaining path (which
+     * ought to be null or a method name). Params is set on the target's test.
+     */
+    public Target resolveTarget(TestParams paramsArg, String targetPath) {
+        TestFmwk test = this;
+        test.params = paramsArg;
+
+        if (targetPath != null) {
+            if (targetPath.length() == 0) {
+                targetPath = null;
+            } else {
+                int p = 0;
+                int e = targetPath.length();
+
+                // trim all leading and trailing '/'
+                while (targetPath.charAt(p) == '/') {
+                    ++p;
+                }
+                while (e > p && targetPath.charAt(e - 1) == '/') {
+                    --e;
+                }
+                if (p > 0 || e < targetPath.length()) {
+                    targetPath = targetPath.substring(p, e - p);
+                    p = 0;
+                    e = targetPath.length();
+                }
+
+                try {
+                    for (;;) {
+                        int n = targetPath.indexOf('/');
+                        String prefix = n == -1 ? targetPath : targetPath
+                                .substring(0, n);
+                        TestFmwk subtest = test.getSubtest(prefix);
+
+                        if (subtest == null) {
+                            break;
+                        }
+
+                        test = subtest;
+
+                        if (n == -1) {
+                            targetPath = null;
+                            break;
+                        }
+
+                        targetPath = targetPath.substring(n + 1);
+                    }
+                } catch (TestFmwkException ex) {
+                    return test.new Target(targetPath);
+                }
+            }
+        }
+
+        return test.new ClassTarget(targetPath);
+    }
+
+    /**
+     * Return true if we can run this test (allows test to inspect jvm,
+     * environment, params before running)
+     */
+    protected boolean validate() {
+        return true;
+    }
+
+    /**
+     * Return the targets for this test. If targetName is null, return all
+     * targets, otherwise return a target for just that name. The returned
+     * target can be null.
+     * 
+     * The default implementation returns a MethodTarget for each public method
+     * of the object's class whose name starts with "Test" or "test".
+     */
+    protected Target getTargets(String targetName) {
+        return getClassTargets(getClass(), targetName);
+    }
+
+    protected Target getClassTargets(Class cls, String targetName) {
+        if (cls == null) {
+            return null;
+        }
+
+        Target target = null;
+        if (targetName != null) {
+            try {
+                Method method = cls.getMethod(targetName, (Class[])null);
+                target = new MethodTarget(targetName, method);
+            } catch (NoSuchMethodException e) {
+        if (!inheritTargets()) {
+            return new Target(targetName); // invalid target
+        }
+            } catch (SecurityException e) {
+                return null;
+            }
+        } else {
+            if (params.doMethods()) {
+                Method[] methods = cls.getDeclaredMethods();
+                for (int i = methods.length; --i >= 0;) {
+                    String name = methods[i].getName();
+                    if (name.startsWith("Test") || name.startsWith("test")) {
+                        target = new MethodTarget(name, methods[i])
+                                .setNext(target);
+                    }
+                }
+            }
+        }
+
+        if (inheritTargets()) {
+          Target parentTarget = getClassTargets(cls.getSuperclass(), targetName);
+          if (parentTarget == null) {
+            return target;
+          }
+          if (target == null) {
+            return parentTarget;
+          }
+          return parentTarget.append(target);
+        }
+
+        return target;
+    }
+
+    protected boolean inheritTargets() {
+        return false;
+    }
+
+    protected String getDescription() {
+        return null;
+    }
+
+    protected boolean validateMethod(String name) {
+        return true;
+    }
+
+    protected String getMethodDescription(String name) {
+        return null;
+    }
+
+    // method tests have no subtests, group tests override
+    protected TestFmwk getSubtest(String prefix) throws TestFmwkException {
+        return null;
+    }
+
+    public boolean isVerbose() {
+        return params.verbose;
+    }
+
+    public boolean noData() {
+        return params.nodata;
+    }
+
+    public boolean isTiming() {
+        return params.timing < Long.MAX_VALUE;
+    }
+
+    public boolean isMemTracking() {
+        return params.memusage;
+    }
+
+    /**
+     * 0 = fewest tests, 5 is normal build, 10 is most tests
+     */
+    public int getInclusion() {
+        return params.inclusion;
+    }
+
+    public boolean isModularBuild() {
+        return params.warnings;
+    }
+
+    public boolean isQuick() {
+        return params.inclusion == 0;
+    }
+
+    public void msg(String message, int level, boolean incCount, boolean newln) {
+        params.msg(message, level, incCount, newln);
+    }
+
+    protected int getErrorCount() {
+        return params.errorCount;
+    }
+
+    public String getProperty(String key) {
+        String val = null;
+        if (key != null && key.length() > 0 && params.props != null) {
+            val = (String)params.props.get(key.toLowerCase());
+        }
+        return val;
+    }
+
+    protected TimeZone safeGetTimeZone(String id) {
+        TimeZone tz = TimeZone.getTimeZone(id);
+        if (tz == null) {
+            // should never happen
+            errln("FAIL: TimeZone.getTimeZone(" + id + ") => null");
+        }
+        if (!tz.getID().equals(id)) {
+            warnln("FAIL: TimeZone.getTimeZone(" + id + ") => " + tz.getID());
+        }
+        return tz;
+    }
+
+    /**
+     * Print a usage message for this test class.
+     */
+    public void usage() {
+        usage(new PrintWriter(System.out), getClass().getName());
+    }
+    
+    public static void usage(PrintWriter pw, String className) {
+        pw.println("Usage: " + className + " option* target*");
+        pw.println();
+        pw.println("Options:");
+        pw.println(" -d[escribe] Print a short descriptive string for this test and all");
+        pw.println("       listed targets.");
+        pw.println(" -e<n> Set exhaustiveness from 0..10.  Default is 0, fewest tests.\n"
+                 + "       To run all tests, specify -e10.  Giving -e with no <n> is\n"
+                 + "       the same as -e5.");
+        pw.println(" -filter:<str> Only tests matching filter will be run or listed.\n"
+                 + "       <str> is of the form ['^']text[','['^']text].\n"
+                 + "       Each string delimited by ',' is a separate filter argument.\n"
+                 + "       If '^' is prepended to an argument, its matches are excluded.\n"
+                 + "       Filtering operates on test groups as well as tests, if a test\n"
+                 + "       group is included, all its subtests that are not excluded will\n"
+                 + "       be run.  Examples:\n"
+                 + "    -filter:A -- only tests matching A are run.  If A matches a group,\n"
+                 + "       all subtests of this group are run.\n"
+                 + "    -filter:^A -- all tests except those matching A are run.  If A matches\n"
+                 + "        a group, no subtest of that group will be run.\n"
+                 + "    -filter:A,B,^C,^D -- tests matching A or B and not C and not D are run\n"
+                 + "       Note: Filters are case insensitive.");
+        pw.println(" -h[elp] Print this help text and exit.");
+        pw.println(" -l[ist] List immediate targets of this test");
+        pw.println("   -la, -listAll List immediate targets of this test, and all subtests");
+        pw.println("   -le, -listExaustive List all subtests and targets");
+        // don't know how to get useful numbers for memory usage using java API
+        // calls
+        //      pw.println(" -m[emory] print memory usage and force gc for
+        // each test");
+        pw.println(" -n[othrow] Message on test failure rather than exception");
+        pw.println(" -p[rompt] Prompt before exiting");
+        pw.println(" -prop:<key>=<value> Set optional property used by this test");
+        pw.println(" -q[uiet] Do not show warnings");
+        pw.println(" -r[andom][:<n>] If present, randomize targets.  If n is present,\n"
+                        + "       use it as the seed.  If random is not set, targets will\n"
+                        + "       be in alphabetical order to ensure cross-platform consistency.");
+        pw.println(" -s[ilent] No output except error summary or exceptions.");
+        pw.println(" -tfilter:<str> Transliterator Test filter of ids.");
+        pw.println(" -t[ime][:<n>] Print elapsed time for each test.  if n is present\n"
+                        + "       only print times >= n milliseconds.");
+        pw.println(" -v[erbose] Show log messages");
+        pw.println(" -u[nicode] Don't escape error or log messages");
+        pw.println(" -w[arning] Continue in presence of warnings, and disable missing test warnings.");
+        pw.println(" -nodata | -nd Do not warn if resource data is not present.");
+        pw.println();
+        pw.println(" If a list or describe option is provided, no tests are run.");
+        pw.println();
+        pw.println("Targets:");
+        pw.println(" If no target is specified, all targets for this test are run.");
+        pw.println(" If a target contains no '/' characters, and matches a target");
+        pw.println(" of this test, the target is run.  Otherwise, the part before the");
+        pw.println(" '/' is used to match a subtest, which then evaluates the");
+        pw.println(" remainder of the target as above.  Target matching is case-insensitive.");
+        pw.println();
+        pw.println(" If multiple targets are provided, each is executed in order.");
+        pw.flush();
+    }
+    public static String hex(char[] s){
+        StringBuffer result = new StringBuffer();
+        for (int i = 0; i < s.length; ++i) {
+            if (i != 0) result.append(',');
+            result.append(hex(s[i]));
+        }
+        return result.toString();
+    }
+    public static String hex(byte[] s){
+        StringBuffer result = new StringBuffer();
+        for (int i = 0; i < s.length; ++i) {
+            if (i != 0) result.append(',');
+            result.append(hex(s[i]));
+        }
+        return result.toString();
+    }
+    public static String hex(char ch) {
+        StringBuffer result = new StringBuffer();
+        String foo = Integer.toString(ch, 16).toUpperCase();
+        for (int i = foo.length(); i < 4; ++i) {
+            result.append('0');
+        }
+        return result + foo;
+    }
+
+    public static String hex(int ch) {
+        StringBuffer result = new StringBuffer();
+        String foo = Integer.toString(ch, 16).toUpperCase();
+        for (int i = foo.length(); i < 4; ++i) {
+            result.append('0');
+        }
+        return result + foo;
+    }
+
+    public static String hex(String s) {
+        StringBuffer result = new StringBuffer();
+        for (int i = 0; i < s.length(); ++i) {
+            if (i != 0)
+                result.append(',');
+            result.append(hex(s.charAt(i)));
+        }
+        return result.toString();
+    }
+
+    public static String hex(StringBuffer s) {
+        return hex(s.toString());
+    }
+    public static String prettify(String s) {
+        StringBuffer result = new StringBuffer();
+        int ch;
+        for (int i = 0; i < s.length(); i += UTF16.getCharCount(ch)) {
+            ch = UTF16.charAt(s, i);
+            if (ch > 0xfffff) {
+                result.append("\\U00");
+                result.append(hex(ch));
+            } else if (ch > 0xffff) {
+                result.append("\\U000");
+                result.append(hex(ch));
+            } else if (ch > 0x7f) {
+                result.append("\\u");
+                result.append(hex(ch));
+            } else {
+                result.append((char) ch);
+            }
+
+        }
+        return result.toString();
+    }
+    public static String prettify(StringBuffer s) {
+        return prettify(s.toString());
+    }
+
+    private static java.util.GregorianCalendar cal;
+
+    /**
+     * Return a Date given a year, month, and day of month. This is similar to
+     * new Date(y-1900, m, d). It uses the default time zone at the time this
+     * method is first called.
+     * 
+     * @param year
+     *            use 2000 for 2000, unlike new Date()
+     * @param month
+     *            use Calendar.JANUARY etc.
+     * @param dom
+     *            day of month, 1-based
+     * @return a Date object for the given y/m/d
+     */
+    protected static synchronized java.util.Date getDate(int year, int month,
+            int dom) {
+        if (cal == null) {
+            cal = new java.util.GregorianCalendar();
+        }
+        cal.clear();
+        cal.set(year, month, dom);
+        return cal.getTime();
+    }
+
+    public static class NullWriter extends PrintWriter {
+        public NullWriter() {
+            super(System.out, false);
+        }
+        public void write(int c) {
+        }
+        public void write(char[] buf, int off, int len) {
+        }
+        public void write(String s, int off, int len) {
+        }
+        public void println() {
+        }
+    }
+
+    public static class ASCIIWriter extends PrintWriter {
+        private StringBuffer buffer = new StringBuffer();
+
+        // Characters that we think are printable but that escapeUnprintable
+        // doesn't
+        private static final String PRINTABLES = "\t\n\r";
+
+        public ASCIIWriter(Writer w, boolean autoFlush) {
+            super(w, autoFlush);
+        }
+
+        public ASCIIWriter(OutputStream os, boolean autoFlush) {
+            super(os, autoFlush);
+        }
+
+        public void write(int c) {
+            synchronized (lock) {
+                buffer.setLength(0);
+                if (PRINTABLES.indexOf(c) < 0
+                        && TestUtil.escapeUnprintable(buffer, c)) {
+                    super.write(buffer.toString());
+                } else {
+                    super.write(c);
+                }
+            }
+        }
+
+        public void write(char[] buf, int off, int len) {
+            synchronized (lock) {
+                buffer.setLength(0);
+                int limit = off + len;
+                while (off < limit) {
+                    int c = UTF16Util.charAt(buf, 0, buf.length, off);
+                    off += UTF16Util.getCharCount(c);
+                    if (PRINTABLES.indexOf(c) < 0
+                            && TestUtil.escapeUnprintable(buffer, c)) {
+                        super.write(buffer.toString());
+                        buffer.setLength(0);
+                    } else {
+                        super.write(c);
+                    }
+                }
+            }
+        }
+
+        public void write(String s, int off, int len) {
+            write(s.substring(off, off + len).toCharArray(), 0, len);
+        }
+    }
+
+    // filters
+    // match against the entire hierarchy
+    // A;B;!C;!D --> (A ||B) && (!C && !D)
+    // positive, negative, unknown matches
+    // positive -- known to be included, negative- known to be excluded
+    // positive only if no excludes, and matches at least one include, if any
+    // negative only if matches at least one exclude
+    // otherwise, we wait
+
+    public static class TestParams {
+        public boolean prompt;
+        public boolean nothrow;
+        public boolean verbose;
+        public boolean quiet;
+        public int listlevel;
+        public boolean describe;
+        public boolean warnings;
+        public boolean nodata;
+        public long timing = Long.MAX_VALUE;
+        public boolean memusage;
+        public int inclusion;
+        public String filter;
+        public long seed;
+        public String tfilter; // for transliterator tests
+
+        public State stack;
+
+        public StringBuffer errorSummary;
+        private StringBuffer timeLog;
+
+        public PrintWriter log;
+        public int indentLevel;
+        private boolean needLineFeed;
+        private boolean suppressIndent;
+        public int errorCount;
+        public int warnCount;
+        public int invalidCount;
+        public int testCount;
+        private NumberFormat tformat;
+        public Random random;
+        public int maxTargetSec = 10;
+        public HashMap props;
+
+        private TestParams() {
+        }
+        
+        public static TestParams create(String arglist, PrintWriter log) {
+            String[] args = null;
+            if (arglist != null && arglist.length() > 0) {
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//##            args = Utility.split(arglist, '\u0020');
+//#else
+                args = arglist.split("\\s");
+//#endif
+            }
+            return create(args, log);
+        }
+        
+        /**
+         * Create a TestParams from a list of arguments.  If successful, return the params object,
+         * else return null.  Error messages will be reported on errlog if it is not null.
+         * Arguments and values understood by this method will be removed from the args array
+         * and existing args will be shifted down, to be filled by nulls at the end.
+         * @param args the list of arguments
+         * @param log the error log, or null if no error log is desired
+         * @return the new TestParams object, or null if error
+         */
+        public static TestParams create(String[] args, PrintWriter log) {
+            TestParams params = new TestParams();
+            
+            if(log == null){
+                params.log = new NullWriter();
+            }else{
+                params.log =  new ASCIIWriter(log, true);
+            }
+            
+            boolean usageError = false;
+            String filter = null;
+            int wx = 0; // write argets.
+            if (args != null) {
+                for (int i = 0; i < args.length; i++) {
+                    String arg = args[i];
+                    if (arg == null || arg.length() == 0) {
+                        continue;
+                    }
+                    if (arg.charAt(0) == '-') {
+                        arg = arg.toLowerCase();
+                        if (arg.equals("-verbose") || arg.equals("-v")) {
+                            params.verbose = true;
+                            params.quiet = false;
+                        } else if (arg.equals("-quiet") || arg.equals("-q")) {
+                            params.quiet = true;
+                            params.verbose = false;
+                        } else if (arg.equals("-help") || arg.equals("-h")) {
+                            usageError = true;
+                        } else if (arg.equals("-warning") || arg.equals("-w")) {
+                            params.warnings = true;
+                        } else if (arg.equals("-nodata") || arg.equals("-nd")) {
+                            params.nodata = true;
+                        } else if (arg.equals("-list") || arg.equals("-l")) {
+                            params.listlevel = 1;
+                        } else if (arg.equals("-listall") || arg.equals("-la")) {
+                            params.listlevel = 2;
+                        } else if (arg.equals("-listexaustive") || arg.equals("-le")) {
+                            params.listlevel = 3;
+                        } else if (arg.equals("-memory") || arg.equals("-m")) {
+                            params.memusage = true;
+                        } else if (arg.equals("-nothrow") || arg.equals("-n")) {
+                            params.nothrow = true;
+                            params.errorSummary = new StringBuffer();
+                        } else if (arg.equals("-describe") || arg.equals("-d")) {
+                            params.describe = true;
+                        } else if (arg.startsWith("-r")) {
+                            String s = null;
+                            int n = arg.indexOf(':');
+                            if (n != -1) {
+                                s = arg.substring(n + 1);
+                                arg = arg.substring(0, n);
+                            }
+
+                            if (arg.equals("-r") || arg.equals("-random")) {
+                                if (s == null) {
+                                    params.seed = System.currentTimeMillis();
+                                } else {
+                                    params.seed = Long.parseLong(s);
+                                }
+                            } else {
+                                log.println("*** Error: unrecognized argument: " + arg);
+                                usageError = true;
+                                break;
+                            }
+                        } else if (arg.startsWith("-e")) {
+                            // see above
+                            params.inclusion = (arg.length() == 2) 
+                                ? 5 
+                                : Integer.parseInt(arg.substring(2));
+                            if (params.inclusion < 0 || params.inclusion > 10) {
+                                usageError = true;
+                                break;
+                            }
+                        } else if (arg.startsWith("-tfilter:")) {
+                            params.tfilter = arg.substring(8);
+                        } else if (arg.startsWith("-time") || arg.startsWith("-t")) {
+                            long val = 0;
+                            int inx = arg.indexOf(':');
+                            if (inx > 0) {
+                                String num = arg.substring(inx + 1);
+                                try {
+                                    val = Long.parseLong(num);
+                                } catch (Exception e) {
+                                    log.println("*** Error: could not parse time threshold '"
+                                                + num + "'");
+                                    usageError = true;
+                                    break;
+                                }
+                            }
+                            params.timing = val;
+                            String fmt = "#,00s";
+                            if (val <= 10) {
+                                fmt = "#,##0.000s";
+                            } else if (val <= 100) {
+                                fmt = "#,##0.00s";
+                            } else if (val <= 1000) {
+                                fmt = "#,##0.0s";
+                            }
+                            params.tformat = new DecimalFormat(fmt);
+                        } else if (arg.startsWith("-filter:")) {
+                            String temp = arg.substring(8).toLowerCase();
+                            filter = filter == null ? temp : filter + "," + temp;
+                        } else if (arg.startsWith("-f:")) {
+                            String temp = arg.substring(3).toLowerCase();
+                            filter = filter == null ? temp : filter + "," + temp;
+                        } else if (arg.startsWith("-s")) {
+                            params.log = new NullWriter();
+                        } else if (arg.startsWith("-u")) {
+                            if (params.log instanceof ASCIIWriter) {
+                                params.log = log;
+                            }
+                        } else if (arg.startsWith("-prop:")) {
+                            String temp = arg.substring(6);
+                            int eql = temp.indexOf('=');
+                            if (eql <= 0) {
+                                log.println("*** Error: could not parse custom property '" + arg + "'");
+                                usageError = true;
+                                break;
+                            }
+                            if (params.props == null) {
+                                params.props = new HashMap();
+                            }
+                            params.props.put(temp.substring(0, eql), temp.substring(eql+1));
+                        } else {
+                            log.println("*** Error: unrecognized argument: "
+                                        + args[i]);
+                            usageError = true;
+                            break;
+                        }
+                    } else {
+                        args[wx++] = arg; // shift down
+                    }
+                }
+
+                while (wx < args.length) {
+                    args[wx++] = null;
+                }
+            }
+            
+            if (usageError) {
+                usage(log, "TestAll");
+                return null;
+            }
+
+            if (filter != null) {
+                params.filter = filter.toLowerCase();
+            }
+
+            params.init();
+            
+            return params;
+        }
+        
+        public String errorSummary() {
+            return errorSummary == null ? "" : errorSummary.toString();
+        }
+
+        public void init() {
+            indentLevel = 0;
+            needLineFeed = false;
+            suppressIndent = false;
+            errorCount = 0;
+            warnCount = 0;
+            invalidCount = 0;
+            testCount = 0;
+            random = seed == 0 ? null : new Random(seed);
+        }
+
+        public class State {
+            State link;
+            String name;
+            StringBuffer buffer;
+            int level;
+            int ec;
+            int wc;
+            int ic;
+            int tc;
+            boolean flushed;
+            public boolean included;
+            long mem;
+            long millis;
+
+            public State(State link, String name, boolean included) {
+                this.link = link;
+                this.name = name;
+                if (link == null) {
+                    this.level = 0;
+                    this.included = included;
+                } else {
+                    this.level = link.level + 1;
+                    this.included = included || link.included;
+                }
+                this.ec = errorCount;
+                this.wc = warnCount;
+                this.ic = invalidCount;
+                this.tc = testCount;
+
+                if (link == null || this.included) {
+                    flush();
+                }
+
+                mem = getmem();
+                millis = System.currentTimeMillis();
+            }
+
+            void flush() {
+                if (!flushed) {
+                    if (link != null) {
+                        link.flush();
+                    }
+
+                    indent(level);
+                    log.print(name);
+                    log.flush();
+
+                    flushed = true;
+
+                    needLineFeed = true;
+                }
+            }
+
+            void appendPath(StringBuffer buf) {
+                if (this.link != null) {
+                    this.link.appendPath(buf);
+                    buf.append('/');
+                }
+                buf.append(name);
+            }
+        }
+
+        public void push(String name, String description, boolean included) {
+            if (inDocMode() && describe && description != null) {
+                name += ": " + description;
+            }
+            stack = new State(stack, name, included);
+        }
+
+        public void pop() {
+            if (stack != null) {
+                writeTestResult();
+                stack = stack.link;
+            }
+        }
+
+        public boolean inDocMode() {
+            return describe || listlevel != 0;
+        }
+
+        public boolean doMethods() {
+            return !inDocMode() || listlevel == 3
+                    || (indentLevel == 1 && listlevel > 0);
+        }
+
+        public boolean doRecurse() {
+            return !inDocMode() || listlevel > 1
+                    || (indentLevel == 1 && listlevel > 0);
+        }
+
+        public boolean doRecurseGroupsOnly() {
+            return inDocMode()
+                    && (listlevel == 2 || (indentLevel == 1 && listlevel > 0));
+        }
+
+        // return 0, -1, or 1
+        // 1: run this test
+        // 0: might run this test, no positive include or exclude on this group
+        // -1: exclude this test
+        public int filter(String testName) {
+            int result = 0;
+            if (filter == null) {
+                result = 1;
+            } else {
+                boolean noIncludes = true;
+                boolean noExcludes = filter.indexOf('^') == -1;
+                testName = testName.toLowerCase();
+                int ix = 0;
+                while (ix < filter.length()) {
+                    int nix = filter.indexOf(',', ix);
+                    if (nix == -1) {
+                        nix = filter.length();
+                    }
+                    if (filter.charAt(ix) == '^') {
+                        if (testName.indexOf(filter.substring(ix + 1, nix)) != -1) {
+                            result = -1;
+                            break;
+                        }
+                    } else {
+                        noIncludes = false;
+                        if (testName.indexOf(filter.substring(ix, nix)) != -1) {
+                            result = 1;
+                            if (noExcludes) {
+                                break;
+                            }
+                        }
+                    }
+
+                    ix = nix + 1;
+                }
+                if (result == 0 && noIncludes) {
+                    result = 1;
+                }
+            }
+            //              System.out.println("filter: " + testName + " returns: " +
+            // result);
+            return result;
+        }
+
+        /**
+         * Log access.
+         * @param msg The string message to write
+         */
+        public void write(String msg) {
+            write(msg, false);
+        }
+        
+        public void writeln(String msg) {
+            write(msg, true);
+        }
+        
+        private void write(String msg, boolean newln) {
+            if (!suppressIndent) {
+                if (needLineFeed) {
+                    log.println();
+                    needLineFeed = false;
+                }
+                log.print(spaces.substring(0, indentLevel * 2));
+            }
+            log.print(msg);
+            if (newln) {
+                log.println(); 
+            }
+            log.flush();
+            suppressIndent = !newln;
+        }
+        
+        private void msg(String message, int level, boolean incCount,
+                boolean newln) {
+            if (level == WARN && (!warnings && !nodata)){
+                level = ERR;
+            }
+
+            if (incCount) {
+                if (level == WARN) {
+                    warnCount++;
+                    invalidCount++;
+                } else if (level == ERR) {
+                    errorCount++;
+                }
+            }
+
+            // should roll indentation stuff into log ???
+            if (verbose || level > (quiet ? WARN : LOG)) {
+                if (!suppressIndent) {
+                    indent(indentLevel + 1);
+                    final String[] MSGNAMES = {"", "Warning: ", "Error: "};
+                    log.print(MSGNAMES[level]);
+                }
+
+                log.print(message);
+                if (newln) {
+                    log.println();
+                }
+                log.flush();
+            }
+
+            if (level == ERR) {
+                if (!nothrow) {
+                    throw new RuntimeException(message);
+                }
+                if (!suppressIndent && errorSummary != null && stack !=null 
+                        && (errorCount == stack.ec + 1)) {
+                    stack.appendPath(errorSummary);
+                    errorSummary.append("\n");
+                }
+            }
+
+            suppressIndent = !newln;
+        }
+
+        private void writeTestInvalid(String name, boolean nodataArg) {
+            //              msg("***" + name + "*** not found or not valid.", WARN, true,
+            // true);
+            if (inDocMode()) {
+                if (!warnings) {
+                    if (stack != null) {
+                        stack.flush();
+                    }
+                    log.println(" *** Target not found or not valid.");
+                    log.flush();
+                    needLineFeed = false;
+                }
+            } else {
+                if(!nodataArg){
+                    msg("Test " + name + " not found or not valid.", WARN, true,
+                        true);
+                }
+            }
+        }
+
+        long getmem() {
+            long newmem = 0;
+            if (memusage) {
+                Runtime rt = Runtime.getRuntime();
+                long lastmem = Long.MAX_VALUE;
+                do {
+                    rt.gc();
+                    rt.gc();
+                    try {
+                        Thread.sleep(50);
+                    } catch (Exception e) {
+                        break;
+                    }
+                    lastmem = newmem;
+                    newmem = rt.totalMemory() - rt.freeMemory();
+                } while (newmem < lastmem);
+            }
+            return newmem;
+        }
+
+        private void writeTestResult() {
+            if (inDocMode()) {
+                if (needLineFeed) {
+                    log.println();
+                    log.flush();
+                }
+                needLineFeed = false;
+                return;
+            }
+
+            long dmem = getmem() - stack.mem;
+            long dtime = System.currentTimeMillis() - stack.millis;
+
+            int testDelta = testCount - stack.tc;
+            if (testDelta == 0) {
+                return;
+            }
+
+            int errorDelta = errorCount - stack.ec;
+            int invalidDelta = invalidCount - stack.ic;
+
+            stack.flush();
+
+            if (!needLineFeed) {
+                indent(indentLevel);
+                log.print("}");
+            }
+            needLineFeed = false;
+
+            if (memusage || dtime >= timing) {
+                log.print(" (");
+                if (memusage) {
+                    log.print("dmem: " + dmem);
+                }
+                if (dtime >= timing) {
+                    if (memusage) {
+                        log.print(", ");
+                    }
+                    log.print(tformat.format(dtime / 1000f));
+                }
+                log.print(")");
+            }
+
+            if (errorDelta != 0) {
+                log.println(" FAILED ("
+                        + errorDelta
+                        + " failures"
+                        + ((invalidDelta != 0) ? ", " + invalidDelta
+                                + " tests skipped)" : ")"));
+            } else if (invalidDelta != 0) {
+                log.println(" Qualified (" + invalidDelta + " tests skipped)");
+            } else {
+                log.println(" Passed");
+            }
+        }
+
+        private final void indent(int distance) {
+            boolean idm = inDocMode();
+            if (needLineFeed) {
+                if (idm) {
+                    log.println();
+                } else {
+                    log.println(" {");
+                }
+                needLineFeed = false;
+            }
+
+            log.print(spaces.substring(0, distance * (idm ? 3 : 2)));
+
+            if (idm) {
+                log.print("-- ");
+            }
+        }
+    }
+
+    public String getTranslitTestFilter() {
+        return params.tfilter;
+    }
+
+    /**
+     * Return the target name for a test class. This is either the end of the
+     * class name, or if the class declares a public static field
+     * CLASS_TARGET_NAME, the value of that field.
+     */
+    private static String getClassTargetName(Class testClass) {
+        String name = testClass.getName();
+        try {
+            Field f = testClass.getField("CLASS_TARGET_NAME");
+            name = (String) f.get(null);
+        } catch (IllegalAccessException e) {
+            throw new IllegalStateException(
+                    "static field CLASS_TARGET_NAME must be accessible");
+        } catch (NoSuchFieldException e) {
+            int n = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));
+            if (n != -1) {
+                name = name.substring(n + 1);
+            }
+        }
+        return name;
+    }
+
+    /**
+     * Check the given array to see that all the strings in the expected array
+     * are present.
+     * 
+     * @param msg
+     *            string message, for log output
+     * @param array
+     *            array of strings to check
+     * @param expected
+     *            array of strings we expect to see, or null
+     * @return the length of 'array', or -1 on error
+     */
+    protected int checkArray(String msg, String array[], String expected[]) {
+        int explen = (expected != null) ? expected.length : 0;
+        if (!(explen >= 0 && explen < 31)) { // [sic] 31 not 32
+            errln("Internal error");
+            return -1;
+        }
+        int i = 0;
+        StringBuffer buf = new StringBuffer();
+        int seenMask = 0;
+        for (; i < array.length; ++i) {
+            String s = array[i];
+            if (i != 0)
+                buf.append(", ");
+            buf.append(s);
+            // check expected list
+            for (int j = 0, bit = 1; j < explen; ++j, bit <<= 1) {
+                if ((seenMask & bit) == 0) {
+                    if (s.equals(expected[j])) {
+                        seenMask |= bit;
+                        logln("Ok: \"" + s + "\" seen");
+                    }
+                }
+            }
+        }
+        logln(msg + " = [" + buf + "] (" + i + ")");
+        // did we see all expected strings?
+        if (((1 << explen) - 1) != seenMask) {
+            for (int j = 0, bit = 1; j < expected.length; ++j, bit <<= 1) {
+                if ((seenMask & bit) == 0) {
+                    errln("\"" + expected[j] + "\" not seen");
+                }
+            }
+        }
+        return array.length;
+    }
+
+    /**
+     * Check the given array to see that all the locales in the expected array
+     * are present.
+     * 
+     * @param msg
+     *            string message, for log output
+     * @param array
+     *            array of locales to check
+     * @param expected
+     *            array of locales names we expect to see, or null
+     * @return the length of 'array'
+     */
+    protected int checkArray(String msg, Locale array[], String expected[]) {
+        String strs[] = new String[array.length];
+        for (int i = 0; i < array.length; ++i)
+            strs[i] = array[i].toString();
+        return checkArray(msg, strs, expected);
+    }
+
+    /**
+     * Check the given array to see that all the locales in the expected array
+     * are present.
+     * 
+     * @param msg
+     *            string message, for log output
+     * @param array
+     *            array of locales to check
+     * @param expected
+     *            array of locales names we expect to see, or null
+     * @return the length of 'array'
+     */
+    protected int checkArray(String msg, ULocale array[], String expected[]) {
+        String strs[] = new String[array.length];
+        for (int i = 0; i < array.length; ++i)
+            strs[i] = array[i].toString();
+        return checkArray(msg, strs, expected);
+    }
+
+    // JUnit-like assertions.
+
+    protected boolean assertTrue(String message, boolean condition) {
+        return handleAssert(condition, message, "true", null);
+    }
+
+    protected boolean assertFalse(String message, boolean condition) {
+        return handleAssert(!condition, message, "false", null);
+    }
+
+    protected boolean assertEquals(String message, boolean expected,
+            boolean actual) {
+        return handleAssert(expected == actual, message, String
+                .valueOf(expected), String.valueOf(actual));
+    }
+
+    protected boolean assertEquals(String message, long expected, long actual) {
+        return handleAssert(expected == actual, message, String
+                .valueOf(expected), String.valueOf(actual));
+    }
+
+    // do NaN and range calculations to precision of float, don't rely on
+    // promotion to double
+    protected boolean assertEquals(String message, float expected,
+            float actual, double error) {
+        boolean result = Float.isInfinite(expected)
+                ? expected == actual
+                : !(Math.abs(expected - actual) > error); // handles NaN
+        return handleAssert(result, message, String.valueOf(expected)
+                + (error == 0 ? "" : " (within " + error + ")"), String
+                .valueOf(actual));
+    }
+
+    protected boolean assertEquals(String message, double expected,
+            double actual, double error) {
+        boolean result = Double.isInfinite(expected)
+                ? expected == actual
+                : !(Math.abs(expected - actual) > error); // handles NaN
+        return handleAssert(result, message, String.valueOf(expected)
+                + (error == 0 ? "" : " (within " + error + ")"), String
+                .valueOf(actual));
+    }
+
+    protected boolean assertEquals(String message, Object expected,
+            Object actual) {
+        boolean result = expected == null ? actual == null : expected
+                .equals(actual);
+        return handleAssert(result, message, stringFor(expected),
+                stringFor(actual));
+    }
+
+    protected boolean assertNotEquals(String message, Object expected,
+            Object actual) {
+        boolean result = !(expected == null ? actual == null : expected
+                .equals(actual));
+        return handleAssert(result, message, stringFor(expected),
+                stringFor(actual), "not equal to", true);
+    }
+
+    protected boolean assertSame(String message, Object expected, Object actual) {
+        return handleAssert(expected == actual, message, stringFor(expected),
+                stringFor(actual), "==", false);
+    }
+
+    protected boolean assertNotSame(String message, Object expected,
+            Object actual) {
+        return handleAssert(expected != actual, message, stringFor(expected),
+                stringFor(actual), "!=", true);
+    }
+
+    protected boolean assertNull(String message, Object actual) {
+        return handleAssert(actual == null, message, null, stringFor(actual));
+    }
+
+    protected boolean assertNotNull(String message, Object actual) {
+        return handleAssert(actual != null, message, null, stringFor(actual),
+                "!=", true);
+    }
+
+    protected void fail(String message) {
+        errln(message);
+    }
+
+    private boolean handleAssert(boolean result, String message,
+            String expected, String actual) {
+        return handleAssert(result, message, expected, actual, null, false);
+    }
+
+    public boolean handleAssert(boolean result, String message,
+            Object expected, Object actual, String relation, boolean flip) {
+        if (!result || isVerbose()) {
+            message = message == null ? "" : " " + message;
+            relation = relation == null ? ", got " : " " + relation + " ";
+            if (result) {
+                logln("OK" + message + ": "
+                        + (flip ? expected + relation + actual : expected));
+            } else {
+                // assert must assume errors are true errors and not just warnings
+                // so cannot warnln here
+                errln(message
+                        + ": expected"
+                        + (flip ? relation + expected : " " + expected
+                                + (actual != null ? relation + actual : "")));
+            }
+        }
+        return result;
+    }
+
+    private final String stringFor(Object obj) {
+        if (obj == null)
+            return "null";
+        if (obj instanceof String)
+            return "\"" + obj + '"';
+        return obj.getClass().getName() + "<" + obj + ">";
+    }
+
+    // End JUnit-like assertions
+
+    // PrintWriter support
+
+    public PrintWriter getErrorLogPrintWriter() {
+        return new PrintWriter(new TestLogWriter(this, TestLog.ERR));
+    }
+
+    public PrintWriter getLogPrintWriter() {
+        return new PrintWriter(new TestLogWriter(this, TestLog.LOG));
+    }
+
+    // end PrintWriter support
+
+    protected TestParams params = null;
+
+    private final static String spaces = "                                          ";
+    
+}