6966737: (pack200) the pack200 regression tests need to be more robust.
authorksrini
Fri, 20 Aug 2010 08:18:54 -0700
changeset 6314 8ab691ddb904
parent 6313 470912c9e214
child 6315 a02eddc607b7
6966737: (pack200) the pack200 regression tests need to be more robust. Reviewed-by: jrose, ohair
jdk/test/tools/pack200/CommandLineTests.java
jdk/test/tools/pack200/Pack200Simple.sh
jdk/test/tools/pack200/Pack200Test.java
jdk/test/tools/pack200/PackageVersionTest.java
jdk/test/tools/pack200/SegmentLimit.java
jdk/test/tools/pack200/Utils.java
jdk/test/tools/pack200/pack200-verifier/data/README
jdk/test/tools/pack200/pack200-verifier/data/golden.jar
jdk/test/tools/pack200/pack200-verifier/make/build.xml
jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/ClassCompare.java
jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Globals.java
jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/JarFileCompare.java
jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Main.java
jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/VerifyTreeSet.java
jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassReader.java
jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassSyntax.java
jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassWriter.java
jdk/test/tools/pack200/pack200-verifier/src/xmlkit/CommandLineParser.java
jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionAssembler.java
jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionSyntax.java
jdk/test/tools/pack200/pack200-verifier/src/xmlkit/TokenList.java
jdk/test/tools/pack200/pack200-verifier/src/xmlkit/XMLKit.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/CommandLineTests.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2007, 2010 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test CommandLineTests.sh
+ * @bug  6521334 6965836 6965836
+ * @compile -XDignore.symbol.file CommandLineTests.java Pack200Test.java
+ * @run main/timeout=1200 CommandLineTests
+ * @summary An ad hoc test to verify the behavior of pack200/unpack200 CLIs,
+ *           and a simulation of pack/unpacking in the install repo.
+ * @author ksrini
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+/*
+ * We try a potpouri of things ie. we have pack.conf to setup some
+ * options as well as a couple of command line options. We also test
+ * the packing and unpacking mechanism using the Java APIs. This also
+ * simulates pack200 the install workspace, noting that this is a simulation
+ * and can only test jars that are guaranteed to be available, also the
+ * configuration may not be in sync with the installer workspace.
+ */
+
+public class CommandLineTests {
+    private static final File CWD = new File(".");
+    private static final File EXP_SDK = new File(CWD, "exp-sdk-image");
+    private static final File EXP_SDK_LIB_DIR = new File(EXP_SDK, "lib");
+    private static final File EXP_SDK_BIN_DIR = new File(EXP_SDK, "bin");
+    private static final File EXP_JRE_DIR = new File(EXP_SDK, "jre");
+    private static final File EXP_JRE_LIB_DIR = new File(EXP_JRE_DIR, "lib");
+    private static final File RtJar = new File(EXP_JRE_LIB_DIR, "rt.jar");
+    private static final File CharsetsJar = new File(EXP_JRE_LIB_DIR, "charsets.jar");
+    private static final File JsseJar = new File(EXP_JRE_LIB_DIR, "jsse.jar");
+    private static final File ToolsJar = new File(EXP_SDK_LIB_DIR, "tools.jar");
+    private static final File javaCmd;
+    private static final File javacCmd;
+    private static final File ConfigFile = new File("pack.conf");
+    private static final List<File> jarList;
+
+    static {
+        javaCmd = Utils.IsWindows
+                    ? new File(EXP_SDK_BIN_DIR, "java.exe")
+                    : new File(EXP_SDK_BIN_DIR, "java");
+
+        javacCmd = Utils.IsWindows
+                    ? new File(EXP_SDK_BIN_DIR, "javac.exe")
+                    : new File(EXP_SDK_BIN_DIR, "javac");
+
+        jarList = new ArrayList<File>();
+        jarList.add(RtJar);
+        jarList.add(CharsetsJar);
+        jarList.add(JsseJar);
+        jarList.add(ToolsJar);
+    }
+
+    // init test area with a copy of the sdk
+    static void init() throws IOException {
+            Utils.recursiveCopy(Utils.JavaSDK, EXP_SDK);
+            creatConfigFile();
+    }
+
+    // Hopefully, this should be kept in sync with what the installer does.
+    static void creatConfigFile() throws IOException {
+        FileOutputStream fos = null;
+        PrintStream ps = null;
+        try {
+            fos = new FileOutputStream(ConfigFile);
+            ps = new PrintStream(fos);
+            ps.println("com.sun.java.util.jar.pack.debug.verbose=0");
+            ps.println("pack.modification.time=keep");
+            ps.println("pack.keep.class.order=true");
+            ps.println("pack.deflate.hint=false");
+            // Fail the build, if new or unknown attributes are introduced.
+            ps.println("pack.unknown.attribute=error");
+            ps.println("pack.segment.limit=-1");
+            // BugId: 6328502,  These files will be passed-through as-is.
+            ps.println("pack.pass.file.0=java/lang/Error.class");
+            ps.println("pack.pass.file.1=java/lang/LinkageError.class");
+            ps.println("pack.pass.file.2=java/lang/Object.class");
+            ps.println("pack.pass.file.3=java/lang/Throwable.class");
+            ps.println("pack.pass.file.4=java/lang/VerifyError.class");
+            ps.println("pack.pass.file.5=com/sun/demo/jvmti/hprof/Tracker.class");
+        } finally {
+            Utils.close(ps);
+            Utils.close(fos);
+        }
+    }
+
+    static void runPack200(boolean jre) throws IOException {
+        List<String> cmdsList = new ArrayList<String>();
+        for (File f : jarList) {
+            if (jre && f.getName().equals("tools.jar")) {
+                continue;  // need not worry about tools.jar for JRE
+            }
+            // make a backup copy for re-use
+            File bakFile = new File(f.getName() + ".bak");
+            if (!bakFile.exists()) {  // backup
+                Utils.copyFile(f.getAbsoluteFile(), bakFile.getAbsoluteFile());
+            } else {  // restore
+                Utils.copyFile(bakFile.getAbsoluteFile(), f.getAbsoluteFile());
+            }
+            cmdsList.clear();
+            cmdsList.add(Utils.getPack200Cmd());
+            cmdsList.add("-J-esa");
+            cmdsList.add("-J-ea");
+            cmdsList.add(Utils.Is64Bit ? "-J-Xmx1g" : "-J-Xmx512m");
+            cmdsList.add("--repack");
+            cmdsList.add("--config-file=" + ConfigFile.getAbsolutePath());
+            if (jre) {
+                cmdsList.add("--strip-debug");
+            }
+            // NOTE: commented until 6965836 is fixed
+            // cmdsList.add("--code-attribute=StackMapTable=strip");
+            cmdsList.add(f.getAbsolutePath());
+            Utils.runExec(cmdsList);
+        }
+    }
+
+    static void testJRE() throws IOException {
+        runPack200(true);
+        // the speciment JRE
+        List<String> cmdsList = new ArrayList<String>();
+        cmdsList.add(javaCmd.getAbsolutePath());
+        cmdsList.add("-verify");
+        cmdsList.add("-version");
+        Utils.runExec(cmdsList);
+    }
+
+    static void testJDK() throws IOException {
+        runPack200(false);
+        // test the specimen JDK
+        List<String> cmdsList = new ArrayList<String>();
+        cmdsList.add(javaCmd.getAbsolutePath());
+        cmdsList.add("-verify");
+        cmdsList.add("-version");
+        Utils.runExec(cmdsList);
+
+        // invoke javac to test the tools.jar
+        cmdsList.clear();
+        cmdsList.add(javacCmd.getAbsolutePath());
+        cmdsList.add("-J-verify");
+        cmdsList.add("-help");
+        Utils.runExec(cmdsList);
+    }
+    public static void main(String... args) {
+        try {
+            init();
+            testJRE();
+            testJDK();
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+}
--- a/jdk/test/tools/pack200/Pack200Simple.sh	Thu Aug 19 14:08:04 2010 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-#
-# Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved.
-# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
-#
-# This code is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 2 only, as
-# published by the Free Software Foundation.
-#
-# This code is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-# version 2 for more details (a copy is included in the LICENSE file that
-# accompanied this code).
-#
-# You should have received a copy of the GNU General Public License version
-# 2 along with this work; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
-# or visit www.oracle.com if you need additional information or have any
-# questions.
-#
-
-# @test Pack200Simple.sh
-# @bug  6521334
-# @build Pack200Test
-# @run shell/timeout=1200 Pack200Simple.sh
-# @summary An ad hoc test to verify class-file format.
-# @author Kumar Srinivasan
-
-# The goal of this test is to assist javac or other developers 
-# who modify class file formats, to quickly test those modifications
-# without having to build the install workspace. However it must
-# be noted that building the install workspace is the only know
-# way to prevent build breakages.
-
-# Pack200 developers could use this  as a basic smoke-test, however 
-# please note, there are other more elaborate and  thorough tests for 
-# this very purpose. 
-
-# We try a potpouri of things ie. we have pack.conf to setup some 
-# options as well as a couple of command line options. We also test
-# the packing and unpacking mechanism using the Java APIs.
-
-# print error and exit with a message 
-errorOut() {
-  if [ "x$1" = "x" ]; then
-    printf "Error: Unknown error\n"
-  else
-    printf "Error: %s\n" "$1"
-  fi
-
-  exit 1
-}
-
-# Verify directory context variables are set
-if [ "${TESTJAVA}" = "" ]; then
-  errorOut "TESTJAVA not set.  Test cannot execute.  Failed."
-fi
-
-if [ "${TESTSRC}" = "" ]; then
-  errorOut "TESTSRC not set.  Test cannot execute.  Failed."
-fi
-
-
-if [ "${TESTCLASSES}" = "" ]; then
-  errorOut "TESTCLASSES not set.  Test cannot execute.  Failed."
-fi
-
-# The common java utils we need
-PACK200=${TESTJAVA}/bin/pack200
-UNPACK200=${TESTJAVA}/bin/unpack200
-JAR=${TESTJAVA}/bin/jar
-
-# For Windows and Linux needs the heap to be set, for others ergonomics
-# will do the rest. It is important to use ea, which can expose class
-# format errors much earlier than later.
-
-OS=`uname -s`
-
-
-case "$OS" in
-  Windows*|CYGWIN* )
-    PackOptions="-J-Xmx512m -J-ea"
-    break
-    ;;
-
-  Linux )
-    PackOptions="-J-Xmx512m -J-ea"
-    break
-    ;;
-
-  * )
-    PackOptions="-J-ea"
-    ;;
-esac
-
-# Creates a packfile of choice expects 1 argument the filename
-createConfigFile() {
- # optimize for speed
- printf "pack.effort=1\n" > $1
- # we DO want to know about new attributes
- printf "pack.unknown.attribute=error\n" >> $1
- # optimize for speed
- printf "pack.deflate.hint=false\n" >> $1
- # keep the ordering for easy compare
- printf "pack.keep.class.order=true\n" >> $1
-}
-
-
-# Tests a given jar, expects 1 argument the fully qualified
-# name to a test jar, it writes all output to the current
-# directory which is a scratch  area.
-testAJar() {
-  PackConf="pack.conf"
-  createConfigFile $PackConf
-
-  # Try some command line options
-  CLIPackOptions="$PackOptions -v --no-gzip --segment-limit=10000 --config-file=$PackConf"
-
-  jfName=`basename $1`
-
-  ${PACK200}   $CLIPackOptions  ${jfName}.pack $1 > ${jfName}.pack.log 2>&1
-  if [ $? != 0 ]; then
-    errorOut "$jfName packing failed"
-  fi
-
-  # We want to test unpack200, therefore we dont use -r with pack
-  ${UNPACK200} -v ${jfName}.pack $jfName > ${jfName}.unpack.log 2>&1
-  if [ $? != 0 ]; then
-    errorOut "$jfName unpacking failed"
-  fi
-
-  # A quick crc compare test to ensure a well formed zip
-  # archive, this is a critical unpack200 behaviour.
-
-  unzip -t $jfName > ${jfName}.unzip.log 2>&1 
-  if [ $? != 0 ]; then
-    errorOut "$jfName unzip -t test failed"
-  fi
-
-  # The PACK200 signature should be at the top of the log
-  # this tag is critical for deployment related tools.
-
-  head -5 ${jfName}.unzip.log | grep PACK200 > /dev/null 2>&1
-  if [ $? != 0 ]; then
-    errorOut "$jfName PACK200 signature missing"
-  fi
-
-
-  # we know  the size fields don't match, strip 'em out, its
-  # extremely important to ensure that the date stamps match up.
-  # Don't EVER sort the output we are checking for correct ordering.
-
-  ${JAR} -tvf $1 | sed -e 's/^ *[0-9]* //g'> ${jfName}.ref.txt
-  ${JAR} -tvf $jfName | sed -e 's/^ *[0-9]* //g'> ${jfName}.cmp.txt
-
-  diff ${jfName}.ref.txt ${jfName}.cmp.txt > ${jfName}.diff.log 2>&1
-  if [ $? != 0 ]; then
-    errorOut "$jfName files missing"
-  fi
-}
-
-# These JARs are the largest and also the most likely specimens to 
-# expose class format issues and stress the packer as well.
-
-JLIST="${TESTJAVA}/lib/tools.jar ${TESTJAVA}/jre/lib/rt.jar"
-
-
-# Test the Command Line Interfaces (CLI).
-mkdir cliTestDir 
-_pwd=`pwd`
-cd cliTestDir
-
-for jarfile in $JLIST ; do 
-  if [ -f $jarfile ]; then
-    testAJar $jarfile
-  else
-    errorOut "Error: '$jarFile' does not exist\nTest requires a j2sdk-image\n"
-  fi
-done
-cd $_pwd
-
-# Test the Java APIs.
-mkdir apiTestDir 
-_pwd=`pwd`
-cd  apiTestDir
-
-# Strip out the -J prefixes.
-JavaPackOptions=`printf %s "$PackOptions" | sed -e 's/-J//g'`
-
-# Test the Java APIs now.
-$TESTJAVA/bin/java $JavaPackOptions -cp $TESTCLASSES Pack200Test $JLIST || exit 1
-
-cd $_pwd
-
-exit 0
--- a/jdk/test/tools/pack200/Pack200Test.java	Thu Aug 19 14:08:04 2010 -0700
+++ b/jdk/test/tools/pack200/Pack200Test.java	Fri Aug 20 08:18:54 2010 -0700
@@ -24,111 +24,97 @@
 
 import java.util.*;
 import java.io.*;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
 import java.util.jar.*;
-import java.util.zip.*;
 
-/*
- * Pack200Test.java
- *
- * @author ksrini
- */
+ /*
+  * @test
+  * @bug 6521334 6712743
+  * @summary check for memory leaks, test general packer/unpacker functionality\
+  *          using native and java unpackers
+  * @compile -XDignore.symbol.file Utils.java Pack200Test.java
+  * @run main/othervm/timeout=1200 -Xmx512m Pack200Test
+  * @author ksrini
+  */
 
 /**
- * These tests are very rudimentary smoke tests to ensure that the packing
- * unpacking process works on a select set of JARs.
+ * Tests the packing/unpacking via the APIs.
  */
 public class Pack200Test {
 
     private static ArrayList <File> jarList = new ArrayList<File>();
-    static final String PACKEXT = ".pack";
+    static final MemoryMXBean mmxbean = ManagementFactory.getMemoryMXBean();
+    static final long m0 = getUsedMemory();
+    static final int LEAK_TOLERANCE = 20000; // OS and GC related variations.
 
     /** Creates a new instance of Pack200Test */
     private Pack200Test() {}
 
-    private static void doPackUnpack() {
-        for (File in : jarList) {
-            Pack200.Packer packer = Pack200.newPacker();
-            Map<String, String> p = packer.properties();
-            // Take the time optimization vs. space
-            p.put(packer.EFFORT, "1");  // CAUTION: do not use 0.
-            // Make the memory consumption as effective as possible
-            p.put(packer.SEGMENT_LIMIT,"10000");
-            // throw an error if an attribute is unrecognized
-            p.put(packer.UNKNOWN_ATTRIBUTE, packer.ERROR);
-            // ignore all JAR deflation requests to save time
-            p.put(packer.DEFLATE_HINT, packer.FALSE);
-            // save the file ordering of the original JAR
-            p.put(packer.KEEP_FILE_ORDER, packer.TRUE);
-
-            try {
-                JarFile jarFile = new JarFile(in);
-
-                // Write out to a jtreg scratch area
-                FileOutputStream fos = new FileOutputStream(in.getName() + PACKEXT);
+    static long getUsedMemory() {
+        mmxbean.gc();
+        mmxbean.gc();
+        mmxbean.gc();
+        return mmxbean.getHeapMemoryUsage().getUsed()/1024;
+    }
 
-                System.out.print("Packing [" + in.toString() + "]...");
-                // Call the packer
-                packer.pack(jarFile, fos);
-                jarFile.close();
-                fos.close();
-
-                System.out.print("Unpacking...");
-                File f = new File(in.getName() + PACKEXT);
-
-                // Write out to current directory, jtreg will setup a scratch area
-                JarOutputStream jostream = new JarOutputStream(new FileOutputStream(in.getName()));
-
-                // Unpack the files
-                Pack200.Unpacker unpacker = Pack200.newUnpacker();
-                // Call the unpacker
-                unpacker.unpack(f, jostream);
-                // Must explicitly close the output.
-                jostream.close();
-                System.out.print("Testing...");
-                // Ok we have unpacked the file, lets test it.
-                doTest(in);
-                System.out.println("Done.");
-            } catch (Exception e) {
-                System.out.println("ERROR: " + e.getMessage());
-                System.exit(1);
-            }
+    private static void leakCheck() throws Exception {
+        long diff = getUsedMemory() - m0;
+        System.out.println("  Info: memory diff = " + diff + "K");
+        if ( diff  > LEAK_TOLERANCE) {
+            throw new Exception("memory leak detected " + diff);
         }
     }
 
-    private static ArrayList <String> getZipFileEntryNames(ZipFile z) {
-        ArrayList <String> out = new ArrayList<String>();
-        for (ZipEntry ze : Collections.list(z.entries())) {
-            out.add(ze.getName());
-        }
-        return out;
-    }
+    private static void doPackUnpack() {
+        for (File in : jarList) {
+            JarOutputStream javaUnpackerStream = null;
+            JarOutputStream nativeUnpackerStream = null;
+            JarFile jarFile = null;
+            try {
+                jarFile = new JarFile(in);
 
-    private static void doTest(File in) throws Exception {
-       // make sure all the files in the original jar exists in the other
-       ArrayList <String> refList = getZipFileEntryNames(new ZipFile(in));
-       ArrayList <String> cmpList = getZipFileEntryNames(new ZipFile(in.getName()));
+                // Write out to a jtreg scratch area
+                File packFile = new File(in.getName() + Utils.PACK_FILE_EXT);
 
-       System.out.print(refList.size() + "/" + cmpList.size() + " entries...");
+                System.out.println("Packing [" + in.toString() + "]");
+                // Call the packer
+                Utils.pack(jarFile, packFile);
+                jarFile.close();
+                leakCheck();
 
-       if (refList.size() != cmpList.size()) {
-           throw new Exception("Missing: files ?, entries don't match");
-       }
+                System.out.println("  Unpacking using java unpacker");
+                File javaUnpackedJar = new File("java-" + in.getName());
+                // Write out to current directory, jtreg will setup a scratch area
+                javaUnpackerStream = new JarOutputStream(
+                        new FileOutputStream(javaUnpackedJar));
+                Utils.unpackj(packFile, javaUnpackerStream);
+                javaUnpackerStream.close();
+                System.out.println("  Testing...java unpacker");
+                leakCheck();
+                // Ok we have unpacked the file, lets test it.
+                Utils.doCompareVerify(in.getAbsoluteFile(), javaUnpackedJar);
 
-       for (String ename: refList) {
-          if (!cmpList.contains(ename)) {
-              throw new Exception("Does not contain : " + ename);
-          }
-       }
-    }
-
-    private static void doSanity(String[] args) {
-        for (String s: args) {
-            File f = new File(s);
-            if (f.exists()) {
-                jarList.add(f);
-            } else {
-                System.out.println("Warning: The JAR file " + f.toString() + " does not exist,");
-                System.out.println("         this test requires a JDK image, this file will be skipped.");
+                System.out.println("  Unpacking using native unpacker");
+                // Write out to current directory
+                File nativeUnpackedJar = new File("native-" + in.getName());
+                nativeUnpackerStream = new JarOutputStream(
+                        new FileOutputStream(nativeUnpackedJar));
+                Utils.unpackn(packFile, nativeUnpackerStream);
+                nativeUnpackerStream.close();
+                System.out.println("  Testing...native unpacker");
+                leakCheck();
+                // the unpackers (native and java) should produce identical bits
+                // so we use use bit wise compare, the verification compare is
+                // very expensive wrt. time.
+                Utils.doCompareBitWise(javaUnpackedJar, nativeUnpackedJar);
+                System.out.println("Done.");
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            } finally {
+                Utils.close(nativeUnpackerStream);
+                Utils.close(javaUnpackerStream);
+                Utils.close((Closeable) jarFile);
             }
         }
     }
@@ -137,11 +123,12 @@
      * @param args the command line arguments
      */
     public static void main(String[] args) {
-        if (args.length < 1) {
-            System.out.println("Usage: jar1 jar2 jar3 .....");
-            System.exit(1);
-        }
-        doSanity(args);
+        // select the jars carefully, adding more jars will increase the
+        // testing time, especially for jprt.
+        jarList.add(Utils.locateJar("tools.jar"));
+        jarList.add(Utils.locateJar("rt.jar"));
+        jarList.add(Utils.locateJar("golden.jar"));
+        System.out.println(jarList);
         doPackUnpack();
     }
 }
--- a/jdk/test/tools/pack200/PackageVersionTest.java	Thu Aug 19 14:08:04 2010 -0700
+++ b/jdk/test/tools/pack200/PackageVersionTest.java	Fri Aug 20 08:18:54 2010 -0700
@@ -22,13 +22,14 @@
  * questions.
  */
 
-/**
-  * @test
-  * @bug 6712743
-  * @summary verify package versioning
-  * @compile -XDignore.symbol.file PackageVersionTest.java
-  * @run main PackageVersionTest
-  */
+/*
+ * @test
+ * @bug 6712743
+ * @summary verify package versions
+ * @compile -XDignore.symbol.file Utils.java PackageVersionTest.java
+ * @run main PackageVersionTest
+ * @author ksrini
+ */
 
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
@@ -74,14 +75,6 @@
                 JAVA5_PACKAGE_MINOR_VERSION);
     }
 
-    static void close(Closeable c) {
-        if (c == null) {
-            return;
-        }
-        try {
-            c.close();
-        } catch (IOException ignore) {}
-    }
 
     static void createClassFile(String name) {
         createJavaFile(name);
@@ -93,7 +86,7 @@
             name.substring(name.length() - 1),
             name + ".java"
         };
-        compileJava(javacCmds);
+        Utils.compiler(javacCmds);
     }
 
     static void createJavaFile(String name) {
@@ -108,22 +101,8 @@
         } catch (IOException ioe) {
             throw new RuntimeException("creation of test file failed");
         } finally {
-            close(ps);
-            close(fos);
-        }
-    }
-
-    static void compileJava(String... javacCmds) {
-        if (com.sun.tools.javac.Main.compile(javacCmds) != 0) {
-            throw new RuntimeException("compilation failed");
-        }
-    }
-
-    static void makeJar(String... jargs) {
-        sun.tools.jar.Main jarTool =
-                new sun.tools.jar.Main(System.out, System.err, "jartool");
-        if (!jarTool.run(jargs)) {
-            throw new RuntimeException("jar command failed");
+            Utils.close(ps);
+            Utils.close(fos);
         }
     }
 
@@ -136,7 +115,7 @@
             jarFileName.getName(),
             filename
         };
-        makeJar(jargs);
+        Utils.jar(jargs);
         JarFile jfin = null;
 
         try {
@@ -163,7 +142,7 @@
         } catch (IOException ioe) {
             throw new RuntimeException(ioe.getMessage());
         } finally {
-            close(jfin);
+            Utils.close((Closeable) jfin);
         }
     }
 }
--- a/jdk/test/tools/pack200/SegmentLimit.java	Thu Aug 19 14:08:04 2010 -0700
+++ b/jdk/test/tools/pack200/SegmentLimit.java	Fri Aug 20 08:18:54 2010 -0700
@@ -21,22 +21,18 @@
  * questions.
  */
 
-/**
+/*
  * @test
  * @bug 6575373
  * @summary verify default segment limit
- * @compile SegmentLimit.java
+ * @compile -XDignore.symbol.file Utils.java SegmentLimit.java
  * @run main SegmentLimit
+ * @author ksrini
  */
 
-import java.io.BufferedReader;
-import java.io.Closeable;
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
 
 /*
  * Run this against a large jar file, by default the packer should generate only
@@ -45,89 +41,36 @@
 
 public class SegmentLimit {
 
-    private static final File  javaHome = new File(System.getProperty("java.home"));
-
     public static void main(String... args) {
-        if (!javaHome.getName().endsWith("jre")) {
-            throw new RuntimeException("Error: requires an SDK to run");
-        }
-
-        File out = new File("test" + Pack200Test.PACKEXT);
+        File out = new File("test" + Utils.PACK_FILE_EXT);
         out.delete();
         runPack200(out);
     }
 
-    static void close(Closeable c) {
-        if (c == null) {
-            return;
-        }
-        try {
-            c.close();
-        } catch (IOException ignore) {}
-    }
-
     static void runPack200(File outFile) {
-        File binDir = new File(javaHome, "bin");
-        File pack200Exe = System.getProperty("os.name").startsWith("Windows")
-                ? new File(binDir, "pack200.exe")
-                : new File(binDir, "pack200");
-        File sdkHome = javaHome.getParentFile();
+        File sdkHome = Utils.JavaSDK;
         File testJar = new File(new File(sdkHome, "lib"), "tools.jar");
 
-        System.out.println("using pack200: " + pack200Exe.getAbsolutePath());
-
-        String[] cmds = { pack200Exe.getAbsolutePath(),
-                          "--effort=1",
-                          "--verbose",
-                          "--no-gzip",
-                          outFile.getName(),
-                          testJar.getAbsolutePath()
-        };
-        InputStream is = null;
-        BufferedReader br = null;
-        InputStreamReader ir = null;
-
-        FileOutputStream fos = null;
-        PrintStream ps = null;
-
-        try {
-            ProcessBuilder pb = new ProcessBuilder(cmds);
-            pb.redirectErrorStream(true);
-            Process p = pb.start();
-            is = p.getInputStream();
-            ir = new InputStreamReader(is);
-            br = new BufferedReader(ir);
+        System.out.println("using pack200: " + Utils.getPack200Cmd());
 
-            File logFile = new File("pack200.log");
-            fos = new FileOutputStream(logFile);
-            ps  = new PrintStream(fos);
+        List<String> cmdsList = new ArrayList<String>();
+        cmdsList.add(Utils.getPack200Cmd());
+        cmdsList.add("--effort=1");
+        cmdsList.add("--verbose");
+        cmdsList.add("--no-gzip");
+        cmdsList.add(outFile.getName());
+        cmdsList.add(testJar.getAbsolutePath());
+        List<String> outList = Utils.runExec(cmdsList);
 
-            String line = br.readLine();
-            int count = 0;
-            while (line != null) {
-                line = line.trim();
-                if (line.matches(".*Transmitted.*files of.*input bytes in a segment of.*bytes")) {
-                    count++;
-                }
-                ps.println(line);
-                line=br.readLine();
+        int count = 0;
+        for (String line : outList) {
+            System.out.println(line);
+            if (line.matches(".*Transmitted.*files of.*input bytes in a segment of.*bytes")) {
+                count++;
             }
-            p.waitFor();
-            if (p.exitValue() != 0) {
-                throw new RuntimeException("pack200 failed");
-            }
-            p.destroy();
-            if (count > 1) {
-                throw new Error("test fails: check for multiple segments(" +
-                        count + ") in: " + logFile.getAbsolutePath());
-            }
-        } catch (IOException ex) {
-            throw new RuntimeException(ex.getMessage());
-        } catch (InterruptedException ignore){
-        } finally {
-            close(is);
-            close(ps);
-            close(fos);
+        }
+        if (count != 1) {
+            throw new Error("test fails: check for 0 or multiple segments");
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/Utils.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,499 @@
+/*
+ * Copyright (c) 2007, 2010 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Pack200;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ *
+ * @author ksrini
+ */
+
+/*
+ * This class contains all the commonly used utilities used by various tests
+ * in this directory.
+ */
+class Utils {
+    static final String JavaHome = System.getProperty("test.java",
+            System.getProperty("java.home"));
+    static final boolean IsWindows =
+            System.getProperty("os.name").startsWith("Windows");
+    static final boolean Is64Bit =
+            System.getProperty("sun.arch.data.model", "32").equals("64");
+    static final File   JavaSDK =  new File(JavaHome).getParentFile();
+
+    static final String PACK_FILE_EXT   = ".pack";
+    static final String JAVA_FILE_EXT   = ".java";
+    static final String CLASS_FILE_EXT  = ".class";
+    static final String JAR_FILE_EXT    = ".jar";
+
+    static final File   TEST_SRC_DIR = new File(System.getProperty("test.src"));
+    static final String VERIFIER_DIR_NAME = "pack200-verifier";
+    static final File   VerifierJar = new File(VERIFIER_DIR_NAME + JAR_FILE_EXT);
+
+    private Utils() {} // all static
+
+    static {
+        if (!JavaHome.endsWith("jre")) {
+            throw new RuntimeException("Error: requires an SDK to run");
+        }
+    }
+
+    private static void init() throws IOException {
+        if (VerifierJar.exists()) {
+            return;
+        }
+        File srcDir = new File(TEST_SRC_DIR, VERIFIER_DIR_NAME);
+        List<File> javaFileList = findFiles(srcDir, createFilter(JAVA_FILE_EXT));
+        File tmpFile = File.createTempFile("javac", ".tmp");
+        File classesDir = new File("xclasses");
+        classesDir.mkdirs();
+        FileOutputStream fos = null;
+        PrintStream ps = null;
+        try {
+            fos = new FileOutputStream(tmpFile);
+            ps = new PrintStream(fos);
+            for (File f : javaFileList) {
+                ps.println(f.getAbsolutePath());
+            }
+        } finally {
+            close(ps);
+            close(fos);
+        }
+
+        compiler("-d",
+                "xclasses",
+                "@" + tmpFile.getAbsolutePath());
+
+        jar("cvfe",
+            VerifierJar.getName(),
+            "sun.tools.pack.verify.Main",
+            "-C",
+            "xclasses",
+            ".");
+    }
+
+    static void dirlist(File dir) {
+        File[] files = dir.listFiles();
+        System.out.println("--listing " + dir.getAbsolutePath() + "---");
+        for (File f : files) {
+            StringBuffer sb = new StringBuffer();
+            sb.append(f.isDirectory() ? "d " : "- ");
+            sb.append(f.getName());
+            System.out.println(sb);
+        }
+    }
+    static void doCompareVerify(File reference, File specimen) throws IOException {
+        init();
+        List<String> cmds = new ArrayList<String>();
+        cmds.add(getJavaCmd());
+        cmds.add("-jar");
+        cmds.add(VerifierJar.getName());
+        cmds.add(reference.getAbsolutePath());
+        cmds.add(specimen.getAbsolutePath());
+        cmds.add("-O");
+        runExec(cmds);
+    }
+
+    static void doCompareBitWise(File reference, File specimen)
+            throws IOException {
+        init();
+        List<String> cmds = new ArrayList<String>();
+        cmds.add(getJavaCmd());
+        cmds.add("-jar");
+        cmds.add(VerifierJar.getName());
+        cmds.add(reference.getName());
+        cmds.add(specimen.getName());
+        cmds.add("-O");
+        cmds.add("-b");
+        runExec(cmds);
+    }
+
+    static FileFilter createFilter(final String extension) {
+        return new FileFilter() {
+            @Override
+            public boolean accept(File pathname) {
+                String name = pathname.getName();
+                if (name.endsWith(extension)) {
+                    return true;
+                }
+                return false;
+            }
+        };
+    }
+
+    static final FileFilter DIR_FILTER = new FileFilter() {
+        public boolean accept(File pathname) {
+            if (pathname.isDirectory()) {
+                return true;
+            }
+            return false;
+        }
+    };
+
+    static final FileFilter FILE_FILTER = new FileFilter() {
+        public boolean accept(File pathname) {
+            if (pathname.isFile()) {
+                return true;
+            }
+            return false;
+        }
+    };
+
+    private static void setFileAttributes(File src, File dst) throws IOException {
+        dst.setExecutable(src.canExecute());
+        dst.setReadable(src.canRead());
+        dst.setWritable(src.canWrite());
+        dst.setLastModified(src.lastModified());
+    }
+
+    static void copyFile(File src, File dst) throws IOException {
+        if (src.isDirectory()) {
+            dst.mkdirs();
+            setFileAttributes(src, dst);
+            return;
+        } else {
+            File baseDirFile = dst.getParentFile();
+            if (!baseDirFile.exists()) {
+                baseDirFile.mkdirs();
+            }
+        }
+        FileInputStream in = null;
+        FileOutputStream out = null;
+        FileChannel srcChannel = null;
+        FileChannel dstChannel = null;
+        try {
+            in = new FileInputStream(src);
+            out = new FileOutputStream(dst);
+            srcChannel = in.getChannel();
+            dstChannel = out.getChannel();
+
+            long retval = srcChannel.transferTo(0, src.length(), dstChannel);
+            if (src.length() != dst.length()) {
+                throw new IOException("file copy failed for " + src);
+            }
+        } finally {
+            close(srcChannel);
+            close(dstChannel);
+            close(in);
+            close(out);
+        }
+        setFileAttributes(src, dst);
+    }
+
+    /*
+     * Suppose a path is provided which consists of a full path
+     * this method returns the sub path for a full path ex: /foo/bar/baz/foobar.z
+     * and the base path is /foo/bar it will will return baz/foobar.z.
+     */
+    private static String getEntryPath(String basePath, String fullPath) {
+        if (!fullPath.startsWith(basePath)) {
+            return null;
+        }
+        return fullPath.substring(basePath.length());
+    }
+
+    static String getEntryPath(File basePathFile, File fullPathFile) {
+        return getEntryPath(basePathFile.toString(), fullPathFile.toString());
+    }
+
+    public static void recursiveCopy(File src, File dest) throws IOException {
+        if (!src.exists() || !src.canRead()) {
+            throw new IOException("file not found or readable: " + src);
+        }
+        if (dest.exists() && !dest.isDirectory() && !dest.canWrite()) {
+            throw new IOException("file not found or writeable: " + dest);
+        }
+        if (!dest.exists()) {
+            dest.mkdirs();
+        }
+        List<File> a = directoryList(src);
+        for (File f : a) {
+            copyFile(f, new File(dest, getEntryPath(src, f)));
+        }
+    }
+
+    static List<File> directoryList(File dirname) {
+        List<File>  dirList = new ArrayList<File>();
+        return directoryList(dirname, dirList, null);
+    }
+
+    private static List<File> directoryList(File dirname, List<File> dirList,
+            File[] dirs) {
+        dirList.addAll(Arrays.asList(dirname.listFiles(FILE_FILTER)));
+        dirs = dirname.listFiles(DIR_FILTER);
+        for (File f : dirs) {
+            if (f.isDirectory() && !f.equals(dirname)) {
+                dirList.add(f);
+                directoryList(f, dirList, dirs);
+            }
+        }
+        return dirList;
+    }
+
+    static void recursiveDelete(File dir) throws IOException {
+        if (dir.isFile()) {
+            dir.delete();
+        } else if (dir.isDirectory()) {
+            File[] entries = dir.listFiles();
+            for (int i = 0; i < entries.length; i++) {
+                if (entries[i].isDirectory()) {
+                    recursiveDelete(entries[i]);
+                }
+                entries[i].delete();
+            }
+            dir.delete();
+        }
+    }
+
+    static List<File> findFiles(File startDir, FileFilter filter)
+            throws IOException {
+        List<File> list = new ArrayList<File>();
+        findFiles0(startDir, list, filter);
+        return list;
+    }
+    /*
+     * finds files in the start directory using the the filter, appends
+     * the files to the dirList.
+     */
+    private static void findFiles0(File startDir, List<File> list,
+                                    FileFilter filter) throws IOException {
+        File[] foundFiles = startDir.listFiles(filter);
+        list.addAll(Arrays.asList(foundFiles));
+        File[] dirs = startDir.listFiles(DIR_FILTER);
+        for (File dir : dirs) {
+            findFiles0(dir, list, filter);
+        }
+    }
+
+    static void close(Closeable c) {
+        if (c == null) {
+            return;
+        }
+        try {
+            c.close();
+        } catch (IOException ignore) {
+        }
+    }
+
+    static void compiler(String... javacCmds) {
+        if (com.sun.tools.javac.Main.compile(javacCmds) != 0) {
+            throw new RuntimeException("compilation failed");
+        }
+    }
+
+    static void jar(String... jargs) {
+        sun.tools.jar.Main jarTool =
+                new sun.tools.jar.Main(System.out, System.err, "jartool");
+        if (!jarTool.run(jargs)) {
+            throw new RuntimeException("jar command failed");
+        }
+    }
+
+    // given a jar file foo.jar will write to foo.pack
+    static void pack(JarFile jarFile, File packFile) throws IOException {
+        Pack200.Packer packer = Pack200.newPacker();
+        Map<String, String> p = packer.properties();
+        // Take the time optimization vs. space
+        p.put(packer.EFFORT, "1");  // CAUTION: do not use 0.
+        // Make the memory consumption as effective as possible
+        p.put(packer.SEGMENT_LIMIT, "10000");
+        // ignore all JAR deflation requests to save time
+        p.put(packer.DEFLATE_HINT, packer.FALSE);
+        // save the file ordering of the original JAR
+        p.put(packer.KEEP_FILE_ORDER, packer.TRUE);
+        FileOutputStream fos = null;
+        try {
+            // Write out to a jtreg scratch area
+            fos = new FileOutputStream(packFile);
+            // Call the packer
+            packer.pack(jarFile, fos);
+        } finally {
+            close(fos);
+        }
+    }
+
+    // uses java unpacker, slow but useful to discover issues with the packer
+    static void unpackj(File inFile, JarOutputStream jarStream)
+            throws IOException {
+        unpack0(inFile, jarStream, true);
+
+    }
+
+    // uses native unpacker using the java APIs
+    static void unpackn(File inFile, JarOutputStream jarStream)
+            throws IOException {
+        unpack0(inFile, jarStream, false);
+    }
+
+    // given a packed file, create the jar file in the current directory.
+    private static void unpack0(File inFile, JarOutputStream jarStream,
+            boolean useJavaUnpack) throws IOException {
+        // Unpack the files
+        Pack200.Unpacker unpacker = Pack200.newUnpacker();
+        Map<String, String> props = unpacker.properties();
+        if (useJavaUnpack) {
+            props.put("com.sun.java.util.jar.pack.disable.native", "true");
+        }
+        // Call the unpacker
+        unpacker.unpack(inFile, jarStream);
+    }
+
+    static byte[] getBuffer(ZipFile zf, ZipEntry ze) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte buf[] = new byte[8192];
+        InputStream is = null;
+        try {
+            is = zf.getInputStream(ze);
+            int n = is.read(buf);
+            while (n > 0) {
+                baos.write(buf, 0, n);
+                n = is.read(buf);
+            }
+            return baos.toByteArray();
+        } finally {
+            close(is);
+        }
+    }
+
+    static ArrayList<String> getZipFileEntryNames(ZipFile z) {
+        ArrayList<String> out = new ArrayList<String>();
+        for (ZipEntry ze : Collections.list(z.entries())) {
+            out.add(ze.getName());
+        }
+        return out;
+    }
+
+    static List<String> runExec(List<String> cmdsList) {
+        ArrayList<String> alist = new ArrayList<String>();
+        ProcessBuilder pb =
+                new ProcessBuilder(cmdsList);
+        Map<String, String> env = pb.environment();
+        pb.directory(new File("."));
+        dirlist(new File("."));
+        for (String x : cmdsList) {
+            System.out.print(x + " ");
+        }
+        System.out.println("");
+        int retval = 0;
+        Process p = null;
+        InputStreamReader ir = null;
+        BufferedReader rd = null;
+        InputStream is = null;
+        try {
+            pb.redirectErrorStream(true);
+            p = pb.start();
+            is = p.getInputStream();
+            ir = new InputStreamReader(is);
+            rd = new BufferedReader(ir, 8192);
+
+            String in = rd.readLine();
+            while (in != null) {
+                alist.add(in);
+                System.out.println(in);
+                in = rd.readLine();
+            }
+            retval = p.waitFor();
+            if (retval != 0) {
+                throw new RuntimeException("process failed with non-zero exit");
+            }
+        } catch (Exception ex) {
+            throw new RuntimeException(ex.getMessage());
+        } finally {
+            close(rd);
+            close(ir);
+            close(is);
+            if (p != null) {
+                p.destroy();
+            }
+        }
+        return alist;
+    }
+
+    static String getUnpack200Cmd() {
+        return getAjavaCmd("unpack200");
+    }
+
+    static String getPack200Cmd() {
+        return getAjavaCmd("pack200");
+    }
+
+    static String getJavaCmd() {
+        return getAjavaCmd("java");
+    }
+
+    static String getAjavaCmd(String cmdStr) {
+        File binDir = new File(JavaHome, "bin");
+        File unpack200File = IsWindows
+                ? new File(binDir, cmdStr + ".exe")
+                : new File(binDir, cmdStr);
+
+        String cmd = unpack200File.getAbsolutePath();
+        if (!unpack200File.canExecute()) {
+            throw new RuntimeException("please check" +
+                    cmd + " exists and is executable");
+        }
+        return cmd;
+    }
+
+    private static List<File> locaterCache = null;
+    // search the source dir and jdk dir for requested file and returns
+    // the first location it finds.
+    static File locateJar(String name) {
+        try {
+            if (locaterCache == null) {
+                locaterCache = new ArrayList<File>();
+                locaterCache.addAll(findFiles(TEST_SRC_DIR, createFilter(JAR_FILE_EXT)));
+                locaterCache.addAll(findFiles(JavaSDK, createFilter(JAR_FILE_EXT)));
+            }
+            for (File f : locaterCache) {
+                if (f.getName().equals(name)) {
+                    return f;
+                }
+            }
+            throw new IOException("file not found: " + name);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/data/README	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,45 @@
+The files contained in the golden.jar have been harvested from many
+different sources, some are hand-crafted invalid class files (odds directory),
+or from random JDK builds.
+
+Generally these files serve to ensure the integrity of the packer and unpacker
+by,
+   1. maximizing the test coverage.
+   2. exercising all the Bands in the pack200 specification.
+   2. testing the behavior of the packer with invalid classes.
+   3. testing the archive integrity, ordering and description (date, sizes,
+      CRC etc.)
+
+Build:
+To rebuild this JAR follow these steps:
+    1. unzip the golden.jar to some directory lets call it "example"
+    2. now we can add any directories with files into example.
+    2. run the script BUILDME.sh  as
+       % sh BUILDME.sh example
+
+Note: the BUILDME.sh is known to work on all Unix platforms as well as Windows
+      using Cygwin.
+
+The above will create two JAR files in the current directory,
+example.jar and example-cls.jar, now the example.jar can be used as the
+golden.jar.
+
+To ensure the JAR has been built correctly use jar -tvf and compare the
+results of the old jar and the newly built one, note that the compressed sizes
+may differ, however the timestamps etc. should be consistent.
+
+Test:
+    Basic:
+        % pack200 --repack test.jar golden.jar
+
+    Advanced:
+       Create a pack.conf as follows:
+       % cat pack.conf
+       com.sun.java.util.jar.pack.dump.bands=true
+
+       % pack200 --no-gzip --config-file=pack.conf \
+           --verbose golden.jar.pack golden.jar
+
+       This command will dump the Bands in a unique directory BD_XXXXXX,
+       one can inspect the directory to ensure all of the bands are being
+       generated. Familiarity of the  Pack200 specification is suggested.
\ No newline at end of file
Binary file jdk/test/tools/pack200/pack200-verifier/data/golden.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/make/build.xml	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,59 @@
+<project name="PackageVerify" default="dist" basedir="..">
+  <!-- Requires ant 1.6.1+ and JDK 1.6+-->
+
+  <!-- set global properties for this build -->
+  <property name="src"      value="${basedir}/src"/>
+  <property name="build"    value="${basedir}/build"/>
+  <property name="dist"     value="${basedir}/dist"/>
+  <property name="make"     value="${basedir}/make"/>
+  <property name="classes"  value="${build}/classes"/>
+  <property name="api"      value="${build}/api"/>
+
+  <target name="init">
+    <!-- Create the time stamp -->
+    <tstamp/>
+    <!-- Create the build directory structure used by compile -->
+    <mkdir dir="${build}"/>
+    <mkdir dir="${dist}"/>
+    <mkdir dir="${classes}"/>
+    <mkdir dir="${api}"/>
+  </target>
+
+  <target name="compile" depends="init">
+    <!-- Compile the java code from ${src} into ${build} -->
+    <javac 
+	source="1.6"
+	srcdir="${src}"
+	destdir="${build}/classes"
+	verbose="no"
+	debug="on"
+    />
+  </target>
+
+  <target name="doc" depends="init, compile">
+      <javadoc
+        source="1.6"
+        sourcepath="${src}"
+        destdir="${api}"
+      />
+  </target>
+  <target name="dist" depends="compile, doc">
+    <!-- Put everything in jar file -->
+    <jar destfile="${dist}/pack200-verifier.jar">
+	<manifest>
+	   <attribute name="Main-Class" value="sun.tools.pack.verify.Main"/>
+	</manifest>
+	<fileset dir="${classes}"/>
+    </jar>
+    <zip destfile="dist/pack200-verifier-doc.zip">
+        <fileset dir="${api}"/>
+    </zip>
+  </target>
+
+  <target name="clean">
+    <!-- Delete the ${build} and ${dist} directory trees -->
+    <delete dir="${build}"/>
+    <delete dir="${dist}"/>
+  </target>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/ClassCompare.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.tools.pack.verify;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import xmlkit.*;
+
+public class ClassCompare {
+
+    /*
+     * @author ksrini
+     */
+    private static XMLKit.Element getXMLelement(InputStream is,
+            boolean ignoreUnkAttrs,
+            List<String> ignoreElements) throws IOException {
+
+        ClassReader cr = new ClassReader();
+        cr.keepOrder = false;
+        XMLKit.Element e = cr.readFrom(is);
+
+        if (ignoreElements != null) {
+            XMLKit.Filter filter = XMLKit.elementFilter(ignoreElements);
+            e.removeAllInTree(filter);
+        }
+
+        if (ignoreUnkAttrs == true) {
+            // This removes any unknown attributes
+            e.removeAllInTree(XMLKit.elementFilter("Attribute"));
+        }
+        return e;
+    }
+
+    private static String getXMLPrettyString(XMLKit.Element e) throws IOException {
+        StringWriter out = new StringWriter();
+        e.writePrettyTo(out);
+        return out.toString();
+    }
+
+    private static boolean compareClass0(JarFile jf1, JarFile jf2,
+            JarEntry je, boolean ignoreUnkAttrs,
+            List<String> ignoreElements)
+            throws IOException {
+
+        InputStream is1 = jf1.getInputStream(je);
+        InputStream is2 = jf2.getInputStream(je);
+
+        // First we try to compare the bits if they are the same
+        boolean bCompare = JarFileCompare.compareStreams(is1, is2);
+
+        // If they are the same there is nothing more to do.
+        if (bCompare) {
+            Globals.println("+++" + je.getName() + "+++\t"
+                    + "b/b:PASS");
+            return bCompare;
+        }
+        is1.close();
+        is2.close();
+
+        is1 = jf1.getInputStream(je);
+        is2 = jf2.getInputStream(je);
+
+
+        XMLKit.Element e1 = getXMLelement(is1, ignoreUnkAttrs, ignoreElements);
+        XMLKit.Element e2 = getXMLelement(is2, ignoreUnkAttrs, ignoreElements);
+
+        Globals.print("+++" + je.getName() + "+++\t"
+                + e1.size() + "/" + e1.size() + ":");
+
+        boolean result = true;
+
+        if (e1.equals(e2)) {
+            Globals.println("PASS");
+        } else {
+            Globals.println("FAIL");
+            Globals.log("Strings differs");
+            Globals.log(getXMLPrettyString(e1));
+            Globals.log("----------");
+            Globals.log(getXMLPrettyString(e2));
+            result = false;
+        }
+        return result;
+    }
+
+    /*
+     * Given two Class Paths could be jars the first being a reference
+     * will execute a series of comparisons on the classname  specified
+     * The className could be null in which case it will iterate through
+     * all the classes, otherwise it will compare one class and exit.
+     */
+    public static boolean compareClass(String jar1, String jar2,
+            String className, boolean ignoreUnkAttrs,
+            List<String> ignoreElements)
+            throws IOException {
+
+        Globals.println("Unknown attributes ignored:" + ignoreUnkAttrs);
+        if (ignoreElements != null) {
+            Globals.println(ignoreElements.toString());
+        }
+
+        JarFile jf1 = new JarFile(jar1);
+        JarFile jf2 = new JarFile(jar2);
+
+        boolean result = true;
+
+        if (className == null) {
+            for (JarEntry je1 : Collections.list((Enumeration<JarEntry>) jf1.entries())) {
+                if (je1.getName().endsWith(".class")) {
+                    JarEntry je2 = jf2.getJarEntry(je1.getName());
+                    boolean pf = compareClass0(jf1, jf2, je1, ignoreUnkAttrs, ignoreElements);
+                    if (result == true) {
+                        result = pf;
+                    }
+                }
+            }
+        } else {
+            JarEntry je1 = jf1.getJarEntry(className);
+            result = compareClass0(jf1, jf2, je1, ignoreUnkAttrs, ignoreElements);
+        }
+        if (result == false) {
+            throw new RuntimeException("Class structural comparison failure");
+        }
+        return result;
+    }
+
+    public static boolean compareClass(String jar1, String jar2,
+            String className) throws IOException {
+
+        Stack<String> s = new Stack();
+        if (Globals.ignoreDebugAttributes()) {
+            s = new Stack();
+            s.push("LocalVariable");
+            s.push("LocalVariableType");
+            s.push("LineNumber");
+            s.push("SourceFile");
+        }
+        return compareClass(jar1, jar2, className, Globals.ignoreUnknownAttributes(), s);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Globals.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * A collection of useful global utilities commonly used.
+ */
+package sun.tools.pack.verify;
+
+import java.io.*;
+import java.util.*;
+
+/*
+ * @author ksrini
+ */
+
+class Globals {
+
+    private static int errors = 0;
+    private static PrintWriter _pw = null;
+    private static String _logFileName = null;
+    private static final String DEFAULT_LOG_FILE = "verifier.log";
+    private static boolean _verbose = true;
+    private static boolean _ignoreJarDirectories = false;
+    private static boolean _checkJarClassOrdering = true;
+    private static boolean _bitWiseClassCompare = false;
+    // Ignore Deprecated, SourceFile and Synthetic
+    private static boolean _ignoreCompileAttributes = false;
+    // Ignore Debug Attributes LocalVariableTable, LocalVariableType,LineNumberTable
+    private static boolean _ignoreDebugAttributes = false;
+    private static boolean _ignoreUnknownAttributes = false;
+    private static boolean _validateClass = true;
+    private static Globals _instance = null;
+
+    static Globals getInstance() {
+        if (_instance == null) {
+            _instance = new Globals();
+            _verbose = (System.getProperty("sun.tools.pack.verify.verbose") == null)
+                    ? false : true;
+            _ignoreJarDirectories = (System.getProperty("ignoreJarDirectories") == null)
+                    ? false : true;
+        }
+        return _instance;
+    }
+
+    static boolean ignoreCompileAttributes() {
+        return _ignoreCompileAttributes;
+    }
+
+    static boolean ignoreDebugAttributes() {
+        return _ignoreDebugAttributes;
+    }
+
+    static boolean ignoreUnknownAttributes() {
+        return _ignoreUnknownAttributes;
+    }
+
+    static boolean ignoreJarDirectories() {
+        return _ignoreJarDirectories;
+    }
+
+    static boolean validateClass() {
+        return _validateClass;
+    }
+
+    static void setCheckJarClassOrdering(boolean flag) {
+        _checkJarClassOrdering = flag;
+    }
+
+    static boolean checkJarClassOrdering() {
+        return _checkJarClassOrdering;
+    }
+
+    static boolean bitWiseClassCompare() {
+        return _bitWiseClassCompare;
+    }
+
+    static boolean setBitWiseClassCompare(boolean flag) {
+        return _bitWiseClassCompare = flag;
+    }
+
+    public static boolean setIgnoreCompileAttributes(boolean flag) {
+        return _ignoreCompileAttributes = flag;
+    }
+
+    static boolean setIgnoreDebugAttributes(boolean flag) {
+        return _ignoreDebugAttributes = flag;
+    }
+
+    static boolean setIgnoreUnknownAttributes(boolean flag) {
+        return _ignoreUnknownAttributes = flag;
+    }
+
+    static boolean setValidateClass(boolean flag) {
+        return _validateClass = flag;
+    }
+
+    static int getErrors() {
+        return errors;
+    }
+
+    static void trace(String s) {
+        if (_verbose) {
+            println(s);
+        }
+    }
+
+    static void print(String s) {
+        _pw.print(s);
+    }
+
+    static void println(String s) {
+        _pw.println(s);
+    }
+
+    static void log(String s) {
+        errors++;
+        _pw.println("ERROR:" + s);
+    }
+
+    static void lognoln(String s) {
+        errors++;
+        _pw.print(s);
+    }
+
+    private static PrintWriter openFile(String fileName) {
+        //Lets create the directory if it does not exist.
+        File f = new File(fileName);
+        File baseDir = f.getParentFile();
+        if (baseDir != null && baseDir.exists() == false) {
+            baseDir.mkdirs();
+        }
+        try {
+            return new PrintWriter(new FileWriter(f), true);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void closeFile() {
+        _pw.flush();
+        _pw.close();
+    }
+
+    static void printPropsToLog() {
+        println("Log started " + new Date(System.currentTimeMillis()));
+        print(System.getProperty("java.vm.version"));
+        println("\t" + System.getProperty("java.vm.name"));
+
+        println("System properties");
+        println("\tjava.home=" + System.getProperty("java.home"));
+        println("\tjava.class.version=" + System.getProperty("java.class.version"));
+        println("\tjava.class.path=" + System.getProperty("java.class.path"));
+        println("\tjava.ext.dirs=" + System.getProperty("java.ext.dirs"));
+        println("\tos.name=" + System.getProperty("os.name"));
+        println("\tos.arch=" + System.getProperty("os.arch"));
+        println("\tos.version=" + System.getProperty("os.version"));
+        println("\tuser.name=" + System.getProperty("user.name"));
+        println("\tuser.home=" + System.getProperty("user.home"));
+        println("\tuser.dir=" + System.getProperty("user.dir"));
+        println("\tLocale.getDefault=" + Locale.getDefault());
+        println("System properties end");
+    }
+
+    static void openLog(String s) {
+        _logFileName = (s != null) ? s : "." + File.separator + DEFAULT_LOG_FILE;
+        _logFileName = (new File(_logFileName).isDirectory())
+                ? _logFileName + File.separator + DEFAULT_LOG_FILE : _logFileName;
+        _pw = openFile(_logFileName);
+        printPropsToLog();
+    }
+
+    static void closeLog() {
+        closeFile();
+    }
+
+    static String getLogFileName() {
+        return _logFileName;
+    }
+
+    static void diffCharData(String s1, String s2) {
+        boolean diff = false;
+        char[] c1 = s1.toCharArray();
+        char[] c2 = s2.toCharArray();
+        if (c1.length != c2.length) {
+            diff = true;
+            Globals.log("Length differs: " + (c1.length - c2.length));
+        }
+        // Take the smaller of the two arrays to prevent Array...Exception
+        int minlen = (c1.length < c2.length) ? c1.length : c2.length;
+        for (int i = 0; i < c1.length; i++) {
+            if (c1[i] != c2[i]) {
+                diff = true;
+                Globals.lognoln("\t idx[" + i + "] 0x" + Integer.toHexString(c1[i]) + "<>" + "0x" + Integer.toHexString(c2[i]));
+                Globals.log(" -> " + c1[i] + "<>" + c2[i]);
+            }
+        }
+    }
+
+    static void diffByteData(String s1, String s2) {
+        boolean diff = false;
+        byte[] b1 = s1.getBytes();
+        byte[] b2 = s2.getBytes();
+
+        if (b1.length != b2.length) {
+            diff = true;
+            //(+) b1 is greater, (-) b2 is greater
+            Globals.log("Length differs diff: " + (b1.length - b2.length));
+        }
+        // Take the smaller of the two array to prevent Array...Exception
+        int minlen = (b1.length < b2.length) ? b1.length : b2.length;
+        for (int i = 0; i < b1.length; i++) {
+            if (b1[i] != b2[i]) {
+                diff = true;
+                Globals.log("\t" + "idx[" + i + "] 0x" + Integer.toHexString(b1[i]) + "<>" + "0x" + Integer.toHexString(b2[i]));
+            }
+        }
+    }
+
+    static void dumpToHex(String s) {
+        try {
+            dumpToHex(s.getBytes("UTF-8"));
+        } catch (UnsupportedEncodingException uce) {
+            throw new RuntimeException(uce);
+        }
+    }
+
+    static void dumpToHex(byte[] buffer) {
+        int linecount = 0;
+        byte[] b = new byte[16];
+        for (int i = 0; i < buffer.length; i += 16) {
+            if (buffer.length - i > 16) {
+                System.arraycopy(buffer, i, b, 0, 16);
+                print16Bytes(b, linecount);
+                linecount += 16;
+            } else {
+                System.arraycopy(buffer, i, b, 0, buffer.length - i);
+                for (int n = buffer.length - (i + 1); n < 16; n++) {
+                    b[n] = 0;
+                }
+                print16Bytes(b, linecount);
+                linecount += 16;
+            }
+        }
+        Globals.log("-----------------------------------------------------------------");
+    }
+
+    static void print16Bytes(byte[] buffer, int linecount) {
+        final int MAX = 4;
+        Globals.lognoln(paddedHexString(linecount, 4) + " ");
+
+        for (int i = 0; i < buffer.length; i += 2) {
+            int iOut = pack2Bytes2Int(buffer[i], buffer[i + 1]);
+            Globals.lognoln(paddedHexString(iOut, 4) + " ");
+        }
+
+        Globals.lognoln("| ");
+
+        StringBuilder sb = new StringBuilder(new String(buffer));
+
+        for (int i = 0; i < buffer.length; i++) {
+            if (Character.isISOControl(sb.charAt(i))) {
+                sb.setCharAt(i, '.');
+            }
+        }
+        Globals.log(sb.toString());
+    }
+
+    static int pack2Bytes2Int(byte b1, byte b2) {
+        int out = 0x0;
+        out += b1;
+        out <<= 8;
+        out &= 0x0000ffff;
+        out |= 0x000000ff & b2;
+        return out;
+    }
+
+    static String paddedHexString(int n, int max) {
+        char[] c = Integer.toHexString(n).toCharArray();
+        char[] out = new char[max];
+
+        for (int i = 0; i < max; i++) {
+            out[i] = '0';
+        }
+        int offset = (max - c.length < 0) ? 0 : max - c.length;
+        for (int i = 0; i < c.length; i++) {
+            out[offset + i] = c[i];
+        }
+        return new String(out);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/JarFileCompare.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.tools.pack.verify;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+
+class JarFileCompare {
+    /*
+     * @author ksrini
+     */
+
+    private static VerifyTreeSet getVerifyTreeSet(String jarPath) {
+        VerifyTreeSet vts = new VerifyTreeSet();
+        try {
+            JarFile j = new JarFile(jarPath);
+            for (JarEntry je : Collections.list((Enumeration<JarEntry>) j.entries())) {
+                if (!je.isDirectory()) { // totally ignore directories
+                    vts.add(je.getName());
+                }
+            }
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+        return vts;
+    }
+
+    private static LinkedList getListOfClasses(String jarPath) {
+        LinkedList l = new LinkedList();
+        try {
+            JarFile j = new JarFile(jarPath);
+            for (JarEntry je : Collections.list((Enumeration<JarEntry>) j.entries())) {
+                if (!je.isDirectory() && je.getName().endsWith(".class")) {
+                    l.add(je.getName());
+                }
+            }
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+        return l;
+    }
+
+    private static void jarDirectoryCompare(String jarPath1, String jarPath2) {
+        VerifyTreeSet vts1 = getVerifyTreeSet(jarPath1);
+        VerifyTreeSet vts2 = getVerifyTreeSet(jarPath2);
+
+        TreeSet diff1 = vts1.diff(vts2);
+        if (diff1.size() > 0) {
+            Globals.log("Left has the following entries that right does not have");
+            Globals.log(diff1.toString());
+        }
+        TreeSet diff2 = vts2.diff(vts1);
+        if (diff2.size() > 0) {
+            Globals.log("Right has the following entries that left does not have");
+            Globals.log(diff2.toString());
+        }
+        if (Globals.checkJarClassOrdering()) {
+            boolean error = false;
+            Globals.println("Checking Class Ordering");
+            LinkedList l1 = getListOfClasses(jarPath1);
+            LinkedList l2 = getListOfClasses(jarPath2);
+            if (l1.size() != l2.size()) {
+                error = true;
+                Globals.log("The number of classes differs");
+                Globals.log("\t" + l1.size() + "<>" + l2.size());
+            }
+            for (int i = 0; i < l1.size(); i++) {
+                String s1 = (String) l1.get(i);
+                String s2 = (String) l2.get(i);
+                if (s1.compareTo(s2) != 0) {
+                    error = true;
+                    Globals.log("Ordering differs at[" + i + "] = " + s1);
+                    Globals.log("\t" + s2);
+                }
+            }
+        }
+    }
+
+    /*
+     * Returns true if the two Streams are bit identical, and false if they
+     * are not, no further diagnostics
+     */
+    static boolean compareStreams(InputStream is1, InputStream is2) {
+
+        BufferedInputStream bis1 = new BufferedInputStream(is1, 8192);
+        BufferedInputStream bis2 = new BufferedInputStream(is2, 8192);
+        try {
+            int i1, i2;
+            int count = 0;
+            while ((i1 = bis1.read()) == (i2 = bis2.read())) {
+                count++;
+                if (i1 < 0) {
+                    // System.out.println("bytes read " + count);
+                    return true;  // got all the way to EOF
+                }
+            }
+            return false;  // reads returned dif
+
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    private static void checkEntry(JarFile jf1, JarFile jf2, JarEntry je) throws IOException {
+        InputStream is1 = jf1.getInputStream(je);
+        InputStream is2 = jf2.getInputStream(je);
+        if (is1 != null && is2 != null) {
+            if (!compareStreams(jf1.getInputStream(je), jf2.getInputStream(je))) {
+                Globals.println("+++" + je.getName() + "+++");
+                Globals.log("Error: File:" + je.getName()
+                        + " differs, use a diff util for further diagnostics");
+            }
+        } else {
+            Globals.println("+++" + je.getName() + "+++");
+            Globals.log("Error: File:" + je.getName() + " not found in " + jf2.getName());
+        }
+    }
+
+    /*
+     * Given two jar files we compare and see if the jarfiles have all the
+     * entries. The property ignoreJarDirectories is set to true by default
+     * which means that Directory entries in a jar may be ignore.
+     */
+    static void jarCompare(String jarPath1, String jarPath2) {
+        jarDirectoryCompare(jarPath1, jarPath2);
+
+        try {
+            JarFile jf1 = new JarFile(jarPath1);
+            JarFile jf2 = new JarFile(jarPath2);
+
+            int nclasses = 0;
+            int nentries = 0;
+            int entries_checked = 0;
+            int classes_checked = 0;
+
+            for (JarEntry je : Collections.list((Enumeration<JarEntry>) jf1.entries())) {
+                if (!je.isDirectory() && !je.getName().endsWith(".class")) {
+                    nentries++;
+                } else if (je.getName().endsWith(".class")) {
+                    nclasses++;
+                }
+            }
+
+            for (JarEntry je : Collections.list((Enumeration<JarEntry>) jf1.entries())) {
+                if (je.isDirectory()) {
+                    continue;  // Ignore directories
+                }
+                if (!je.getName().endsWith(".class")) {
+                    entries_checked++;
+                    if (je.getName().compareTo("META-INF/MANIFEST.MF") == 0) {
+                        Manifest mf1 = new Manifest(jf1.getInputStream(je));
+                        Manifest mf2 = new Manifest(jf2.getInputStream(je));
+                        if (!mf1.equals(mf2)) {
+                            Globals.log("Error: Manifests differ");
+                            Globals.log("Manifest1");
+                            Globals.log(mf1.getMainAttributes().entrySet().toString());
+                            Globals.log("Manifest2");
+                            Globals.log(mf2.getMainAttributes().entrySet().toString());
+                        }
+                    } else {
+                        checkEntry(jf1, jf2, je);
+                    }
+                } else if (Globals.bitWiseClassCompare() == true) {
+                    checkEntry(jf1, jf2, je);
+                    classes_checked++;
+                }
+            }
+            if (Globals.bitWiseClassCompare()) {
+                Globals.println("Class entries checked (byte wise)/Total Class entries = "
+                        + classes_checked + "/" + nclasses);
+            }
+            Globals.println("Non-class entries checked/Total non-class entries = "
+                    + entries_checked + "/" + nentries);
+        } catch (IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/Main.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+// The Main Entry point
+package sun.tools.pack.verify;
+
+import java.io.*;
+
+/**
+ * This class provides a convenient entry point to the pack200 verifier. This
+ * compares two classes, either in path or in an archive.
+ * @see xmlkit.XMLKit
+ * @author ksrini
+ */
+public class Main {
+
+    private static void syntax() {
+        System.out.println("Usage: ");
+        System.out.println("\tREFERENCE_CLASSPATH COMPARED_CLASSPATH [Options]");
+        System.out.println("\tOptions:");
+        System.out.println("\t\t-O check jar ordering");
+        System.out.println("\t\t-C ignore compile attributes (Deprecated, SourceFile, Synthetic, )");
+        System.out.println("\t\t-D ignore debug attributes (LocalVariable, LineNumber)");
+        System.out.println("\t\t-u ignore unknown attributes");
+        System.out.println("\t\t-V turn off class validation");
+        System.out.println("\t\t-c CLASS, compare CLASS only");
+        System.out.println("\t\t-b Compares all entries bitwise only");
+        System.out.println("\t\t-l Directory or Log File Name");
+    }
+
+    /**
+     * main entry point to the class file comparator, which compares semantically
+     * class files in a classpath or an archive.
+     * @param args String array as described below
+     * @throws RuntimeException
+     * <pre>
+     *  Usage:
+     *     ReferenceClasspath SpecimenClaspath [Options]
+     *     Options:
+     *      -O check jar ordering
+     *      -C do not compare compile attributes (Deprecated, SourceFile, Synthetic)
+     *      -D do not compare debug attribute (LocalVariableTable, LineNumberTable)
+     *      -u ignore unknown attributes
+     *      -V turn off class validation
+     *      -c class, compare a single class
+     *      -b compares all entries bitwise (fastest)
+     *      -l directory or log file name
+     * </pre>
+     */
+    public static void main(String args[]) {
+        Globals.getInstance();
+        if (args == null || args.length < 2) {
+            syntax();
+            System.exit(1);
+        }
+        String refJarFileName = null;
+        String cmpJarFileName = null;
+        String specificClass = null;
+        String logDirFileName = null;
+
+        for (int i = 0; i < args.length; i++) {
+            if (i == 0) {
+                refJarFileName = args[0];
+                continue;
+            }
+            if (i == 1) {
+                cmpJarFileName = args[1];
+                continue;
+            }
+
+            if (args[i].startsWith("-O")) {
+                Globals.setCheckJarClassOrdering(true);
+            }
+
+            if (args[i].startsWith("-b")) {
+                Globals.setBitWiseClassCompare(true);
+            }
+
+            if (args[i].startsWith("-C")) {
+                Globals.setIgnoreCompileAttributes(true);
+            }
+
+            if (args[i].startsWith("-D")) {
+                Globals.setIgnoreDebugAttributes(true);
+            }
+
+            if (args[i].startsWith("-V")) {
+                Globals.setValidateClass(false);
+            }
+
+            if (args[i].startsWith("-c")) {
+                i++;
+                specificClass = args[i].trim();
+            }
+
+            if (args[i].startsWith("-u")) {
+                i++;
+                Globals.setIgnoreUnknownAttributes(true);
+            }
+
+            if (args[i].startsWith("-l")) {
+                i++;
+                logDirFileName = args[i].trim();
+            }
+        }
+
+        Globals.openLog(logDirFileName);
+
+        File refJarFile = new File(refJarFileName);
+        File cmpJarFile = new File(cmpJarFileName);
+
+        String f1 = refJarFile.getAbsoluteFile().toString();
+        String f2 = cmpJarFile.getAbsoluteFile().toString();
+
+        System.out.println("LogFile:" + Globals.getLogFileName());
+        System.out.println("Reference JAR:" + f1);
+        System.out.println("Compared  JAR:" + f2);
+
+        Globals.println("LogFile:" + Globals.getLogFileName());
+        Globals.println("Reference JAR:" + f1);
+        Globals.println("Compared  JAR:" + f2);
+
+        Globals.println("Ignore Compile Attributes:" + Globals.ignoreCompileAttributes());
+        Globals.println("Ignore Debug   Attributes:" + Globals.ignoreDebugAttributes());
+        Globals.println("Ignore Unknown Attributes:" + Globals.ignoreUnknownAttributes());
+        Globals.println("Class ordering check:" + Globals.checkJarClassOrdering());
+        Globals.println("Class validation check:" + Globals.validateClass());
+        Globals.println("Bit-wise compare:" + Globals.bitWiseClassCompare());
+        Globals.println("ClassName:" + ((specificClass == null) ? "ALL" : specificClass));
+
+        if (specificClass == null && Globals.bitWiseClassCompare() == true) {
+            JarFileCompare.jarCompare(refJarFileName, cmpJarFileName);
+        } else {
+            try {
+                ClassCompare.compareClass(refJarFileName, cmpJarFileName, specificClass);
+            } catch (Exception e) {
+                Globals.log("Exception " + e);
+                throw new RuntimeException(e);
+            }
+        }
+
+        if (Globals.getErrors() > 0) {
+            System.out.println("FAIL");
+            Globals.println("FAIL");
+            System.exit(Globals.getErrors());
+        }
+
+        System.out.println("PASS");
+        Globals.println("PASS");
+        System.exit(Globals.getErrors());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/sun/tools/pack/verify/VerifyTreeSet.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.tools.pack.verify;
+
+import java.util.*;
+/*
+ * @author ksrini
+ */
+
+class VerifyTreeSet<K> extends java.util.TreeSet {
+
+    VerifyTreeSet() {
+        super();
+    }
+
+    public VerifyTreeSet(Comparator c) {
+        super(c);
+    }
+
+    public TreeSet<K> diff(TreeSet in) {
+        TreeSet<K> delta = (TreeSet<K>) this.clone();
+        delta.removeAll(in);
+        return delta;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassReader.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,1003 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
+
+import java.util.*;
+import java.util.jar.*;
+import java.lang.reflect.*;
+import java.io.*;
+import xmlkit.XMLKit.Element;
+
+/*
+ * @author jrose
+ */
+public class ClassReader extends ClassSyntax {
+
+    private static final CommandLineParser CLP = new CommandLineParser(""
+            + "-source:     +> = \n"
+            + "-dest:       +> = \n"
+            + "-encoding:   +> = \n"
+            + "-jcov           $ \n   -nojcov         !-jcov        \n"
+            + "-verbose        $ \n   -noverbose      !-verbose     \n"
+            + "-pretty         $ \n   -nopretty       !-pretty      \n"
+            + "-keepPath       $ \n   -nokeepPath     !-keepPath    \n"
+            + "-keepCP         $ \n   -nokeepCP       !-keepCP      \n"
+            + "-keepBytes      $ \n   -nokeepBytes    !-keepBytes   \n"
+            + "-parseBytes     $ \n   -noparseBytes   !-parseBytes  \n"
+            + "-resolveRefs    $ \n   -noresolveRefs  !-resolveRefs \n"
+            + "-keepOrder      $ \n   -nokeepOrder    !-keepOrder   \n"
+            + "-keepSizes      $ \n   -nokeepSizes    !-keepSizes   \n"
+            + "-continue       $ \n   -nocontinue     !-continue    \n"
+            + "-attrDef        & \n"
+            + "-@         >-@  . \n"
+            + "-              +? \n"
+            + "\n");
+
+    public static void main(String[] ava) throws IOException {
+        ArrayList<String> av = new ArrayList<String>(Arrays.asList(ava));
+        HashMap<String, String> props = new HashMap<String, String>();
+        props.put("-encoding:", "UTF8");  // default
+        props.put("-keepOrder", null);    // CLI default
+        props.put("-pretty", "1");     // CLI default
+        props.put("-continue", "1");     // CLI default
+        CLP.parse(av, props);
+        //System.out.println(props+" ++ "+av);
+        File source = asFile(props.get("-source:"));
+        File dest = asFile(props.get("-dest:"));
+        String encoding = props.get("-encoding:");
+        boolean contError = props.containsKey("-continue");
+        ClassReader options = new ClassReader();
+        options.copyOptionsFrom(props);
+        /*
+        if (dest == null && av.size() > 1) {
+        dest = File.createTempFile("TestOut", ".dir", new File("."));
+        dest.delete();
+        if (!dest.mkdir())
+        throw new RuntimeException("Cannot create "+dest);
+        System.out.println("Writing results to "+dest);
+        }
+         */
+        if (av.isEmpty()) {
+            av.add("doit");  //to enter this loop
+        }
+        boolean readList = false;
+        for (String a : av) {
+            if (readList) {
+                readList = false;
+                InputStream fin;
+                if (a.equals("-")) {
+                    fin = System.in;
+                } else {
+                    fin = new FileInputStream(a);
+                }
+
+                BufferedReader files = makeReader(fin, encoding);
+                for (String file; (file = files.readLine()) != null;) {
+                    doFile(file, source, dest, options, encoding, contError);
+                }
+                if (fin != System.in) {
+                    fin.close();
+                }
+            } else if (a.equals("-@")) {
+                readList = true;
+            } else if (a.startsWith("-")) {
+                throw new RuntimeException("Bad flag argument: " + a);
+            } else if (source.getName().endsWith(".jar")) {
+                doJar(a, source, dest, options, encoding, contError);
+            } else {
+                doFile(a, source, dest, options, encoding, contError);
+            }
+        }
+    }
+
+    private static File asFile(String str) {
+        return (str == null) ? null : new File(str);
+    }
+
+    private static void doFile(String a,
+            File source, File dest,
+            ClassReader options, String encoding,
+            boolean contError) throws IOException {
+        if (!contError) {
+            doFile(a, source, dest, options, encoding);
+        } else {
+            try {
+                doFile(a, source, dest, options, encoding);
+            } catch (Exception ee) {
+                System.out.println("Error processing " + source + ": " + ee);
+            }
+        }
+    }
+
+    private static void doJar(String a, File source, File dest, ClassReader options,
+            String encoding, Boolean contError) throws IOException {
+        try {
+            JarFile jf = new JarFile(source);
+            for (JarEntry je : Collections.list((Enumeration<JarEntry>) jf.entries())) {
+                String name = je.getName();
+                if (!name.endsWith(".class")) {
+                    continue;
+                }
+                doStream(name, jf.getInputStream(je), dest, options, encoding);
+            }
+        } catch (IOException ioe) {
+            if (contError) {
+                System.out.println("Error processing " + source + ": " + ioe);
+            } else {
+                throw ioe;
+            }
+        }
+    }
+
+    private static void doStream(String a, InputStream in, File dest,
+            ClassReader options, String encoding) throws IOException {
+
+        File f = new File(a);
+        ClassReader cr = new ClassReader(options);
+        Element e = cr.readFrom(in);
+
+        OutputStream out;
+        if (dest == null) {
+            //System.out.println(e.prettyString());
+            out = System.out;
+        } else {
+            File outf = new File(dest, f.isAbsolute() ? f.getName() : f.getPath());
+            String outName = outf.getName();
+            File outSubdir = outf.getParentFile();
+            outSubdir.mkdirs();
+            int extPos = outName.lastIndexOf('.');
+            if (extPos > 0) {
+                outf = new File(outSubdir, outName.substring(0, extPos) + ".xml");
+            }
+            out = new FileOutputStream(outf);
+        }
+
+        Writer outw = makeWriter(out, encoding);
+        if (options.pretty || !options.keepOrder) {
+            e.writePrettyTo(outw);
+        } else {
+            e.writeTo(outw);
+        }
+        if (out == System.out) {
+            outw.write("\n");
+            outw.flush();
+        } else {
+            outw.close();
+        }
+    }
+
+    private static void doFile(String a,
+            File source, File dest,
+            ClassReader options, String encoding) throws IOException {
+        File inf = new File(source, a);
+        if (dest != null && options.verbose) {
+            System.out.println("Reading " + inf);
+        }
+
+        BufferedInputStream in = new BufferedInputStream(new FileInputStream(inf));
+
+        doStream(a, in, dest, options, encoding);
+
+    }
+
+    public static BufferedReader makeReader(InputStream in, String encoding) throws IOException {
+        // encoding in DEFAULT, '', UTF8, 8BIT, , or any valid encoding name
+        if (encoding.equals("8BIT")) {
+            encoding = EIGHT_BIT_CHAR_ENCODING;
+        }
+        if (encoding.equals("UTF8")) {
+            encoding = UTF8_ENCODING;
+        }
+        if (encoding.equals("DEFAULT")) {
+            encoding = null;
+        }
+        if (encoding.equals("-")) {
+            encoding = null;
+        }
+        Reader inw;
+        in = new BufferedInputStream(in);  // add buffering
+        if (encoding == null) {
+            inw = new InputStreamReader(in);
+        } else {
+            inw = new InputStreamReader(in, encoding);
+        }
+        return new BufferedReader(inw);  // add buffering
+    }
+
+    public static Writer makeWriter(OutputStream out, String encoding) throws IOException {
+        // encoding in DEFAULT, '', UTF8, 8BIT, , or any valid encoding name
+        if (encoding.equals("8BIT")) {
+            encoding = EIGHT_BIT_CHAR_ENCODING;
+        }
+        if (encoding.equals("UTF8")) {
+            encoding = UTF8_ENCODING;
+        }
+        if (encoding.equals("DEFAULT")) {
+            encoding = null;
+        }
+        if (encoding.equals("-")) {
+            encoding = null;
+        }
+        Writer outw;
+        if (encoding == null) {
+            outw = new OutputStreamWriter(out);
+        } else {
+            outw = new OutputStreamWriter(out, encoding);
+        }
+        return new BufferedWriter(outw);  // add buffering
+    }
+
+    public Element result() {
+        return cfile;
+    }
+    protected InputStream in;
+    protected ByteArrayOutputStream buf = new ByteArrayOutputStream(1024);
+    protected byte cpTag[];
+    protected String cpName[];
+    protected String[] callables;     // varies
+    public static final String REF_PREFIX = "#";
+    // input options
+    public boolean pretty = false;
+    public boolean verbose = false;
+    public boolean keepPath = false;
+    public boolean keepCP = false;
+    public boolean keepBytes = false;
+    public boolean parseBytes = true;
+    public boolean resolveRefs = true;
+    public boolean keepOrder = true;
+    public boolean keepSizes = false;
+
+    public ClassReader() {
+        super.cfile = new Element("ClassFile");
+    }
+
+    public ClassReader(ClassReader options) {
+        this();
+        copyOptionsFrom(options);
+    }
+
+    public void copyOptionsFrom(ClassReader options) {
+        pretty = options.pretty;
+        verbose = options.verbose;
+        keepPath = options.keepPath;
+        keepCP = options.keepCP;
+        keepBytes = options.keepBytes;
+        parseBytes = options.parseBytes;
+        resolveRefs = options.resolveRefs;
+        keepSizes = options.keepSizes;
+        keepOrder = options.keepOrder;
+        attrTypes = options.attrTypes;
+    }
+
+    public void copyOptionsFrom(Map<String, String> options) {
+        if (options.containsKey("-pretty")) {
+            pretty = (options.get("-pretty") != null);
+        }
+        if (options.containsKey("-verbose")) {
+            verbose = (options.get("-verbose") != null);
+        }
+        if (options.containsKey("-keepPath")) {
+            keepPath = (options.get("-keepPath") != null);
+        }
+        if (options.containsKey("-keepCP")) {
+            keepCP = (options.get("-keepCP") != null);
+        }
+        if (options.containsKey("-keepBytes")) {
+            keepBytes = (options.get("-keepBytes") != null);
+        }
+        if (options.containsKey("-parseBytes")) {
+            parseBytes = (options.get("-parseBytes") != null);
+        }
+        if (options.containsKey("-resolveRefs")) {
+            resolveRefs = (options.get("-resolveRefs") != null);
+        }
+        if (options.containsKey("-keepSizes")) {
+            keepSizes = (options.get("-keepSizes") != null);
+        }
+        if (options.containsKey("-keepOrder")) {
+            keepOrder = (options.get("-keepOrder") != null);
+        }
+        if (options.containsKey("-attrDef")) {
+            addAttrTypes(options.get("-attrDef").split(" "));
+        }
+        if (options.get("-jcov") != null) {
+            addJcovAttrTypes();
+        }
+    }
+
+    public Element readFrom(InputStream in) throws IOException {
+        this.in = in;
+        // read the file header
+        int magic = u4();
+        if (magic != 0xCAFEBABE) {
+            throw new RuntimeException("bad magic number " + Integer.toHexString(magic));
+        }
+        cfile.setAttr("magic", "" + magic);
+        int minver = u2();
+        int majver = u2();
+        cfile.setAttr("minver", "" + minver);
+        cfile.setAttr("majver", "" + majver);
+        readCP();
+        readClass();
+        return result();
+    }
+
+    public Element readFrom(File file) throws IOException {
+        InputStream in = null;
+        try {
+            in = new FileInputStream(file);
+            Element e = readFrom(new BufferedInputStream(in));
+            if (keepPath) {
+                e.setAttr("path", file.toString());
+            }
+            return e;
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    private void readClass() throws IOException {
+        klass = new Element("Class");
+        cfile.add(klass);
+        int flags = u2();
+        String thisk = cpRef();
+        String superk = cpRef();
+        klass.setAttr("name", thisk);
+        boolean flagsSync = ((flags & Modifier.SYNCHRONIZED) != 0);
+        flags &= ~Modifier.SYNCHRONIZED;
+        String flagString = flagString(flags, klass);
+        if (!flagsSync) {
+            if (flagString.length() > 0) {
+                flagString += " ";
+            }
+            flagString += "!synchronized";
+        }
+        klass.setAttr("flags", flagString);
+        klass.setAttr("super", superk);
+        for (int len = u2(), i = 0; i < len; i++) {
+            String interk = cpRef();
+            klass.add(new Element("Interface", "name", interk));
+        }
+        Element fields = readMembers("Field");
+        klass.addAll(fields);
+        Element methods = readMembers("Method");
+        if (!keepOrder) {
+            methods.sort();
+        }
+        klass.addAll(methods);
+        readAttributesFor(klass);
+        klass.trimToSize();
+        if (keepSizes) {
+            attachTo(cfile, formatAttrSizes());
+        }
+        if (paddingSize != 0) {
+            cfile.setAttr("padding", "" + paddingSize);
+        }
+    }
+
+    private Element readMembers(String kind) throws IOException {
+        int len = u2();
+        Element members = new Element(len);
+        for (int i = 0; i < len; i++) {
+            Element member = new Element(kind);
+            int flags = u2();
+            String name = cpRef();
+            String type = cpRef();
+            member.setAttr("name", name);
+            member.setAttr("type", type);
+            member.setAttr("flags", flagString(flags, member));
+            readAttributesFor(member);
+            member.trimToSize();
+            members.add(member);
+        }
+        return members;
+    }
+
+    protected String flagString(int flags, Element holder) {
+        // Superset of Modifier.toString.
+        int kind = 0;
+        if (holder.getName() == "Field") {
+            kind = 1;
+        }
+        if (holder.getName() == "Method") {
+            kind = 2;
+        }
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; flags != 0; i++, flags >>>= 1) {
+            if ((flags & 1) != 0) {
+                if (sb.length() > 0) {
+                    sb.append(' ');
+                }
+                if (i < modifierNames.length) {
+                    String[] names = modifierNames[i];
+                    String name = (kind < names.length) ? names[kind] : null;
+                    for (String name2 : names) {
+                        if (name != null) {
+                            break;
+                        }
+                        name = name2;
+                    }
+                    sb.append(name);
+                } else {
+                    sb.append("#").append(1 << i);
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    private void readAttributesFor(Element x) throws IOException {
+        Element prevCurrent;
+        Element y = new Element();
+        if (x.getName() == "Code") {
+            prevCurrent = currentCode;
+            currentCode = x;
+        } else {
+            prevCurrent = currentMember;
+            currentMember = x;
+        }
+        for (int len = u2(), i = 0; i < len; i++) {
+            int ref = u2();
+            String uname = cpName(ref).intern();
+            String refName = uname;
+            if (!resolveRefs) {
+                refName = (REF_PREFIX + ref).intern();
+            }
+            String qname = (x.getName() + "." + uname).intern();
+            String wname = ("*." + uname).intern();
+            String type = attrTypes.get(qname);
+            if (type == null || "".equals(type)) {
+                type = attrTypes.get(wname);
+            }
+            if ("".equals(type)) {
+                type = null;
+            }
+            int size = u4();
+            int[] countVar = attrSizes.get(qname);
+            if (countVar == null) {
+                attrSizes.put(qname, countVar = new int[2]);
+            }
+            countVar[0] += 1;
+            countVar[1] += size;
+            buf.reset();
+            for (int j = 0; j < size; j++) {
+                buf.write(u1());
+            }
+            if (type == null && size == 0) {
+                y.add(new Element(uname)); // <Bridge>, etc.
+            } else if (type == null) {
+                //System.out.println("Warning:  No attribute type description: "+qname);
+                // write cdata attribute
+                Element a = new Element("Attribute",
+                        new String[]{"Name", refName},
+                        buf.toString(EIGHT_BIT_CHAR_ENCODING));
+                a.addContent(getCPDigest());
+                y.add(a);
+            } else if (type.equals("")) {
+                // ignore this attribute...
+            } else {
+                InputStream in0 = in;
+                int fileSize0 = fileSize;
+                ByteArrayInputStream in1 = new ByteArrayInputStream(buf.toByteArray());
+                boolean ok = false;
+                try {
+                    in = in1;
+                    // parse according to type desc.
+                    Element aval;
+                    if (type.equals("<Code>...")) {
+                        // delve into Code attribute
+                        aval = readCode();
+                    } else if (type.equals("<Frame>...")) {
+                        // delve into StackMap attribute
+                        aval = readStackMap(false);
+                    } else if (type.equals("<FrameX>...")) {
+                        // delve into StackMap attribute
+                        aval = readStackMap(true);
+                    } else if (type.startsWith("[")) {
+                        aval = readAttributeCallables(type);
+                    } else {
+                        aval = readAttribute(type);
+                    }
+                    //System.out.println("attachTo 1 "+y+" <- "+aval);
+                    attachTo(y, aval);
+                    if (false
+                            && in1.available() != 0) {
+                        throw new RuntimeException("extra bytes in " + qname + " :" + in1.available());
+                    }
+                    ok = true;
+                } finally {
+                    in = in0;
+                    fileSize = fileSize0;
+                    if (!ok) {
+                        System.out.println("*** Failed to read " + type);
+                    }
+                }
+            }
+        }
+        if (x.getName() == "Code") {
+            currentCode = prevCurrent;
+        } else {
+            currentMember = prevCurrent;
+        }
+        if (!keepOrder) {
+            y.sort();
+            y.sortAttrs();
+        }
+        //System.out.println("attachTo 2 "+x+" <- "+y);
+        attachTo(x, y);
+    }
+    private int fileSize = 0;
+    private int paddingSize = 0;
+    private HashMap<String, int[]> attrSizes = new HashMap<String, int[]>();
+
+    private Element formatAttrSizes() {
+        Element e = new Element("Sizes");
+        e.setAttr("fileSize", "" + fileSize);
+        for (Map.Entry<String, int[]> ie : attrSizes.entrySet()) {
+            int[] countVar = ie.getValue();
+            e.add(new Element("AttrSize",
+                    "name", ie.getKey().toString(),
+                    "count", "" + countVar[0],
+                    "size", "" + countVar[1]));
+        }
+        return e;
+    }
+
+    private void attachTo(Element x, Object aval0) {
+        if (aval0 == null) {
+            return;
+        }
+        //System.out.println("attachTo "+x+" : "+aval0);
+        if (!(aval0 instanceof Element)) {
+            x.add(aval0);
+            return;
+        }
+        Element aval = (Element) aval0;
+        if (!aval.isAnonymous()) {
+            x.add(aval);
+            return;
+        }
+        for (int imax = aval.attrSize(), i = 0; i < imax; i++) {
+            //%%
+            attachAttrTo(x, aval.getAttrName(i), aval.getAttr(i));
+        }
+        x.addAll(aval);
+    }
+
+    private void attachAttrTo(Element x, String aname, String aval) {
+        //System.out.println("attachAttrTo "+x+" : "+aname+"="+aval);
+        String aval0 = x.getAttr(aname);
+        if (aval0 != null) {
+            aval = aval0 + " " + aval;
+        }
+        x.setAttr(aname, aval);
+    }
+
+    private Element readAttributeCallables(String type) throws IOException {
+        assert (callables == null);
+        callables = getBodies(type);
+        Element res = readAttribute(callables[0]);
+        callables = null;
+        return res;
+    }
+
+    private Element readAttribute(String type) throws IOException {
+        //System.out.println("readAttribute "+type);
+        Element aval = new Element();
+        String nextAttrName = null;
+        for (int len = type.length(), next, i = 0; i < len; i = next) {
+            String value;
+            switch (type.charAt(i)) {
+                case '<':
+                    assert (nextAttrName == null);
+                    next = type.indexOf('>', ++i);
+                    String form = type.substring(i, next++);
+                    if (form.indexOf('=') < 0) {
+                        //  elem_placement = '<' elemname '>'
+                        assert (aval.attrSize() == 0);
+                        assert (aval.isAnonymous());
+                        aval.setName(form.intern());
+                    } else {
+                        //  attr_placement = '<' attrname '=' (value)? '>'
+                        int eqPos = form.indexOf('=');
+                        nextAttrName = form.substring(0, eqPos).intern();
+                        if (eqPos != form.length() - 1) {
+                            value = form.substring(eqPos + 1);
+                            attachAttrTo(aval, nextAttrName, value);
+                            nextAttrName = null;
+                        }
+                        // ...else subsequent type parsing will find the attr value
+                        // and add it as "nextAttrName".
+                    }
+                    continue;
+                case '(':
+                    next = type.indexOf(')', ++i);
+                    int callee = Integer.parseInt(type.substring(i, next++));
+                    attachTo(aval, readAttribute(callables[callee]));
+                    continue;
+                case 'N': // replication = 'N' int '[' type ... ']'
+                {
+                    int count = getInt(type.charAt(i + 1), false);
+                    assert (count >= 0);
+                    next = i + 2;
+                    String type1 = getBody(type, next);
+                    next += type1.length() + 2;  // skip body and brackets
+                    for (int j = 0; j < count; j++) {
+                        attachTo(aval, readAttribute(type1));
+                    }
+                }
+                continue;
+                case 'T': // union = 'T' any_int union_case* '(' ')' '[' body ']'
+                    int tagValue;
+                    if (type.charAt(++i) == 'S') {
+                        tagValue = getInt(type.charAt(++i), true);
+                    } else {
+                        tagValue = getInt(type.charAt(i), false);
+                    }
+                    attachAttrTo(aval, "tag", "" + tagValue);  // always named "tag"
+                    ++i;  // skip the int type char
+                    // union_case = '(' uc_tag (',' uc_tag)* ')' '[' body ']'
+                    // uc_tag = ('-')? digit+
+                    for (boolean foundCase = false;; i = next) {
+                        assert (type.charAt(i) == '(');
+                        next = type.indexOf(')', ++i);
+                        assert (next >= i);
+                        if (type.charAt(next - 1) == '\\'
+                                && type.charAt(next - 2) != '\\') // Skip an escaped paren.
+                        {
+                            next = type.indexOf(')', next + 1);
+                        }
+                        String caseStr = type.substring(i, next++);
+                        String type1 = getBody(type, next);
+                        next += type1.length() + 2;  // skip body and brackets
+                        boolean lastCase = (caseStr.length() == 0);
+                        if (!foundCase
+                                && (lastCase || matchTag(tagValue, caseStr))) {
+                            foundCase = true;
+                            // Execute this body.
+                            attachTo(aval, readAttribute(type1));
+                        }
+                        if (lastCase) {
+                            break;
+                        }
+                    }
+                    continue;
+                case 'B':
+                case 'H':
+                case 'I': // int = oneof "BHI"
+                    next = i + 1;
+                    value = "" + getInt(type.charAt(i), false);
+                    break;
+                case 'K':
+                    assert ("IJFDLQ".indexOf(type.charAt(i + 1)) >= 0);
+                    assert (type.charAt(i + 2) == 'H');  // only H works for now
+                    next = i + 3;
+                    value = cpRef();
+                    break;
+                case 'R':
+                    assert ("CSDFMIU?".indexOf(type.charAt(i + 1)) >= 0);
+                    assert (type.charAt(i + 2) == 'H');  // only H works for now
+                    next = i + 3;
+                    value = cpRef();
+                    break;
+                case 'P':  // bci = 'P' int
+                    next = i + 2;
+                    value = "" + getInt(type.charAt(i + 1), false);
+                    break;
+                case 'S':  // signed_int = 'S' int
+                    next = i + 2;
+                    value = "" + getInt(type.charAt(i + 1), true);
+                    break;
+                case 'F':
+                    next = i + 2;
+                    value = flagString(getInt(type.charAt(i + 1), false), currentMember);
+                    break;
+                default:
+                    throw new RuntimeException("bad attr format '" + type.charAt(i) + "': " + type);
+            }
+            // store the value
+            if (nextAttrName != null) {
+                attachAttrTo(aval, nextAttrName, value);
+                nextAttrName = null;
+            } else {
+                attachTo(aval, value);
+            }
+        }
+        //System.out.println("readAttribute => "+aval);
+        assert (nextAttrName == null);
+        return aval;
+    }
+
+    private int getInt(char ch, boolean signed) throws IOException {
+        if (signed) {
+            switch (ch) {
+                case 'B':
+                    return (byte) u1();
+                case 'H':
+                    return (short) u2();
+                case 'I':
+                    return (int) u4();
+            }
+        } else {
+            switch (ch) {
+                case 'B':
+                    return u1();
+                case 'H':
+                    return u2();
+                case 'I':
+                    return u4();
+            }
+        }
+        assert ("BHIJ".indexOf(ch) >= 0);
+        return 0;
+    }
+
+    private Element readCode() throws IOException {
+        int stack = u2();
+        int local = u2();
+        int length = u4();
+        StringBuilder sb = new StringBuilder(length);
+        for (int i = 0; i < length; i++) {
+            sb.append((char) u1());
+        }
+        String bytecodes = sb.toString();
+        Element e = new Element("Code",
+                "stack", "" + stack,
+                "local", "" + local);
+        Element bytes = new Element("Bytes", (String[]) null, bytecodes);
+        if (keepBytes) {
+            e.add(bytes);
+        }
+        if (parseBytes) {
+            e.add(parseByteCodes(bytecodes));
+        }
+        for (int len = u2(), i = 0; i < len; i++) {
+            int start = u2();
+            int end = u2();
+            int catsh = u2();
+            String clasz = cpRef();
+            e.add(new Element("Handler",
+                    "start", "" + start,
+                    "end", "" + end,
+                    "catch", "" + catsh,
+                    "class", clasz));
+        }
+        readAttributesFor(e);
+        e.trimToSize();
+        return e;
+    }
+
+    private Element parseByteCodes(String bytecodes) {
+        Element e = InstructionSyntax.parse(bytecodes);
+        for (Element ins : e.elements()) {
+            Number ref = ins.getAttrNumber("ref");
+            if (ref != null && resolveRefs) {
+                int id = ref.intValue();
+                String val = cpName(id);
+                if (ins.getName().startsWith("ldc")) {
+                    // Yuck:  Arb. string cannot be an XML attribute.
+                    ins.add(val);
+                    val = "";
+                    byte tag = (id >= 0 && id < cpTag.length) ? cpTag[id] : 0;
+                    if (tag != 0) {
+                        ins.setAttrLong("tag", tag);
+                    }
+                }
+                if (ins.getName() == "invokeinterface"
+                        && computeInterfaceNum(val) == ins.getAttrLong("num")) {
+                    ins.setAttr("num", null);  // garbage bytes
+                }
+                ins.setAttr("ref", null);
+                ins.setAttr("val", val);
+            }
+        }
+        return e;
+    }
+
+    private Element readStackMap(boolean hasXOption) throws IOException {
+        Element result = new Element();
+        Element bytes = currentCode.findElement("Bytes");
+        assert (bytes != null && bytes.size() == 1);
+        int byteLength = ((String) bytes.get(0)).length();
+        boolean uoffsetIsU4 = (byteLength >= (1 << 16));
+        boolean ulocalvarIsU4 = currentCode.getAttrLong("local") >= (1 << 16);
+        boolean ustackIsU4 = currentCode.getAttrLong("stack") >= (1 << 16);
+        if (hasXOption || uoffsetIsU4 || ulocalvarIsU4 || ustackIsU4) {
+            Element flags = new Element("StackMapFlags");
+            if (hasXOption) {
+                flags.setAttr("hasXOption", "true");
+            }
+            if (uoffsetIsU4) {
+                flags.setAttr("uoffsetIsU4", "true");
+            }
+            if (ulocalvarIsU4) {
+                flags.setAttr("ulocalvarIsU4", "true");
+            }
+            if (ustackIsU4) {
+                flags.setAttr("ustackIsU4", "true");
+            }
+            currentCode.add(flags);
+        }
+        int frame_count = (uoffsetIsU4 ? u4() : u2());
+        for (int i = 0; i < frame_count; i++) {
+            int bci = (uoffsetIsU4 ? u4() : u2());
+            int flags = (hasXOption ? u1() : 0);
+            Element frame = new Element("Frame");
+            result.add(frame);
+            if (flags != 0) {
+                frame.setAttr("flags", "" + flags);
+            }
+            frame.setAttr("bci", "" + bci);
+            // Scan local and stack types in this frame:
+            final int LOCALS = 0, STACK = 1;
+            for (int j = LOCALS; j <= STACK; j++) {
+                int typeSize;
+                if (j == LOCALS) {
+                    typeSize = (ulocalvarIsU4 ? u4() : u2());
+                } else { // STACK
+                    typeSize = (ustackIsU4 ? u4() : u2());
+                }
+                Element types = new Element(j == LOCALS ? "Local" : "Stack");
+                for (int k = 0; k < typeSize; k++) {
+                    int tag = u1();
+                    Element type = new Element(itemTagName(tag));
+                    types.add(type);
+                    switch (tag) {
+                        case ITEM_Object:
+                            type.setAttr("class", cpRef());
+                            break;
+                        case ITEM_Uninitialized:
+                        case ITEM_ReturnAddress:
+                            type.setAttr("bci", "" + (uoffsetIsU4 ? u4() : u2()));
+                            break;
+                    }
+                }
+                if (types.size() > 0) {
+                    frame.add(types);
+                }
+            }
+        }
+        return result;
+    }
+
+    private void readCP() throws IOException {
+        int cpLen = u2();
+        cpTag = new byte[cpLen];
+        cpName = new String[cpLen];
+        int cpTem[][] = new int[cpLen][];
+        for (int i = 1; i < cpLen; i++) {
+            cpTag[i] = (byte) u1();
+            switch (cpTag[i]) {
+                case CONSTANT_Utf8:
+                    buf.reset();
+                    for (int len = u2(), j = 0; j < len; j++) {
+                        buf.write(u1());
+                    }
+                    cpName[i] = buf.toString(UTF8_ENCODING);
+                    break;
+                case CONSTANT_Integer:
+                    cpName[i] = String.valueOf((int) u4());
+                    break;
+                case CONSTANT_Float:
+                    cpName[i] = String.valueOf(Float.intBitsToFloat(u4()));
+                    break;
+                case CONSTANT_Long:
+                    cpName[i] = String.valueOf(u8());
+                    i += 1;
+                    break;
+                case CONSTANT_Double:
+                    cpName[i] = String.valueOf(Double.longBitsToDouble(u8()));
+                    i += 1;
+                    break;
+                case CONSTANT_Class:
+                case CONSTANT_String:
+                    cpTem[i] = new int[]{u2()};
+                    break;
+                case CONSTANT_Fieldref:
+                case CONSTANT_Methodref:
+                case CONSTANT_InterfaceMethodref:
+                case CONSTANT_NameAndType:
+                    cpTem[i] = new int[]{u2(), u2()};
+                    break;
+            }
+        }
+        for (int i = 1; i < cpLen; i++) {
+            switch (cpTag[i]) {
+                case CONSTANT_Class:
+                case CONSTANT_String:
+                    cpName[i] = cpName[cpTem[i][0]];
+                    break;
+                case CONSTANT_NameAndType:
+                    cpName[i] = cpName[cpTem[i][0]] + " " + cpName[cpTem[i][1]];
+                    break;
+            }
+        }
+        // do fieldref et al after nameandtype are all resolved
+        for (int i = 1; i < cpLen; i++) {
+            switch (cpTag[i]) {
+                case CONSTANT_Fieldref:
+                case CONSTANT_Methodref:
+                case CONSTANT_InterfaceMethodref:
+                    cpName[i] = cpName[cpTem[i][0]] + " " + cpName[cpTem[i][1]];
+                    break;
+            }
+        }
+        cpool = new Element("ConstantPool", cpName.length);
+        for (int i = 0; i < cpName.length; i++) {
+            if (cpName[i] == null) {
+                continue;
+            }
+            cpool.add(new Element(cpTagName(cpTag[i]),
+                    new String[]{"id", "" + i},
+                    cpName[i]));
+        }
+        if (keepCP) {
+            cfile.add(cpool);
+        }
+    }
+
+    private String cpRef() throws IOException {
+        int ref = u2();
+        if (resolveRefs) {
+            return cpName(ref);
+        } else {
+            return REF_PREFIX + ref;
+        }
+    }
+
+    private String cpName(int id) {
+        if (id >= 0 && id < cpName.length) {
+            return cpName[id];
+        } else {
+            return "[CP#" + Integer.toHexString(id) + "]";
+        }
+    }
+
+    private long u8() throws IOException {
+        return ((long) u4() << 32) + (((long) u4() << 32) >>> 32);
+    }
+
+    private int u4() throws IOException {
+        return (u2() << 16) + u2();
+    }
+
+    private int u2() throws IOException {
+        return (u1() << 8) + u1();
+    }
+
+    private int u1() throws IOException {
+        int x = in.read();
+        if (x < 0) {
+            paddingSize++;
+            return 0;  // error recovery
+        }
+        fileSize++;
+        assert (x == (x & 0xFF));
+        return x;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassSyntax.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,518 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
+import xmlkit.XMLKit.*;
+
+import java.util.*;
+import java.security.MessageDigest;
+import java.nio.ByteBuffer;
+import xmlkit.XMLKit.Element;
+/*
+ * @author jrose
+ */
+public abstract class ClassSyntax {
+
+    public interface GetCPIndex {
+
+        int getCPIndex(int tag, String name);  // cp finder
+    }
+    public static final int CONSTANT_Utf8 = 1,
+            CONSTANT_Integer = 3,
+            CONSTANT_Float = 4,
+            CONSTANT_Long = 5,
+            CONSTANT_Double = 6,
+            CONSTANT_Class = 7,
+            CONSTANT_String = 8,
+            CONSTANT_Fieldref = 9,
+            CONSTANT_Methodref = 10,
+            CONSTANT_InterfaceMethodref = 11,
+            CONSTANT_NameAndType = 12;
+    private static final String[] cpTagName = {
+        /* 0:  */null,
+        /* 1:  */ "Utf8",
+        /* 2:  */ null,
+        /* 3:  */ "Integer",
+        /* 4:  */ "Float",
+        /* 5:  */ "Long",
+        /* 6:  */ "Double",
+        /* 7:  */ "Class",
+        /* 8:  */ "String",
+        /* 9:  */ "Fieldref",
+        /* 10: */ "Methodref",
+        /* 11: */ "InterfaceMethodref",
+        /* 12: */ "NameAndType",
+        null
+    };
+    private static final Set<String> cpTagNames;
+
+    static {
+        Set<String> set = new HashSet<String>(Arrays.asList(cpTagName));
+        set.remove(null);
+        cpTagNames = Collections.unmodifiableSet(set);
+    }
+    public static final int ITEM_Top = 0, // replicates by [1..4,1..4]
+            ITEM_Integer = 1, // (ditto)
+            ITEM_Float = 2,
+            ITEM_Double = 3,
+            ITEM_Long = 4,
+            ITEM_Null = 5,
+            ITEM_UninitializedThis = 6,
+            ITEM_Object = 7,
+            ITEM_Uninitialized = 8,
+            ITEM_ReturnAddress = 9,
+            ITEM_LIMIT = 10;
+    private static final String[] itemTagName = {
+        "Top",
+        "Integer",
+        "Float",
+        "Double",
+        "Long",
+        "Null",
+        "UninitializedThis",
+        "Object",
+        "Uninitialized",
+        "ReturnAddress",};
+    private static final Set<String> itemTagNames;
+
+    static {
+        Set<String> set = new HashSet<String>(Arrays.asList(itemTagName));
+        set.remove(null);
+        itemTagNames = Collections.unmodifiableSet(set);
+    }
+    protected static final HashMap<String, String> attrTypesBacking;
+    protected static final Map<String, String> attrTypesInit;
+
+    static {
+        HashMap<String, String> at = new HashMap<String, String>();
+
+        //at.put("*.Deprecated", "<deprecated=true>");
+        //at.put("*.Synthetic", "<synthetic=true>");
+        ////at.put("Field.ConstantValue", "<constantValue=>KQH");
+        //at.put("Class.SourceFile", "<sourceFile=>RUH");
+        at.put("Method.Bridge", "<Bridge>");
+        at.put("Method.Varargs", "<Varargs>");
+        at.put("Class.Enum", "<Enum>");
+        at.put("*.Signature", "<Signature>RSH");
+        //at.put("*.Deprecated", "<Deprecated>");
+        //at.put("*.Synthetic", "<Synthetic>");
+        at.put("Field.ConstantValue", "<ConstantValue>KQH");
+        at.put("Class.SourceFile", "<SourceFile>RUH");
+        at.put("Class.InnerClasses", "NH[<InnerClass><class=>RCH<outer=>RCH<name=>RUH<flags=>FH]");
+        at.put("Code.LineNumberTable", "NH[<LineNumber><bci=>PH<line=>H]");
+        at.put("Code.LocalVariableTable", "NH[<LocalVariable><bci=>PH<span=>H<name=>RUH<type=>RSH<slot=>H]");
+        at.put("Code.LocalVariableTypeTable", "NH[<LocalVariableType><bci=>PH<span=>H<name=>RUH<type=>RSH<slot=>H]");
+        at.put("Method.Exceptions", "NH[<Exception><name=>RCH]");
+        at.put("Method.Code", "<Code>...");
+        at.put("Code.StackMapTable", "<Frame>...");
+        //at.put("Code.StkMapX", "<FrameX>...");
+        if (true) {
+            at.put("Code.StackMapTable",
+                    "[NH[<Frame>(1)]]"
+                    + "[TB"
+                    + "(64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79"
+                    + ",80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95"
+                    + ",96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111"
+                    + ",112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127"
+                    + ")[<SameLocals1StackItemFrame>(4)]"
+                    + "(247)[<SameLocals1StackItemExtended>H(4)]"
+                    + "(248)[<Chop3>H]"
+                    + "(249)[<Chop2>H]"
+                    + "(250)[<Chop1>H]"
+                    + "(251)[<SameFrameExtended>H]"
+                    + "(252)[<Append1>H(4)]"
+                    + "(253)[<Append2>H(4)(4)]"
+                    + "(254)[<Append3>H(4)(4)(4)]"
+                    + "(255)[<FullFrame>H(2)(3)]"
+                    + "()[<SameFrame>]]"
+                    + "[NH[<Local>(4)]]"
+                    + "[NH[<Stack>(4)]]"
+                    + "[TB"
+                    + ("(0)[<Top>]"
+                    + "(1)[<ItemInteger>](2)[<ItemFloat>](3)[<ItemDouble>](4)[<ItemLong>]"
+                    + "(5)[<ItemNull>](6)[<ItemUninitializedThis>]"
+                    + "(7)[<ItemObject><class=>RCH]"
+                    + "(8)[<ItemUninitialized><bci=>PH]"
+                    + "()[<ItemUnknown>]]"));
+        }
+
+        at.put("Class.EnclosingMethod", "<EnclosingMethod><class=>RCH<desc=>RDH");//RDNH
+
+        // Layouts of metadata attrs:
+        String vpf = "[<RuntimeVisibleAnnotation>";
+        String ipf = "[<RuntimeInvisibleAnnotation>";
+        String apf = "[<Annotation>";
+        String mdanno2 = ""
+                + "<type=>RSHNH[<Member><name=>RUH(3)]]"
+                + ("[TB"
+                + "(\\B,\\C,\\I,\\S,\\Z)[<value=>KIH]"
+                + "(\\D)[<value=>KDH]"
+                + "(\\F)[<value=>KFH]"
+                + "(\\J)[<value=>KJH]"
+                + "(\\c)[<class=>RSH]"
+                + "(\\e)[<type=>RSH<name=>RUH]"
+                + "(\\s)[<String>RUH]"
+                + "(\\@)[(2)]"
+                + "(\\[)[NH[<Element>(3)]]"
+                + "()[]"
+                + "]");
+        String visanno = "[NH[(2)]][(1)]" + vpf + mdanno2;
+        String invanno = "[NH[(2)]][(1)]" + ipf + mdanno2;
+        String vparamanno = ""
+                + "[NB[<RuntimeVisibleParameterAnnotation>(1)]][NH[(2)]]"
+                + apf + mdanno2;
+        String iparamanno = ""
+                + "[NB[<RuntimeInvisibleParameterAnnotation>(1)]][NH[(2)]]"
+                + apf + mdanno2;
+        String mdannodef = "[<AnnotationDefault>(3)][(1)]" + apf + mdanno2;
+        String[] mdplaces = {"Class", "Field", "Method"};
+        for (String place : mdplaces) {
+            at.put(place + ".RuntimeVisibleAnnotations", visanno);
+            at.put(place + ".RuntimeInvisibleAnnotations", invanno);
+        }
+        at.put("Method.RuntimeVisibleParameterAnnotations", vparamanno);
+        at.put("Method.RuntimeInvisibleParameterAnnotations", iparamanno);
+        at.put("Method.AnnotationDefault", mdannodef);
+
+        attrTypesBacking = at;
+        attrTypesInit = Collections.unmodifiableMap(at);
+    }
+
+    ;
+    private static final String[] jcovAttrTypes = {
+        "Code.CoverageTable=NH[<Coverage><bci=>PH<type=>H<line=>I<pos=>I]",
+        "Code.CharacterRangeTable=NH[<CharacterRange><bci=>PH<endbci=>POH<from=>I<to=>I<flag=>H]",
+        "Class.SourceID=<SourceID><id=>RUH",
+        "Class.CompilationID=<CompilationID><id=>RUH"
+    };
+    protected static final String[][] modifierNames = {
+        {"public"},
+        {"private"},
+        {"protected"},
+        {"static"},
+        {"final"},
+        {"synchronized"},
+        {null, "volatile", "bridge"},
+        {null, "transient", "varargs"},
+        {null, null, "native"},
+        {"interface"},
+        {"abstract"},
+        {"strictfp"},
+        {"synthetic"},
+        {"annotation"},
+        {"enum"},};
+    protected static final String EIGHT_BIT_CHAR_ENCODING = "ISO8859_1";
+    protected static final String UTF8_ENCODING = "UTF8";
+    // What XML tags are used by this syntax, apart from attributes?
+    protected static final Set<String> nonAttrTags;
+
+    static {
+        HashSet<String> tagSet = new HashSet<String>();
+        Collections.addAll(tagSet, new String[]{
+                    "ConstantPool",// the CP
+                    "Class", // the class
+                    "Interface", // implemented interfaces
+                    "Method", // methods
+                    "Field", // fields
+                    "Handler", // exception handler pseudo-attribute
+                    "Attribute", // unparsed attribute
+                    "Bytes", // bytecodes
+                    "Instructions" // bytecodes, parsed
+                });
+        nonAttrTags = Collections.unmodifiableSet(tagSet);
+    }
+
+    // Accessors.
+    public static Set<String> nonAttrTags() {
+        return nonAttrTags;
+    }
+
+    public static String cpTagName(int t) {
+        t &= 0xFF;
+        String ts = null;
+        if (t < cpTagName.length) {
+            ts = cpTagName[t];
+        }
+        if (ts != null) {
+            return ts;
+        }
+        return ("UnknownTag" + (int) t).intern();
+    }
+
+    public static int cpTagValue(String name) {
+        for (int t = 0; t < cpTagName.length; t++) {
+            if (name.equals(cpTagName[t])) {
+                return t;
+            }
+        }
+        return 0;
+    }
+
+    public static String itemTagName(int t) {
+        t &= 0xFF;
+        String ts = null;
+        if (t < itemTagName.length) {
+            ts = itemTagName[t];
+        }
+        if (ts != null) {
+            return ts;
+        }
+        return ("UnknownItem" + (int) t).intern();
+    }
+
+    public static int itemTagValue(String name) {
+        for (int t = 0; t < itemTagName.length; t++) {
+            if (name.equals(itemTagName[t])) {
+                return t;
+            }
+        }
+        return -1;
+    }
+
+    public void addJcovAttrTypes() {
+        addAttrTypes(jcovAttrTypes);
+    }
+    // Public methods for declaring attribute types.
+    protected Map<String, String> attrTypes = attrTypesInit;
+
+    public void addAttrType(String opt) {
+        int eqpos = opt.indexOf('=');
+        addAttrType(opt.substring(0, eqpos), opt.substring(eqpos + 1));
+    }
+
+    public void addAttrTypes(String[] opts) {
+        for (String opt : opts) {
+            addAttrType(opt);
+        }
+    }
+
+    private void checkAttr(String attr) {
+        if (!attr.startsWith("Class.")
+                && !attr.startsWith("Field.")
+                && !attr.startsWith("Method.")
+                && !attr.startsWith("Code.")
+                && !attr.startsWith("*.")) {
+            throw new IllegalArgumentException("attr name must start with 'Class.', etc.");
+        }
+        String uattr = attr.substring(attr.indexOf('.') + 1);
+        if (nonAttrTags.contains(uattr)) {
+            throw new IllegalArgumentException("attr name must not be one of " + nonAttrTags);
+        }
+    }
+
+    private void checkAttrs(Map<String, String> at) {
+        for (String attr : at.keySet()) {
+            checkAttr(attr);
+        }
+    }
+
+    private void modAttrs() {
+        if (attrTypes == attrTypesInit) {
+            // Make modifiable.
+            attrTypes = new HashMap<String, String>(attrTypesBacking);
+        }
+    }
+
+    public void addAttrType(String attr, String fmt) {
+        checkAttr(attr);
+        modAttrs();
+        attrTypes.put(attr, fmt);
+    }
+
+    public void addAttrTypes(Map<String, String> at) {
+        checkAttrs(at);
+        modAttrs();
+        attrTypes.putAll(at);
+    }
+
+    public Map<String, String> getAttrTypes() {
+        if (attrTypes == attrTypesInit) {
+            return attrTypes;
+        }
+        return Collections.unmodifiableMap(attrTypes);
+    }
+
+    public void setAttrTypes(Map<String, String> at) {
+        checkAttrs(at);
+        modAttrs();
+        attrTypes.keySet().retainAll(at.keySet());
+        attrTypes.putAll(at);
+    }
+
+    // attr format helpers
+    protected static boolean matchTag(int tagValue, String caseStr) {
+        //System.out.println("matchTag "+tagValue+" in "+caseStr);
+        for (int pos = 0, max = caseStr.length(), comma;
+                pos < max;
+                pos = comma + 1) {
+            int caseValue;
+            if (caseStr.charAt(pos) == '\\') {
+                caseValue = caseStr.charAt(pos + 1);
+                comma = pos + 2;
+                assert (comma == max || caseStr.charAt(comma) == ',');
+            } else {
+                comma = caseStr.indexOf(',', pos);
+                if (comma < 0) {
+                    comma = max;
+                }
+                caseValue = Integer.parseInt(caseStr.substring(pos, comma));
+            }
+            if (tagValue == caseValue) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected static String[] getBodies(String type) {
+        ArrayList<String> bodies = new ArrayList<String>();
+        for (int i = 0; i < type.length();) {
+            String body = getBody(type, i);
+            bodies.add(body);
+            i += body.length() + 2;  // skip body and brackets
+        }
+        return bodies.toArray(new String[bodies.size()]);
+    }
+
+    protected static String getBody(String type, int i) {
+        assert (type.charAt(i) == '[');
+        int next = ++i;  // skip bracket
+        for (int depth = 1; depth > 0; next++) {
+            switch (type.charAt(next)) {
+                case '[':
+                    depth++;
+                    break;
+                case ']':
+                    depth--;
+                    break;
+                case '(':
+                    next = type.indexOf(')', next);
+                    break;
+                case '<':
+                    next = type.indexOf('>', next);
+                    break;
+            }
+            assert (next > 0);
+        }
+        --next;  // get before bracket
+        assert (type.charAt(next) == ']');
+        return type.substring(i, next);
+    }
+
+    public Element makeCPDigest(int length) {
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("MD5");
+        } catch (java.security.NoSuchAlgorithmException ee) {
+            throw new Error(ee);
+        }
+        int items = 0;
+        for (Element e : cpool.elements()) {
+            if (items == length) {
+                break;
+            }
+            if (cpTagNames.contains(e.getName())) {
+                items += 1;
+                md.update((byte) cpTagValue(e.getName()));
+                try {
+                    md.update(e.getText().toString().getBytes(UTF8_ENCODING));
+                } catch (java.io.UnsupportedEncodingException ee) {
+                    throw new Error(ee);
+                }
+            }
+        }
+        ByteBuffer bb = ByteBuffer.wrap(md.digest());
+        String l0 = Long.toHexString(bb.getLong(0));
+        String l1 = Long.toHexString(bb.getLong(8));
+        while (l0.length() < 16) {
+            l0 = "0" + l0;
+        }
+        while (l1.length() < 16) {
+            l1 = "0" + l1;
+        }
+        return new Element("Digest",
+                "length", "" + items,
+                "bytes", l0 + l1);
+    }
+
+    public Element getCPDigest(int length) {
+        if (length == -1) {
+            length = cpool.countAll(XMLKit.elementFilter(cpTagNames));
+        }
+        for (Element md : cpool.findAllElements("Digest").elements()) {
+            if (md.getAttrLong("length") == length) {
+                return md;
+            }
+        }
+        Element md = makeCPDigest(length);
+        cpool.add(md);
+        return md;
+    }
+
+    public Element getCPDigest() {
+        return getCPDigest(-1);
+    }
+
+    public boolean checkCPDigest(Element md) {
+        return md.equals(getCPDigest((int) md.getAttrLong("length")));
+    }
+
+    public static int computeInterfaceNum(String intMethRef) {
+        intMethRef = intMethRef.substring(1 + intMethRef.lastIndexOf(' '));
+        if (!intMethRef.startsWith("(")) {
+            return -1;
+        }
+        int signum = 1;  // start with one for "this"
+        scanSig:
+        for (int i = 1; i < intMethRef.length(); i++) {
+            char ch = intMethRef.charAt(i);
+            signum++;
+            switch (ch) {
+                case ')':
+                    --signum;
+                    break scanSig;
+                case 'L':
+                    i = intMethRef.indexOf(';', i);
+                    break;
+                case '[':
+                    while (ch == '[') {
+                        ch = intMethRef.charAt(++i);
+                    }
+                    if (ch == 'L') {
+                        i = intMethRef.indexOf(';', i);
+                    }
+                    break;
+            }
+        }
+        int num = (signum << 8) | 0;
+        //System.out.println("computeInterfaceNum "+intMethRef+" => "+num);
+        return num;
+    }
+    // Protected state for representing the class file.
+    protected Element cfile;          // <ClassFile ...>
+    protected Element cpool;          // <ConstantPool ...>
+    protected Element klass;          // <Class ...>
+    protected Element currentMember;  // varies during scans
+    protected Element currentCode;    // varies during scans
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/ClassWriter.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,818 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
+
+import java.util.*;
+import java.lang.reflect.*;
+import java.io.*;
+import xmlkit.XMLKit.Element;
+/*
+ * @author jrose
+ */
+public class ClassWriter extends ClassSyntax implements ClassSyntax.GetCPIndex {
+
+    private static final CommandLineParser CLP = new CommandLineParser(""
+            + "-source:     +>  = \n"
+            + "-dest:       +>  = \n"
+            + "-encoding:   +>  = \n"
+            + "-parseBytes      $ \n"
+            + "-               *? \n"
+            + "\n");
+
+    public static void main(String[] ava) throws IOException {
+        ArrayList<String> av = new ArrayList<String>(Arrays.asList(ava));
+        HashMap<String, String> props = new HashMap<String, String>();
+        props.put("-encoding:", "UTF8");  // default
+        CLP.parse(av, props);
+        File source = asFile(props.get("-source:"));
+        File dest = asFile(props.get("-dest:"));
+        String encoding = props.get("-encoding:");
+        boolean parseBytes = props.containsKey("-parseBytes");
+        boolean destMade = false;
+
+        for (String a : av) {
+            File f;
+            File inf = new File(source, a);
+            System.out.println("Reading " + inf);
+            Element e;
+            if (inf.getName().endsWith(".class")) {
+                ClassReader cr = new ClassReader();
+                cr.parseBytes = parseBytes;
+                e = cr.readFrom(inf);
+                f = new File(a);
+            } else if (inf.getName().endsWith(".xml")) {
+                InputStream in = new FileInputStream(inf);
+                Reader inw = ClassReader.makeReader(in, encoding);
+                e = XMLKit.readFrom(inw);
+                e.findAllInTree(XMLKit.and(XMLKit.elementFilter(nonAttrTags()),
+                        XMLKit.methodFilter(Element.method("trimText"))));
+                //System.out.println(e);
+                inw.close();
+                f = new File(a.substring(0, a.length() - ".xml".length()) + ".class");
+            } else {
+                System.out.println("Warning: unknown input " + a);
+                continue;
+            }
+            // Now write it:
+            if (!destMade) {
+                destMade = true;
+                if (dest == null) {
+                    dest = File.createTempFile("TestOut", ".dir", new File("."));
+                    dest.delete();
+                    System.out.println("Writing results to " + dest);
+                }
+                if (!(dest.isDirectory() || dest.mkdir())) {
+                    throw new RuntimeException("Cannot create " + dest);
+                }
+            }
+            File outf = new File(dest, f.isAbsolute() ? f.getName() : f.getPath());
+            outf.getParentFile().mkdirs();
+            new ClassWriter(e).writeTo(outf);
+        }
+    }
+
+    private static File asFile(String str) {
+        return (str == null) ? null : new File(str);
+    }
+
+    public void writeTo(File file) throws IOException {
+        OutputStream out = null;
+        try {
+            out = new BufferedOutputStream(new FileOutputStream(file));
+            writeTo(out);
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+    protected String[] callables;     // varies
+    protected int cpoolSize = 0;
+    protected HashMap<String, String> attrTypesByTag;
+    protected OutputStream out;
+    protected HashMap<String, int[]> cpMap = new HashMap<String, int[]>();
+    protected ArrayList<ByteArrayOutputStream> attrBufs = new ArrayList<ByteArrayOutputStream>();
+
+    private void setupAttrTypes() {
+        attrTypesByTag = new HashMap<String, String>();
+        for (String key : attrTypes.keySet()) {
+            String pfx = key.substring(0, key.indexOf('.') + 1);
+            String val = attrTypes.get(key);
+            int pos = val.indexOf('<');
+            if (pos >= 0) {
+                String tag = val.substring(pos + 1, val.indexOf('>', pos));
+                attrTypesByTag.put(pfx + tag, key);
+            }
+        }
+        //System.out.println("attrTypesByTag: "+attrTypesByTag);
+    }
+
+    protected ByteArrayOutputStream getAttrBuf() {
+        int nab = attrBufs.size();
+        if (nab == 0) {
+            return new ByteArrayOutputStream(1024);
+        }
+        ByteArrayOutputStream ab = attrBufs.get(nab - 1);
+        attrBufs.remove(nab - 1);
+        return ab;
+    }
+
+    protected void putAttrBuf(ByteArrayOutputStream ab) {
+        ab.reset();
+        attrBufs.add(ab);
+    }
+
+    public ClassWriter(Element root) {
+        this(root, null);
+    }
+
+    public ClassWriter(Element root, ClassSyntax cr) {
+        if (cr != null) {
+            attrTypes = cr.attrTypes;
+        }
+        setupAttrTypes();
+        if (root.getName() == "ClassFile") {
+            cfile = root;
+            cpool = root.findElement("ConstantPool");
+            klass = root.findElement("Class");
+        } else if (root.getName() == "Class") {
+            cfile = new Element("ClassFile",
+                    new String[]{
+                        "magic", String.valueOf(0xCAFEBABE),
+                        "minver", "0", "majver", "46",});
+            cpool = new Element("ConstantPool");
+            klass = root;
+        } else {
+            throw new IllegalArgumentException("bad element type " + root.getName());
+        }
+        if (cpool == null) {
+            cpool = new Element("ConstantPool");
+        }
+
+        int cpLen = 1 + cpool.size();
+        for (Element c : cpool.elements()) {
+            int id = (int) c.getAttrLong("id");
+            int tag = cpTagValue(c.getName());
+            setCPIndex(tag, c.getText().toString(), id);
+            switch (tag) {
+                case CONSTANT_Long:
+                case CONSTANT_Double:
+                    cpLen += 1;
+            }
+        }
+        cpoolSize = cpLen;
+    }
+
+    public int findCPIndex(int tag, String name) {
+        if (name == null) {
+            return 0;
+        }
+        int[] ids = cpMap.get(name.toString());
+        return (ids == null) ? 0 : ids[tag];
+    }
+
+    public int getCPIndex(int tag, String name) {
+        //System.out.println("getCPIndex "+cpTagName(tag)+" "+name);
+        if (name == null) {
+            return 0;
+        }
+        int id = findCPIndex(tag, name);
+        if (id == 0) {
+            id = cpoolSize;
+            cpoolSize += 1;
+            setCPIndex(tag, name, id);
+            cpool.add(new Element(cpTagName(tag),
+                    new String[]{"id", "" + id},
+                    new Object[]{name}));
+            int pos;
+            switch (tag) {
+                case CONSTANT_Long:
+                case CONSTANT_Double:
+                    cpoolSize += 1;
+                    break;
+                case CONSTANT_Class:
+                case CONSTANT_String:
+                    getCPIndex(CONSTANT_Utf8, name);
+                    break;
+                case CONSTANT_Fieldref:
+                case CONSTANT_Methodref:
+                case CONSTANT_InterfaceMethodref:
+                    pos = name.indexOf(' ');
+                    getCPIndex(CONSTANT_Class, name.substring(0, pos));
+                    getCPIndex(CONSTANT_NameAndType, name.substring(pos + 1));
+                    break;
+                case CONSTANT_NameAndType:
+                    pos = name.indexOf(' ');
+                    getCPIndex(CONSTANT_Utf8, name.substring(0, pos));
+                    getCPIndex(CONSTANT_Utf8, name.substring(pos + 1));
+                    break;
+            }
+        }
+        return id;
+    }
+
+    public void setCPIndex(int tag, String name, int id) {
+        //System.out.println("setCPIndex id="+id+" tag="+tag+" name="+name);
+        int[] ids = cpMap.get(name);
+        if (ids == null) {
+            cpMap.put(name, ids = new int[13]);
+        }
+        if (ids[tag] != 0 && ids[tag] != id) {
+            System.out.println("Warning: Duplicate CP entries for " + ids[tag] + " and " + id);
+        }
+        //assert(ids[tag] == 0 || ids[tag] == id);
+        ids[tag] = id;
+    }
+
+    public int parseFlags(String flagString) {
+        int flags = 0;
+        int i = -1;
+        for (String[] names : modifierNames) {
+            ++i;
+            for (String name : names) {
+                if (name == null) {
+                    continue;
+                }
+                int pos = flagString.indexOf(name);
+                if (pos >= 0) {
+                    flags |= (1 << i);
+                }
+            }
+        }
+        return flags;
+    }
+
+    public void writeTo(OutputStream realOut) throws IOException {
+        OutputStream headOut = realOut;
+        ByteArrayOutputStream tailOut = new ByteArrayOutputStream();
+
+        // write the body of the class file first
+        this.out = tailOut;
+        writeClass();
+
+        // write the file header last
+        this.out = headOut;
+        u4((int) cfile.getAttrLong("magic"));
+        u2((int) cfile.getAttrLong("minver"));
+        u2((int) cfile.getAttrLong("majver"));
+        writeCP();
+
+        // recopy the file tail
+        this.out = null;
+        tailOut.writeTo(realOut);
+    }
+
+    void writeClass() throws IOException {
+        int flags = parseFlags(klass.getAttr("flags"));
+        flags ^= Modifier.SYNCHRONIZED;
+        u2(flags);
+        cpRef(CONSTANT_Class, klass.getAttr("name"));
+        cpRef(CONSTANT_Class, klass.getAttr("super"));
+        Element interfaces = klass.findAllElements("Interface");
+        u2(interfaces.size());
+        for (Element e : interfaces.elements()) {
+            cpRef(CONSTANT_Class, e.getAttr("name"));
+        }
+        for (int isMethod = 0; isMethod <= 1; isMethod++) {
+            Element members = klass.findAllElements(isMethod != 0 ? "Method" : "Field");
+            u2(members.size());
+            for (Element m : members.elements()) {
+                writeMember(m, isMethod != 0);
+            }
+        }
+        writeAttributesFor(klass);
+    }
+
+    private void writeMember(Element member, boolean isMethod) throws IOException {
+        //System.out.println("writeMember "+member);
+        u2(parseFlags(member.getAttr("flags")));
+        cpRef(CONSTANT_Utf8, member.getAttr("name"));
+        cpRef(CONSTANT_Utf8, member.getAttr("type"));
+        writeAttributesFor(member);
+    }
+
+    protected void writeAttributesFor(Element x) throws IOException {
+        LinkedHashSet<String> attrNames = new LinkedHashSet<String>();
+        for (Element e : x.elements()) {
+            attrNames.add(e.getName());  // uniquifying
+        }
+        attrNames.removeAll(nonAttrTags());
+        u2(attrNames.size());
+        if (attrNames.isEmpty()) {
+            return;
+        }
+        Element prevCurrent;
+        if (x.getName() == "Code") {
+            prevCurrent = currentCode;
+            currentCode = x;
+        } else {
+            prevCurrent = currentMember;
+            currentMember = x;
+        }
+        OutputStream realOut = this.out;
+        for (String utag : attrNames) {
+            String qtag = x.getName() + "." + utag;
+            String wtag = "*." + utag;
+            String key = attrTypesByTag.get(qtag);
+            if (key == null) {
+                key = attrTypesByTag.get(wtag);
+            }
+            String type = attrTypes.get(key);
+            //System.out.println("tag "+qtag+" => key "+key+"; type "+type);
+            Element attrs = x.findAllElements(utag);
+            ByteArrayOutputStream attrBuf = getAttrBuf();
+            if (type == null) {
+                if (attrs.size() != 1 || !attrs.get(0).equals(new Element(utag))) {
+                    System.out.println("Warning:  No attribute type description: " + qtag);
+                }
+                key = wtag;
+            } else {
+                try {
+                    this.out = attrBuf;
+                    // unparse according to type desc.
+                    if (type.equals("<Code>...")) {
+                        writeCode((Element) attrs.get(0));  // assume only 1
+                    } else if (type.equals("<Frame>...")) {
+                        writeStackMap(attrs, false);
+                    } else if (type.equals("<FrameX>...")) {
+                        writeStackMap(attrs, true);
+                    } else if (type.startsWith("[")) {
+                        writeAttributeRecursive(attrs, type);
+                    } else {
+                        writeAttribute(attrs, type);
+                    }
+                } finally {
+                    //System.out.println("Attr Bytes = \""+attrBuf.toString(EIGHT_BIT_CHAR_ENCODING).replace('"', (char)('"'|0x80))+"\"");
+                    this.out = realOut;
+                }
+            }
+            cpRef(CONSTANT_Utf8, key.substring(key.indexOf('.') + 1));
+            u4(attrBuf.size());
+            attrBuf.writeTo(out);
+            putAttrBuf(attrBuf);
+        }
+        if (x.getName() == "Code") {
+            currentCode = prevCurrent;
+        } else {
+            currentMember = prevCurrent;
+        }
+    }
+
+    private void writeAttributeRecursive(Element aval, String type) throws IOException {
+        assert (callables == null);
+        callables = getBodies(type);
+        writeAttribute(aval, callables[0]);
+        callables = null;
+    }
+
+    private void writeAttribute(Element aval, String type) throws IOException {
+        //System.out.println("writeAttribute "+aval+"  using "+type);
+        String nextAttrName = null;
+        boolean afterElemHead = false;
+        for (int len = type.length(), next, i = 0; i < len; i = next) {
+            int value;
+            char intKind;
+            int tag;
+            int sigChar;
+            String attrValue;
+            switch (type.charAt(i)) {
+                case '<':
+                    assert (nextAttrName == null);
+                    next = type.indexOf('>', i);
+                    String form = type.substring(i + 1, next++);
+                    if (form.indexOf('=') < 0) {
+                        //  elem_placement = '<' elemname '>'
+                        if (aval.isAnonymous()) {
+                            assert (aval.size() == 1);
+                            aval = (Element) aval.get(0);
+                        }
+                        assert (aval.getName().equals(form)) : aval + " // " + form;
+                        afterElemHead = true;
+                    } else {
+                        //  attr_placement = '(' attrname '=' (value)? ')'
+                        int eqPos = form.indexOf('=');
+                        assert (eqPos >= 0);
+                        nextAttrName = form.substring(0, eqPos).intern();
+                        if (eqPos != form.length() - 1) {
+                            // value is implicit, not placed in file
+                            nextAttrName = null;
+                        }
+                        afterElemHead = false;
+                    }
+                    continue;
+                case '(':
+                    next = type.indexOf(')', ++i);
+                    int callee = Integer.parseInt(type.substring(i, next++));
+                    writeAttribute(aval, callables[callee]);
+                    continue;
+                case 'N': // replication = 'N' int '[' type ... ']'
+                {
+                    assert (nextAttrName == null);
+                    afterElemHead = false;
+                    char countType = type.charAt(i + 1);
+                    next = i + 2;
+                    String type1 = getBody(type, next);
+                    Element elems = aval;
+                    if (type1.startsWith("<")) {
+                        // Select only matching members of aval.
+                        String elemName = type1.substring(1, type1.indexOf('>'));
+                        elems = aval.findAllElements(elemName);
+                    }
+                    putInt(elems.size(), countType);
+                    next += type1.length() + 2;  // skip body and brackets
+                    for (Element elem : elems.elements()) {
+                        writeAttribute(elem, type1);
+                    }
+                }
+                continue;
+                case 'T': // union = 'T' any_int union_case* '(' ')' '[' body ']'
+                    // write the value
+                    value = (int) aval.getAttrLong("tag");
+                    assert (aval.getAttr("tag") != null) : aval;
+                    intKind = type.charAt(++i);
+                    if (intKind == 'S') {
+                        intKind = type.charAt(++i);
+                    }
+                    putInt(value, intKind);
+                    nextAttrName = null;
+                    afterElemHead = false;
+                    ++i;  // skip the int type char
+                    // union_case = '(' ('-')? digit+ ')' '[' body ']'
+                    for (boolean foundCase = false;;) {
+                        assert (type.charAt(i) == '(');
+                        next = type.indexOf(')', ++i);
+                        assert (next >= i);
+                        String caseStr = type.substring(i, next++);
+                        String type1 = getBody(type, next);
+                        next += type1.length() + 2;  // skip body and brackets
+                        boolean lastCase = (caseStr.length() == 0);
+                        if (!foundCase
+                                && (lastCase || matchTag(value, caseStr))) {
+                            foundCase = true;
+                            // Execute this body.
+                            writeAttribute(aval, type1);
+                        }
+                        if (lastCase) {
+                            break;
+                        }
+                    }
+                    continue;
+                case 'B':
+                case 'H':
+                case 'I': // int = oneof "BHI"
+                    value = (int) aval.getAttrLong(nextAttrName);
+                    intKind = type.charAt(i);
+                    next = i + 1;
+                    break;
+                case 'K':
+                    sigChar = type.charAt(i + 1);
+                    if (sigChar == 'Q') {
+                        assert (currentMember.getName() == "Field");
+                        assert (aval.getName() == "ConstantValue");
+                        String sig = currentMember.getAttr("type");
+                        sigChar = sig.charAt(0);
+                        switch (sigChar) {
+                            case 'Z':
+                            case 'B':
+                            case 'C':
+                            case 'S':
+                                sigChar = 'I';
+                                break;
+                        }
+                    }
+                    switch (sigChar) {
+                        case 'I':
+                            tag = CONSTANT_Integer;
+                            break;
+                        case 'J':
+                            tag = CONSTANT_Long;
+                            break;
+                        case 'F':
+                            tag = CONSTANT_Float;
+                            break;
+                        case 'D':
+                            tag = CONSTANT_Double;
+                            break;
+                        case 'L':
+                            tag = CONSTANT_String;
+                            break;
+                        default:
+                            assert (false);
+                            tag = 0;
+                    }
+                    assert (type.charAt(i + 2) == 'H');  // only H works for now
+                    next = i + 3;
+                    assert (afterElemHead || nextAttrName != null);
+                    //System.out.println("get attr "+nextAttrName+" in "+aval);
+                    if (nextAttrName != null) {
+                        attrValue = aval.getAttr(nextAttrName);
+                        assert (attrValue != null);
+                    } else {
+                        assert (aval.isText()) : aval;
+                        attrValue = aval.getText().toString();
+                    }
+                    value = getCPIndex(tag, attrValue);
+                    intKind = 'H'; //type.charAt(i+2);
+                    break;
+                case 'R':
+                    sigChar = type.charAt(i + 1);
+                    switch (sigChar) {
+                        case 'C':
+                            tag = CONSTANT_Class;
+                            break;
+                        case 'S':
+                            tag = CONSTANT_Utf8;
+                            break;
+                        case 'D':
+                            tag = CONSTANT_Class;
+                            break;
+                        case 'F':
+                            tag = CONSTANT_Fieldref;
+                            break;
+                        case 'M':
+                            tag = CONSTANT_Methodref;
+                            break;
+                        case 'I':
+                            tag = CONSTANT_InterfaceMethodref;
+                            break;
+                        case 'U':
+                            tag = CONSTANT_Utf8;
+                            break;
+                        //case 'Q': tag = CONSTANT_Class; break;
+                        default:
+                            assert (false);
+                            tag = 0;
+                    }
+                    assert (type.charAt(i + 2) == 'H');  // only H works for now
+                    next = i + 3;
+                    assert (afterElemHead || nextAttrName != null);
+                    //System.out.println("get attr "+nextAttrName+" in "+aval);
+                    if (nextAttrName != null) {
+                        attrValue = aval.getAttr(nextAttrName);
+                    } else if (aval.hasText()) {
+                        attrValue = aval.getText().toString();
+                    } else {
+                        attrValue = null;
+                    }
+                    value = getCPIndex(tag, attrValue);
+                    intKind = 'H'; //type.charAt(i+2);
+                    break;
+                case 'P':  // bci = 'P' int
+                case 'S':  // signed_int = 'S' int
+                    next = i + 2;
+                    value = (int) aval.getAttrLong(nextAttrName);
+                    intKind = type.charAt(i + 1);
+                    break;
+                case 'F':
+                    next = i + 2;
+                    value = parseFlags(aval.getAttr(nextAttrName));
+                    intKind = type.charAt(i + 1);
+                    break;
+                default:
+                    throw new RuntimeException("bad attr format '" + type.charAt(i) + "': " + type);
+            }
+            // write the value
+            putInt(value, intKind);
+            nextAttrName = null;
+            afterElemHead = false;
+        }
+        assert (nextAttrName == null);
+    }
+
+    private void putInt(int x, char ch) throws IOException {
+        switch (ch) {
+            case 'B':
+                u1(x);
+                break;
+            case 'H':
+                u2(x);
+                break;
+            case 'I':
+                u4(x);
+                break;
+        }
+        assert ("BHI".indexOf(ch) >= 0);
+    }
+
+    private void writeCode(Element code) throws IOException {
+        //System.out.println("writeCode "+code);
+        //Element m = new Element(currentMember); m.remove(code);
+        //System.out.println("       in "+m);
+        int stack = (int) code.getAttrLong("stack");
+        int local = (int) code.getAttrLong("local");
+        Element bytes = code.findElement("Bytes");
+        Element insns = code.findElement("Instructions");
+        String bytecodes;
+        if (insns == null) {
+            bytecodes = bytes.getText().toString();
+        } else {
+            bytecodes = InstructionSyntax.assemble(insns, this);
+            // Cache the assembled bytecodes:
+            bytes = new Element("Bytes", (String[]) null, bytecodes);
+            code.add(0, bytes);
+        }
+        u2(stack);
+        u2(local);
+        int length = bytecodes.length();
+        u4(length);
+        for (int i = 0; i < length; i++) {
+            u1((byte) bytecodes.charAt(i));
+        }
+        Element handlers = code.findAllElements("Handler");
+        u2(handlers.size());
+        for (Element handler : handlers.elements()) {
+            int start = (int) handler.getAttrLong("start");
+            int end = (int) handler.getAttrLong("end");
+            int catsh = (int) handler.getAttrLong("catch");
+            u2(start);
+            u2(end);
+            u2(catsh);
+            cpRef(CONSTANT_Class, handler.getAttr("class"));
+        }
+        writeAttributesFor(code);
+    }
+
+    protected void writeStackMap(Element attrs, boolean hasXOption) throws IOException {
+        Element bytes = currentCode.findElement("Bytes");
+        assert (bytes != null && bytes.size() == 1);
+        int byteLength = ((String) bytes.get(0)).length();
+        boolean uoffsetIsU4 = (byteLength >= (1 << 16));
+        boolean ulocalvarIsU4 = currentCode.getAttrLong("local") >= (1 << 16);
+        boolean ustackIsU4 = currentCode.getAttrLong("stack") >= (1 << 16);
+        if (uoffsetIsU4) {
+            u4(attrs.size());
+        } else {
+            u2(attrs.size());
+        }
+        for (Element frame : attrs.elements()) {
+            int bci = (int) frame.getAttrLong("bci");
+            if (uoffsetIsU4) {
+                u4(bci);
+            } else {
+                u2(bci);
+            }
+            if (hasXOption) {
+                u1((int) frame.getAttrLong("flags"));
+            }
+            // Scan local and stack types in this frame:
+            final int LOCALS = 0, STACK = 1;
+            for (int j = LOCALS; j <= STACK; j++) {
+                Element types = frame.findElement(j == LOCALS ? "Local" : "Stack");
+                int typeSize = (types == null) ? 0 : types.size();
+                if (j == LOCALS) {
+                    if (ulocalvarIsU4) {
+                        u4(typeSize);
+                    } else {
+                        u2(typeSize);
+                    }
+                } else { // STACK
+                    if (ustackIsU4) {
+                        u4(typeSize);
+                    } else {
+                        u2(typeSize);
+                    }
+                }
+                if (types == null) {
+                    continue;
+                }
+                for (Element type : types.elements()) {
+                    int tag = itemTagValue(type.getName());
+                    u1(tag);
+                    switch (tag) {
+                        case ITEM_Object:
+                            cpRef(CONSTANT_Class, type.getAttr("class"));
+                            break;
+                        case ITEM_Uninitialized:
+                        case ITEM_ReturnAddress: {
+                            int offset = (int) type.getAttrLong("bci");
+                            if (uoffsetIsU4) {
+                                u4(offset);
+                            } else {
+                                u2(offset);
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    public void writeCP() throws IOException {
+        int cpLen = cpoolSize;
+        u2(cpLen);
+        ByteArrayOutputStream buf = getAttrBuf();
+        for (Element c : cpool.elements()) {
+            if (!c.isText()) {
+                System.out.println("## !isText " + c);
+            }
+            int id = (int) c.getAttrLong("id");
+            int tag = cpTagValue(c.getName());
+            String name = c.getText().toString();
+            int pos;
+            u1(tag);
+            switch (tag) {
+                case CONSTANT_Utf8: {
+                    int done = 0;
+                    buf.reset();
+                    int nameLen = name.length();
+                    while (done < nameLen) {
+                        int next = name.indexOf((char) 0, done);
+                        if (next < 0) {
+                            next = nameLen;
+                        }
+                        if (done < next) {
+                            buf.write(name.substring(done, next).getBytes(UTF8_ENCODING));
+                        }
+                        if (next < nameLen) {
+                            buf.write(0300);
+                            buf.write(0200);
+                            next++;
+                        }
+                        done = next;
+                    }
+                    u2(buf.size());
+                    buf.writeTo(out);
+                }
+                break;
+                case CONSTANT_Integer:
+                    u4(Integer.parseInt(name));
+                    break;
+                case CONSTANT_Float:
+                    u4(Float.floatToIntBits(Float.parseFloat(name)));
+                    break;
+                case CONSTANT_Long:
+                    u8(Long.parseLong(name));
+                    //i += 1;  // no need:  extra cp slot is implicit
+                    break;
+                case CONSTANT_Double:
+                    u8(Double.doubleToLongBits(Double.parseDouble(name)));
+                    //i += 1;  // no need:  extra cp slot is implicit
+                    break;
+                case CONSTANT_Class:
+                case CONSTANT_String:
+                    u2(getCPIndex(CONSTANT_Utf8, name));
+                    break;
+                case CONSTANT_Fieldref:
+                case CONSTANT_Methodref:
+                case CONSTANT_InterfaceMethodref:
+                    pos = name.indexOf(' ');
+                    u2(getCPIndex(CONSTANT_Class, name.substring(0, pos)));
+                    u2(getCPIndex(CONSTANT_NameAndType, name.substring(pos + 1)));
+                    break;
+                case CONSTANT_NameAndType:
+                    pos = name.indexOf(' ');
+                    u2(getCPIndex(CONSTANT_Utf8, name.substring(0, pos)));
+                    u2(getCPIndex(CONSTANT_Utf8, name.substring(pos + 1)));
+                    break;
+            }
+        }
+        putAttrBuf(buf);
+    }
+
+    public void cpRef(int tag, String name) throws IOException {
+        u2(getCPIndex(tag, name));
+    }
+
+    public void u8(long x) throws IOException {
+        u4((int) (x >>> 32));
+        u4((int) (x >>> 0));
+    }
+
+    public void u4(int x) throws IOException {
+        u2(x >>> 16);
+        u2(x >>> 0);
+    }
+
+    public void u2(int x) throws IOException {
+        u1(x >>> 8);
+        u1(x >>> 0);
+    }
+
+    public void u1(int x) throws IOException {
+        out.write(x & 0xFF);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/CommandLineParser.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
+
+import java.util.*;
+/*
+ * @author jrose
+ */
+public class CommandLineParser {
+
+    public CommandLineParser(String optionString) {
+        setOptionMap(optionString);
+    }
+    TreeMap<String, String[]> optionMap;
+
+    public void setOptionMap(String options) {
+        // Convert options string into optLines dictionary.
+        TreeMap<String, String[]> optmap = new TreeMap<String, String[]>();
+        loadOptmap:
+        for (String optline : options.split("\n")) {
+            String[] words = optline.split("\\p{Space}+");
+            if (words.length == 0) {
+                continue loadOptmap;
+            }
+            String opt = words[0];
+            words[0] = "";  // initial word is not a spec
+            if (opt.length() == 0 && words.length >= 1) {
+                opt = words[1];  // initial "word" is empty due to leading ' '
+                words[1] = "";
+            }
+            if (opt.length() == 0) {
+                continue loadOptmap;
+            }
+            String[] prevWords = optmap.put(opt, words);
+            if (prevWords != null) {
+                throw new RuntimeException("duplicate option: "
+                        + optline.trim());
+            }
+        }
+        optionMap = optmap;
+    }
+
+    public String getOptionMap() {
+        TreeMap<String, String[]> optmap = optionMap;
+        StringBuffer sb = new StringBuffer();
+        for (String opt : optmap.keySet()) {
+            sb.append(opt);
+            for (String spec : optmap.get(opt)) {
+                sb.append(' ').append(spec);
+            }
+            sb.append('\n');
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Remove a set of command-line options from args,
+     * storing them in the properties map in a canonicalized form.
+     */
+    public String parse(List<String> args, Map<String, String> properties) {
+        //System.out.println(args+" // "+properties);
+
+        String resultString = null;
+        TreeMap<String, String[]> optmap = optionMap;
+
+        // State machine for parsing a command line.
+        ListIterator<String> argp = args.listIterator();
+        ListIterator<String> pbp = new ArrayList<String>().listIterator();
+        doArgs:
+        for (;;) {
+            // One trip through this loop per argument.
+            // Multiple trips per option only if several options per argument.
+            String arg;
+            if (pbp.hasPrevious()) {
+                arg = pbp.previous();
+                pbp.remove();
+            } else if (argp.hasNext()) {
+                arg = argp.next();
+            } else {
+                // No more arguments at all.
+                break doArgs;
+            }
+            tryOpt:
+            for (int optlen = arg.length();; optlen--) {
+                // One time through this loop for each matching arg prefix.
+                String opt;
+                // Match some prefix of the argument to a key in optmap.
+                findOpt:
+                for (;;) {
+                    opt = arg.substring(0, optlen);
+                    if (optmap.containsKey(opt)) {
+                        break findOpt;
+                    }
+                    if (optlen == 0) {
+                        break tryOpt;
+                    }
+                    // Decide on a smaller prefix to search for.
+                    SortedMap<String, String[]> pfxmap = optmap.headMap(opt);
+                    // pfxmap.lastKey is no shorter than any prefix in optmap.
+                    int len = pfxmap.isEmpty() ? 0 : pfxmap.lastKey().length();
+                    optlen = Math.min(len, optlen - 1);
+                    opt = arg.substring(0, optlen);
+                    // (Note:  We could cut opt down to its common prefix with
+                    // pfxmap.lastKey, but that wouldn't save many cycles.)
+                }
+                opt = opt.intern();
+                assert (arg.startsWith(opt));
+                assert (opt.length() == optlen);
+                String val = arg.substring(optlen);  // arg == opt+val
+
+                // Execute the option processing specs for this opt.
+                // If no actions are taken, then look for a shorter prefix.
+                boolean didAction = false;
+                boolean isError = false;
+
+                int pbpMark = pbp.nextIndex();  // in case of backtracking
+                String[] specs = optmap.get(opt);
+                eachSpec:
+                for (String spec : specs) {
+                    if (spec.length() == 0) {
+                        continue eachSpec;
+                    }
+                    if (spec.startsWith("#")) {
+                        break eachSpec;
+                    }
+                    int sidx = 0;
+                    char specop = spec.charAt(sidx++);
+
+                    // Deal with '+'/'*' prefixes (spec conditions).
+                    boolean ok;
+                    switch (specop) {
+                        case '+':
+                            // + means we want an non-empty val suffix.
+                            ok = (val.length() != 0);
+                            specop = spec.charAt(sidx++);
+                            break;
+                        case '*':
+                            // * means we accept empty or non-empty
+                            ok = true;
+                            specop = spec.charAt(sidx++);
+                            break;
+                        default:
+                            // No condition prefix means we require an exact
+                            // match, as indicated by an empty val suffix.
+                            ok = (val.length() == 0);
+                            break;
+                    }
+                    if (!ok) {
+                        continue eachSpec;
+                    }
+
+                    String specarg = spec.substring(sidx);
+                    switch (specop) {
+                        case '.':  // terminate the option sequence
+                            resultString = (specarg.length() != 0) ? specarg.intern() : opt;
+                            break doArgs;
+                        case '?':  // abort the option sequence
+                            resultString = (specarg.length() != 0) ? specarg.intern() : arg;
+                            isError = true;
+                            break eachSpec;
+                        case '@':  // change the effective opt name
+                            opt = specarg.intern();
+                            break;
+                        case '>':  // shift remaining arg val to next arg
+                            pbp.add(specarg + val);  // push a new argument
+                            val = "";
+                            break;
+                        case '!':  // negation option
+                            String negopt = (specarg.length() != 0) ? specarg.intern() : opt;
+                            properties.remove(negopt);
+                            properties.put(negopt, null);  // leave placeholder
+                            didAction = true;
+                            break;
+                        case '$':  // normal "boolean" option
+                            String boolval;
+                            if (specarg.length() != 0) {
+                                // If there is a given spec token, store it.
+                                boolval = specarg;
+                            } else {
+                                String old = properties.get(opt);
+                                if (old == null || old.length() == 0) {
+                                    boolval = "1";
+                                } else {
+                                    // Increment any previous value as a numeral.
+                                    boolval = "" + (1 + Integer.parseInt(old));
+                                }
+                            }
+                            properties.put(opt, boolval);
+                            didAction = true;
+                            break;
+                        case '=':  // "string" option
+                        case '&':  // "collection" option
+                            // Read an option.
+                            boolean append = (specop == '&');
+                            String strval;
+                            if (pbp.hasPrevious()) {
+                                strval = pbp.previous();
+                                pbp.remove();
+                            } else if (argp.hasNext()) {
+                                strval = argp.next();
+                            } else {
+                                resultString = arg + " ?";
+                                isError = true;
+                                break eachSpec;
+                            }
+                            if (append) {
+                                String old = properties.get(opt);
+                                if (old != null) {
+                                    // Append new val to old with embedded delim.
+                                    String delim = specarg;
+                                    if (delim.length() == 0) {
+                                        delim = " ";
+                                    }
+                                    strval = old + specarg + strval;
+                                }
+                            }
+                            properties.put(opt, strval);
+                            didAction = true;
+                            break;
+                        default:
+                            throw new RuntimeException("bad spec for "
+                                    + opt + ": " + spec);
+                    }
+                }
+
+                // Done processing specs.
+                if (didAction && !isError) {
+                    continue doArgs;
+                }
+
+                // The specs should have done something, but did not.
+                while (pbp.nextIndex() > pbpMark) {
+                    // Remove anything pushed during these specs.
+                    pbp.previous();
+                    pbp.remove();
+                }
+
+                if (isError) {
+                    throw new IllegalArgumentException(resultString);
+                }
+
+                if (optlen == 0) {
+                    // We cannot try a shorter matching option.
+                    break tryOpt;
+                }
+            }
+
+            // If we come here, there was no matching option.
+            // So, push back the argument, and return to caller.
+            pbp.add(arg);
+            break doArgs;
+        }
+        // Report number of arguments consumed.
+        args.subList(0, argp.nextIndex()).clear();
+        // Report any unconsumed partial argument.
+        while (pbp.hasPrevious()) {
+            args.add(0, pbp.previous());
+        }
+        //System.out.println(args+" // "+properties+" -> "+resultString);
+        return resultString;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionAssembler.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
+
+import xmlkit.XMLKit.Element;
+import java.util.HashMap;
+/*
+ * @author jrose
+ */
+abstract class InstructionAssembler extends InstructionSyntax {
+
+    InstructionAssembler() {
+    }
+
+    public static String assemble(Element instructions, String pcAttrName,
+            ClassSyntax.GetCPIndex getCPI) {
+        int insCount = instructions.size();
+        Element[] insElems = new Element[insCount];
+        int[] elemToIndexMap;
+        int[] insLocs;
+        byte[] ops = new byte[insCount];
+        int[] operands = new int[insCount];
+        boolean[] isWide = new boolean[insCount];
+        int[] branches;
+        int[] branchInsLocs;
+        HashMap<String, String> labels = new HashMap<String, String>();
+
+        final int WIDE = 0xc4;
+        final int GOTO = 0xa7;
+        final int GOTO_W = 0xc8;
+        final int GOTO_LEN = 3;
+        final int GOTO_W_LEN = 5;
+        assert ("wide".equals(bcNames[WIDE]));
+        assert ("goto".equals(bcNames[GOTO]));
+        assert ("goto_w".equals(bcNames[GOTO_W]));
+        assert (bcFormats[GOTO].length() == GOTO_LEN);
+        assert (bcFormats[GOTO_W].length() == GOTO_W_LEN);
+
+        // Unpack instructions into temp. arrays, and find branches and labels.
+        {
+            elemToIndexMap = (pcAttrName != null) ? new int[insCount] : null;
+            int[] buffer = operands;
+            int id = 0;
+            int branchCount = 0;
+            for (int i = 0; i < insCount; i++) {
+                Element ins = (Element) instructions.get(i);
+                if (elemToIndexMap != null) {
+                    elemToIndexMap[i] = (ins.getAttr(pcAttrName) != null ? id : -1);
+                }
+                String lab = ins.getAttr("pc");
+                if (lab != null) {
+                    labels.put(lab, String.valueOf(id));
+                }
+                int op = opCode(ins.getName());
+                if (op < 0) {
+                    assert (ins.getAttr(pcAttrName) != null
+                            || ins.getName().equals("label"));
+                    continue;  // delete PC holder element
+                }
+                if (op == WIDE) { //0xc4
+                    isWide[id] = true;  // force wide format
+                    continue;
+                }
+                if (bcFormats[op].indexOf('o') >= 0) {
+                    buffer[branchCount++] = id;
+                }
+                if (bcFormats[op] == bcWideFormats[op]) {
+                    isWide[id] = false;
+                }
+                insElems[id] = ins;
+                ops[id] = (byte) op;
+                id++;
+            }
+            insCount = id;  // maybe we deleted some wide prefixes, etc.
+            branches = new int[branchCount + 1];
+            System.arraycopy(buffer, 0, branches, 0, branchCount);
+            branches[branchCount] = -1;  // sentinel
+        }
+
+        // Compute instruction sizes.  These sizes are final,
+        // except for branch instructions, which may need lengthening.
+        // Some instructions (ldc, bipush, iload, iinc) are automagically widened.
+        insLocs = new int[insCount + 1];
+        int loc = 0;
+        for (int bn = 0, id = 0; id < insCount; id++) {
+            insLocs[id] = loc;
+            Element ins = insElems[id];
+            int op = ops[id] & 0xFF;
+            String format = opFormat(op, isWide[id]);
+            // Make sure operands fit within the given format.
+            for (int j = 1, jlimit = format.length(); j < jlimit; j++) {
+                char fc = format.charAt(j);
+                int x = 0;
+                switch (fc) {
+                    case 'l':
+                        x = (int) ins.getAttrLong("loc");
+                        assert (x >= 0);
+                        if (x > 0xFF && !isWide[id]) {
+                            isWide[id] = true;
+                            format = opFormat(op, isWide[id]);
+                        }
+                        assert (x <= 0xFFFF);
+                        break;
+                    case 'k':
+                        char fc2 = format.charAt(Math.min(j + 1, format.length() - 1));
+                        x = getCPIndex(ins, fc2, getCPI);
+                        if (x > 0xFF && j == jlimit - 1) {
+                            assert (op == 0x12); //ldc
+                            ops[id] = (byte) (op = 0x13); //ldc_w
+                            format = opFormat(op);
+                        }
+                        assert (x <= 0xFFFF);
+                        j++;  // skip type-of-constant marker
+                        break;
+                    case 'x':
+                        x = (int) ins.getAttrLong("num");
+                        assert (x >= 0 && x <= ((j == jlimit - 1) ? 0xFF : 0xFFFF));
+                        break;
+                    case 's':
+                        x = (int) ins.getAttrLong("num");
+                        if (x != (byte) x && j == jlimit - 1) {
+                            switch (op) {
+                                case 0x10: //bipush
+                                    ops[id] = (byte) (op = 0x11); //sipush
+                                    break;
+                                case 0x84: //iinc
+                                    isWide[id] = true;
+                                    format = opFormat(op, isWide[id]);
+                                    break;
+                                default:
+                                    assert (false);  // cannot lengthen
+                            }
+                        }
+                        // unsign the value now, to make later steps clearer
+                        if (j == jlimit - 1) {
+                            assert (x == (byte) x);
+                            x = x & 0xFF;
+                        } else {
+                            assert (x == (short) x);
+                            x = x & 0xFFFF;
+                        }
+                        break;
+                    case 'o':
+                        assert (branches[bn] == id);
+                        bn++;
+                        // make local copies of the branches, and fix up labels
+                        insElems[id] = ins = new Element(ins);
+                        String newLab = labels.get(ins.getAttr("lab"));
+                        assert (newLab != null);
+                        ins.setAttr("lab", newLab);
+                        int prevCas = 0;
+                        int k = 0;
+                        for (Element cas : ins.elements()) {
+                            assert (cas.getName().equals("Case"));
+                            ins.set(k++, cas = new Element(cas));
+                            newLab = labels.get(cas.getAttr("lab"));
+                            assert (newLab != null);
+                            cas.setAttr("lab", newLab);
+                            int thisCas = (int) cas.getAttrLong("num");
+                            assert (op == 0xab
+                                    || op == 0xaa && (k == 0 || thisCas == prevCas + 1));
+                            prevCas = thisCas;
+                        }
+                        break;
+                    case 't':
+                        // switch table is represented as Switch.Case sub-elements
+                        break;
+                    default:
+                        assert (false);
+                }
+                operands[id] = x;  // record operand (last if there are 2)
+                // skip redundant chars
+                while (j + 1 < jlimit && format.charAt(j + 1) == fc) {
+                    ++j;
+                }
+            }
+
+            switch (op) {
+                case 0xaa: //tableswitch
+                    loc = switchBase(loc);
+                    loc += 4 * (3 + ins.size());
+                    break;
+                case 0xab: //lookupswitch
+                    loc = switchBase(loc);
+                    loc += 4 * (2 + 2 * ins.size());
+                    break;
+                default:
+                    if (isWide[id]) {
+                        loc++;  // 'wide' opcode prefix
+                    }
+                    loc += format.length();
+                    break;
+            }
+        }
+        insLocs[insCount] = loc;
+
+        // compute branch offsets, and see if any branches need expansion
+        for (int maxTries = 9, tries = 0;; ++tries) {
+            boolean overflowing = false;
+            boolean[] branchExpansions = null;
+            for (int bn = 0; bn < branches.length - 1; bn++) {
+                int id = branches[bn];
+                Element ins = insElems[id];
+                int insSize = insLocs[id + 1] - insLocs[id];
+                int origin = insLocs[id];
+                int target = insLocs[(int) ins.getAttrLong("lab")];
+                int offset = target - origin;
+                operands[id] = offset;
+                //System.out.println("branch id="+id+" len="+insSize+" to="+target+" offset="+offset);
+                assert (insSize == GOTO_LEN || insSize == GOTO_W_LEN || ins.getName().indexOf("switch") > 0);
+                boolean thisOverflow = (insSize == GOTO_LEN && (offset != (short) offset));
+                if (thisOverflow && !overflowing) {
+                    overflowing = true;
+                    branchExpansions = new boolean[branches.length];
+                }
+                if (thisOverflow || tries == maxTries - 1) {
+                    // lengthen the branch
+                    assert (!(thisOverflow && isWide[id]));
+                    isWide[id] = true;
+                    branchExpansions[bn] = true;
+                }
+            }
+            if (!overflowing) {
+                break;  // done, usually on first try
+            }
+            assert (tries <= maxTries);
+
+            // Walk over all instructions, expanding branches and updating locations.
+            int fixup = 0;
+            for (int bn = 0, id = 0; id < insCount; id++) {
+                insLocs[id] += fixup;
+                if (branches[bn] == id) {
+                    int op = ops[id] & 0xFF;
+                    int wop;
+                    boolean invert;
+                    if (branchExpansions[bn]) {
+                        switch (op) {
+                            case GOTO: //0xa7
+                                wop = GOTO_W; //0xc8
+                                invert = false;
+                                break;
+                            case 0xa8: //jsr
+                                wop = 0xc9; //jsr_w
+                                invert = false;
+                                break;
+                            default:
+                                wop = invertBranchOp(op);
+                                invert = true;
+                                break;
+                        }
+                        assert (op != wop);
+                        ops[id] = (byte) wop;
+                        isWide[id] = invert;
+                        if (invert) {
+                            fixup += GOTO_W_LEN;  //branch around a wide goto
+                        } else {
+                            fixup += (GOTO_W_LEN - GOTO_LEN);
+                        }
+                        // done expanding:  ops and isWide reflect the decision
+                    }
+                    bn++;
+                }
+            }
+            insLocs[insCount] += fixup;
+        }
+        // we know the layout now
+
+        // notify the caller of offsets, if requested
+        if (elemToIndexMap != null) {
+            for (int i = 0; i < elemToIndexMap.length; i++) {
+                int id = elemToIndexMap[i];
+                if (id >= 0) {
+                    Element ins = (Element) instructions.get(i);
+                    ins.setAttr(pcAttrName, "" + insLocs[id]);
+                }
+            }
+            elemToIndexMap = null;  // release the pointer
+        }
+
+        // output the bytes
+        StringBuffer sbuf = new StringBuffer(insLocs[insCount]);
+        for (int bn = 0, id = 0; id < insCount; id++) {
+            //System.out.println("output id="+id+" loc="+insLocs[id]+" len="+(insLocs[id+1]-insLocs[id])+" #sbuf="+sbuf.length());
+            assert (sbuf.length() == insLocs[id]);
+            Element ins;
+            int pc = insLocs[id];
+            int nextpc = insLocs[id + 1];
+            int op = ops[id] & 0xFF;
+            int opnd = operands[id];
+            String format;
+            if (branches[bn] == id) {
+                bn++;
+                sbuf.append((char) op);
+                if (isWide[id]) {
+                    // emit <ifop lab=1f> <goto_w target> <label pc=1f>
+                    int target = pc + opnd;
+                    putInt(sbuf, nextpc - pc, -2);
+                    assert (sbuf.length() == pc + GOTO_LEN);
+                    sbuf.append((char) GOTO_W);
+                    putInt(sbuf, target - (pc + GOTO_LEN), 4);
+                } else if (op == 0xaa || //tableswitch
+                        op == 0xab) {  //lookupswitch
+                    ins = insElems[id];
+                    for (int pad = switchBase(pc) - (pc + 1); pad > 0; pad--) {
+                        sbuf.append((char) 0);
+                    }
+                    assert (pc + opnd == insLocs[(int) ins.getAttrLong("lab")]);
+                    putInt(sbuf, opnd, 4); // default label
+                    if (op == 0xaa) {  //tableswitch
+                        Element cas0 = (Element) ins.get(0);
+                        int lowCase = (int) cas0.getAttrLong("num");
+                        Element casN = (Element) ins.get(ins.size() - 1);
+                        int highCase = (int) casN.getAttrLong("num");
+                        assert (highCase - lowCase + 1 == ins.size());
+                        putInt(sbuf, lowCase, 4);
+                        putInt(sbuf, highCase, 4);
+                        int caseForAssert = lowCase;
+                        for (Element cas : ins.elements()) {
+                            int target = insLocs[(int) cas.getAttrLong("lab")];
+                            assert (cas.getAttrLong("num") == caseForAssert++);
+                            putInt(sbuf, target - pc, 4);
+                        }
+                    } else {  //lookupswitch
+                        int caseCount = ins.size();
+                        putInt(sbuf, caseCount, 4);
+                        for (Element cas : ins.elements()) {
+                            int target = insLocs[(int) cas.getAttrLong("lab")];
+                            putInt(sbuf, (int) cas.getAttrLong("num"), 4);
+                            putInt(sbuf, target - pc, 4);
+                        }
+                    }
+                    assert (nextpc == sbuf.length());
+                } else {
+                    putInt(sbuf, opnd, -(nextpc - (pc + 1)));
+                }
+            } else if (nextpc == pc + 1) {
+                // a single-byte instruction
+                sbuf.append((char) op);
+            } else {
+                // picky stuff
+                boolean wide = isWide[id];
+                if (wide) {
+                    sbuf.append((char) WIDE);
+                    pc++;
+                }
+                sbuf.append((char) op);
+                int opnd1;
+                int opnd2 = opnd;
+                switch (op) {
+                    case 0x84:  //iinc
+                        ins = insElems[id];
+                        opnd1 = (int) ins.getAttrLong("loc");
+                        if (isWide[id]) {
+                            putInt(sbuf, opnd1, 2);
+                            putInt(sbuf, opnd2, 2);
+                        } else {
+                            putInt(sbuf, opnd1, 1);
+                            putInt(sbuf, opnd2, 1);
+                        }
+                        break;
+                    case 0xc5: //multianewarray
+                        ins = insElems[id];
+                        opnd1 = getCPIndex(ins, 'c', getCPI);
+                        putInt(sbuf, opnd1, 2);
+                        putInt(sbuf, opnd2, 1);
+                        break;
+                    case 0xb9: //invokeinterface
+                        ins = insElems[id];
+                        opnd1 = getCPIndex(ins, 'n', getCPI);
+                        putInt(sbuf, opnd1, 2);
+                        opnd2 = (int) ins.getAttrLong("num");
+                        if (opnd2 == 0) {
+                            opnd2 = ClassSyntax.computeInterfaceNum(ins.getAttr("val"));
+                        }
+                        putInt(sbuf, opnd2, 2);
+                        break;
+                    default:
+                        // put the single operand and be done
+                        putInt(sbuf, opnd, nextpc - (pc + 1));
+                        break;
+                }
+            }
+        }
+        assert (sbuf.length() == insLocs[insCount]);
+
+        return sbuf.toString();
+    }
+
+    static int getCPIndex(Element ins, char ctype,
+            ClassSyntax.GetCPIndex getCPI) {
+        int x = (int) ins.getAttrLong("ref");
+        if (x == 0 && getCPI != null) {
+            String val = ins.getAttr("val");
+            if (val == null || val.equals("")) {
+                val = ins.getText().toString();
+            }
+            byte tag;
+            switch (ctype) {
+                case 'k':
+                    tag = (byte) ins.getAttrLong("tag");
+                    break;
+                case 'c':
+                    tag = ClassSyntax.CONSTANT_Class;
+                    break;
+                case 'f':
+                    tag = ClassSyntax.CONSTANT_Fieldref;
+                    break;
+                case 'm':
+                    tag = ClassSyntax.CONSTANT_Methodref;
+                    break;
+                case 'n':
+                    tag = ClassSyntax.CONSTANT_InterfaceMethodref;
+                    break;
+                default:
+                    throw new Error("bad ctype " + ctype + " in " + ins);
+            }
+            x = getCPI.getCPIndex(tag, val);
+            //System.out.println("getCPIndex "+ins+" => "+tag+"/"+val+" => "+x);
+        } else {
+            assert (x > 0);
+        }
+        return x;
+    }
+
+    static void putInt(StringBuffer sbuf, int x, int len) {
+        //System.out.println("putInt x="+x+" len="+len);
+        boolean isSigned = false;
+        if (len < 0) {
+            len = -len;
+            isSigned = true;
+        }
+        assert (len == 1 || len == 2 || len == 4);
+        int insig = ((4 - len) * 8);  // how many insignificant bits?
+        int sx = x << insig;
+        ;
+        assert (x == (isSigned ? (sx >> insig) : (sx >>> insig)));
+        for (int i = 0; i < len; i++) {
+            sbuf.append((char) (sx >>> 24));
+            sx <<= 8;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/InstructionSyntax.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
+
+import xmlkit.XMLKit.Element;
+import java.util.HashMap;
+import java.util.Map;
+/*
+ * @author jrose
+ */
+public abstract class InstructionSyntax {
+
+    InstructionSyntax() {
+    }
+    static final String[] bcNames;
+    static final String[] bcFormats;
+    static final String[] bcWideFormats;
+    static final HashMap<String, Integer> bcCodes;
+    static final HashMap<String, Element> abbrevs;
+    static final HashMap<Element, String> rabbrevs;
+
+    static {
+        TokenList tl = new TokenList(
+                " nop aconst_null iconst_m1 iconst_0 iconst_1 iconst_2 iconst_3"
+                + " iconst_4 iconst_5 lconst_0 lconst_1 fconst_0 fconst_1 fconst_2"
+                + " dconst_0 dconst_1 bipush/s sipush/ss ldc/k ldc_w/kk ldc2_w/kk"
+                + " iload/wl lload/wl fload/wl dload/wl aload/wl iload_0 iload_1"
+                + " iload_2 iload_3 lload_0 lload_1 lload_2 lload_3 fload_0 fload_1"
+                + " fload_2 fload_3 dload_0 dload_1 dload_2 dload_3 aload_0 aload_1"
+                + " aload_2 aload_3 iaload laload faload daload aaload baload caload"
+                + " saload istore/wl lstore/wl fstore/wl dstore/wl astore/wl"
+                + " istore_0 istore_1 istore_2 istore_3 lstore_0 lstore_1 lstore_2"
+                + " lstore_3 fstore_0 fstore_1 fstore_2 fstore_3 dstore_0 dstore_1"
+                + " dstore_2 dstore_3 astore_0 astore_1 astore_2 astore_3 iastore"
+                + " lastore fastore dastore aastore bastore castore sastore pop pop2"
+                + " dup dup_x1 dup_x2 dup2 dup2_x1 dup2_x2 swap iadd ladd fadd dadd"
+                + " isub lsub fsub dsub imul lmul fmul dmul idiv ldiv fdiv ddiv irem"
+                + " lrem frem drem ineg lneg fneg dneg ishl lshl ishr lshr iushr"
+                + " lushr iand land ior lor ixor lxor iinc/wls i2l i2f i2d l2i l2f"
+                + " l2d f2i f2l f2d d2i d2l d2f i2b i2c i2s lcmp fcmpl fcmpg dcmpl"
+                + " dcmpg ifeq/oo ifne/oo iflt/oo ifge/oo ifgt/oo ifle/oo"
+                + " if_icmpeq/oo if_icmpne/oo if_icmplt/oo if_icmpge/oo if_icmpgt/oo"
+                + " if_icmple/oo if_acmpeq/oo if_acmpne/oo goto/oo jsr/oo ret/wl"
+                + " tableswitch/oooot lookupswitch/oooot ireturn lreturn freturn dreturn areturn"
+                + " return getstatic/kf putstatic/kf getfield/kf putfield/kf"
+                + " invokevirtual/km invokespecial/km invokestatic/km"
+                + " invokeinterface/knxx xxxunusedxxx new/kc newarray/x anewarray/kc"
+                + " arraylength athrow checkcast/kc instanceof/kc monitorenter"
+                + " monitorexit wide multianewarray/kcx ifnull/oo ifnonnull/oo"
+                + " goto_w/oooo jsr_w/oooo");
+        assert (tl.size() == 202);  // this many instructions!
+        HashMap<String, Integer> map = new HashMap<String, Integer>(tl.size());
+        String[] names = tl.toArray(new String[tl.size()]);
+        String[] formats = new String[names.length];
+        String[] wideFormats = new String[names.length];
+        StringBuilder sbuf = new StringBuilder();
+        sbuf.append('i');  // all op formats begin with "i"
+        int i = 0;
+        for (String ins : names) {
+            assert (ins == ins.trim());  // no whitespace
+            int sfx = ins.indexOf('/');
+            String format = "i";
+            String wideFormat = null;
+            if (sfx >= 0) {
+                format = ins.substring(sfx + 1);
+                ins = ins.substring(0, sfx);
+                if (format.charAt(0) == 'w') {
+                    format = format.substring(1);
+                    sbuf.setLength(1);
+                    for (int j = 0; j < format.length(); j++) {
+                        // double everything except the initial 'i'
+                        sbuf.append(format.charAt(j));
+                        sbuf.append(format.charAt(j));
+                    }
+                    wideFormat = sbuf.toString().intern();
+                }
+                sbuf.setLength(1);
+                sbuf.append(format);
+                format = sbuf.toString().intern();
+            }
+            ins = ins.intern();
+            names[i] = ins;
+            formats[i] = format;
+            wideFormats[i] = (wideFormat != null) ? wideFormat : format;
+            //System.out.println(ins+" "+format+" "+wideFormat);
+            map.put(ins, i++);
+        }
+        //map = Collections.unmodifiableMap(map);
+
+        HashMap<String, Element> abb = new HashMap<String, Element>(tl.size() / 2);
+        abb.put("iconst_m1", new Element("bipush", "num", "-1"));
+        for (String ins : names) {
+            int sfx = ins.indexOf('_');
+            if (sfx >= 0 && Character.isDigit(ins.charAt(sfx + 1))) {
+                String pfx = ins.substring(0, sfx).intern();
+                String num = ins.substring(sfx + 1);
+                String att = pfx.endsWith("const") ? "num" : "loc";
+                Element exp = new Element(pfx, att, num).deepFreeze();
+                abb.put(ins, exp);
+            }
+        }
+        //abb = Collections.unmodifiableMap(abb);
+        HashMap<Element, String> rabb = new HashMap<Element, String>(tl.size() / 2);
+        for (Map.Entry<String, Element> e : abb.entrySet()) {
+            rabb.put(e.getValue(), e.getKey());
+        }
+        //rabb = Collections.unmodifiableMap(rabb);
+
+
+        bcNames = names;
+        bcFormats = formats;
+        bcWideFormats = wideFormats;
+        bcCodes = map;
+        abbrevs = abb;
+        rabbrevs = rabb;
+    }
+
+    public static String opName(int op) {
+        if (op >= 0 && op < bcNames.length) {
+            return bcNames[op];
+        }
+        return "unknown#" + op;
+    }
+
+    public static String opFormat(int op) {
+        return opFormat(op, false);
+    }
+
+    public static String opFormat(int op, boolean isWide) {
+        if (op >= 0 && op < bcFormats.length) {
+            return (isWide ? bcWideFormats[op] : bcFormats[op]);
+        }
+        return "?";
+    }
+
+    public static int opCode(String opName) {
+        Integer op = (Integer) bcCodes.get(opName);
+        if (op != null) {
+            return op.intValue();
+        }
+        return -1;
+    }
+
+    public static Element expandAbbrev(String opName) {
+        return abbrevs.get(opName);
+    }
+
+    public static String findAbbrev(Element op) {
+        return rabbrevs.get(op);
+    }
+
+    public static int invertBranchOp(int op) {
+        assert (opFormat(op).indexOf('o') >= 0);
+        final int IFMIN = 0x99;
+        final int IFMAX = 0xa6;
+        final int IFMIN2 = 0xc6;
+        final int IFMAX2 = 0xc7;
+        assert (bcNames[IFMIN] == "ifeq");
+        assert (bcNames[IFMAX] == "if_acmpne");
+        assert (bcNames[IFMIN2] == "ifnonnull");
+        assert (bcNames[IFMAX2] == "ifnull");
+        int rop;
+        if (op >= IFMIN && op <= IFMAX) {
+            rop = IFMIN + ((op - IFMIN) ^ 1);
+        } else if (op >= IFMIN2 && op <= IFMAX2) {
+            rop = IFMIN2 + ((op - IFMIN2) ^ 1);
+        } else {
+            assert (false);
+            rop = op;
+        }
+        assert (opFormat(rop).indexOf('o') >= 0);
+        return rop;
+    }
+
+    public static Element parse(String bytes) {
+        Element e = new Element("Instructions", bytes.length());
+        boolean willBeWide;
+        boolean isWide = false;
+        Element[] tempMap = new Element[bytes.length()];
+        for (int pc = 0, nextpc; pc < bytes.length(); pc = nextpc) {
+            int op = bytes.charAt(pc);
+            Element i = new Element(opName(op));
+
+            nextpc = pc + 1;
+            int locarg = 0;
+            int cparg = 0;
+            int intarg = 0;
+            int labelarg = 0;
+
+            willBeWide = false;
+            switch (op) {
+                case 0xc4: //wide
+                    willBeWide = true;
+                    break;
+                case 0x10: //bipush
+                    intarg = nextpc++;
+                    intarg *= -1;  //mark signed
+                    break;
+                case 0x11: //sipush
+                    intarg = nextpc;
+                    nextpc += 2;
+                    intarg *= -1;  //mark signed
+                    break;
+                case 0x12: //ldc
+                    cparg = nextpc++;
+                    break;
+                case 0x13: //ldc_w
+                case 0x14: //ldc2_w
+                case 0xb2: //getstatic
+                case 0xb3: //putstatic
+                case 0xb4: //getfield
+                case 0xb5: //putfield
+                case 0xb6: //invokevirtual
+                case 0xb7: //invokespecial
+                case 0xb8: //invokestatic
+                case 0xbb: //new
+                case 0xbd: //anewarray
+                case 0xc0: //checkcast
+                case 0xc1: //instanceof
+                    cparg = nextpc;
+                    nextpc += 2;
+                    break;
+                case 0xb9: //invokeinterface
+                    cparg = nextpc;
+                    nextpc += 2;
+                    intarg = nextpc;
+                    nextpc += 2;
+                    break;
+                case 0xc5: //multianewarray
+                    cparg = nextpc;
+                    nextpc += 2;
+                    intarg = nextpc++;
+                    break;
+                case 0x15: //iload
+                case 0x16: //lload
+                case 0x17: //fload
+                case 0x18: //dload
+                case 0x19: //aload
+                case 0x36: //istore
+                case 0x37: //lstore
+                case 0x38: //fstore
+                case 0x39: //dstore
+                case 0x3a: //astore
+                case 0xa9: //ret
+                    locarg = nextpc++;
+                    if (isWide) {
+                        nextpc++;
+                    }
+                    break;
+                case 0x84: //iinc
+                    locarg = nextpc++;
+                    if (isWide) {
+                        nextpc++;
+                    }
+                    intarg = nextpc++;
+                    if (isWide) {
+                        nextpc++;
+                    }
+                    intarg *= -1;  //mark signed
+                    break;
+                case 0x99: //ifeq
+                case 0x9a: //ifne
+                case 0x9b: //iflt
+                case 0x9c: //ifge
+                case 0x9d: //ifgt
+                case 0x9e: //ifle
+                case 0x9f: //if_icmpeq
+                case 0xa0: //if_icmpne
+                case 0xa1: //if_icmplt
+                case 0xa2: //if_icmpge
+                case 0xa3: //if_icmpgt
+                case 0xa4: //if_icmple
+                case 0xa5: //if_acmpeq
+                case 0xa6: //if_acmpne
+                case 0xa7: //goto
+                case 0xa8: //jsr
+                    labelarg = nextpc;
+                    nextpc += 2;
+                    break;
+                case 0xbc: //newarray
+                    intarg = nextpc++;
+                    break;
+                case 0xc6: //ifnull
+                case 0xc7: //ifnonnull
+                    labelarg = nextpc;
+                    nextpc += 2;
+                    break;
+                case 0xc8: //goto_w
+                case 0xc9: //jsr_w
+                    labelarg = nextpc;
+                    nextpc += 4;
+                    break;
+
+                // save the best for last:
+                case 0xaa: //tableswitch
+                    nextpc = parseSwitch(bytes, pc, true, i);
+                    break;
+                case 0xab: //lookupswitch
+                    nextpc = parseSwitch(bytes, pc, false, i);
+                    break;
+            }
+
+            String format = null;
+            assert ((format = opFormat(op, isWide)) != null);
+            //System.out.println("pc="+pc+" len="+(nextpc - pc)+" w="+isWide+" op="+op+" name="+opName(op)+" format="+format);
+            assert ((nextpc - pc) == format.length() || format.indexOf('t') >= 0);
+
+            // Parse out instruction fields.
+            if (locarg != 0) {
+                int len = nextpc - locarg;
+                if (intarg != 0) {
+                    len /= 2;  // split
+                }
+                i.setAttr("loc", "" + getInt(bytes, locarg, len));
+                assert ('l' == format.charAt(locarg - pc + 0));
+                assert ('l' == format.charAt(locarg - pc + len - 1));
+            }
+            if (cparg != 0) {
+                int len = nextpc - cparg;
+                if (len > 2) {
+                    len = 2;
+                }
+                i.setAttr("ref", "" + getInt(bytes, cparg, len));
+                assert ('k' == format.charAt(cparg - pc + 0));
+            }
+            if (intarg != 0) {
+                boolean isSigned = (intarg < 0);
+                if (isSigned) {
+                    intarg *= -1;
+                }
+                int len = nextpc - intarg;
+                i.setAttr("num", "" + getInt(bytes, intarg, isSigned ? -len : len));
+                assert ((isSigned ? 's' : 'x') == format.charAt(intarg - pc + 0));
+                assert ((isSigned ? 's' : 'x') == format.charAt(intarg - pc + len - 1));
+            }
+            if (labelarg != 0) {
+                int len = nextpc - labelarg;
+                int offset = getInt(bytes, labelarg, -len);
+                int target = pc + offset;
+                i.setAttr("lab", "" + target);
+                assert ('o' == format.charAt(labelarg - pc + 0));
+                assert ('o' == format.charAt(labelarg - pc + len - 1));
+            }
+
+            e.add(i);
+            tempMap[pc] = i;
+            isWide = willBeWide;
+        }
+
+        // Mark targets of branches.
+        for (Element i : e.elements()) {
+            for (int j = -1; j < i.size(); j++) {
+                Element c = (j < 0) ? i : (Element) i.get(j);
+                Number targetNum = c.getAttrNumber("lab");
+                if (targetNum != null) {
+                    int target = targetNum.intValue();
+                    Element ti = null;
+                    if (target >= 0 && target < tempMap.length) {
+                        ti = tempMap[target];
+                    }
+                    if (ti != null) {
+                        ti.setAttr("pc", "" + target);
+                    } else {
+                        c.setAttr("lab.error", "");
+                    }
+                }
+            }
+        }
+
+        // Shrink to fit:
+        for (Element i : e.elements()) {
+            i.trimToSize();
+        }
+        e.trimToSize();
+
+        /*
+        String assem = assemble(e);
+        if (!assem.equals(bytes)) {
+        System.out.println("Bytes: "+bytes);
+        System.out.println("Insns: "+e);
+        System.out.println("Assem: "+parse(assem));
+        }
+         */
+
+        return e;
+    }
+
+    static int switchBase(int pc) {
+        int apc = pc + 1;
+        apc += (-apc) & 3;
+        return apc;
+    }
+
+    static int parseSwitch(String s, int pc, boolean isTable, Element i) {
+        int apc = switchBase(pc);
+        int defLabel = pc + getInt(s, apc + 4 * 0, 4);
+        i.setAttr("lab", "" + defLabel);
+        if (isTable) {
+            int lowCase = getInt(s, apc + 4 * 1, 4);
+            int highCase = getInt(s, apc + 4 * 2, 4);
+            int caseCount = highCase - lowCase + 1;
+            for (int n = 0; n < caseCount; n++) {
+                Element c = new Element("Case", 4);
+                int caseVal = lowCase + n;
+                int caseLab = getInt(s, apc + 4 * (3 + n), 4) + pc;
+                c.setAttr("num", "" + caseVal);
+                c.setAttr("lab", "" + caseLab);
+                assert (c.getExtraCapacity() == 0);
+                i.add(c);
+            }
+            return apc + 4 * (3 + caseCount);
+        } else {
+            int caseCount = getInt(s, apc + 4 * 1, 4);
+            for (int n = 0; n < caseCount; n++) {
+                Element c = new Element("Case", 4);
+                int caseVal = getInt(s, apc + 4 * (2 + (2 * n) + 0), 4);
+                int caseLab = getInt(s, apc + 4 * (2 + (2 * n) + 1), 4) + pc;
+                c.setAttr("num", "" + caseVal);
+                c.setAttr("lab", "" + caseLab);
+                assert (c.getExtraCapacity() == 0);
+                i.add(c);
+            }
+            return apc + 4 * (2 + 2 * caseCount);
+        }
+    }
+
+    static int getInt(String s, int pc, int len) {
+        //System.out.println("getInt s["+s.length()+"] pc="+pc+" len="+len);
+        int result = s.charAt(pc);
+        if (len < 0) {
+            len = -len;
+            result = (byte) result;
+        }
+        if (!(len == 1 || len == 2 || len == 4)) {
+            System.out.println("len=" + len);
+        }
+        assert (len == 1 || len == 2 || len == 4);
+        for (int i = 1; i < len; i++) {
+            result <<= 8;
+            result += s.charAt(pc + i) & 0xFF;
+        }
+        return result;
+    }
+
+    public static String assemble(Element instructions) {
+        return InstructionAssembler.assemble(instructions, null, null);
+    }
+
+    public static String assemble(Element instructions, String pcAttrName) {
+        return InstructionAssembler.assemble(instructions, pcAttrName, null);
+    }
+
+    public static String assemble(Element instructions, ClassSyntax.GetCPIndex getCPI) {
+        return InstructionAssembler.assemble(instructions, null, getCPI);
+    }
+
+    public static String assemble(Element instructions, String pcAttrName,
+            ClassSyntax.GetCPIndex getCPI) {
+        return InstructionAssembler.assemble(instructions, pcAttrName, getCPI);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/TokenList.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
+
+import java.util.*;
+
+/**
+ * A List of Strings each representing a word or token.
+ * This object itself is a CharSequence whose characters consist
+ * of all the tokens, separated by blanks.
+ *
+ * @author jrose
+ */
+public class TokenList extends ArrayList<String> implements CharSequence {
+
+    protected String separator;
+    protected boolean frozen;
+
+    public TokenList() {
+        this.separator = " ";
+    }
+
+    public TokenList(Collection<? extends Object> tokens) {
+        super(tokens.size());
+        this.separator = " ";
+        addTokens(tokens);
+    }
+
+    public TokenList(Collection<? extends Object> tokens, String separator) {
+        super(tokens.size());
+        this.separator = separator;
+        addTokens(tokens);
+    }
+
+    public TokenList(Object[] tokens) {
+        super(tokens.length);
+        this.separator = " ";
+        addTokens(tokens, 0, tokens.length);
+    }
+
+    public TokenList(Object[] tokens, int beg, int end) {
+        super(end - beg);  // capacity
+        this.separator = " ";
+        addTokens(tokens, beg, end);
+    }
+
+    public TokenList(Object[] tokens, int beg, int end, String separator) {
+        super(end - beg);  // capacity
+        this.separator = separator;
+        addTokens(tokens, beg, end);
+    }
+
+    public TokenList(String tokenStr) {
+        this(tokenStr, " ", false);
+    }
+
+    public TokenList(String tokenStr, String separator) {
+        this(tokenStr, separator, true);
+    }
+
+    public TokenList(String tokenStr, String separator, boolean allowNulls) {
+        super(tokenStr.length() / 5);
+        this.separator = separator;
+        addTokens(tokenStr, allowNulls);
+    }
+    static public final TokenList EMPTY;
+
+    static {
+        TokenList tl = new TokenList(new Object[0]);
+        tl.freeze();
+        EMPTY = tl;
+    }
+
+    public void freeze() {
+        if (!frozen) {
+            for (ListIterator<String> i = listIterator(); i.hasNext();) {
+                i.set(i.next().toString());
+            }
+            trimToSize();
+            frozen = true;
+        }
+    }
+
+    public boolean isFrozen() {
+        return frozen;
+    }
+
+    void checkNotFrozen() {
+        if (isFrozen()) {
+            throw new UnsupportedOperationException("cannot modify frozen TokenList");
+        }
+    }
+
+    public String getSeparator() {
+        return separator;
+    }
+
+    public void setSeparator(String separator) {
+        checkNotFrozen();
+        this.separator = separator;
+    }
+
+    /// All normal List mutators must check the frozen bit:
+    public String set(int index, String o) {
+        checkNotFrozen();
+        return super.set(index, o);
+    }
+
+    public boolean add(String o) {
+        checkNotFrozen();
+        return super.add(o);
+    }
+
+    public void add(int index, String o) {
+        checkNotFrozen();
+        super.add(index, o);
+    }
+
+    public boolean addAll(Collection<? extends String> c) {
+        checkNotFrozen();
+        return super.addAll(c);
+    }
+
+    public boolean addAll(int index, Collection<? extends String> c) {
+        checkNotFrozen();
+        return super.addAll(index, c);
+    }
+
+    public boolean remove(Object o) {
+        checkNotFrozen();
+        return super.remove(o);
+    }
+
+    public String remove(int index) {
+        checkNotFrozen();
+        return super.remove(index);
+    }
+
+    public void clear() {
+        checkNotFrozen();
+        super.clear();
+    }
+
+    /** Add a collection of tokens to the list, applying toString to each. */
+    public boolean addTokens(Collection<? extends Object> tokens) {
+        // Note that if this sequence is empty, no tokens are added.
+        // This is different from adding a null string, which is
+        // a single token.
+        boolean added = false;
+        for (Object token : tokens) {
+            add(token.toString());
+            added = true;
+        }
+        return added;
+    }
+
+    public boolean addTokens(Object[] tokens, int beg, int end) {
+        boolean added = false;
+        for (int i = beg; i < end; i++) {
+            add(tokens[i].toString());
+            added = true;
+        }
+        return added;
+    }
+
+    public boolean addTokens(String tokenStr) {
+        return addTokens(tokenStr, false);
+    }
+
+    public boolean addTokens(String tokenStr, boolean allowNulls) {
+        boolean added = false;
+        int pos = 0, limit = tokenStr.length(), sep = limit;
+        while (pos < limit) {
+            sep = tokenStr.indexOf(separator, pos);
+            if (sep < 0) {
+                sep = limit;
+            }
+            if (sep == pos) {
+                if (allowNulls) {
+                    add("");
+                    added = true;
+                }
+                pos += separator.length();
+            } else {
+                add(tokenStr.substring(pos, sep));
+                added = true;
+                pos = sep + separator.length();
+            }
+        }
+        if (allowNulls && sep < limit) {
+            // Input was something like "tok1 tok2 ".
+            add("");
+            added = true;
+        }
+        return added;
+    }
+
+    public boolean addToken(Object token) {
+        return add(token.toString());
+    }
+
+    /** Format the token string, using quotes and escapes.
+     *  Quotes must contain an odd number of 3 or more elements,
+     *  a sequence of begin/end quote pairs, plus a superquote.
+     *  For each token, the first begin/end pair is used for
+     *  which the end quote does not occur in the token.
+     *  If the token contains all end quotes, the last pair
+     *  is used, with all occurrences of the end quote replaced
+     *  by the superquote.  If an end quote is the empty string,
+     *  the separator is used instead.
+     */
+    public String format(String separator, String[] quotes) {
+        return ""; //@@
+    }
+    protected int[] lengths;
+    protected static final int MODC = 0, HINT = 1, BEG0 = 2, END0 = 3;
+
+    // Layout of lengths:
+    //   { modCount, hint, -1==beg[0], end[0]==beg[1], ..., length }
+    // Note that each beg[i]..end[i] span includes a leading separator,
+    // which is not part of the corresponding token.
+    protected final CharSequence getCS(int i) {
+        return (CharSequence) get(i);
+    }
+
+    // Produce (and cache) an table of indexes for each token.
+    protected int[] getLengths() {
+        int[] lengths = this.lengths;
+        ;
+        int sepLength = separator.length();
+        if (lengths == null || lengths[MODC] != modCount) {
+            int size = this.size();
+            lengths = new int[END0 + size + (size == 0 ? 1 : 0)];
+            lengths[MODC] = modCount;
+            int end = -sepLength;  // cancels leading separator
+            lengths[BEG0] = end;
+            for (int i = 0; i < size; i++) {
+                end += sepLength;  // count leading separator
+                end += getCS(i).length();
+                lengths[END0 + i] = end;
+            }
+            this.lengths = lengths;
+        }
+        return lengths;
+    }
+
+    public int length() {
+        int[] lengths = getLengths();
+        return lengths[lengths.length - 1];
+    }
+
+    // Which token does the given index belong to?
+    protected int which(int i) {
+        if (i < 0) {
+            return -1;
+        }
+        int[] lengths = getLengths();
+        for (int hint = lengths[HINT];; hint = 0) {
+            for (int wh = hint; wh < lengths.length - END0; wh++) {
+                int beg = lengths[BEG0 + wh];
+                int end = lengths[END0 + wh];
+                if (i >= beg && i < end) {
+                    lengths[HINT] = wh;
+                    return wh;
+                }
+            }
+            if (hint == 0) {
+                return size();  // end of the line
+            }
+        }
+    }
+
+    public char charAt(int i) {
+        if (i < 0) {
+            return "".charAt(i);
+        }
+        int wh = which(i);
+        int beg = lengths[BEG0 + wh];
+        int j = i - beg;
+        int sepLength = separator.length();
+        if (j < sepLength) {
+            return separator.charAt(j);
+        }
+        return getCS(wh).charAt(j - sepLength);
+    }
+
+    public CharSequence subSequence(int beg, int end) {
+        //System.out.println("i: "+beg+".."+end);
+        if (beg == end) {
+            return "";
+        }
+        if (beg < 0) {
+            charAt(beg);  // raise exception
+        }
+        if (beg > end) {
+            charAt(-1);   // raise exception
+        }
+        int begWh = which(beg);
+        int endWh = which(end);
+        if (endWh == size() || end == lengths[BEG0 + endWh]) {
+            --endWh;
+        }
+        //System.out.println("wh: "+begWh+".."+endWh);
+        int begBase = lengths[BEG0 + begWh];
+        int endBase = lengths[BEG0 + endWh];
+        int sepLength = separator.length();
+        int begFrag = 0;
+        if ((beg - begBase) < sepLength) {
+            begFrag = sepLength - (beg - begBase);
+            beg += begFrag;
+        }
+        int endFrag = 0;
+        if ((end - endBase) < sepLength) {
+            endFrag = (end - endBase);
+            end = endBase;
+            endBase = lengths[BEG0 + --endWh];
+        }
+        if (false) {
+            System.out.print("beg[wbf]end[wbf]");
+            int pr[] = {begWh, begBase, begFrag, beg, endWh, endBase, endFrag, end};
+            for (int k = 0; k < pr.length; k++) {
+                System.out.print((k == 4 ? "   " : " ") + (pr[k]));
+            }
+            System.out.println();
+        }
+        if (begFrag > 0 && (end + endFrag) - begBase <= sepLength) {
+            // Special case:  Slice the separator.
+            beg -= begFrag;
+            end += endFrag;
+            return separator.substring(beg - begBase, end - begBase);
+        }
+        if (begWh == endWh && (begFrag + endFrag) == 0) {
+            // Special case:  Slice a single token.
+            return getCS(begWh).subSequence(beg - begBase - sepLength,
+                    end - endBase - sepLength);
+        }
+        Object[] subTokens = new Object[1 + (endWh - begWh) + 1];
+        int fillp = 0;
+        if (begFrag == sepLength) {
+            // Insert a leading null token to force an initial separator.
+            subTokens[fillp++] = "";
+            begFrag = 0;
+        }
+        for (int wh = begWh; wh <= endWh; wh++) {
+            CharSequence cs = getCS(wh);
+            if (wh == begWh || wh == endWh) {
+                // Slice it.
+                int csBeg = (wh == begWh) ? (beg - begBase) - sepLength : 0;
+                int csEnd = (wh == endWh) ? (end - endBase) - sepLength : cs.length();
+                cs = cs.subSequence(csBeg, csEnd);
+                if (begFrag > 0 && wh == begWh) {
+                    cs = separator.substring(sepLength - begFrag) + cs;
+                }
+                if (endFrag > 0 && wh == endWh) {
+                    cs = cs.toString() + separator.substring(0, endFrag);
+                }
+            }
+            subTokens[fillp++] = cs;
+        }
+        return new TokenList(subTokens, 0, fillp, separator);
+    }
+
+    /** Returns the concatenation of all tokens,
+     *  with intervening separator characters.
+     */
+    public String toString() {
+        StringBuilder buf = new StringBuilder(length());
+        int size = this.size();
+        for (int i = 0; i < size; i++) {
+            if (i > 0) {
+                buf.append(separator);
+            }
+            buf.append(get(i));
+        }
+        return buf.toString();
+    }
+
+    /*---- TESTING CODE ----
+    public static void main(String[] av) {
+    if (av.length == 0)  av = new String[]{"one", "2", "", "four"};
+    TokenList ts = new TokenList();
+    final String SEP = ", ";
+    ts.setSeparator(SEP);
+    for (int i = -1; i < av.length; i++) {
+    if (i >= 0)  ts.addToken(av[i]);
+    {
+    TokenList tsCopy = new TokenList(ts.toString(), SEP);
+    if (!tsCopy.equals(ts)) {
+    tsCopy.setSeparator(")(");
+    System.out.println("!= ("+tsCopy+")");
+    }
+    }
+    {
+    TokenList tsBar = new TokenList(ts, "|");
+    tsBar.add(0, "[");
+    tsBar.add("]");
+    System.out.println(tsBar);
+    }
+    if (false) {
+    int[] ls = ts.getLengths();
+    System.out.println("ts: "+ts);
+    System.out.print("ls: {");
+    for (int j = 0; j < ls.length; j++)  System.out.print(" "+ls[j]);
+    System.out.println(" }");
+    }
+    assert0(ts.size() == i+1);
+    assert0(i < 0 || ts.get(i) == av[i]);
+    String tss = ts.toString();
+    int tslen = tss.length();
+    assert0(ts.length() == tss.length());
+    for (int n = 0; n < tslen; n++) {
+    assert0(ts.charAt(n) == tss.charAt(n));
+    }
+    for (int j = 0; j < tslen; j++) {
+    for (int k = tslen; k >= j; k--) {
+    CharSequence sub = ts.subSequence(j, k);
+    //System.out.println("|"+sub+"|");
+    assert0(sub.toString().equals(tss.substring(j, k)));
+    }
+    }
+    }
+    }
+    static void assert0(boolean z) {
+    if (!z)  throw new RuntimeException("assert failed");
+    }
+    // ---- TESTING CODE ----*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/pack200/pack200-verifier/src/xmlkit/XMLKit.java	Fri Aug 20 08:18:54 2010 -0700
@@ -0,0 +1,4330 @@
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package xmlkit; // -*- mode: java; indent-tabs-mode: nil -*-
+
+// XML Implementation packages:
+import java.util.*;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.io.OutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.BufferedReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.StringReader;
+
+import java.io.IOException;
+
+import org.xml.sax.XMLReader;
+import org.xml.sax.InputSource;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.Attributes;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * A kit of methods and classes useful for manipulating XML trees in
+ * memory. They are very compact and easy to use. An XML element
+ * occupies six pointers of overhead (like two arrays) plus a pointer
+ * for its name, each attribute name and value, and each sub-element.
+ * Many useful XML operations (or Lisp-like calls) can be accomplished
+ * with a single method call on an element itself.
+ * <p>
+ * There is strong integration with the Java collection classes.
+ * There are viewing and conversion operators to and from various
+ * collection types. Elements directly support list iterators.
+ * Most <tt>List</tt> methods work analogously on elements.
+ * <p>
+ * Because of implementation compromises, these XML trees are less
+ * functional than many standard XML classes.
+ * <ul>
+ * <li>There are no parent or sibling pointers in the tree.</li>
+ * <li>Attribute names are simple strings, with no namespaces.</li>
+ * <li>There is no internal support for schemas or validation.</li>
+ * </ul>
+ * <p>
+ * Here is a summary of functionality in <tt>XMLKit</tt>.
+ * (Overloaded groups of methods are summarized by marking some
+ * arguments optional with their default values. Some overloaded
+ * arguments are marked with their alternative types separated by
+ * a bar "|". Arguments or return values for which a null is
+ * specially significant are marked by an alternative "|null".
+ * Accessors which have corresponding setters are marked
+ * by "/set". Removers which have corresponding retainers are marked
+ * by "/retain".)
+ * <pre>
+ * --- element construction
+ * new Element(int elemCapacity=4), String name=""
+ * new Element(String name, String[] attrs={}, Element[] elems={}, int elemCapacity=4)
+ * new Element(String name, String[] attrs, Object[] elems, int elemCapacity=4)
+ * new Element(Element original) // shallow copy
+ * new Element(String name="", Collection elems) // coercion
+ *
+ * Element shallowCopy()
+ * Element shallowFreeze() // side-effecting
+ * Element deepCopy()
+ * Element deepFreeze() // not side-effecting
+ *
+ * EMPTY // frozen empty anonymous element
+ * void ensureExtraCapacity(int)
+ * void trimToSize()
+ * void sortAttrs() // sort by key
+ *
+ * --- field accessors
+ * String getName()/set
+ * int size()
+ * boolean isEmpty()
+ * boolean isFrozen()
+ * boolean isAnonymous()
+ * int getExtraCapacity()/set
+ * int attrSize()
+ *
+ * --- attribute accessors
+ * String getAttr(int i)/set
+ * String getAttrName(int i)
+ *
+ * String getAttr(String key)/set
+ * List getAttrList(String key)/set
+ * Number getAttrNumber(String key)/set
+ * long getAttrLong(String key)/set
+ * double getAttrDouble(String key)/set
+ *
+ * String getAttr(String key, String dflt=null)
+ * long getAttrLong(String key, long dflt=0)
+ * double getAttrDouble(String key, double dflt=0)
+ *
+ * Element copyAttrsOnly()
+ * Element getAttrs()/set =&gt; <em>&lt;&gt;&lt;key&gt;value&lt;/key&gt;...&lt;/&gt;</em>
+ * void addAttrs(Element attrs)
+ *
+ * void removeAttr(int i)
+ * void clearAttrs()
+ *
+ * --- element accessors
+ * Object get(int i)/set
+ * Object getLast() | null
+ * Object[] toArray()
+ * Element copyContentOnly()
+ *
+ * void add(int i=0, Object subElem)
+ * int addAll(int i=0, Collection | Element elems)
+ * int addContent(int i=0, TokenList|Element|Object|null)
+ * void XMLKit.addContent(TokenList|Element|Object|null, Collection sink|null)
+ *
+ * void clear(int beg=0, int end=size)
+ * void sort(Comparator=contentOrder())
+ * void reverse()
+ * void shuffle(Random rnd=(anonymous))
+ * void rotate(int distance)
+ * Object min/max(Comparator=contentOrder())
+ *
+ * --- text accessors
+ * CharSequence getText()/set
+ * CharSequence getUnmarkedText()
+ * int addText(int i=size, CharSequence)
+ * void trimText();
+ *
+ * --- views
+ * List asList() // element view
+ * ListIterator iterator()
+ * PrintWriter asWriter()
+ * Map asAttrMap()
+ * Iterable<CharSequence> texts()
+ * Iterable<Element> elements()
+ * Iterable<T> partsOnly(Class<T>)
+ * String[] toStrings()
+ *
+ * --- queries
+ * boolean equals(Element | Object)
+ * int compareTo(Element | Object)
+ * boolean equalAttrs(Element)
+ * int hashCode()
+ * boolean isText() // every sub-elem is CharSequence
+ * boolean hasText() // some sub-elem is CharSequence
+ *
+ * boolean contains(Object)
+ * boolean containsAttr(String)
+ *
+ * int indexOf(Object)
+ * int indexOf(Filter, int fromIndex=0)
+ * int lastIndexOf(Object)
+ * int lastIndexOf(Filter, int fromIndex=size-1)
+ *
+ * int indexOfAttr(String)
+ *
+ * // finders, removers, and replacers do addContent of each filtered value
+ * // (i.e., TokenLists and anonymous Elements are broken out into their parts)
+ * boolean matches(Filter)
+ *
+ * Object find(Filter, int fromIndex=0)
+ * Object findLast(Filter, int fromIndex=size-1)
+ * Element findAll(Filter, int fromIndex=0 &amp; int toIndex=size)
+ * int findAll(Filter, Collection sink | null, int fromIndex=0 &amp; int toIndex=size)
+ *
+ * Element removeAllInTree(Filter)/retain
+ * int findAllInTree(Filter, Collection sink | null)
+ * int countAllInTree(Filter)
+ * Element removeAllInTree(Filter)/retain
+ * int removeAllInTree(Filter, Collection sink | null)/retain
+ * void replaceAllInTree(Filter)
+ *
+ * Element findElement(String name=any)
+ * Element findAllElements(String name=any)
+ *
+ * Element findWithAttr(String key, String value=any)
+ * Element findAllWithAttr(String key, String value=any)
+ *
+ * Element removeElement(String name=any)
+ * Element removeAllElements(String name=any)/retain
+ *
+ * Element removeWithAttr(String key, String value=any)
+ * Element removeAllWithAttr(String key, String value=any)/retain
+ *
+ * //countAll is the same as findAll but with null sink
+ * int countAll(Filter)
+ * int countAllElements(String name=any)
+ * int countAllWithAttr(String key, String value=any)
+ *
+ * void replaceAll(Filter, int fromIndex=0 &amp; int toIndex=size)
+ * void replaceAllInTree(Filter)
+ * void XMLKit.replaceAll(Filter, List target) //if(fx){remove x;addContent fx}
+ *
+ * --- element mutators
+ * boolean remove(Object)
+ * Object remove(int)
+ * Object removeLast() | null
+ *
+ * Object remove(Filter, int fromIndex=0)
+ * Object removeLast(Filter, int fromIndex=size-1)
+ * Element sink = removeAll(Filter, int fromIndex=0 &amp; int toIndex=size)/retain
+ * int count = removeAll(Filter, int fromIndex=0 &amp; int toIndex=size, Collection sink | null)/retain
+ *
+ * Element removeAllElements(String name=any)
+ *
+ * --- attribute mutators
+ * ??int addAllAttrsFrom(Element attrSource)
+ *
+ * --- parsing and printing
+ * void tokenize(String delims=whitespace, returnDelims=false)
+ * void writeTo(Writer)
+ * void writePrettyTo(Writer)
+ * String prettyString()
+ * String toString()
+ *
+ * ContentHandler XMLKit.makeBuilder(Collection sink, tokenizing=false, makeFrozen=false) // for standard XML parser
+ * Element XMLKit.readFrom(Reader, tokenizing=false, makeFrozen=false)
+ * void XMLKit.prettyPrintTo(Writer | OutputStream, Element)
+ * class XMLKit.Printer(Writer) { void print/Recursive(Element) }
+ * void XMLKit.output(Object elem, ContentHandler, LexicalHandler=null)
+ * void XMLKit.writeToken(String, char quote, Writer)
+ * void XMLKit.writeCData(String, Writer)
+ * Number XMLKit.convertToNumber(String, Number dflt=null)
+ * long XMLKit.convertToLong(String, long dflt=0)
+ * double XMLKit.convertToDouble(String, double dflt=0)
+ *
+ * --- filters
+ * XMLKit.ElementFilter { Element filter(Element) }
+ * XMLKit.elementFilter(String name=any | Collection nameSet)
+ * XMLKit.AttrFilter(String key) { boolean test(String value) }
+ * XMLKit.attrFilter(String key, String value=any)
+ * XMLKit.attrFilter(Element matchThis, String key)
+ * XMLKit.classFilter(Class)
+ * XMLKit.textFilter() // matches any CharSequence
+ * XMLKit.specialFilter() // matches any Special element
+ * XMLKit.methodFilter(Method m, Object[] args=null, falseResult=null)
+ * XMLKit.testMethodFilter(Method m, Object[] args=null)
+ * XMLKit.not(Filter) // inverts sense of Filter
+ * XMLKit.and(Filter&amp;Filter | Filter[])
+ * XMLKit.or(Filter&amp;Filter | Filter[])
+ * XMLKit.stack(Filter&amp;Filter | Filter[]) // result is (fx && g(fx))
+ * XMLKit.content(Filter, Collection sink) // copies content to sink
+ * XMLKit.replaceInTree(Filter pre, Filter post=null) // pre-replace else recur
+ * XMLKit.findInTree(Filter pre, Collection sink=null) // pre-find else recur
+ * XMLKit.nullFilter() // ignores input, always returns null (i.e., false)
+ * XMLKit.selfFilter( ) // always returns input (i.e., true)
+ * XMLKit.emptyFilter() // ignores input, always returns EMPTY
+ * XMLKit.constantFilter(Object) // ignores input, always returns constant
+ *
+ * --- misc
+ * Comparator XMLKit.contentOrder() // for comparing/sorting mixed content
+ * Method XMLKit.Element.method(String name) // returns Element method
+ * </pre>
+ *
+ * @author jrose
+ */
+public abstract class XMLKit {
+
+    private XMLKit() {
+    }
+    // We need at least this much slop if the element is to stay unfrozen.
+    static final int NEED_SLOP = 1;
+    static final Object[] noPartsFrozen = {};
+    static final Object[] noPartsNotFrozen = new Object[NEED_SLOP];
+    static final String WHITESPACE_CHARS = " \t\n\r\f";
+    static final String ANON_NAME = new String("*");  // unique copy of "*"
+
+    public static final class Element implements Comparable<Element>, Iterable<Object> {
+        // Note:  Does not implement List, because it has more
+        // significant parts besides its sub-elements.  Therefore,
+        // hashCode and equals must be more distinctive than Lists.
+
+        // <name> of element
+        String name;
+        // number of child elements, in parts[0..size-1]
+        int size;
+        // The parts start with child elements::  {e0, e1, e2, ...}.
+        // Following that are optional filler elements, all null.
+        // Following that are attributes as key/value pairs.
+        // They are in reverse: {...key2, val2, key1, val1, key0, val0}.
+        // Child elements and attr keys and values are never null.
+        Object[] parts;
+
+        // Build a partially-constructed node.
+        // Caller is responsible for initializing promised attributes.
+        Element(String name, int size, int capacity) {
+            this.name = name.toString();
+            this.size = size;
+            assert (size <= capacity);
+            this.parts = capacity > 0 ? new Object[capacity] : noPartsFrozen;
+        }
+
+        /** An anonymous, empty element.
+         *  Optional elemCapacity argument is expected number of sub-elements.
+         */
+        public Element() {
+            this(ANON_NAME, 0, NEED_SLOP + 4);
+        }
+
+        public Element(int extraCapacity) {
+            this(ANON_NAME, 0, NEED_SLOP + Math.max(0, extraCapacity));
+        }
+
+        /** An empty element with the given name.
+         *  Optional extraCapacity argument is expected number of sub-elements.
+         */
+        public Element(String name) {
+            this(name, 0, NEED_SLOP + 4);
+        }
+
+        public Element(String name, int extraCapacity) {
+            this(name, 0, NEED_SLOP + Math.max(0, extraCapacity));
+        }
+
+        /** An empty element with the given name and attributes.
+         *  Optional extraCapacity argument is expected number of sub-elements.
+         */
+        public Element(String name, String... attrs) {
+            this(name, attrs, (Element[]) null, 0);
+        }
+
+        public Element(String name, String[] attrs, int extraCapacity) {
+            this(name, attrs, (Element[]) null, extraCapacity);
+        }
+
+        /** An empty element with the given name and sub-elements.
+         *  Optional extraCapacity argument is expected extra sub-elements.
+         */
+        public Element(String name, Element... elems) {
+            this(name, (String[]) null, elems, 0);
+        }
+
+        public Element(String name, Element[] elems, int extraCapacity) {
+            this(name, (String[]) null, elems, extraCapacity);
+        }
+
+        /** An empty element with the given name, attributes, and sub-elements.
+         *  Optional extraCapacity argument is expected extra sub-elements.
+         */
+        public Element(String name, String[] attrs, Object... elems) {
+            this(name, attrs, elems, 0);
+        }
+
+        public Element(String name, String[] attrs, Object[] elems, int extraCapacity) {
+            this(name, 0,
+                    ((elems == null) ? 0 : elems.length)
+                    + Math.max(0, extraCapacity)
+                    + NEED_SLOP
+                    + ((attrs == null) ? 0 : attrs.length));
+            int ne = ((elems == null) ? 0 : elems.length);
+            int na = ((attrs == null) ? 0 : attrs.length);
+            int fillp = 0;
+            for (int i = 0; i < ne; i++) {
+                if (elems[i] != null) {
+                    parts[fillp++] = elems[i];
+                }
+            }
+            size = fillp;
+            for (int i = 0; i < na; i += 2) {
+                setAttr(attrs[i + 0], attrs[i + 1]);
+            }
+        }
+
+        public Element(Collection c) {
+            this(c.size());
+            addAll(c);
+        }
+
+        public Element(String name, Collection c) {
+            this(name, c.size());
+            addAll(c);
+        }
+
+        /** Shallow copy.  Same as old.shallowCopy().
+         *  Optional extraCapacity argument is expected extra sub-elements.
+         */
+        public Element(Element old) {
+            this(old, 0);
+        }
+
+        public Element(Element old, int extraCapacity) {
+            this(old.name, old.size,
+                    old.size
+                    + Math.max(0, extraCapacity) + NEED_SLOP
+                    + old.attrLength());
+            // copy sub-elements
+            System.arraycopy(old.parts, 0, parts, 0, size);
+            int alen = parts.length
+                    - (size + Math.max(0, extraCapacity) + NEED_SLOP);
+            // copy attributes
+            System.arraycopy(old.parts, old.parts.length - alen,
+                    parts, parts.length - alen,
+                    alen);
+            assert (!isFrozen());
+        }
+
+        /** Shallow copy.  Same as new Element(this). */
+        public Element shallowCopy() {
+            return new Element(this);
+        }
+        static public final Element EMPTY = new Element(ANON_NAME, 0, 0);
+
+        Element deepFreezeOrCopy(boolean makeFrozen) {
+            if (makeFrozen && isFrozen()) {
+                return this;  // no need to copy it
+            }
+            int alen = attrLength();
+            int plen = size + (makeFrozen ? 0 : NEED_SLOP) + alen;
+            Element copy = new Element(name, size, plen);
+            // copy attributes
+            System.arraycopy(parts, parts.length - alen, copy.parts, plen - alen, alen);
+            // copy sub-elements
+            for (int i = 0; i < size; i++) {
+                Object e = parts[i];
+                String str;
+                if (e instanceof Element) {  // recursion is common case
+                    e = ((Element) e).deepFreezeOrCopy(makeFrozen);
+                } else if (makeFrozen) {
+                    // Freeze StringBuffers, etc.
+                    e = fixupString(e);
+                }
+                copy.setRaw(i, e);
+            }
+            return copy;
+        }
+
+        /** Returns new Element(this), and also recursively copies sub-elements. */
+        public Element deepCopy() {
+            return deepFreezeOrCopy(false);
+        }
+
+        /** Returns frozen version of deepCopy. */
+        public Element deepFreeze() {
+            return deepFreezeOrCopy(true);
+        }
+
+        /** Freeze this element.
+         *  Throw an IllegalArgumentException if any sub-element is not already frozen.
+         *  (Use deepFreeze() to make a frozen copy of an entire element tree.)
+         */
+        public void shallowFreeze() {
+            if (isFrozen()) {
+                return;
+            }
+            int alen = attrLength();
+            Object[] nparts = new Object[size + alen];
+            // copy attributes
+            System.arraycopy(parts, parts.length - alen, nparts, size, alen);
+            // copy sub-elements
+            for (int i = 0; i < size; i++) {
+                Object e = parts[i];
+                String str;
+                if (e instanceof Element) {  // recursion is common case
+                    if (!((Element) e).isFrozen()) {
+                        throw new IllegalArgumentException("Sub-element must be frozen.");
+                    }
+                } else {
+                    // Freeze StringBuffers, etc.
+                    e = fixupString(e);
+                }
+                nparts[i] = e;
+            }
+            parts = nparts;
+            assert (isFrozen());
+        }
+
+        /** Return the name of this element. */
+        public String getName() {
+            return name;
+        }
+
+        /** Change the name of this element. */
+        public void setName(String name) {
+            checkNotFrozen();
+            this.name = name.toString();
+        }
+
+        /** Reports if the element's name is a particular string (spelled "*").
+         *  Such elements are created by the nullary Element constructor,
+         *  and by query functions which return multiple values,
+         *  such as <tt>findAll</tt>.
+         */
+        public boolean isAnonymous() {
+            return name == ANON_NAME;
+        }
+
+        /** Return number of elements.  (Does not include attributes.) */
+        public int size() {
+            return size;
+        }
+
+        /** True if no elements.  (Does not consider attributes.) */
+        public boolean isEmpty() {
+            return size == 0;
+        }
+
+        /** True if this element does not allow modification. */
+        public boolean isFrozen() {
+            // It is frozen iff there is no slop space.
+            return !hasNulls(NEED_SLOP);
+        }
+
+        void checkNotFrozen() {
+            if (isFrozen()) {
+                throw new UnsupportedOperationException("cannot modify frozen element");
+            }
+        }
+
+        /** Remove specified elements.  (Does not affect attributes.) */
+        public void clear() {
+            clear(0, size);
+        }
+
+        public void clear(int beg) {
+            clear(beg, size);
+        }
+
+        public void clear(int beg, int end) {
+            if (end > size) {
+                badIndex(end);
+            }
+            if (beg < 0 || beg > end) {
+                badIndex(beg);
+            }
+            if (beg == end) {
+                return;
+            }
+            checkNotFrozen();
+            if (end == size) {
+                if (beg == 0
+                        && parts.length > 0 && parts[parts.length - 1] == null) {
+                    // If no attributes, free the parts array.
+                    parts = noPartsNotFrozen;
+                    size = 0;
+                } else {
+                    clearParts(beg, size);
+                    size = beg;
+                }
+            } else {
+                close(beg, end - beg);
+            }
+        }
+
+        void clearParts(int beg, int end) {
+            for (int i = beg; i < end; i++) {
+                parts[i] = null;
+            }
+        }
+
+        /** True if name, attributes, and elements are the same. */
+        public boolean equals(Element that) {
+            if (!this.name.equals(that.name)) {
+                return false;
+            }
+            if (this.size != that.size) {
+                return false;
+            }
+            // elements must be equal and ordered
+            Object[] thisParts = this.parts;
+            Object[] thatParts = that.parts;
+            for (int i = 0; i < size; i++) {
+                Object thisPart = thisParts[i];
+                Object thatPart = thatParts[i];
+
+                if (thisPart instanceof Element) { // recursion is common case
+                    if (!thisPart.equals(thatPart)) {
+                        return false;
+                    }
+                } else {
+                    // If either is a non-string char sequence, normalize it.
+                    thisPart = fixupString(thisPart);
+                    thatPart = fixupString(thatPart);
+                    if (!thisPart.equals(thatPart)) {
+                        return false;
+                    }
+                }
+            }
+            // finally, attributes must be equal (unordered)
+            return this.equalAttrs(that);
+        }
+        // bridge method
+
+        public boolean equals(Object o) {
+            if (!(o instanceof Element)) {
+                return false;
+            }
+            return equals((Element) o);
+        }
+
+        public int hashCode() {
+            int hc = 0;
+            int alen = attrLength();
+            for (int i = parts.length - alen; i < parts.length; i += 2) {
+                hc += (parts[i + 0].hashCode() ^ parts[i + 1].hashCode());
+            }
+            hc ^= hc << 11;
+            hc += name.hashCode();
+            for (int i = 0; i < size; i++) {
+                hc ^= hc << 7;
+                Object p = parts[i];
+                if (p instanceof Element) {
+                    hc += p.hashCode();  // recursion is common case
+                } else {
+                    hc += fixupString(p).hashCode();
+                }
+            }
+            hc ^= hc >>> 19;
+            return hc;
+        }
+
+        /** Compare lexicographically.  Earlier-spelled attrs are more sigificant. */
+        public int compareTo(Element that) {
+            int r;
+            // Primary key is element name.
+            r = this.name.compareTo(that.name);
+            if (r != 0) {
+                return r;
+            }
+
+            // Secondary key is attributes, as if in normal key order.
+            // The key/value pairs are sorted as a token sequence.
+            int thisAlen = this.attrLength();
+            int thatAlen = that.attrLength();
+            if (thisAlen != 0 || thatAlen != 0) {
+                r = compareAttrs(thisAlen, that, thatAlen, true);
+                assert (assertAttrCompareOK(r, that));
+                if (r != 0) {
+                    return r;
+                }
+            }
+
+            // Finally, elements should be equal and ordered,
+            // and the first difference rules.
+            Object[] thisParts = this.parts;
+            Object[] thatParts = that.parts;
+            int minSize = this.size;
+            if (minSize > that.size) {
+                minSize = that.size;
+            }
+            Comparator<Object> cc = contentOrder();
+            for (int i = 0; i < minSize; i++) {
+                r = cc.compare(thisParts[i], thatParts[i]);
+                if (r != 0) {
+                    return r;
+                }
+            }
+            //if (this.size < that.size)  return -1;
+            return this.size - that.size;
+        }
+
+        private boolean assertAttrCompareOK(int r, Element that) {
+            Element e0 = this.copyAttrsOnly();
+            Element e1 = that.copyAttrsOnly();
+            e0.sortAttrs();
+            e1.sortAttrs();
+            int r2;
+            for (int k = 0;; k++) {
+                boolean con0 = e0.containsAttr(k);
+                boolean con1 = e1.containsAttr(k);
+                if (con0 != con1) {
+                    if (!con0) {
+                        r2 = 0 - 1;
+                        break;
+                    }
+                    if (!con1) {
+                        r2 = 1 - 0;
+                        break;
+                    }
+                }
+                if (!con0) {
+                    r2 = 0;
+                    break;
+                }
+                String k0 = e0.getAttrName(k);
+                String k1 = e1.getAttrName(k);
+                r2 = k0.compareTo(k1);
+                if (r2 != 0) {
+                    break;
+                }
+                String v0 = e0.getAttr(k);
+                String v1 = e1.getAttr(k);
+                r2 = v0.compareTo(v1);
+                if (r2 != 0) {
+                    break;
+                }
+            }
+            if (r != 0) {
+                r = (r > 0) ? 1 : -1;
+            }
+            if (r2 != 0) {
+                r2 = (r2 > 0) ? 1 : -1;
+            }
+            if (r != r2) {
+                System.out.println("*** wrong attr compare, " + r + " != " + r2);
+                System.out.println(" this = " + this);
+                System.out.println("  attr->" + e0);
+                System.out.println(" that = " + that);
+                System.out.println("  attr->" + e1);
+            }
+            return r == r2;
+        }
+
+        private void badIndex(int i) {
+            Object badRef = (new Object[0])[i];
+        }
+
+        public Object get(int i) {
+            if (i >= size) {
+                badIndex(i);
+            }
+            return parts[i];
+        }
+
+        public Object set(int i, Object e) {
+            if (i >= size) {
+                badIndex(i);
+            }
+            e.getClass();  // null check
+            checkNotFrozen();
+            Object old = parts[i];
+            setRaw(i, e);
+            return old;
+        }
+
+        void setRaw(int i, Object e) {
+            parts[i] = e;
+        }
+
+        public boolean remove(Object e) {
+            int i = indexOf(e);
+            if (i < 0) {
+                return false;
+            }
+            close(i, 1);
+            return true;
+        }
+
+        public Object remove(int i) {
+            if (i >= size) {
+                badIndex(i);
+            }
+            Object e = parts[i];
+            close(i, 1);
+            return e;
+        }
+
+        public Object removeLast() {
+            if (size == 0) {
+                return null;
+            }
+            return remove(size - 1);
+        }
+
+        /** Remove the first element matching the given filter.
+         *  Return the filtered value.
+         */
+        public Object remove(Filter f) {
+            return findOrRemove(f, 0, true);
+        }
+
+        public Object remove(Filter f, int fromIndex) {
+            if (fromIndex < 0) {
+                fromIndex = 0;
+            }
+            return findOrRemove(f, fromIndex, true);
+        }
+
+        /** Remove the last element matching the given filter.
+         *  Return the filtered value.
+         */
+        public Object removeLast(Filter f) {
+            return findOrRemoveLast(f, size - 1, true);
+        }
+
+        public Object removeLast(Filter f, int fromIndex) {
+            if (fromIndex >= size) {
+                fromIndex = size - 1;
+            }
+            return findOrRemoveLast(f, fromIndex, true);
+        }
+
+        /** Remove all elements matching the given filter.
+         *  If there is a non-null collection given as a sink,
+         *  transfer removed elements to the given collection.
+         *  The int result is the number of removed elements.
+         *  If there is a null sink given, the removed elements
+         *  are discarded.  If there is no sink given, the removed
+         *  elements are returned in an anonymous container element.
+         */
+        public Element removeAll(Filter f) {
+            Element result = new Element();
+            findOrRemoveAll(f, false, 0, size, result.asList(), true);
+            return result;
+        }
+
+        public Element removeAll(Filter f, int fromIndex, int toIndex) {
+            Element result = new Element();
+            findOrRemoveAll(f, true, fromIndex, toIndex, result.asList(), true);
+            return result;
+        }
+
+        public int removeAll(Filter f, Collection<Object> sink) {
+            return findOrRemoveAll(f, false, 0, size, sink, true);
+        }
+
+        public int removeAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {
+            return findOrRemoveAll(f, false, fromIndex, toIndex, sink, true);
+        }
+
+        /** Remove all elements not matching the given filter.
+         *  If there is a non-null collection given as a sink,
+         *  transfer removed elements to the given collection.
+         *  The int result is the number of removed elements.
+         *  If there is a null sink given, the removed elements
+         *  are discarded.  If there is no sink given, the removed
+         *  elements are returned in an anonymous container element.
+         */
+        public Element retainAll(Filter f) {
+            Element result = new Element();
+            findOrRemoveAll(f, true, 0, size, result.asList(), true);
+            return result;
+        }
+
+        public Element retainAll(Filter f, int fromIndex, int toIndex) {
+            Element result = new Element();
+            findOrRemoveAll(f, true, fromIndex, toIndex, result.asList(), true);
+            return result;
+        }
+
+        public int retainAll(Filter f, Collection<Object> sink) {
+            return findOrRemoveAll(f, true, 0, size, sink, true);
+        }
+
+        public int retainAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {
+            return findOrRemoveAll(f, true, fromIndex, toIndex, sink, true);
+        }
+
+        public void add(int i, Object e) {
+            // (The shape of this method is tweaked for common cases.)
+            e.getClass();  // force a null check on e
+            if (hasNulls(1 + NEED_SLOP)) {
+                // Common case:  Have some slop space.
+                if (i == size) {
+                    // Most common case:  Append.
+                    setRaw(i, e);
+                    size++;
+                    return;
+                }
+                if (i > size) {
+                    badIndex(i);
+                }
+                // Second most common case:  Shift right by one.
+                open(i, 1);
+                setRaw(i, e);
+                return;
+            }
+            // Ran out of space.  Do something complicated.
+            size = expand(i, 1);
+            setRaw(i, e);
+        }
+
+        public boolean add(Object e) {
+            add(size, e);
+            return true;
+        }
+
+        public Object getLast() {
+            return size == 0 ? null : parts[size - 1];
+        }
+
+        /** Returns the text of this Element.
+         *  All sub-elements of this Element must be of type CharSequence.
+         *  A ClassCastException is raised if there are non-character sub-elements.
+         *  If there is one sub-element, return it.
+         *  Otherwise, returns a TokenList of all sub-elements.
+         *  This results in a space being placed between each adjacent pair of sub-elements.
+         */
+        public CharSequence getText() {
+            checkTextOnly();
+            if (size == 1) {
+                return parts[0].toString();
+            } else {
+                return new TokenList(parts, 0, size);
+            }
+        }
+
+        /** Provides an iterable view of this object as a series of texts.
+         *  All sub-elements of this Element must be of type CharSequence.
+         *  A ClassCastException is raised if there are non-character sub-elements.
+         */
+        public Iterable<CharSequence> texts() {
+            checkTextOnly();
+            return (Iterable<CharSequence>) (Iterable) this;
+        }
+
+        /** Returns an array of strings derived from the sub-elements of this object.
+         *  All sub-elements of this Element must be of type CharSequence.
+         *  A ClassCastException is raised if there are non-character sub-elements.
+         */
+        public String[] toStrings() {
+            //checkTextOnly();
+            String[] result = new String[size];
+            for (int i = 0; i < size; i++) {
+                result[i] = ((CharSequence) parts[i]).toString();
+            }
+            return result;
+        }
+
+        /** Like getText, except that it disregards non-text elements.
+         *  Non-text elements are replaced by their textual contents, if any.
+         *  Text elements which were separated only by non-text element
+         *  boundaries are merged into single tokens.
+         *  <p>
+         *  There is no corresponding setter, since this accessor does
+         *  not report the full state of the element.
+         */
+        public CharSequence getFlatText() {
+            if (size == 1) {
+                // Simple cases.
+                if (parts[0] instanceof CharSequence) {
+                    return parts[0].toString();
+                } else {
+                    return new TokenList();
+                }
+            }
+            if (isText()) {
+                return getText();
+            }
+            // Filter and merge.
+            Element result = new Element(size);
+            boolean merge = false;
+            for (int i = 0; i < size; i++) {
+                Object text = parts[i];
+                if (!(text instanceof CharSequence)) {
+                    // Skip, but erase this boundary.
+                    if (text instanceof Element) {
+                        Element te = (Element) text;
+                        if (!te.isEmpty()) {
+                            result.addText(te.getFlatText());
+                        }
+                    }
+                    merge = true;
+                    continue;
+                }
+                if (merge) {
+                    // Merge w/ previous token.
+                    result.addText((CharSequence) text);
+                    merge = false;
+                } else {
+                    result.add(text);
+                }
+            }
+            if (result.size() == 1) {
+                return (CharSequence) result.parts[0];
+            } else {
+                return result.getText();
+            }
+        }
+
+        /** Return true if all sub-elements are of type CharSequence. */
+        public boolean isText() {
+            for (int i = 0; i < size; i++) {
+                if (!(parts[i] instanceof CharSequence)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /** Return true if at least one sub-element is of type CharSequence. */
+        public boolean hasText() {
+            for (int i = 0; i < size; i++) {
+                if (parts[i] instanceof CharSequence) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /** Raise a ClassCastException if !isText. */
+        public void checkTextOnly() {
+            for (int i = 0; i < size; i++) {
+                ((CharSequence) parts[i]).getClass();
+            }
+        }
+
+        /** Clears out all sub-elements, and replaces them by the given text.
+         *  A ClassCastException is raised if there are non-character sub-elements,
+         *  either before or after the change.
+         */
+        public void setText(CharSequence text) {
+            checkTextOnly();
+            clear();
+            if (text instanceof TokenList) {
+                // TL's contain only strings
+                addAll(0, (TokenList) text);
+            } else {
+                add(text);
+            }
+        }
+
+        /** Add text at the given position, merging with any previous
+         *  text element, but preserving token boundaries where possible.
+         *  <p>
+         *  In all cases, the new value of getText() is the string
+         *  concatenation of the old value of getText() plus the new text.
+         *  <p>
+         *  The total effect is to concatenate the given text to any
+         *  pre-existing text, and to do so efficiently even if there
+         *  are many such concatenations.  Also, getText calls which
+         *  return multiple tokens (in a TokenList) are respected.
+         *  For example, if x is empty, x.addText(y.getText()) puts
+         *  an exact structural copy of y's text into x.
+         *  <p>
+         *  Internal token boundaries in the original text, and in the new
+         *  text (i.e., if it is a TokenList), are preserved.  However,
+         *  at the point where new text joins old text, a StringBuffer
+         *  or new String may be created to join the last old and first
+         *  new token.
+         *  <p>
+         *  If the given text is a TokenList, add the tokens as
+         *  separate sub-elements, possibly merging the first token to
+         *  a previous text item (to avoid making a new token boundary).
+         *  <p>
+         *  If the element preceding position i is a StringBuffer,
+         *  append the first new token to it.
+         *  <p>
+         *  If the preceding element is a CharSequence, replace it by a
+         *  StringBuffer containing both its and the first new token.
+         *  <p>
+         *  If tokens are added after a StringBuffer, freeze it into a String.
+         *  <p>
+         *  Every token not merged into a previous CharSequence is added
+         *  as a new sub-element, starting at position i.
+         *  <p>
+         *  Returns the number of elements added, which is useful
+         *  for further calls to addText.  This number is zero
+         *  if the input string was null, or was successfully
+         *  merged into a StringBuffer at position i-1.
+         *  <p>
+         *  By contrast, calling add(text) always adds a new sub-element.
+         *  In that case, if there is a previous string, a separating
+         *  space is virtually present also, and will be observed if
+         *  getText() is used to return all the text together.
+         */
+        public int addText(int i, CharSequence text) {
+            if (text instanceof String) {
+                return addText(i, (String) text);
+            } else if (text instanceof TokenList) {
+                // Text is a list of tokens.
+                TokenList tl = (TokenList) text;
+                int tlsize = tl.size();
+                if (tlsize == 0) {
+                    return 0;
+                }
+                String token0 = tl.get(0).toString();
+                if (tlsize == 1) {
+                    return addText(i, token0);
+                }
+                if (mergeWithPrev(i, token0, false)) {
+                    // Add the n-1 remaining tokens.
+                    addAll(i, tl.subList(1, tlsize));
+                    return tlsize - 1;
+                } else {
+                    addAll(i, (Collection) tl);
+                    return tlsize;
+                }
+            } else {
+                return addText(i, text.toString());
+            }
+        }
+
+        public int addText(CharSequence text) {
+            return addText(size, text);
+        }
+
+        private // no reason to make this helper public
+                int addText(int i, String text) {
+            if (text.length() == 0) {
+                return 0;  // Trivial success.
+            }
+            if (mergeWithPrev(i, text, true)) {
+                return 0;  // Merged with previous token.
+            }
+            // No previous token.
+            add(i, text);
+            return 1;
+        }
+
+        // Tries to merge token with previous contents.
+        // Returns true if token is successfully disposed of.
+        // If keepSB is false, any previous StringBuffer is frozen.
+        // If keepSB is true, a StringBuffer may be created to hold
+        // the merged token.
+        private boolean mergeWithPrev(int i, String token, boolean keepSB) {
+            if (i == 0) // Trivial success if the token is length zero.
+            {
+                return (token.length() == 0);
+            }
+            Object prev = parts[i - 1];
+            if (prev instanceof StringBuffer) {
+                StringBuffer psb = (StringBuffer) prev;
+                psb.append(token);
+                if (!keepSB) {
+                    parts[i - 1] = psb.toString();
+                }
+                return true;
+            }
+            if (token.length() == 0) {
+                return true;  // Trivial success.
+            }
+            if (prev instanceof CharSequence) {
+                // Must concatenate.
+                StringBuffer psb = new StringBuffer(prev.toString());
+                psb.append(token);
+                if (keepSB) {
+                    parts[i - 1] = psb;
+                } else {
+                    parts[i - 1] = psb.toString();
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /** Trim all strings, using String.trim().
+         *  Remove empty strings.
+         *  Normalize CharSequences to Strings.
+         */
+        public void trimText() {
+            checkNotFrozen();
+            int fillp = 0;
+            int size = this.size;
+            Object[] parts = this.parts;
+            for (int i = 0; i < size; i++) {
+                Object e = parts[i];
+                if (e instanceof CharSequence) {
+                    String tt = e.toString().trim();
+                    if (tt.length() == 0) {
+                        continue;
+                    }
+                    e = tt;
+                }
+                parts[fillp++] = e;
+            }
+            while (size > fillp) {
+                parts[--size] = null;
+            }
+            this.size = fillp;
+        }
+
+        /** Add one or more subelements at the given position.
+         *  If the object reference is null, nothing happens.
+         *  If the object is an anonymous Element, addAll is called.
+         *  If the object is a TokenList, addAll is called (to add the tokens).
+         *  Otherwise, add is called, adding a single subelement or string.
+         *  The net effect is to add zero or more tokens.
+         *  The returned value is the number of added elements.
+         *  <p>
+         *  Note that getText() can return a TokenList which preserves
+         *  token boundaries in the text source.  Such a text will be
+         *  added as multiple text sub-elements.
+         *  <p>
+         *  If a text string is added adjacent to an immediately
+         *  preceding string, there will be a token boundary between
+         *  the strings, which will print as an extra space.
+         */
+        public int addContent(int i, Object e) {
+            if (e == null) {
+                return 0;
+            } else if (e instanceof TokenList) {
+                return addAll(i, (Collection) e);
+            } else if (e instanceof Element
+                    && ((Element) e).isAnonymous()) {
+                return addAll(i, (Element) e);
+            } else {
+                add(i, e);
+                return 1;
+            }
+        }
+
+        public int addContent(Object e) {
+            return addContent(size, e);
+        }
+
+        public Object[] toArray() {
+            Object[] result = new Object[size];
+            System.arraycopy(parts, 0, result, 0, size);
+            return result;
+        }
+
+        public Element copyContentOnly() {
+            Element content = new Element(size);
+            System.arraycopy(parts, 0, content.parts, 0, size);
+            content.size = size;
+            return content;
+        }
+
+        public void sort(Comparator<Object> c) {
+            Arrays.sort(parts, 0, size, c);
+        }
+
+        public void sort() {
+            sort(CONTENT_ORDER);
+        }
+
+        /** Equivalent to Collections.reverse(this.asList()). */
+        public void reverse() {
+            for (int i = 0, mid = size >> 1, j = size - 1; i < mid; i++, j--) {
+                Object p = parts[i];
+                parts[i] = parts[j];
+                parts[j] = p;
+            }
+        }
+
+        /** Equivalent to Collections.shuffle(this.asList() [, rnd]). */
+        public void shuffle() {
+            Collections.shuffle(this.asList());
+        }
+
+        public void shuffle(Random rnd) {
+            Collections.shuffle(this.asList(), rnd);
+        }
+
+        /** Equivalent to Collections.rotate(this.asList(), dist). */
+        public void rotate(int dist) {
+            Collections.rotate(this.asList(), dist);
+        }
+
+        /** Equivalent to Collections.min(this.asList(), c). */
+        public Object min(Comparator<Object> c) {
+            return Collections.min(this.asList(), c);
+        }
+
+        public Object min() {
+            return min(CONTENT_ORDER);
+        }
+
+        /** Equivalent to Collections.max(this.asList(), c). */
+        public Object max(Comparator<Object> c) {
+            return Collections.max(this.asList(), c);
+        }
+
+        public Object max() {
+            return max(CONTENT_ORDER);
+        }
+
+        public int addAll(int i, Collection c) {
+            if (c instanceof LView) {
+                return addAll(i, ((LView) c).asElement());
+            } else {
+                int csize = c.size();
+                if (csize == 0) {
+                    return 0;
+                }
+                openOrExpand(i, csize);
+                int fill = i;
+                for (Object part : c) {
+                    parts[fill++] = part;
+                }
+                return csize;
+            }
+        }
+
+        public int addAll(int i, Element e) {
+            int esize = e.size;
+            if (esize == 0) {
+                return 0;
+            }
+            openOrExpand(i, esize);
+            System.arraycopy(e.parts, 0, parts, i, esize);
+            return esize;
+        }
+
+        public int addAll(Collection c) {
+            return addAll(size, c);
+        }
+
+        public int addAll(Element e) {
+            return addAll(size, e);
+        }
+
+        public int addAllAttrsFrom(Element e) {
+            int added = 0;
+            for (int k = 0; e.containsAttr(k); k++) {
+                String old = setAttr(e.getAttrName(k), e.getAttr(k));
+                if (old == null) {
+                    added += 1;
+                }
+            }
+            // Return number of added (not merely changed) attrs.
+            return added;
+        }
+
+        // Search.
+        public boolean matches(Filter f) {
+            return f.filter(this) != null;
+        }
+
+        public Object find(Filter f) {
+            return findOrRemove(f, 0, false);
+        }
+
+        public Object find(Filter f, int fromIndex) {
+            if (fromIndex < 0) {
+                fromIndex = 0;
+            }
+            return findOrRemove(f, fromIndex, false);
+        }
+
+        /** Find the last element matching the given filter.
+         *  Return the filtered value.
+         */
+        public Object findLast(Filter f) {
+            return findOrRemoveLast(f, size - 1, false);
+        }
+
+        public Object findLast(Filter f, int fromIndex) {
+            if (fromIndex >= size) {
+                fromIndex = size - 1;
+            }
+            return findOrRemoveLast(f, fromIndex, false);
+        }
+
+        /** Find all elements matching the given filter.
+         *  If there is a non-null collection given as a sink,
+         *  transfer matching elements to the given collection.
+         *  The int result is the number of matching elements.
+         *  If there is a null sink given, the matching elements are
+         *  not collected.  If there is no sink given, the matching
+         *  elements are returned in an anonymous container element.
+         *  In no case is the receiver element changed.
+         *  <p>
+         *  Note that a simple count of matching elements can be
+         *  obtained by passing a null collection argument.
+         */
+        public Element findAll(Filter f) {
+            Element result = new Element();
+            findOrRemoveAll(f, false, 0, size, result.asList(), false);
+            return result;
+        }
+
+        public Element findAll(Filter f, int fromIndex, int toIndex) {
+            Element result = new Element(name);
+            findOrRemoveAll(f, false, fromIndex, toIndex, result.asList(), false);
+            return result;
+        }
+
+        public int findAll(Filter f, Collection<Object> sink) {
+            return findOrRemoveAll(f, false, 0, size, sink, false);
+        }
+
+        public int findAll(Filter f, int fromIndex, int toIndex, Collection<Object> sink) {
+            return findOrRemoveAll(f, false, fromIndex, toIndex, sink, false);
+        }
+
+        /// Driver routines.
+        private Object findOrRemove(Filter f, int fromIndex, boolean remove) {
+            for (int i = fromIndex; i < size; i++) {
+                Object x = f.filter(parts[i]);
+                if (x != null) {
+                    if (remove) {
+                        close(i, 1);
+                    }
+                    return x;
+                }
+            }
+            return null;
+        }
+
+        private Object findOrRemoveLast(Filter f, int fromIndex, boolean remove) {
+            for (int i = fromIndex; i >= 0; i--) {
+                Object x = f.filter(parts[i]);
+                if (x != null) {
+                    if (remove) {
+                        close(i, 1);
+                    }
+                    return x;
+                }
+            }
+            return null;
+        }
+
+        private int findOrRemoveAll(Filter f, boolean fInvert,
+                int fromIndex, int toIndex,
+                Collection<Object> sink, boolean remove) {
+            if (fromIndex < 0) {
+                badIndex(fromIndex);
+            }
+            if (toIndex > size) {
+                badIndex(toIndex);
+            }
+            int found = 0;
+            for (int i = fromIndex; i < toIndex; i++) {
+                Object p = parts[i];
+                Object x = f.filter(p);
+                if (fInvert ? (x == null) : (x != null)) {
+                    if (remove) {
+                        close(i--, 1);
+                        toIndex--;
+                    }
+                    found += XMLKit.addContent(fInvert ? p : x, sink);
+                }
+            }
+            return found;
+        }
+
+        public void replaceAll(Filter f) {
+            XMLKit.replaceAll(f, this.asList());
+        }
+
+        public void replaceAll(Filter f, int fromIndex, int toIndex) {
+            XMLKit.replaceAll(f, this.asList().subList(fromIndex, toIndex));
+        }
+
+        /// Recursive walks.
+        // findAllInTree(f)     == findAll(findInTree(f,S)), S.toElement
+        // findAllInTree(f,S)   == findAll(findInTree(content(f,S)))
+        // removeAllInTree(f)   == replaceAll(replaceInTree(and(f,emptyF)))
+        // removeAllInTree(f,S) == replaceAll(replaceInTree(and(content(f,S),emptyF)))
+        // retainAllInTree(f)   == removeAllInTree(not(f))
+        // replaceAllInTree(f)  == replaceAll(replaceInTree(f))
+        public Element findAllInTree(Filter f) {
+            Element result = new Element();
+            findAllInTree(f, result.asList());
+            return result;
+        }
+
+        public int findAllInTree(Filter f, Collection<Object> sink) {
+            int found = 0;
+            int size = this.size;  // cache copy
+            for (int i = 0; i < size; i++) {
+                Object p = parts[i];
+                Object x = f.filter(p);
+                if (x != null) {
+                    found += XMLKit.addContent(x, sink);
+                } else if (p instanceof Element) {
+                    found += ((Element) p).findAllInTree(f, sink);
+                }
+            }
+            return found;
+        }
+
+        public int countAllInTree(Filter f) {
+            return findAllInTree(f, null);
+        }
+
+        public int removeAllInTree(Filter f, Collection<Object> sink) {
+            if (sink == null) {
+                sink = newCounterColl();
+            }
+            replaceAll(replaceInTree(and(content(f, sink), emptyFilter())));
+            return sink.size();
+        }
+
+        public Element removeAllInTree(Filter f) {
+            Element result = new Element();
+            removeAllInTree(f, result.asList());
+            return result;
+        }
+
+        public int retainAllInTree(Filter f, Collection<Object> sink) {
+            return removeAllInTree(not(f), sink);
+        }
+
+        public Element retainAllInTree(Filter f) {
+            Element result = new Element();
+            retainAllInTree(f, result.asList());
+            return result;
+        }
+
+        public void replaceAllInTree(Filter f) {
+            replaceAll(replaceInTree(f));
+        }
+
+        /** Raise a ClassCastException if any subelements are the wrong type. */
+        public void checkPartsOnly(Class<?> elementClass) {
+            for (int i = 0; i < size; i++) {
+                elementClass.cast(parts[i]).getClass();
+            }
+        }
+
+        /** Return true if all sub-elements are of the given type. */
+        public boolean isPartsOnly(Class<?> elementClass) {
+            for (int i = 0; i < size; i++) {
+                if (!elementClass.isInstance(parts[i])) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /** Provides an iterable view of this object as a series of elements.
+         *  All sub-elements of this Element must be of type Element.
+         *  A ClassCastException is raised if there are non-Element sub-elements.
+         */
+        public <T> Iterable<T> partsOnly(Class<T> elementClass) {
+            checkPartsOnly(elementClass);
+            return (Iterable<T>) (Iterable) this;
+        }
+
+        public Iterable<Element> elements() {
+            return partsOnly(Element.class);
+        }
+
+        /// Useful shorthands.
+        // Finding or removing elements w/o regard to their type or content.
+        public Element findElement() {
+            return (Element) find(elementFilter());
+        }
+
+        public Element findAllElements() {
+            return findAll(elementFilter());
+        }
+
+        public Element removeElement() {
+            return (Element) remove(elementFilter());
+        }
+
+        public Element removeAllElements() {
+            return (Element) removeAll(elementFilter());
+        }
+
+        // Finding or removing by element tag or selected attribute,
+        // as if by elementFilter(name) or attrFilter(name, value).
+        // Roughly akin to Common Lisp ASSOC.
+        public Element findElement(String name) {
+            return (Element) find(elementFilter(name));
+        }
+
+        public Element removeElement(String name) {
+            return (Element) remove(elementFilter(name));
+        }
+
+        public Element findWithAttr(String key) {
+            return (Element) find(attrFilter(name));
+        }
+
+        public Element findWithAttr(String key, String value) {
+            return (Element) find(attrFilter(name, value));
+        }
+
+        public Element removeWithAttr(String key) {
+            return (Element) remove(attrFilter(name));
+        }
+
+        public Element removeWithAttr(String key, String value) {
+            return (Element) remove(attrFilter(name, value));
+        }
+
+        public Element findAllElements(String name) {
+            return findAll(elementFilter(name));
+        }
+
+        public Element removeAllElements(String name) {
+            return removeAll(elementFilter(name));
+        }
+
+        public Element retainAllElements(String name) {
+            return retainAll(elementFilter(name));
+        }
+
+        public Element findAllWithAttr(String key) {
+            return findAll(attrFilter(key));
+        }
+
+        public Element removeAllWithAttr(String key) {
+            return removeAll(attrFilter(key));
+        }
+
+        public Element retainAllWithAttr(String key) {
+            return retainAll(attrFilter(key));
+        }
+
+        public Element findAllWithAttr(String key, String value) {
+            return findAll(attrFilter(key, value));
+        }
+
+        public Element removeAllWithAttr(String key, String value) {
+            return removeAll(attrFilter(key, value));
+        }
+
+        public Element retainAllWithAttr(String key, String value) {
+            return retainAll(attrFilter(key, value));
+        }
+
+        public int countAll(Filter f) {
+            return findAll(f, null);
+        }
+
+        public int countAllElements() {
+            return countAll(elementFilter());
+        }
+
+        public int countAllElements(String name) {
+            return countAll(elementFilter(name));
+        }
+
+        public int countAllWithAttr(String key) {
+            return countAll(attrFilter(name));
+        }
+
+        public int countAllWithAttr(String key, String value) {
+            return countAll(attrFilter(key, value));
+        }
+
+        public int indexOf(Object e) {
+            for (int i = 0; i < size; i++) {
+                if (e.equals(parts[i])) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        public int lastIndexOf(Object e) {
+            for (int i = size - 1; i >= 0; i--) {
+                if (e.equals(parts[i])) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        /** Remove the first element matching the given filter.
+         *  Return the filtered value.
+         */
+        public int indexOf(Filter f) {
+            return indexOf(f, 0);
+        }
+
+        public int indexOf(Filter f, int fromIndex) {
+            if (fromIndex < 0) {
+                fromIndex = 0;
+            }
+            for (int i = fromIndex; i < size; i++) {
+                Object x = f.filter(parts[i]);
+                if (x != null) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        /** Remove the last element matching the given filter.
+         *  Return the filtered value.
+         */
+        public int lastIndexOf(Filter f) {
+            return lastIndexOf(f, size - 1);
+        }
+
+        public int lastIndexOf(Filter f, int fromIndex) {
+            if (fromIndex >= size) {
+                fromIndex = size - 1;
+            }
+            for (int i = fromIndex; i >= 0; i--) {
+                Object x = f.filter(parts[i]);
+                if (x != null) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        public boolean contains(Object e) {
+            return indexOf(e) >= 0;
+        }
+
+        // attributes
+        private int findOrCreateAttr(String key, boolean create) {
+            key.toString();  // null check
+            int attrBase = parts.length;
+            for (int i = parts.length - 2; i >= size; i -= 2) {
+                String akey = (String) parts[i + 0];
+                if (akey == null) {
+                    if (!create) {
+                        return -1;
+                    }
+                    if (i == size) {
+                        break;  // NEED_SLOP
+                    }
+                    parts[i + 0] = key;
+                    //parts[i+1] = ""; //caller responsibility
+                    return i;
+                }
+                attrBase = i;
+                if (akey.equals(key)) {
+                    return i;
+                }
+            }
+            // If we fell through, we ran into an element part.
+            // Therefore we have run out of empty slots.
+            if (!create) {
+                return -1;
+            }
+            assert (!isFrozen());
+            int alen = parts.length - attrBase;
+            expand(size, 2); // generally expands by more than 2
+            // since there was a reallocation, the garbage slots are really null
+            assert (parts[size + 0] == null && parts[size + 1] == null);
+            alen += 2;
+            int i = parts.length - alen;
+            parts[i + 0] = key;
+            //parts[i+1] = ""; //caller responsibility
+            return i;
+        }
+
+        public int attrSize() {
+            return attrLength() >>> 1;
+        }
+
+        public int indexOfAttr(String key) {
+            return findOrCreateAttr(key, false);
+        }
+
+        public boolean containsAttr(String key) {
+            return indexOfAttr(key) >= 0;
+        }
+
+        public String getAttr(String key) {
+            return getAttr(key, null);
+        }
+
+        public String getAttr(String key, String dflt) {
+            int i = findOrCreateAttr(key, false);
+            return (i < 0) ? dflt : (String) parts[i + 1];
+        }
+
+        public TokenList getAttrList(String key) {
+            return convertToList(getAttr(key));
+        }
+
+        public Number getAttrNumber(String key) {
+            return convertToNumber(getAttr(key));
+        }
+
+        public long getAttrLong(String key) {
+            return getAttrLong(key, 0);
+        }
+
+        public double getAttrDouble(String key) {
+            return getAttrDouble(key, 0.0);
+        }
+
+        public long getAttrLong(String key, long dflt) {
+            return convertToLong(getAttr(key), dflt);
+        }
+
+        public double getAttrDouble(String key, double dflt) {
+            return convertToDouble(getAttr(key), dflt);
+        }
+
+        int indexAttr(int k) {
+            int i = parts.length - (k * 2) - 2;
+            if (i < size || parts[i] == null) {
+                return -2;  // always oob
+            }
+            return i;
+        }
+
+        public boolean containsAttr(int k) {
+            return indexAttr(k) >= 0;
+        }
+
+        public String getAttr(int k) {
+            return (String) parts[indexAttr(k) + 1];
+        }
+
+        public String getAttrName(int k) {
+            return (String) parts[indexAttr(k) + 0];
+        }
+
+        public Iterable<String> attrNames() {
+            //return asAttrMap().keySet();
+            return new Iterable<String>() {
+
+                public Iterator<String> iterator() {
+                    return new ANItr();
+                }
+            };
+        }
+
+        // Hand-inlined replacement for asAttrMap().keySet().iterator():
+        class ANItr implements Iterator<String> {
+
+            boolean lastRet;
+            int cursor = -2;  // pointer from end of parts
+
+            public boolean hasNext() {
+                int i = cursor + parts.length;
+                return i >= size && parts[i] == null;
+            }
+
+            public String next() {
+                int i = cursor + parts.length;
+                Object x;
+                if (i < size || (x = parts[i]) == null) {
+                    nsee();
+                    return null;
+                }
+                cursor -= 2;
+                lastRet = true;
+                return (String) x;
+            }
+
+            public void remove() {
+                if (!lastRet) {
+                    throw new IllegalStateException();
+                }
+                Element.this.removeAttr((-4 - cursor) / 2);
+                cursor += 2;
+                lastRet = false;
+            }
+
+            Exception nsee() {
+                throw new NoSuchElementException("attribute " + (-2 - cursor) / 2);
+            }
+        }
+
+        /** Return an anonymous copy of self, but only with attributes.
+         */
+        public Element copyAttrsOnly() {
+            int alen = attrLength();
+            Element attrs = new Element(alen);
+            Object[] attrParts = attrs.parts;
+            assert (attrParts.length == NEED_SLOP + alen);
+            System.arraycopy(parts, parts.length - alen,
+                    attrParts, NEED_SLOP,
+                    alen);
+            return attrs;
+        }
+
+        /** Get all attributes, represented as an element with sub-elements.
+         *  The name of each sub-element is the attribute key, and the text
+         *  This is a fresh copy, and can be updated with affecting the original.
+         *  of each sub-element is the corresponding attribute value.
+         *  See also asAttrMap() for a "live" view of all the attributes as a Map.
+         */
+        public Element getAttrs() {
+            int asize = attrSize();
+            Element attrs = new Element(ANON_NAME, asize, NEED_SLOP + asize);
+            for (int i = 0; i < asize; i++) {
+                Element attr = new Element(getAttrName(i), 1, NEED_SLOP + 1);
+                // %%% normalize attrs to token lists?
+                attr.setRaw(0, getAttr(i));
+                attrs.setRaw(i, attr);
+            }
+            return attrs;
+        }
+
+        public void setAttrs(Element attrs) {
+            int alen = attrLength();
+            clearParts(parts.length - alen, alen);
+            if (!hasNulls(NEED_SLOP + attrs.size * 2)) {
+                expand(size, attrs.size * 2);
+            }
+            addAttrs(attrs);
+        }
+
+        public void addAttrs(Element attrs) {
+            for (int i = 0; i < attrs.size; i++) {
+                Element attr = (Element) attrs.get(i);
+                setAttr(attr.name, attr.getText().toString());
+            }
+        }
+
+        public void removeAttr(int i) {
+            checkNotFrozen();
+            while ((i -= 2) >= size) {
+                Object k = parts[i + 0];
+                Object v = parts[i + 1];
+                if (k == null) {
+                    break;
+                }
+                parts[i + 2] = k;
+                parts[i + 3] = v;
+            }
+            parts[i + 2] = null;
+            parts[i + 3] = null;
+        }
+
+        public void clearAttrs() {
+            if (parts.length == 0 || parts[parts.length - 1] == null) {
+                return;  // no attrs to clear
+            }
+            checkNotFrozen();
+            if (size == 0) {
+                // If no elements, free the parts array.
+                parts = noPartsNotFrozen;
+                return;
+            }
+            for (int i = parts.length - 1; parts[i] != null; i--) {
+                assert (i >= size);
+                parts[i] = null;
+            }
+        }
+
+        public String setAttr(String key, String value) {
+            String old;
+            if (value == null) {
+                int i = findOrCreateAttr(key, false);
+                if (i >= 0) {
+                    old = (String) parts[i + 1];
+                    removeAttr(i);
+                } else {
+                    old = null;
+                }
+            } else {
+                checkNotFrozen();
+                int i = findOrCreateAttr(key, true);
+                old = (String) parts[i + 1];
+                parts[i + 1] = value;
+            }
+            return old;
+        }
+
+        public String setAttrList(String key, List<String> l) {
+            if (l == null) {
+                return setAttr(key, null);
+            }
+            if (!(l instanceof TokenList)) {
+                l = new TokenList(l);
+            }
+            return setAttr(key, l.toString());
+        }
+
+        public String setAttrNumber(String key, Number n) {
+            return setAttr(key, (n == null) ? null : n.toString());
+        }
+
+        public String setAttrLong(String key, long n) {
+            return setAttr(key, (n == 0) ? null : String.valueOf(n));
+        }
+
+        public String setAttrDouble(String key, double n) {
+            return setAttr(key, (n == 0) ? null : String.valueOf(n));
+        }
+
+        public String setAttr(int k, String value) {
+            int i = indexAttr(k);
+            String old = (String) parts[i + 1];
+            if (value == null) {
+                removeAttr(i);
+            } else {
+                checkNotFrozen();
+                parts[i + 1] = value;
+            }
+            return old;
+        }
+
+        int attrLength() {
+            return parts.length - attrBase();
+        }
+
+        /** Are the attributes of the two two elements equal?
+         *  Disregards name, sub-elements, and ordering of attributes.
+         */
+        public boolean equalAttrs(Element that) {
+            int alen = this.attrLength();
+            if (alen != that.attrLength()) {
+                return false;
+            }
+            if (alen == 0) {
+                return true;
+            }
+            return compareAttrs(alen, that, alen, false) == 0;
+        }
+
+        private int compareAttrs(int thisAlen,
+                Element that, int thatAlen,
+                boolean fullCompare) {
+            Object[] thisParts = this.parts;
+            Object[] thatParts = that.parts;
+            int thisBase = thisParts.length - thisAlen;
+            int thatBase = thatParts.length - thatAlen;
+            // search indexes into unmatched parts of this.attrs:
+            int firstI = 0;
+            // search indexes into unmatched parts of that.attrs:
+            int firstJ = 0;
+            int lastJ = thatAlen - 2;
+            // try to find the mismatch with the first key:
+            String firstKey = null;
+            int firstKeyValCmp = 0;
+            int foundKeys = 0;
+            for (int i = 0; i < thisAlen; i += 2) {
+                String key = (String) thisParts[thisBase + i + 0];
+                String val = (String) thisParts[thisBase + i + 1];
+                String otherVal = null;
+                for (int j = firstJ; j <= lastJ; j += 2) {
+                    if (key.equals(thatParts[thatBase + j + 0])) {
+                        foundKeys += 1;
+                        otherVal = (String) thatParts[thatBase + j + 1];
+                        // Optimization:  Narrow subsequent searches when easy.
+                        if (j == lastJ) {
+                            lastJ -= 2;
+                        } else if (j == firstJ) {
+                            firstJ += 2;
+                        }
+                        if (i == firstI) {
+                            firstI += 2;
+                        }
+                        break;
+                    }
+                }
+                int valCmp;
+                if (otherVal != null) {
+                    // The key was found.
+                    if (!fullCompare) {
+                        if (!val.equals(otherVal)) {
+                            return 1 - 0; //arb.
+                        }
+                        continue;
+                    }
+                    valCmp = val.compareTo(otherVal);
+                } else {
+                    // Found the key in this but not that.
+                    // Such a mismatch puts the guy missing the key last.
+                    valCmp = 0 - 1;
+                }
+                if (valCmp != 0) {
+                    // found a mismatch, key present in both elems
+                    if (firstKey == null
+                            || firstKey.compareTo(key) > 0) {
+                        // found a better key
+                        firstKey = key;
+                        firstKeyValCmp = valCmp;
+                    }
+                }
+            }
+            // We have located the first mismatch of all keys in this.attrs.
+            // In general we must also look for keys in that.attrs but missing
+            // from this.attrs; such missing keys, if earlier than firstKey,
+            // rule the comparison.
+
+            // We can sometimes prove quickly there is no missing key.
+            if (foundKeys == thatAlen / 2) {
+                // Exhausted all keys in that.attrs.
+                return firstKeyValCmp;
+            }
+
+            // Search for a missing key in that.attrs earlier than firstKey.
+            findMissingKey:
+            for (int j = firstJ; j <= lastJ; j += 2) {
+                String otherKey = (String) thatParts[thatBase + j + 0];
+                if (firstKey == null
+                        || firstKey.compareTo(otherKey) > 0) {
+                    // Found a better key; is it missing?
+                    for (int i = firstI; i < thisAlen; i += 2) {
+                        if (otherKey.equals(thisParts[thisBase + i + 0])) {
+                            continue findMissingKey;
+                        }
+                    }
+                    // If we get here, there was no match in this.attrs.
+                    return 1 - 0;
+                }
+            }
+
+            // No missing key.  Previous comparison value rules.
+            return firstKeyValCmp;
+        }
+
+        // Binary search looking for first non-null after size.
+        int attrBase() {
+            // Smallest & largest possible attribute indexes:
+            int kmin = 0;
+            int kmax = (parts.length - size) >>> 1;
+            // earlist possible attribute position:
+            int abase = parts.length - (kmax * 2);
+            // binary search using scaled indexes:
+            while (kmin != kmax) {
+                int kmid = kmin + ((kmax - kmin) >>> 1);
+                if (parts[abase + (kmid * 2)] == null) {
+                    kmin = kmid + 1;
+                } else {
+                    kmax = kmid;
+                }
+                assert (kmin <= kmax);
+            }
+            return abase + (kmax * 2);
+        }
+
+        /** Sort attributes by name. */
+        public void sortAttrs() {
+            checkNotFrozen();
+            int abase = attrBase();
+            int alen = parts.length - abase;
+            String[] buf = new String[alen];
+            // collect keys
+            for (int k = 0; k < alen / 2; k++) {
+                String akey = (String) parts[abase + (k * 2) + 0];
+                buf[k] = akey;
+            }
+            Arrays.sort(buf, 0, alen / 2);
+            // collect values
+            for (int k = 0; k < alen / 2; k++) {
+                String akey = buf[k];
+                buf[k + alen / 2] = getAttr(akey);
+            }
+            // reorder keys and values
+            int fillp = parts.length;
+            for (int k = 0; k < alen / 2; k++) {
+                String akey = buf[k];
+                String aval = buf[k + alen / 2];
+                fillp -= 2;
+                parts[fillp + 0] = akey;
+                parts[fillp + 1] = aval;
+            }
+            assert (fillp == abase);
+        }
+
+        /*
+        Notes on whitespace and tokenization.
+        On input, never split CDATA blocks.  They remain single tokens.
+        ?Try to treat encoded characters as CDATA-quoted, also?
+
+        Internally, each String sub-element is logically a token.
+        However, if there was no token-splitting on input,
+        consecutive strings are merged by the parser.
+
+        Internally, we need addToken (intervening blank) and addText
+        (hard concatenation).
+
+        Optionally on input, tokenize unquoted text into words.
+        Between each adjacent word pair, elide either one space
+        or all space.
+
+        On output, we always add spaces between tokens.
+        The Element("a", {"b", "c", Element("d"), "e    f"})
+        outputs as "<a>b c<d/>e    f</a>"
+         */
+        /** Split strings into tokens, using a StringTokenizer. */
+        public void tokenize(String delims, boolean returnDelims) {
+            checkNotFrozen();
+            if (delims == null) {
+                delims = WHITESPACE_CHARS;  // StringTokenizer default
+            }
+            for (int i = 0; i < size; i++) {
+                if (!(parts[i] instanceof CharSequence)) {
+                    continue;
+                }
+                int osize = size;
+                String str = parts[i].toString();
+                StringTokenizer st = new StringTokenizer(str, delims, returnDelims);
+                int nstrs = st.countTokens();
+                switch (nstrs) {
+                    case 0:
+                        close(i--, 1);
+                        break;
+                    case 1:
+                        parts[i] = st.nextToken();
+                        break;
+                    default:
+                        openOrExpand(i + 1, nstrs - 1);
+                        for (int j = 0; j < nstrs; j++) {
+                            parts[i + j] = st.nextToken();
+                        }
+                        i += nstrs - 1;
+                        break;
+                }
+            }
+        }
+
+        public void tokenize(String delims) {
+            tokenize(delims, false);
+        }
+
+        public void tokenize() {
+            tokenize(null, false);
+        }
+
+        // views
+        class LView extends AbstractList<Object> {
+
+            Element asElement() {
+                return Element.this;
+            }
+
+            public int size() {
+                return Element.this.size();
+            }
+
+            public Object get(int i) {
+                return Element.this.get(i);
+            }
+
+            @Override
+            public boolean contains(Object e) {
+                return Element.this.contains(e);
+            }
+
+            @Override
+            public Object[] toArray() {
+                return Element.this.toArray();
+            }
+
+            @Override
+            public int indexOf(Object e) {
+                return Element.this.indexOf(e);
+            }
+
+            @Override
+            public int lastIndexOf(Object e) {
+                return Element.this.lastIndexOf(e);
+            }
+
+            @Override
+            public void add(int i, Object e) {
+                ++modCount;
+                Element.this.add(i, e);
+            }
+
+            @Override
+            public boolean addAll(int i, Collection<? extends Object> c) {
+                ++modCount;
+                return Element.this.addAll(i, c) > 0;
+            }
+
+            @Override
+            public boolean addAll(Collection<? extends Object> c) {
+                ++modCount;
+                return Element.this.addAll(c) > 0;
+            }
+
+            @Override
+            public Object remove(int i) {
+                ++modCount;
+                return Element.this.remove(i);
+            }
+
+            @Override
+            public Object set(int i, Object e) {
+                ++modCount;
+                return Element.this.set(i, e);
+            }
+
+            @Override
+            public void clear() {
+                ++modCount;
+                Element.this.clear();
+            }
+            // Others: toArray(Object[]), containsAll, removeAll, retainAll
+        }
+
+        /** Produce a list view of sub-elements.
+         *  (The list view does not provide access to the element's
+         *  name or attributes.)
+         *  Changes to this view are immediately reflected in the
+         *  element itself.
+         */
+        public List<Object> asList() {
+            return new LView();
+        }
+
+        /** Produce a list iterator on all sub-elements. */
+        public ListIterator<Object> iterator() {
+            //return asList().listIterator();
+            return new Itr();
+        }
+
+        // Hand-inlined replacement for LView.listIterator():
+        class Itr implements ListIterator<Object> {
+
+            int lastRet = -1;
+            int cursor = 0;
+
+            public boolean hasNext() {
+                return cursor < size;
+            }
+
+            public boolean hasPrevious() {
+                return cursor > 0 && cursor <= size;
+            }
+
+            public Object next() {
+                if (!hasNext()) {
+                    nsee();
+                }
+                return parts[lastRet = cursor++];
+            }
+
+            public Object previous() {
+                if (!hasPrevious()) {
+                    nsee();
+                }
+                return parts[--cursor];
+            }
+
+            public int nextIndex() {
+                return cursor;
+            }
+
+            public int previousIndex() {
+                return cursor - 1;
+            }
+
+            public void set(Object x) {
+                parts[lastRet] = x;
+            }
+
+            public void add(Object x) {
+                lastRet = -1;
+                Element.this.add(cursor++, x);
+            }
+
+            public void remove() {
+                if (lastRet < 0) {
+                    throw new IllegalStateException();
+                }
+                Element.this.remove(lastRet);
+                if (lastRet < cursor) {
+                    --cursor;
+                }
+                lastRet = -1;
+            }
+
+            void nsee() {
+                throw new NoSuchElementException("element " + cursor);
+            }
+        }
+
+        /** A PrintWriter which always appends as if by addText.
+         *  Use of this stream may insert a StringBuffer at the end
+         *  of the Element.  The user must not directly modify this
+         *  StringBuffer, or use it in other data structures.
+         *  From time to time, the StringBuffer may be replaced by a
+         *  constant string as a result of using the PrintWriter.
+         */
+        public PrintWriter asWriter() {
+            return new ElemW();
+        }
+
+        class ElemW extends PrintWriter {
+
+            ElemW() {
+                super(new StringWriter());
+            }
+            final StringBuffer buf = ((StringWriter) out).getBuffer();
+
+            {
+                lock = buf;
+            }  // synchronize on this buffer
+
+            @Override
+            public void println() {
+                synchronized (buf) {
+                    ensureCursor();
+                    super.println();
+                }
+            }
+
+            @Override
+            public void write(int ch) {
+                synchronized (buf) {
+                    ensureCursor();
+                    //buf.append(ch);
+                    super.write(ch);
+                }
+            }
+
+            @Override
+            public void write(char buf[], int off, int len) {
+                synchronized (buf) {
+                    ensureCursor();
+                    super.write(buf, off, len);
+                }
+            }
+
+            @Override
+            public void write(String s, int off, int len) {
+                synchronized (buf) {
+                    ensureCursor();
+                    //buf.append(s.substring(off, off+len));
+                    super.write(s, off, len);
+                }
+            }
+
+            @Override
+            public void write(String s) {
+                synchronized (buf) {
+                    ensureCursor();
+                    //buf.append(s);
+                    super.write(s);
+                }
+            }
+
+            private void ensureCursor() {
+                checkNotFrozen();
+                if (getLast() != buf) {
+                    int pos = indexOf(buf);
+                    if (pos >= 0) {
+                        // Freeze the pre-existing use of buf.
+                        setRaw(pos, buf.toString());
+                    }
+                    add(buf);
+                }
+            }
+        }
+
+        /** Produce a map view of attributes, in which the attribute
+         *  name strings are the keys.
+         *  (The map view does not provide access to the element's
+         *  name or sub-elements.)
+         *  Changes to this view are immediately reflected in the
+         *  element itself.
+         */
+        public Map<String, String> asAttrMap() {
+            class Entry implements Map.Entry<String, String> {
+
+                final int k;
+
+                Entry(int k) {
+                    this.k = k;
+                    assert (((String) getKey()).toString() != null);  // check, fail-fast
+                }
+
+                public String getKey() {
+                    return Element.this.getAttrName(k);
+                }
+
+                public String getValue() {
+                    return Element.this.getAttr(k);
+                }
+
+                public String setValue(String v) {
+                    return Element.this.setAttr(k, v.toString());
+                }
+
+                @Override
+                public boolean equals(Object o) {
+                    if (!(o instanceof Map.Entry)) {
+                        return false;
+                    }
+                    Map.Entry that = (Map.Entry) o;
+                    return (this.getKey().equals(that.getKey())
+                            && this.getValue().equals(that.getValue()));
+                }
+
+                @Override
+                public int hashCode() {
+                    return getKey().hashCode() ^ getValue().hashCode();
+                }
+            }
+            class EIter implements Iterator<Map.Entry<String, String>> {
+
+                int k = 0;  // index of pending next() attribute
+
+                public boolean hasNext() {
+                    return Element.this.containsAttr(k);
+                }
+
+                public Map.Entry<String, String> next() {
+                    return new Entry(k++);
+                }
+
+                public void remove() {
+                    Element.this.removeAttr(--k);
+                }
+            }
+            class ESet extends AbstractSet<Map.Entry<String, String>> {
+
+                public int size() {
+                    return Element.this.attrSize();
+                }
+
+                public Iterator<Map.Entry<String, String>> iterator() {
+                    return new EIter();
+                }
+
+                @Override
+                public void clear() {
+                    Element.this.clearAttrs();
+                }
+            }
+            class AView extends AbstractMap<String, String> {
+
+                private transient Set<Map.Entry<String, String>> eSet;
+
+                public Set<Map.Entry<String, String>> entrySet() {
+                    if (eSet == null) {
+                        eSet = new ESet();
+                    }
+                    return eSet;
+                }
+
+                @Override
+                public int size() {
+                    return Element.this.attrSize();
+                }
+
+                public boolean containsKey(String k) {
+                    return Element.this.containsAttr(k);
+                }
+
+                public String get(String k) {
+                    return Element.this.getAttr(k);
+                }
+
+                @Override
+                public String put(String k, String v) {
+                    return Element.this.setAttr(k, v.toString());
+                }
+
+                public String remove(String k) {
+                    return Element.this.setAttr(k, null);
+                }
+            }
+            return new AView();
+        }
+
+        /** Reports number of additional elements this object can accommodate
+         *  without reallocation.
+         */
+        public int getExtraCapacity() {
+            int abase = attrBase();
+            return Math.max(0, abase - size - NEED_SLOP);
+        }
+
+        /** Ensures that at least the given number of additional elements
+         *  can be added to this object without reallocation.
+         */
+        public void ensureExtraCapacity(int cap) {
+            if (cap == 0 || hasNulls(cap + NEED_SLOP)) {
+                return;
+            }
+            setExtraCapacity(cap);
+        }
+
+        /**
+         * Trim excess capacity to zero, or do nothing if frozen.
+         * This minimizes the space occupied by this Element,
+         * at the expense of a reallocation if sub-elements or attributes
+         * are added later.
+         */
+        public void trimToSize() {
+            if (isFrozen()) {
+                return;
+            }
+            setExtraCapacity(0);
+        }
+
+        /** Changes the number of additional elements this object can accommodate
+         *  without reallocation.
+         */
+        public void setExtraCapacity(int cap) {
+            checkNotFrozen();
+            int abase = attrBase();
+            int alen = parts.length - abase;  // slots allocated for attrs
+            int nlen = size + cap + NEED_SLOP + alen;
+            if (nlen != parts.length) {
+                Object[] nparts = new Object[nlen];
+                // copy attributes
+                System.arraycopy(parts, abase, nparts, nlen - alen, alen);
+                // copy sub-elements
+                System.arraycopy(parts, 0, nparts, 0, size);
+                parts = nparts;
+            }
+            assert (cap == getExtraCapacity());
+        }
+
+        // Return true if there are at least len nulls of slop available.
+        boolean hasNulls(int len) {
+            if (len == 0) {
+                return true;
+            }
+            int lastNull = size + len - 1;
+            if (lastNull >= parts.length) {
+                return false;
+            }
+            return (parts[lastNull] == null);
+        }
+
+        // Opens up parts array at pos by len spaces.
+        void open(int pos, int len) {
+            assert (pos < size);
+            assert (hasNulls(len + NEED_SLOP));
+            checkNotFrozen();
+            int nsize = size + len;
+            int tlen = size - pos;
+            System.arraycopy(parts, pos, parts, pos + len, tlen);
+            size = nsize;
+        }
+
+        // Reallocate and open up at parts[pos] to at least len empty places.
+        // Shift anything after pos right by len.  Reallocate if necessary.
+        // If pos < size, caller must fill it in with non-null values.
+        // Returns incremented size; caller is responsible for storing it
+        // down, if desired.
+        int expand(int pos, int len) {
+            assert (pos <= size);
+            // There must be at least len nulls between elems and attrs.
+            assert (!hasNulls(NEED_SLOP + len));  // caller responsibility
+            checkNotFrozen();
+            int nsize = size + len;  // length of all elements
+            int tlen = size - pos;   // length of elements in post-pos tail
+            int abase = attrBase();
+            int alen = parts.length - abase;  // slots allocated for attrs
+            int nlen = nsize + alen + NEED_SLOP;
+            nlen += (nlen >>> 1);  // add new slop!
+            Object[] nparts = new Object[nlen];
+            // copy head of sub-elements
+            System.arraycopy(parts, 0, nparts, 0, pos);
+            // copy tail of sub-elements
+            System.arraycopy(parts, pos, nparts, pos + len, tlen);
+            // copy attributes
+            System.arraycopy(parts, abase, nparts, nlen - alen, alen);
+            // update self
+            parts = nparts;
+            //assert(hasNulls(len));  <- not yet true, since size != nsize
+            return nsize;
+        }
+
+        // Open or expand at the given position, as appropriate.
+        boolean openOrExpand(int pos, int len) {
+            if (pos < 0 || pos > size) {
+                badIndex(pos);
+            }
+            if (hasNulls(len + NEED_SLOP)) {
+                if (pos == size) {
+                    size += len;
+                } else {
+                    open(pos, len);
+                }
+                return false;
+            } else {
+                size = expand(pos, len);
+                return true;
+            }
+        }
+
+        // Close up at parts[pos] len old places.
+        // Shift anything after pos left by len.
+        // Fill unused end of parts with null.
+        void close(int pos, int len) {
+            assert (len > 0);
+            assert ((size - pos) >= len);
+            checkNotFrozen();
+            int tlen = (size - pos) - len;   // length of elements in post-pos tail
+            int nsize = size - len;
+            System.arraycopy(parts, pos + len, parts, pos, tlen);
+            // reinitialize the unoccupied slots to null
+            clearParts(nsize, nsize + len);
+            // update self
+            size = nsize;
+            assert (hasNulls(len));
+        }
+
+        public void writeTo(Writer w) throws IOException {
+            new Printer(w).print(this);
+        }
+
+        public void writePrettyTo(Writer w) throws IOException {
+            prettyPrintTo(w, this);
+        }
+
+        public String prettyString() {
+            StringWriter sw = new StringWriter();
+            try {
+                writePrettyTo(sw);
+            } catch (IOException ee) {
+                throw new Error(ee);  // should not happen
+            }
+            return sw.toString();
+        }
+
+        @Override
+        public String toString() {
+            StringWriter sw = new StringWriter();
+            try {
+                writeTo(sw);
+            } catch (IOException ee) {
+                throw new Error(ee);  // should not happen
+            }
+            return sw.toString();
+        }
+
+        public String dump() {
+            // For debugging only.  Reveals internal layout.
+            StringBuilder buf = new StringBuilder();
+            buf.append("<").append(name).append("[").append(size).append("]");
+            for (int i = 0; i < parts.length; i++) {
+                Object p = parts[i];
+                if (p == null) {
+                    buf.append(" null");
+                } else {
+                    buf.append(" {");
+                    String cname = p.getClass().getName();
+                    cname = cname.substring(1 + cname.indexOf('/'));
+                    cname = cname.substring(1 + cname.indexOf('$'));
+                    cname = cname.substring(1 + cname.indexOf('#'));
+                    if (!cname.equals("String")) {
+                        buf.append(cname).append(":");
+                    }
+                    buf.append(p);
+                    buf.append("}");
+                }
+            }
+            return buf.append(">").toString();
+        }
+
+        public static java.lang.reflect.Method method(String name) {
+            HashMap allM = allMethods;
+            if (allM == null) {
+                allM = makeAllMethods();
+            }
+            java.lang.reflect.Method res = (java.lang.reflect.Method) allMethods.get(name);
+            if (res == null) {
+                throw new IllegalArgumentException(name);
+            }
+            return res;
+        }
+        private static HashMap allMethods;
+
+        private static synchronized HashMap makeAllMethods() {
+            if (allMethods != null) {
+                return allMethods;
+            }
+            java.lang.reflect.Method[] methods = Element.class.getMethods();
+            HashMap<String, java.lang.reflect.Method> allM = new HashMap<String, java.lang.reflect.Method>(),
+                    ambig = new HashMap<String, java.lang.reflect.Method>();
+            for (int i = 0; i < methods.length; i++) {
+                java.lang.reflect.Method m = methods[i];
+                Class[] args = m.getParameterTypes();
+                String name = m.getName();
+                assert (java.lang.reflect.Modifier.isPublic(m.getModifiers()));
+                if (name.startsWith("notify")) {
+                    continue;
+                }
+                if (name.endsWith("Attr")
+                        && args.length > 0 && args[0] == int.class) // ignore getAttr(int), etc.
+                {
+                    continue;
+                }
+                if (name.endsWith("All")
+                        && args.length > 1 && args[0] == Filter.class) // ignore findAll(Filter, int...), etc.
+                {
+                    continue;
+                }
+                java.lang.reflect.Method pm = allM.put(name, m);
+                if (pm != null) {
+                    Class[] pargs = pm.getParameterTypes();
+                    if (pargs.length > args.length) {
+                        allM.put(name, pm);   // put it back
+                    } else if (pargs.length == args.length) {
+                        ambig.put(name, pm);  // make a note of it
+                    }
+                }
+            }
+            // Delete ambiguous methods.
+            for (Map.Entry<String, java.lang.reflect.Method> e : ambig.entrySet()) {
+                String name = e.getKey();
+                java.lang.reflect.Method pm = e.getValue();
+                java.lang.reflect.Method m = allM.get(name);
+                Class[] args = m.getParameterTypes();
+                Class[] pargs = pm.getParameterTypes();
+                if (pargs.length == args.length) {
+                    //System.out.println("ambig: "+pm);
+                    //System.out.println(" with: "+m);
+                    //ambig: int addAll(int,Element)
+                    // with: int addAll(int,Collection)
+                    allM.put(name, null);  // get rid of
+                }
+            }
+            //System.out.println("allM: "+allM);
+            return allMethods = allM;
+        }
+    }
+
+    static Object fixupString(Object part) {
+        if (part instanceof CharSequence && !(part instanceof String)) {
+            return part.toString();
+        } else {
+            return part;
+        }
+    }
+
+    public static final class Special implements Comparable<Special> {
+
+        String kind;
+        Object value;
+
+        public Special(String kind, Object value) {
+            this.kind = kind;
+            this.value = value;
+        }
+
+        public String getKind() {
+            return kind;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Special)) {
+                return false;
+            }
+            Special that = (Special) o;
+            return this.kind.equals(that.kind) && this.value.equals(that.value);
+        }
+
+        @Override
+        public int hashCode() {
+            return kind.hashCode() * 65 + value.hashCode();
+        }
+
+        public int compareTo(Special that) {
+            int r = this.kind.compareTo(that.kind);
+            if (r != 0) {
+                return r;
+            }
+            return ((Comparable) this.value).compareTo(that.value);
+        }
+
+        @Override
+        public String toString() {
+            int split = kind.indexOf(' ');
+            String pref = kind.substring(0, split < 0 ? 0 : split);
+            String post = kind.substring(split + 1);
+            return pref + value + post;
+        }
+    }
+
+    /** Supports sorting of mixed content.  Sorts strings first,
+     *  then Elements, then everything else (as Comparable).
+     */
+    public static Comparator<Object> contentOrder() {
+        return CONTENT_ORDER;
+    }
+    private static Comparator<Object> CONTENT_ORDER = new ContentComparator();
+
+    private static class ContentComparator implements Comparator<Object> {
+
+        public int compare(Object o1, Object o2) {
+            boolean cs1 = (o1 instanceof CharSequence);
+            boolean cs2 = (o2 instanceof CharSequence);
+            if (cs1 && cs2) {
+                String s1 = (String) fixupString(o1);
+                String s2 = (String) fixupString(o2);
+                return s1.compareTo(s2);
+            }
+            if (cs1) {
+                return 0 - 1;
+            }
+            if (cs2) {
+                return 1 - 0;
+            }
+            boolean el1 = (o1 instanceof Element);
+            boolean el2 = (o2 instanceof Element);
+            if (el1 && el2) {
+                return ((Element) o1).compareTo((Element) o2);
+            }
+            if (el1) {
+                return 0 - 1;
+            }
+            if (el2) {
+                return 1 - 0;
+            }
+            return ((Comparable) o1).compareTo(o2);
+        }
+    }
+
+    /** Used to find, filter, or transform sub-elements.
+     *  When used as a predicate, the filter returns a null
+     *  value for false, and the original object value for true.
+     *  When used as a transformer, the filter may return
+     *  null, for no values, the original object, a new object,
+     *  or an anonymous Element (meaning multiple results).
+     */
+    public interface Filter {
+
+        Object filter(Object value);
+    }
+
+    /** Use this to find an element, perhaps with a given name. */
+    public static class ElementFilter implements Filter {
+
+        /** Subclasses may override this to implement better value tests.
+         *  By default, it returns the element itself, thus recognizing
+         *  all elements, regardless of name.
+         */
+        public Element filter(Element elem) {
+            return elem;  // override this
+        }
+
+        public final Object filter(Object value) {
+            if (!(value instanceof Element)) {
+                return null;
+            }
+            return filter((Element) value);
+        }
+
+        @Override
+        public String toString() {
+            return "<ElementFilter name='*'/>";
+        }
+    }
+    private static Filter elementFilter;
+
+    public static Filter elementFilter() {
+        return (elementFilter != null) ? elementFilter : (elementFilter = new ElementFilter());
+    }
+
+    public static Filter elementFilter(final String name) {
+        name.toString();  // null check
+        return new ElementFilter() {
+
+            @Override
+            public Element filter(Element elem) {
+                return name.equals(elem.name) ? elem : null;
+            }
+
+            @Override
+            public String toString() {
+                return "<ElementFilter name='" + name + "'/>";
+            }
+        };
+    }
+
+    public static Filter elementFilter(final Collection nameSet) {
+        nameSet.getClass();  // null check
+        return new ElementFilter() {
+
+            @Override
+            public Element filter(Element elem) {
+                return nameSet.contains(elem.name) ? elem : null;
+            }
+
+            @Override
+            public String toString() {
+                return "<ElementFilter name='" + nameSet + "'/>";
+            }
+        };
+    }
+
+    public static Filter elementFilter(String... nameSet) {
+        Collection<String> ncoll = Arrays.asList(nameSet);
+        if (nameSet.length > 10) {
+            ncoll = new HashSet<String>(ncoll);
+        }
+        return elementFilter(ncoll);
+    }
+
+    /** Use this to find an element with a named attribute,
+     *  possibly with a particular value.
+     *  (Note that an attribute is missing if and only if its value is null.)
+     */
+    public static class AttrFilter extends ElementFilter {
+
+        protected final String attrName;
+
+        public AttrFilter(String attrName) {
+            this.attrName = attrName.toString();
+        }
+
+        /** Subclasses may override this to implement better value tests.
+         *  By default, it returns true for any non-null value, thus
+         *  recognizing any attribute of the given name, regardless of value.
+         */
+        public boolean test(String attrVal) {
+            return attrVal != null;  // override this
+        }
+
+        @Override
+        public final Element filter(Element elem) {
+            return test(elem.getAttr(attrName)) ? elem : null;
+        }
+
+        @Override
+        public String toString() {
+            return "<AttrFilter name='" + attrName + "' value='*'/>";
+        }
+    }
+
+    public static Filter attrFilter(String attrName) {
+        return new AttrFilter(attrName);
+    }
+
+    public static Filter attrFilter(String attrName, final String attrVal) {
+        if (attrVal == null) {
+            return not(attrFilter(attrName));
+        }
+        return new AttrFilter(attrName) {
+
+            @Override
+            public boolean test(String attrVal2) {
+                return attrVal.equals(attrVal2);
+            }
+
+            @Override
+            public String toString() {
+                return "<AttrFilter name='" + attrName + "' value='" + attrVal + "'/>";
+            }
+        };
+    }
+
+    public static Filter attrFilter(Element matchThis, String attrName) {
+        return attrFilter(attrName, matchThis.getAttr(attrName));
+    }
+
+    /** Use this to find a sub-element of a given class. */
+    public static Filter classFilter(final Class clazz) {
+        return new Filter() {
+
+            public Object filter(Object value) {
+                return clazz.isInstance(value) ? value : null;
+            }
+
+            @Override
+            public String toString() {
+                return "<ClassFilter class='" + clazz.getName() + "'/>";
+            }
+        };
+    }
+    private static Filter textFilter;
+
+    public static Filter textFilter() {
+        return (textFilter != null) ? textFilter : (textFilter = classFilter(CharSequence.class));
+    }
+    private static Filter specialFilter;
+
+    public static Filter specialFilter() {
+        return (specialFilter != null) ? specialFilter : (specialFilter = classFilter(Special.class));
+    }
+    private static Filter selfFilter;
+
+    /** This filter always returns its own argument. */
+    public static Filter selfFilter() {
+        if (selfFilter != null) {
+            return selfFilter;
+        }
+        return selfFilter = new Filter() {
+
+            public Object filter(Object value) {
+                return value;
+            }
+
+            @Override
+            public String toString() {
+                return "<Self/>";
+            }
+        };
+    }
+
+    /** This filter always returns a fixed value, regardless of argument. */
+    public static Filter constantFilter(final Object value) {
+        return new Filter() {
+
+            public Object filter(Object ignore) {
+                return value;
+            }
+
+            @Override
+            public String toString() {
+                return "<Constant>" + value + "</Constant>";
+            }
+        };
+    }
+    private static Filter nullFilter;
+
+    public static Filter nullFilter() {
+        return (nullFilter != null) ? nullFilter : (nullFilter = constantFilter(null));
+    }
+    private static Filter emptyFilter;
+
+    public static Filter emptyFilter() {
+        return (emptyFilter != null) ? emptyFilter : (emptyFilter = constantFilter(Element.EMPTY));
+    }
+
+    /** Use this to invert the logical sense of the given filter. */
+    public static Filter not(final Filter f) {
+        return new Filter() {
+
+            public Object filter(Object value) {
+                return f.filter(value) == null ? value : null;
+            }
+
+            @Override
+            public String toString() {
+                return "<Not>" + f + "</Not>";
+            }
+        };
+    }
+
+    /** Use this to combine several filters with logical AND.
+     *  Returns either the first null or the last non-null value.
+     */
+    public static Filter and(final Filter f0, final Filter f1) {
+        return and(new Filter[]{f0, f1});
+    }
+
+    public static Filter and(final Filter... fs) {
+        switch (fs.length) {
+            case 0:
+                return selfFilter();  // always true (on non-null inputs)
+            case 1:
+                return fs[0];
+        }
+        return new Filter() {
+
+            public Object filter(Object value) {
+                Object res = fs[0].filter(value);
+                if (res != null) {
+                    res = fs[1].filter(value);
+                    for (int i = 2; res != null && i < fs.length; i++) {
+                        res = fs[i].filter(value);
+                    }
+                }
+                return res;
+            }
+
+            @Override
+            public String toString() {
+                return opToString("<And>", fs, "</And>");
+            }
+        };
+    }
+
+    /** Use this to combine several filters with logical OR.
+     *  Returns either the first non-null or the last null value.
+     */
+    public static Filter or(final Filter f0, final Filter f1) {
+        return or(new Filter[]{f0, f1});
+    }
+
+    public static Filter or(final Filter... fs) {
+        switch (fs.length) {
+            case 0:
+                return nullFilter();
+            case 1:
+                return fs[0];
+        }
+        return new Filter() {
+
+            public Object filter(Object value) {
+                Object res = fs[0].filter(value);
+                if (res == null) {
+                    res = fs[1].filter(value);
+                    for (int i = 2; res == null && i < fs.length; i++) {
+                        res = fs[i].filter(value);
+                    }
+                }
+                return res;
+            }
+
+            @Override
+            public String toString() {
+                return opToString("<Or>", fs, "</Or>");
+            }
+        };
+    }
+
+    /** Use this to combine several filters with logical AND,
+     *  and where each non-null result is passed as the argument
+     *  to the next filter.
+     *  Returns either the first null or the last non-null value.
+     */
+    public static Filter stack(final Filter f0, final Filter f1) {
+        return stack(new Filter[]{f0, f1});
+    }
+
+    public static Filter stack(final Filter... fs) {
+        switch (fs.length) {
+            case 0:
+                return nullFilter();
+            case 1:
+                return fs[0];
+        }
+        return new Filter() {
+
+            public Object filter(Object value) {
+                Object res = fs[0].filter(value);
+                if (res != null) {
+                    res = fs[1].filter(res);
+                    for (int i = 2; res != null && i < fs.length; i++) {
+                        res = fs[i].filter(res);
+                    }
+                }
+                return res;
+            }
+
+            @Override
+            public String toString() {
+                return opToString("<Stack>", fs, "</Stack>");
+            }
+        };
+    }
+
+    /** Copy everything produced by f to sink, using addContent. */
+    public static Filter content(final Filter f, final Collection<Object> sink) {
+        return new Filter() {
+
+            public Object filter(Object value) {
+                Object res = f.filter(value);
+                addContent(res, sink);
+                return res;
+            }
+
+            @Override
+            public String toString() {
+                return opToString("<addContent>", new Object[]{f, sink},
+                        "</addContent>");
+            }
+        };
+    }
+
+    /** Look down the tree using f, collecting fx, else recursing into x.
+     *  Identities:
+     *  <code>
+     *     findInTree(f, s) == findInTree(content(f, s))
+     *     findInTree(f)    == replaceInTree(and(f, selfFilter())).
+     *  </code>
+     */
+    public static Filter findInTree(Filter f, Collection<Object> sink) {
+        if (sink != null) {
+            f = content(f, sink);
+        }
+        return findInTree(f);
+    }
+
+    /** Look down the tree using f, recursing into x unless fx. */
+    public static Filter findInTree(final Filter f) {
+        return new Filter() {
+
+            public Object filter(Object value) {
+                Object res = f.filter(value);
+                if (res != null) {
+                    return res;
+                }
+                if (value instanceof Element) {
+                    // recurse
+                    return ((Element) value).find(this);
+                }
+                return null;
+            }
+
+            @Override
+            public String toString() {
+                return opToString("<FindInTree>", new Object[]{f},
+                        "</FindInTree>");
+            }
+        };
+    }
+
+    /** Look down the tree using f.  Replace each x with fx, else recurse.
+     *  If post filter g is given, optionally replace with gx after recursion.
+     */
+    public static Filter replaceInTree(final Filter f, final Filter g) {
+        return new Filter() {
+
+            public Object filter(Object value) {
+                Object res = (f == null) ? null : f.filter(value);
+                if (res != null) {
+                    return res;
+                }
+                if (value instanceof Element) {
+                    // recurse
+                    ((Element) value).replaceAll(this);
+                    // Optional postorder traversal:
+                    if (g != null) {
+                        res = g.filter(value);
+                    }
+                }
+                return res;  // usually null, meaning no replacement
+            }
+
+            @Override
+            public String toString() {
+                return opToString("<ReplaceInTree>",
+                        new Object[]{f, g},
+                        "</ReplaceInTree>");
+            }
+        };
+    }
+
+    public static Filter replaceInTree(Filter f) {
+        f.getClass(); // null check
+        return replaceInTree(f, null);
+    }
+
+    /** Make a filter which calls this method on the given element.
+     *  If the method is static, the first argument is passed the
+     *  the subtree value being filtered.
+     *  If the method is non-static, the receiver is the subtree value itself.
+     *  <p>
+     *  Optionally, additional arguments may be specified.
+     *  <p>
+     *  If the filtered value does not match the receiver class
+     *  (or else the first argument type, if the method is static),
+     *  the filter returns null without invoking the method.
+     *  <p>
+     *  The returned filter value is the result returned from the method.
+     *  Optionally, a non-null special false result value may be specified.
+     *  If the result returned from the method is equal to that false value,
+     *  the filter will return null.
+     */
+    public static Filter methodFilter(java.lang.reflect.Method m, Object[] extraArgs,
+            Object falseResult) {
+        return methodFilter(m, false, extraArgs, falseResult);
+    }
+
+    public static Filter methodFilter(java.lang.reflect.Method m,
+            Object[] args) {
+        return methodFilter(m, args, null);
+    }
+
+    public static Filter methodFilter(java.lang.reflect.Method m) {
+        return methodFilter(m, null, null);
+    }
+
+    public static Filter testMethodFilter(java.lang.reflect.Method m, Object[] extraArgs,
+            Object falseResult) {
+        return methodFilter(m, true, extraArgs, falseResult);
+    }
+
+    public static Filter testMethodFilter(java.lang.reflect.Method m, Object[] extraArgs) {
+        return methodFilter(m, true, extraArgs, zeroArgs.get(m.getReturnType()));
+    }
+
+    public static Filter testMethodFilter(java.lang.reflect.Method m) {
+        return methodFilter(m, true, null, zeroArgs.get(m.getReturnType()));
+    }
+
+    private static Filter methodFilter(final java.lang.reflect.Method m,
+            final boolean isTest,
+            Object[] extraArgs, final Object falseResult) {
+        Class[] params = m.getParameterTypes();
+        final boolean isStatic = java.lang.reflect.Modifier.isStatic(m.getModifiers());
+        int insertLen = (isStatic ? 1 : 0);
+        if (insertLen + (extraArgs == null ? 0 : extraArgs.length) > params.length) {
+            throw new IllegalArgumentException("too many arguments");
+        }
+        final Object[] args = (params.length == insertLen) ? null
+                : new Object[params.length];
+        final Class valueType = !isStatic ? m.getDeclaringClass() : params[0];
+        if (valueType.isPrimitive()) {
+            throw new IllegalArgumentException("filtered value must be reference type");
+        }
+        int fillp = insertLen;
+        if (extraArgs != null) {
+            for (int i = 0; i < extraArgs.length; i++) {
+                args[fillp++] = extraArgs[i];
+            }
+        }
+        if (args != null) {
+            while (fillp < args.length) {
+                Class param = params[fillp];
+                args[fillp++] = param.isPrimitive() ? zeroArgs.get(param) : null;
+            }
+        }
+        final Thread curt = Thread.currentThread();
+        class MFilt implements Filter {
+
+            public Object filter(Object value) {
+                if (!valueType.isInstance(value)) {
+                    return null;  // filter fails quickly
+                }
+                Object[] args1 = args;
+                if (isStatic) {
+                    if (args1 == null) {
+                        args1 = new Object[1];
+                    } else if (curt != Thread.currentThread()) // Dirty hack to curtail array copying in common case.
+                    {
+                        args1 = (Object[]) args1.clone();
+                    }
+                    args1[0] = value;
+                }
+                Object res;
+                try {
+                    res = m.invoke(value, args1);
+                } catch (java.lang.reflect.InvocationTargetException te) {
+                    Throwable ee = te.getCause();
+                    if (ee instanceof RuntimeException) {
+                        throw (RuntimeException) ee;
+                    }
+                    if (ee instanceof Error) {
+                        throw (Error) ee;
+                    }
+                    throw new RuntimeException("throw in filter", ee);
+                } catch (IllegalAccessException ee) {
+                    throw new RuntimeException("access error in filter", ee);
+                }
+                if (res == null) {
+                    if (!isTest && m.getReturnType() == Void.TYPE) {
+                        // Void methods return self by convention.
+                        // (But void "tests" always return false.)
+                        res = value;
+                    }
+                } else {
+                    if (falseResult != null && falseResult.equals(res)) {
+                        res = null;
+                    } else if (isTest) {
+                        // Tests return self by convention.
+                        res = value;
+                    }
+                }
+                return res;
+            }
+
+            @Override
+            public String toString() {
+                return "<Method>" + m + "</Method>";
+            }
+        }
+        return new MFilt();
+    }
+    private static HashMap<Class, Object> zeroArgs = new HashMap<Class, Object>();
+
+    static {
+        zeroArgs.put(Boolean.TYPE, Boolean.FALSE);
+        zeroArgs.put(Character.TYPE, new Character((char) 0));
+        zeroArgs.put(Byte.TYPE, new Byte((byte) 0));
+        zeroArgs.put(Short.TYPE, new Short((short) 0));
+        zeroArgs.put(Integer.TYPE, new Integer(0));
+        zeroArgs.put(Float.TYPE, new Float(0));
+        zeroArgs.put(Long.TYPE, new Long(0));
+        zeroArgs.put(Double.TYPE, new Double(0));
+    }
+
+    private static String opToString(String s1, Object[] s2, String s3) {
+        StringBuilder buf = new StringBuilder(s1);
+        for (int i = 0; i < s2.length; i++) {
+            if (s2[i] != null) {
+                buf.append(s2[i]);
+            }
+        }
+        buf.append(s3);
+        return buf.toString();
+    }
+
+    /** Call the filter on each list element x, and replace x with the
+     *  resulting filter value e, or its parts.
+     *  If e is null, keep x.  (This eases use of partial-domain filters.)
+     *  If e is a TokenList or an anonymous Element, add e's parts
+     *  to the list instead of x.
+     *  Otherwise, replace x by e.
+     *  <p>
+     *  The effect at each list position <code>n</code> may be expressed
+     *  in terms of XMLKit.addContent as follows:
+     *  <pre>
+     *     Object e = f.filter(target.get(n));
+     *     if (e != null) {
+     *         target.remove(n);
+     *         addContent(e, target.subList(n,n));
+     *     }
+     *  </pre>
+     *  <p>
+     *  Note:  To force deletion of x, simply have the filter return
+     *  Element.EMPTY or TokenList.EMPTY.
+     *  To force null filter values to have this effect,
+     *  use the expression: <code>or(f, emptyFilter())</code>.
+     */
+    public static void replaceAll(Filter f, List<Object> target) {
+        for (ListIterator<Object> i = target.listIterator(); i.hasNext();) {
+            Object x = i.next();
+            Object fx = f.filter(x);
+            if (fx == null) {
+                // Unliked addContent, a null is a no-op here.
+                // i.remove();
+            } else if (fx instanceof TokenList) {
+                TokenList tl = (TokenList) fx;
+                if (tl.size() == 1) {
+                    i.set(tl);
+                } else {
+                    i.remove();
+                    for (String part : tl) {
+                        i.add(part);
+                    }
+                }
+            } else if (fx instanceof Element
+                    && ((Element) fx).isAnonymous()) {
+                Element anon = (Element) fx;
+                if (anon.size() == 1) {
+                    i.set(anon);
+                } else {
+                    i.remove();
+                    for (Object part : anon) {
+                        i.add(part);
+                    }
+                }
+            } else if (x != fx) {
+                i.set(fx);
+            }
+        }
+    }
+
+    /** If e is null, return zero.
+     *  If e is a TokenList or an anonymous Element, add e's parts
+     *  to the collection, and return the number of parts.
+     *  Otherwise, add e to the collection, and return one.
+     *  If the collection reference is null, the result is as if
+     *  a throwaway collection were used.
+     */
+    public static int addContent(Object e, Collection<Object> sink) {
+        if (e == null) {
+            return 0;
+        } else if (e instanceof TokenList) {
+            TokenList tl = (TokenList) e;
+            if (sink != null) {
+                sink.addAll(tl);
+            }
+            return tl.size();
+        } else if (e instanceof Element
+                && ((Element) e).isAnonymous()) {
+            Element anon = (Element) e;
+            if (sink != null) {
+                sink.addAll(anon.asList());
+            }
+            return anon.size();
+        } else {
+            if (sink != null) {
+                sink.add(e);
+            }
+            return 1;
+        }
+    }
+
+    static Collection<Object> newCounterColl() {
+        return new AbstractCollection<Object>() {
+
+            int size;
+
+            public int size() {
+                return size;
+            }
+
+            @Override
+            public boolean add(Object o) {
+                ++size;
+                return true;
+            }
+
+            public Iterator<Object> iterator() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /** SAX2 document handler for building Element trees. */
+    private static class Builder implements ContentHandler, LexicalHandler {
+        /*, EntityResolver, DTDHandler, ErrorHandler*/
+
+        Collection<Object> sink;
+        boolean makeFrozen;
+        boolean tokenizing;
+
+        Builder(Collection<Object> sink, boolean tokenizing, boolean makeFrozen) {
+            this.sink = sink;
+            this.tokenizing = tokenizing;
+            this.makeFrozen = makeFrozen;
+        }
+        Object[] parts = new Object[30];
+        int nparts = 0;
+        int[] attrBases = new int[10];  // index into parts
+        int[] elemBases = new int[10];  // index into parts
+        int depth = -1;  // index into attrBases, elemBases
+        // Parts is organized this way:
+        // | name0 | akey aval ... | subelem ... | name1 | ... |
+        // The position of the first "akey" after name0 is attrBases[0].
+        // The position of the first "subelem" after name0 is elemBases[0].
+        // The position after the last part is always nparts.
+        int mergeableToken = -1;  // index into parts of recent CharSequence
+        boolean inCData = false;
+
+        void addPart(Object x) {
+            //System.out.println("addPart "+x);
+            if (nparts == parts.length) {
+                Object[] newParts = new Object[parts.length * 2];
+                System.arraycopy(parts, 0, newParts, 0, parts.length);
+                parts = newParts;
+            }
+            parts[nparts++] = x;
+        }
+
+        Object getMergeableToken() {
+            if (mergeableToken == nparts - 1) {
+                assert (parts[mergeableToken] instanceof CharSequence);
+                return parts[nparts - 1];
+            } else {
+                return null;
+            }
+        }
+
+        void clearMergeableToken() {
+            if (mergeableToken >= 0) {
+                // Freeze temporary StringBuffers into strings.
+                assert (parts[mergeableToken] instanceof CharSequence);
+                parts[mergeableToken] = parts[mergeableToken].toString();
+                mergeableToken = -1;
+            }
+        }
+
+        void setMergeableToken() {
+            if (mergeableToken != nparts - 1) {
+                clearMergeableToken();
+                mergeableToken = nparts - 1;
+                assert (parts[mergeableToken] instanceof CharSequence);
+            }
+        }
+
+        // ContentHandler callbacks
+        public void startElement(String ns, String localName, String name, Attributes atts) {
+            clearMergeableToken();
+            addPart(name.intern());
+            ++depth;
+            if (depth == attrBases.length) {
+                int oldlen = depth;
+                int newlen = depth * 2;
+                int[] newAB = new int[newlen];
+                int[] newEB = new int[newlen];
+                System.arraycopy(attrBases, 0, newAB, 0, oldlen);
+                System.arraycopy(elemBases, 0, newEB, 0, oldlen);
+                attrBases = newAB;
+                elemBases = newEB;
+            }
+            attrBases[depth] = nparts;
+            // Collect attributes.
+            int na = atts.getLength();
+            for (int k = 0; k < na; k++) {
+                addPart(atts.getQName(k).intern());
+                addPart(atts.getValue(k));
+            }
+            // Get ready to collect elements.
+            elemBases[depth] = nparts;
+        }
+
+        public void endElement(String ns, String localName, String name) {
+            assert (depth >= 0);
+            clearMergeableToken();
+            int ebase = elemBases[depth];
+            int elen = nparts - ebase;
+            int abase = attrBases[depth];
+            int alen = ebase - abase;
+            int nbase = abase - 1;
+            int cap = alen + (makeFrozen ? 0 : NEED_SLOP) + elen;
+            Element e = new Element((String) parts[nbase], elen, cap);
+            // Set up attributes.
+            for (int k = 0; k < alen; k += 2) {
+                e.parts[cap - k - 2] = parts[abase + k + 0];
+                e.parts[cap - k - 1] = parts[abase + k + 1];
+            }
+            // Set up sub-elements.
+            System.arraycopy(parts, ebase, e.parts, 0, elen);
+            // Back out of this level.
+            --depth;
+            nparts = nbase;
+            assert (e.isFrozen() == makeFrozen);
+            assert (e.size() == elen);
+            assert (e.attrSize() * 2 == alen);
+            if (depth >= 0) {
+                addPart(e);
+            } else {
+                sink.add(e);
+            }
+        }
+
+        public void startCDATA() {
+            inCData = true;
+        }
+
+        public void endCDATA() {
+            inCData = false;
+        }
+
+        public void characters(char[] buf, int off, int len) {
+            boolean headSpace = false;
+            boolean tailSpace = false;
+            int firstLen;
+            if (tokenizing && !inCData) {
+                // Strip unquoted blanks.
+                while (len > 0 && isWhitespace(buf[off])) {
+                    headSpace = true;
+                    ++off;
+                    --len;
+                }
+                if (len == 0) {
+                    tailSpace = true;  // it is all space
+                }
+                while (len > 0 && isWhitespace(buf[off + len - 1])) {
+                    tailSpace = true;
+                    --len;
+                }
+                firstLen = 0;
+                while (firstLen < len && !isWhitespace(buf[off + firstLen])) {
+                    ++firstLen;
+                }
+            } else {
+                firstLen = len;
+            }
+            if (headSpace) {
+                clearMergeableToken();
+            }
+            boolean mergeAtEnd = !tailSpace;
+            // If buffer was empty, or had only ignorable blanks, do nothing.
+            if (len == 0) {
+                return;
+            }
+            // Decide whether to merge some of these chars into a previous token.
+            Object prev = getMergeableToken();
+            if (prev instanceof StringBuffer) {
+                ((StringBuffer) prev).append(buf, off, firstLen);
+            } else if (prev == null) {
+                addPart(new String(buf, off, firstLen));
+            } else {
+                // Merge two strings.
+                String prevStr = prev.toString();
+                StringBuffer prevBuf = new StringBuffer(prevStr.length() + firstLen);
+                prevBuf.append(prevStr);
+                prevBuf.append(buf, off, firstLen);
+                if (mergeAtEnd && len == firstLen) {
+                    // Replace previous string with new StringBuffer.
+                    parts[nparts - 1] = prevBuf;
+                } else {
+                    // Freeze it now.
+                    parts[nparts - 1] = prevBuf.toString();
+                }
+            }
+            off += firstLen;
+            len -= firstLen;
+            if (len > 0) {
+                // Appended only the first token.
+                clearMergeableToken();
+                // Add the rest as separate parts.
+                while (len > 0) {
+                    while (len > 0 && isWhitespace(buf[off])) {
+                        ++off;
+                        --len;
+                    }
+                    int nextLen = 0;
+                    while (nextLen < len && !isWhitespace(buf[off + nextLen])) {
+                        ++nextLen;
+                    }
+                    assert (nextLen > 0);
+                    addPart(new String(buf, off, nextLen));
+                    off += nextLen;
+                    len -= nextLen;
+                }
+            }
+            if (mergeAtEnd) {
+                setMergeableToken();
+            }
+        }
+
+        public void ignorableWhitespace(char[] buf, int off, int len) {
+            clearMergeableToken();
+            if (false) {
+                characters(buf, off, len);
+                clearMergeableToken();
+            }
+        }
+
+        public void comment(char[] buf, int off, int len) {
+            addPart(new Special("<!-- -->", new String(buf, off, len)));
+        }
+
+        public void processingInstruction(String name, String instruction) {
+            Element pi = new Element(name);
+            pi.add(instruction);
+            addPart(new Special("<? ?>", pi));
+        }
+
+        public void skippedEntity(String name) {
+        }
+
+        public void startDTD(String name, String publicId, String systemId) {
+        }
+
+        public void endDTD() {
+        }
+
+        public void startEntity(String name) {
+        }
+
+        public void endEntity(String name) {
+        }
+
+        public void setDocumentLocator(org.xml.sax.Locator locator) {
+        }
+
+        public void startDocument() {
+        }
+
+        public void endDocument() {
+        }
+
+        public void startPrefixMapping(String prefix, String uri) {
+        }
+
+        public void endPrefixMapping(String prefix) {
+        }
+    }
+
+    /** Produce a ContentHandler for use with an XML parser.
+     *  The object is <em>also</em> a LexicalHandler.
+     *  Every top-level Element produced will get added to sink.
+     *  All elements will be frozen iff makeFrozen is true.
+     */
+    public static ContentHandler makeBuilder(Collection<Object> sink, boolean tokenizing, boolean makeFrozen) {
+        return new Builder(sink, tokenizing, makeFrozen);
+    }
+
+    public static ContentHandler makeBuilder(Collection<Object> sink, boolean tokenizing) {
+        return new Builder(sink, tokenizing, false);
+    }
+
+    public static ContentHandler makeBuilder(Collection<Object> sink) {
+        return makeBuilder(sink, false, false);
+    }
+
+    public static Element readFrom(Reader in, boolean tokenizing, boolean makeFrozen) throws IOException {
+        Element sink = new Element();
+        ContentHandler b = makeBuilder(sink.asList(), tokenizing, makeFrozen);
+        XMLReader parser;
+        try {
+            parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
+        } catch (SAXException ee) {
+            throw new Error(ee);
+        }
+        //parser.setFastStandalone(true);
+        parser.setContentHandler(b);
+        try {
+            parser.setProperty("http://xml.org/sax/properties/lexical-handler",
+                    (LexicalHandler) b);
+        } catch (SAXException ee) {
+            // Ignore.  We will miss the comments and whitespace.
+        }
+        try {
+            parser.parse(new InputSource(in));
+        } catch (SAXParseException ee) {
+            throw new RuntimeException("line " + ee.getLineNumber() + " col " + ee.getColumnNumber() + ": ", ee);
+        } catch (SAXException ee) {
+            throw new RuntimeException(ee);
+        }
+        switch (sink.size()) {
+            case 0:
+                return null;
+            case 1:
+                if (sink.get(0) instanceof Element) {
+                    return (Element) sink.get(0);
+                }
+            // fall through
+            default:
+                if (makeFrozen) {
+                    sink.shallowFreeze();
+                }
+                return sink;
+        }
+    }
+
+    public static Element readFrom(Reader in, boolean tokenizing) throws IOException {
+        return readFrom(in, tokenizing, false);
+    }
+
+    public static Element readFrom(Reader in) throws IOException {
+        return readFrom(in, false, false);
+    }
+
+    public static void prettyPrintTo(OutputStream out, Element e) throws IOException {
+        prettyPrintTo(new OutputStreamWriter(out), e);
+    }
+
+    public static void prettyPrintTo(Writer out, Element e) throws IOException {
+        Printer pr = new Printer(out);
+        pr.pretty = true;
+        pr.print(e);
+    }
+
+    static class Outputter {
+
+        ContentHandler ch;
+        LexicalHandler lh;
+
+        Outputter(ContentHandler ch, LexicalHandler lh) {
+            this.ch = ch;
+            this.lh = lh;
+        }
+        AttributesImpl atts = new AttributesImpl();  // handy
+
+        void output(Object x) throws SAXException {
+            // Cf. jdom.org/jdom-b8/src/java/org/jdom/output/SAXOutputter.java
+            if (x instanceof Element) {
+                Element e = (Element) x;
+                atts.clear();
+                for (int asize = e.attrSize(), k = 0; k < asize; k++) {
+                    String key = e.getAttrName(k);
+                    String val = e.getAttr(k);
+                    atts.addAttribute("", "", key, "CDATA", val);
+                }
+                ch.startElement("", "", e.getName(), atts);
+                for (int i = 0; i < e.size(); i++) {
+                    output(e.get(i));
+                }
+                ch.endElement("", "", e.getName());
+            } else if (x instanceof Special) {
+                Special sp = (Special) x;
+                if (sp.kind.startsWith("<!--")) {
+                    char[] chars = sp.value.toString().toCharArray();
+                    lh.comment(chars, 0, chars.length);
+                } else if (sp.kind.startsWith("<?")) {
+                    Element nameInstr = (Element) sp.value;
+                    ch.processingInstruction(nameInstr.name,
+                            nameInstr.get(0).toString());
+                } else {
+                    // drop silently
+                }
+            } else {
+                char[] chars = x.toString().toCharArray();
+                ch.characters(chars, 0, chars.length);
+            }
+        }
+    }
+
+    public static class Printer {
+
+        public Writer w;
+        public boolean tokenizing;
+        public boolean pretty;
+        public boolean abbreviated;  // nonstandard format cuts down on noise
+        int depth = 0;
+        boolean prevStr;
+        int tabStop = 2;
+
+        public Printer(Writer w) {
+            this.w = w;
+        }
+
+        public Printer() {
+            StringWriter sw = new StringWriter();
+            this.w = sw;
+
+        }
+
+        public String nextString() {
+            StringBuffer sb = ((StringWriter) w).getBuffer();
+            String next = sb.toString();
+            sb.setLength(0);  // reset
+            return next;
+        }
+
+        void indent(int depth) throws IOException {
+            if (depth > 0) {
+                w.write("\n");
+            }
+            int nsp = tabStop * depth;
+            while (nsp > 0) {
+                String s = "                ";
+                String t = s.substring(0, nsp < s.length() ? nsp : s.length());
+                w.write(t);
+                nsp -= t.length();
+            }
+        }
+
+        public void print(Element e) throws IOException {
+            if (e.isAnonymous()) {
+                printParts(e);
+                return;
+            }
+            printRecursive(e);
+        }
+
+        public void println(Element e) throws IOException {
+            print(e);
+            w.write("\n");
+            w.flush();
+        }
+
+        public void printRecursive(Element e) throws IOException {
+            boolean indented = false;
+            if (pretty && !prevStr && e.size() + e.attrSize() > 0) {
+                indent(depth);
+                indented = true;
+            }
+            w.write("<");
+            w.write(e.name);
+            for (int asize = e.attrSize(), k = 0; k < asize; k++) {
+                String key = e.getAttrName(k);
+                String val = e.getAttr(k);
+                w.write(" ");
+                w.write(key);
+                w.write("=");
+                if (val == null) {
+                    w.write("null");  // Should not happen....
+                } else if (val.indexOf("\"") < 0) {
+                    w.write("\"");
+                    writeToken(val, '"', w);
+                    w.write("\"");
+                } else {
+                    w.write("'");
+                    writeToken(val, '\'', w);
+                    w.write("'");
+                }
+            }
+            if (e.size() == 0) {
+                w.write("/>");
+            } else {
+                ++depth;
+                if (abbreviated) {
+                    w.write("/");
+                } else {
+                    w.write(">");
+                }
+                prevStr = false;
+                printParts(e);
+                if (abbreviated) {
+                    w.write(">");
+                } else {
+                    if (indented && !prevStr) {
+                        indent(depth - 1);
+                    }
+                    w.write("</");
+                    w.write(e.name);
+                    w.write(">");
+                }
+                prevStr = false;
+                --depth;
+            }
+        }
+
+        private void printParts(Element e) throws IOException {
+            for (int i = 0; i < e.size(); i++) {
+                Object x = e.get(i);
+                if (x instanceof Element) {
+                    printRecursive((Element) x);
+                    prevStr = false;
+                } else if (x instanceof Special) {
+                    w.write(((Special) x).toString());
+                    prevStr = false;
+                } else {
+                    String s = String.valueOf(x);
+                    if (pretty) {
+                        s = s.trim();
+                        if (s.length() == 0) {
+                            continue;
+                        }
+                    }
+                    if (prevStr) {
+                        w.write(' ');
+                    }
+                    writeToken(s, tokenizing ? ' ' : (char) -1, w);
+                    prevStr = true;
+                }
+                if (pretty && depth == 0) {
+                    w.write("\n");
+                    prevStr = false;
+                }
+            }
+        }
+    }
+
+    public static void output(Object e, ContentHandler ch, LexicalHandler lh) throws SAXException {
+        new Outputter(ch, lh).output(e);
+    }
+
+    public static void output(Object e, ContentHandler ch) throws SAXException {
+        if (ch instanceof LexicalHandler) {
+            output(e, ch, (LexicalHandler) ch);
+        } else {
+            output(e, ch, null);
+        }
+    }
+
+    public static void writeToken(String val, char quote, Writer w) throws IOException {
+        int len = val.length();
+        boolean canUseCData = (quote != '"' && quote != '\'');
+        int vpos = 0;
+        for (int i = 0; i < len; i++) {
+            char ch = val.charAt(i);
+            if ((ch == '<' || ch == '&' || ch == '>' || ch == quote)
+                    || (quote == ' ' && isWhitespace(ch))) {
+                if (canUseCData) {
+                    assert (vpos == 0);
+                    writeCData(val, w);
+                    return;
+                } else {
+                    if (vpos < i) {
+                        w.write(val, vpos, i - vpos);
+                    }
+                    String esc;
+                    switch (ch) {
+                        case '&':
+                            esc = "&amp;";
+                            break;
+                        case '<':
+                            esc = "&lt;";
+                            break;
+                        case '\'':
+                            esc = "&apos;";
+                            break;
+                        case '"':
+                            esc = "&quot;";
+                            break;
+                        case '>':
+                            esc = "&gt;";
+                            break;
+                        default:
+                            esc = "&#" + (int) ch + ";";
+                            break;
+                    }
+                    w.write(esc);
+                    vpos = i + 1;  // skip escaped char
+                }
+            }
+        }
+        // write the unquoted tail
+        w.write(val, vpos, val.length() - vpos);
+    }
+
+    public static void writeCData(String val, Writer w) throws IOException {
+        String begCData = "<![CDATA[";
+        String endCData = "]]>";
+        w.write(begCData);
+        for (int vpos = 0, split;; vpos = split) {
+            split = val.indexOf(endCData, vpos);
+            if (split < 0) {
+                w.write(val, vpos, val.length() - vpos);
+                w.write(endCData);
+                return;
+            }
+            split += 2; // bisect the "]]>" goo
+            w.write(val, vpos, split - vpos);
+            w.write(endCData);
+            w.write(begCData);
+        }
+    }
+
+    public static TokenList convertToList(String str) {
+        if (str == null) {
+            return null;
+        }
+        return new TokenList(str);
+    }
+
+    /** If str is null, empty, or blank, returns null.
+     *  Otherwise, return a Double if str spells a double value and contains '.' or 'e'.
+     *  Otherwise, return an Integer if str spells an int value.
+     *  Otherwise, return a Long if str spells a long value.
+     *  Otherwise, return a BigInteger for the string.
+     *  Otherwise, throw NumberFormatException.
+     */
+    public static Number convertToNumber(String str) {
+        if (str == null) {
+            return null;
+        }
+        str = str.trim();
+        if (str.length() == 0) {
+            return null;
+        }
+        if (str.indexOf('.') >= 0
+                || str.indexOf('e') >= 0
+                || str.indexOf('E') >= 0) {
+            return Double.valueOf(str);
+        }
+        try {
+            long lval = Long.parseLong(str);
+            if (lval == (int) lval) {
+                // Narrow to Integer, if possible.
+                return new Integer((int) lval);
+            }
+            return new Long(lval);
+        } catch (NumberFormatException ee) {
+            // Could not represent it as a long.
+            return new java.math.BigInteger(str, 10);
+        }
+    }
+
+    public static Number convertToNumber(String str, Number dflt) {
+        Number n = convertToNumber(str);
+        return (n == null) ? dflt : n;
+    }
+
+    public static long convertToLong(String str) {
+        return convertToLong(str, 0);
+    }
+
+    public static long convertToLong(String str, long dflt) {
+        Number n = convertToNumber(str);
+        return (n == null) ? dflt : n.longValue();
+    }
+
+    public static double convertToDouble(String str) {
+        return convertToDouble(str, 0);
+    }
+
+    public static double convertToDouble(String str, double dflt) {
+        Number n = convertToNumber(str);
+        return (n == null) ? dflt : n.doubleValue();
+    }
+
+    // Testing:
+    public static void main(String... av) throws Exception {
+        Element.method("getAttr");
+        //new org.jdom.input.SAXBuilder().build(file).getRootElement();
+        //jdom.org/jdom-b8/src/java/org/jdom/input/SAXBuilder.java
+        //Document build(InputSource in) throws JDOMException
+
+        int reps = 0;
+
+        boolean tokenizing = false;
+        boolean makeFrozen = false;
+        if (av.length > 0) {
+            tokenizing = true;
+            try {
+                reps = Integer.parseInt(av[0]);
+            } catch (NumberFormatException ee) {
+            }
+        }
+        Reader inR = new BufferedReader(new InputStreamReader(System.in));
+        String inS = null;
+        if (reps > 1) {
+            StringWriter inBufR = new StringWriter(1 << 14);
+            char[] cbuf = new char[1024];
+            for (int nr; (nr = inR.read(cbuf)) >= 0;) {
+                inBufR.write(cbuf, 0, nr);
+            }
+            inS = inBufR.toString();
+            inR = new StringReader(inS);
+        }
+        Element e = XMLKit.readFrom(inR, tokenizing, makeFrozen);
+        System.out.println("transform = " + e.findAll(methodFilter(Element.method("prettyString"))));
+        System.out.println("transform = " + e.findAll(testMethodFilter(Element.method("hasText"))));
+        long tm0 = 0;
+        int warmup = 10;
+        for (int i = 1; i < reps; i++) {
+            inR = new StringReader(inS);
+            readFrom(inR, tokenizing, makeFrozen);
+            if (i == warmup) {
+                System.out.println("Start timing...");
+                tm0 = System.currentTimeMillis();
+            }
+        }
+        if (tm0 != 0) {
+            long tm1 = System.currentTimeMillis();
+            System.out.println((reps - warmup) + " in " + (tm1 - tm0) + " ms");
+        }
+        System.out.println("hashCode = " + e.hashCode());
+        String eStr = e.toString();
+        System.out.println(eStr);
+        Element e2 = readFrom(new StringReader(eStr), tokenizing, !makeFrozen);
+        System.out.println("hashCode = " + e2.hashCode());
+        if (!e.equals(e2)) {
+            System.out.println("**** NOT EQUAL 1\n" + e2);
+        }
+        e = e.deepCopy();
+        System.out.println("hashCode = " + e.hashCode());
+        if (!e.equals(e2)) {
+            System.out.println("**** NOT EQUAL 2");
+        }
+        e2.shallowFreeze();
+        System.out.println("hashCode = " + e2.hashCode());
+        if (!e.equals(e2)) {
+            System.out.println("**** NOT EQUAL 3");
+        }
+        if (false) {
+            System.out.println(e);
+        } else {
+            prettyPrintTo(new OutputStreamWriter(System.out), e);
+        }
+        System.out.println("Flat text:|" + e.getFlatText() + "|");
+        {
+            System.out.println("<!--- Sorted: --->");
+            Element ce = e.copyContentOnly();
+            ce.sort();
+            prettyPrintTo(new OutputStreamWriter(System.out), ce);
+        }
+        {
+            System.out.println("<!--- Trimmed: --->");
+            Element tr = e.deepCopy();
+            findInTree(testMethodFilter(Element.method("trimText"))).filter(tr);
+            System.out.println(tr);
+        }
+        {
+            System.out.println("<!--- Unstrung: --->");
+            Element us = e.deepCopy();
+            int nr = us.retainAllInTree(elementFilter(), null);
+            System.out.println("nr=" + nr);
+            System.out.println(us);
+        }
+        {
+            System.out.println("<!--- Rollup: --->");
+            Element ru = e.deepCopy();
+            Filter makeAnonF =
+                    methodFilter(Element.method("setName"),
+                    new Object[]{ANON_NAME});
+            Filter testSizeF =
+                    testMethodFilter(Element.method("size"));
+            Filter walk =
+                    replaceInTree(and(not(elementFilter()), emptyFilter()),
+                    and(testSizeF, makeAnonF));
+            ru = (Element) walk.filter(ru);
+            //System.out.println(ru);
+            prettyPrintTo(new OutputStreamWriter(System.out), ru);
+        }
+    }
+
+    static boolean isWhitespace(char c) {
+        switch (c) {
+            case 0x20:
+            case 0x09:
+            case 0x0D:
+            case 0x0A:
+                return true;
+        }
+        return false;
+    }
+}