提交 a1c492e8 编写于 作者: K ksrini

6966737: (pack200) the pack200 regression tests need to be more robust.

Reviewed-by: jrose, ohair
上级 b1f0edac
/*
* 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);
}
}
}
#
# 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
......@@ -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() {}
static long getUsedMemory() {
mmxbean.gc();
mmxbean.gc();
mmxbean.gc();
return mmxbean.getHeapMemoryUsage().getUsed()/1024;
}
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 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);
JarOutputStream javaUnpackerStream = null;
JarOutputStream nativeUnpackerStream = null;
JarFile jarFile = null;
try {
JarFile jarFile = new JarFile(in);
jarFile = new JarFile(in);
// Write out to a jtreg scratch area
FileOutputStream fos = new FileOutputStream(in.getName() + PACKEXT);
File packFile = new File(in.getName() + Utils.PACK_FILE_EXT);
System.out.print("Packing [" + in.toString() + "]...");
System.out.println("Packing [" + in.toString() + "]");
// Call the packer
packer.pack(jarFile, fos);
Utils.pack(jarFile, packFile);
jarFile.close();
fos.close();
System.out.print("Unpacking...");
File f = new File(in.getName() + PACKEXT);
leakCheck();
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
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...");
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.
doTest(in);
Utils.doCompareVerify(in.getAbsoluteFile(), javaUnpackedJar);
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) {
System.out.println("ERROR: " + e.getMessage());
System.exit(1);
}
}
}
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 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()));
System.out.print(refList.size() + "/" + cmpList.size() + " entries...");
if (refList.size() != cmpList.size()) {
throw new Exception("Missing: files ?, entries don't match");
}
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.");
throw new RuntimeException(e);
} finally {
Utils.close(nativeUnpackerStream);
Utils.close(javaUnpackerStream);
Utils.close((Closeable) jarFile);
}
}
}
......@@ -137,11 +123,12 @@ public class Pack200Test {
* @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();
}
}
......@@ -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 @@ public class PackageVersionTest {
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 @@ public class PackageVersionTest {
name.substring(name.length() - 1),
name + ".java"
};
compileJava(javacCmds);
Utils.compiler(javacCmds);
}
static void createJavaFile(String name) {
......@@ -108,22 +101,8 @@ public class PackageVersionTest {
} 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 @@ public class PackageVersionTest {
jarFileName.getName(),
filename
};
makeJar(jargs);
Utils.jar(jargs);
JarFile jfin = null;
try {
......@@ -163,7 +142,7 @@ public class PackageVersionTest {
} catch (IOException ioe) {
throw new RuntimeException(ioe.getMessage());
} finally {
close(jfin);
Utils.close((Closeable) jfin);
}
}
}
......@@ -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 @@ import java.io.PrintStream;
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);
File logFile = new File("pack200.log");
fos = new FileOutputStream(logFile);
ps = new PrintStream(fos);
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();
System.out.println("using pack200: " + Utils.getPack200Cmd());
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);
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");
}
}
}
......
/*
* 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);
}
}
}
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
<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>
/*
* 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);
}
}
/*
* 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);
}
}
/*
* 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);
}
}
}
/*
* 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());
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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
}
/*
* 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);
}
}
/*
* 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;
}
}
/*
* 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;
}
}
}
/*
* 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);
}
}
/*
* 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 ----*/
}
/*
* 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;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册