ProGuard 4.4
diff --git a/README b/README
new file mode 100644
index 0000000..8766da5
--- /dev/null
+++ b/README
@@ -0,0 +1,33 @@
+ProGuard, Java class file shrinker, optimizer, obfuscator, and preverifier
+==========================================================================
+
+This distribution contains the following directories:
+
+- bin      : simple wrapper scripts to run ProGuard, its GUI, and ReTrace
+- lib      : the main jars, compiled and ready to use with "java -jar ...."
+- docs     : the complete documentation, licenses, etc. in html format
+- examples : some example configuration files
+- src      : the source code
+- build    : various alternative build scripts
+
+
+The best place to start is docs/index.html
+
+
+Example
+=======
+
+If you want to give ProGuard a spin right away, try processing the ProGuard
+jar itself:
+
+    cd examples
+    java -jar ../lib/proguard.jar @proguard.pro
+
+The resulting proguard_out.jar contains the same application, but it's a lot
+smaller.
+
+Enjoy!
+
+http://proguard.sourceforge.net/
+
+Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
diff --git a/bin/proguard.bat b/bin/proguard.bat
new file mode 100644
index 0000000..7989253
--- /dev/null
+++ b/bin/proguard.bat
@@ -0,0 +1,10 @@
+@ECHO OFF
+
+REM Start-up script for ProGuard -- free class file shrinker, optimizer,
+REM obfuscator, and preverifier for Java bytecode.
+
+IF EXIST "%PROGUARD_HOME%" GOTO home
+SET PROGUARD_HOME=..
+:home
+
+java -jar "%PROGUARD_HOME%"\lib\proguard.jar %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/bin/proguard.sh b/bin/proguard.sh
new file mode 100755
index 0000000..228d044
--- /dev/null
+++ b/bin/proguard.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Start-up script for ProGuard -- free class file shrinker, optimizer,
+# obfuscator, and preverifier for Java bytecode.
+
+PROGUARD_HOME=`dirname "$0"`
+PROGUARD_HOME=`dirname "$PROGUARD_HOME"`
+
+java -jar $PROGUARD_HOME/lib/proguard.jar "$@"
diff --git a/bin/proguardgui.bat b/bin/proguardgui.bat
new file mode 100644
index 0000000..073d98a
--- /dev/null
+++ b/bin/proguardgui.bat
@@ -0,0 +1,10 @@
+@ECHO OFF
+
+REM Start-up script for the GUI of ProGuard -- free class file shrinker,
+REM optimizer, obfuscator, and preverifier for Java bytecode.
+
+IF EXIST "%PROGUARD_HOME%" GOTO home
+SET PROGUARD_HOME=..
+:home
+
+java -jar "%PROGUARD_HOME%"\lib\proguardgui.jar %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/bin/proguardgui.sh b/bin/proguardgui.sh
new file mode 100755
index 0000000..5a25d7b
--- /dev/null
+++ b/bin/proguardgui.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Start-up script for the GUI of ProGuard -- free class file shrinker,
+# optimizer, obfuscator, and preverifier for Java bytecode.
+
+PROGUARD_HOME=`dirname "$0"`
+PROGUARD_HOME=`dirname "$PROGUARD_HOME"`
+
+java -jar $PROGUARD_HOME/lib/proguardgui.jar "$@"
diff --git a/bin/retrace.bat b/bin/retrace.bat
new file mode 100644
index 0000000..70603d3
--- /dev/null
+++ b/bin/retrace.bat
@@ -0,0 +1,10 @@
+@ECHO OFF
+
+REM Start-up script for Retrace -- companion tool for ProGuard, free class file
+REM shrinker, optimizer, obfuscator, and preverifier for Java bytecode.
+
+IF EXIST "%PROGUARD_HOME%" GOTO home
+SET PROGUARD_HOME=..
+:home
+
+java -jar "%PROGUARD_HOME%"\lib\retrace.jar %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/bin/retrace.sh b/bin/retrace.sh
new file mode 100755
index 0000000..5db744e
--- /dev/null
+++ b/bin/retrace.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# Start-up script for Retrace -- companion tool for ProGuard, free class file
+# shrinker, optimizer, obfuscator, and preverifier for Java bytecode.
+
+PROGUARD_HOME=`dirname "$0"`
+PROGUARD_HOME=`dirname "$PROGUARD_HOME"`
+
+java -jar $PROGUARD_HOME/lib/retrace.jar "$@"
diff --git a/build/build.sh b/build/build.sh
new file mode 100755
index 0000000..eb5fcf7
--- /dev/null
+++ b/build/build.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+#
+# GNU/Linux build script for ProGuard.
+
+#
+# Configuration.
+#
+
+ANT_HOME=${ANT_HOME:-/usr/local/java/ant}
+WTK_HOME=${WTK_HOME:-/usr/local/java/wtk}
+
+if [ -z $PROGUARD_HOME ]; then
+  PROGUARD_HOME=$(which "$0")
+  PROGUARD_HOME=$(dirname "$0")/..
+fi
+
+cd "$PROGUARD_HOME"
+
+SRC=src
+CLASSES=classes
+LIB=lib
+
+PROGUARD=proguard/ProGuard
+PROGUARD_GUI=proguard/gui/ProGuardGUI
+RETRACE=proguard/retrace/ReTrace
+ANT_TASK=proguard/ant/ProGuardTask
+WTK_PLUGIN=proguard/wtk/ProGuardObfuscator
+
+ANT_JAR=$ANT_HOME/lib/ant.jar
+WTK_JAR=$WTK_HOME/wtklib/kenv.zip
+
+PROGUARD_JAR=$LIB/proguard.jar
+PROGUARD_GUI_JAR=$LIB/proguardgui.jar
+RETRACE_JAR=$LIB/retrace.jar
+
+#
+# Function definitions.
+#
+
+function compile {
+  # Compile java source files.
+  echo "Compiling ${1//\//.} ..."
+  javac -nowarn -Xlint:none -sourcepath "$SRC" -d "$CLASSES" \
+    "$SRC/$1.java" 2>&1 \
+  | sed -e 's|^|  |'
+
+  # Copy resource files.
+  (cd "$SRC"; find $(dirname $1) -maxdepth 1 \
+     \( -name \*.properties -o -name \*.png -o -name \*.gif -o -name \*.pro \) \
+     -exec cp --parents {} "../$CLASSES" \; )
+}
+
+function createjar {
+  echo "Creating $2..."
+  jar -cfm "$2" "$SRC/$(dirname $1)/MANIFEST.MF" -C "$CLASSES" $(dirname $1)
+}
+
+function updatejar {
+  echo "Updating $PROGUARD_JAR..."
+  jar -uf "$PROGUARD_JAR" -C "$CLASSES" $(dirname $1)
+}
+
+#
+# Main script body.
+#
+
+mkdir -p "$CLASSES"
+
+compile   $PROGUARD
+createjar $PROGUARD "$PROGUARD_JAR"
+
+compile   $PROGUARD_GUI
+createjar $PROGUARD_GUI "$PROGUARD_GUI_JAR"
+
+compile   $RETRACE
+createjar $RETRACE "$RETRACE_JAR"
+
+if [ -f "$ANT_JAR" ]; then
+  export CLASSPATH=$ANT_JAR
+  compile   $ANT_TASK
+  updatejar $ANT_TASK
+else
+  echo "Please make sure the environment variable ANT_HOME is set correctly,"
+  echo "if you want to compile the optional ProGuard Ant task."
+fi
+
+if [ -f "$WTK_JAR" ]; then
+  export CLASSPATH=$WTK_JAR
+  compile   $WTK_PLUGIN
+  updatejar $WTK_PLUGIN
+else
+  echo "Please make sure the environment variable WTK_HOME is set correctly,"
+  echo "if you want to compile the optional ProGuard WTK plugin."
+fi
diff --git a/build/build.xml b/build/build.xml
new file mode 100644
index 0000000..a592945
--- /dev/null
+++ b/build/build.xml
@@ -0,0 +1,171 @@
+<!-- Ant build script for ProGuard. -->
+
+<project name    = "proguard"
+         default = "all"
+         basedir = "..">
+
+  <property file = "build/build.properties"/>
+  <property name = "src"     value = "src"/>
+  <property name = "classes" value = "classes"/>
+  <property name = "lib"     value = "lib"/>
+
+  <property name = "ant.jar" value = "${ant.home}/lib/ant.jar"/>
+  <property name = "wtk.jar" value = "${wtk.home}/wtklib/kenv.jar"/>
+
+  <target name = "all"     depends = "basic,options"/>
+  <target name = "basic"   depends = "proguard,proguardgui,retrace"/>
+  <target name = "options" depends = "anttask,wtkplugin"/>
+
+  <target name = "proguard" depends = "classes,lib">
+    <javac nowarn      = "true"
+           deprecation = "false"
+           srcdir      = "${src}"
+           destdir     = "${classes}"
+           includes    = "proguard/ProGuard.java">
+      <compilerarg value = "-Xlint:none"/>
+    </javac>
+
+    <copy todir = "${classes}">
+      <fileset dir = "${src}">
+        <include name = "proguard/*.properties"/>
+        <include name = "proguard/*.png"/>
+        <include name = "proguard/*.gif"/>
+        <include name = "proguard/*.pro"/>
+      </fileset>
+    </copy>
+
+    <jar jarfile  = "${lib}/proguard.jar"
+         manifest = "${src}/proguard/MANIFEST.MF"
+         basedir  = "${classes}"
+         includes = "proguard/**"/>
+  </target>
+
+  <target name = "proguardgui" depends = "proguard">
+    <javac nowarn      = "true"
+           deprecation = "false"
+           srcdir      = "${src}"
+           destdir     = "${classes}"
+           includes    = "proguard/gui/ProGuardGUI.java">
+      <compilerarg value = "-Xlint:none"/>
+    </javac>
+
+    <copy todir = "${classes}">
+      <fileset dir = "${src}">
+        <include name = "proguard/gui/*.properties"/>
+        <include name = "proguard/gui/*.png"/>
+        <include name = "proguard/gui/*.gif"/>
+        <include name = "proguard/gui/*.pro"/>
+      </fileset>
+    </copy>
+
+    <jar jarfile  = "${lib}/proguardgui.jar"
+         manifest = "${src}/proguard/gui/MANIFEST.MF"
+         basedir  = "${classes}"
+         includes = "proguard/gui/**"/>
+  </target>
+
+  <target name = "retrace" depends = "classes,lib">
+    <javac nowarn      = "true"
+           deprecation = "false"
+           srcdir      = "${src}"
+           destdir     = "${classes}"
+           includes    = "proguard/retrace/ReTrace.java">
+      <compilerarg value = "-Xlint:none"/>
+    </javac>
+
+    <copy todir = "${classes}">
+      <fileset dir = "${src}">
+        <include name = "proguard/retrace/*.properties"/>
+        <include name = "proguard/retrace/*.png"/>
+        <include name = "proguard/retrace/*.gif"/>
+        <include name = "proguard/retrace/*.pro"/>
+      </fileset>
+    </copy>
+
+    <jar jarfile  = "${lib}/retrace.jar"
+         manifest = "${src}/proguard/retrace/MANIFEST.MF"
+         basedir  = "${classes}"
+         includes = "proguard/retrace/**"/>
+  </target>
+
+  <target name = "anttask" depends = "proguard">
+    <fail message="Please set the value of the property ant.home in the file build/build.properties, if you want to build the optional ProGuard Ant task.">
+      <condition>
+        <not>
+          <available file="${ant.jar}"/>
+        </not>
+      </condition>
+    </fail>
+
+    <javac nowarn      = "true"
+           deprecation = "false"
+           classpath   = "${ant.jar}"
+           srcdir      = "${src}"
+           destdir     = "${classes}"
+           includes    = "proguard/ant/ProGuardTask.java">
+      <compilerarg value = "-Xlint:none"/>
+    </javac>
+
+    <copy todir = "${classes}">
+      <fileset dir = "${src}">
+        <include name = "proguard/ant/*.properties"/>
+        <include name = "proguard/ant/*.png"/>
+        <include name = "proguard/ant/*.gif"/>
+        <include name = "proguard/ant/*.pro"/>
+      </fileset>
+    </copy>
+
+    <jar jarfile  = "${lib}/proguard.jar"
+         update   = "true"
+         basedir  = "${classes}"
+         includes = "proguard/ant/**"/>
+  </target>
+
+  <target name = "wtkplugin" depends = "proguard">
+    <fail message="Please set the value of the property wtk.home in the file build/build.properties, if you want to build the optional ProGuard WTK plugin.">
+      <condition>
+        <not>
+          <available file="${wtk.jar}"/>
+        </not>
+      </condition>
+    </fail>
+
+    <javac nowarn      = "true"
+           deprecation = "false"
+           classpath   = "${wtk.jar}"
+           srcdir      = "${src}"
+           destdir     = "${classes}"
+           includes    = "proguard/wtk/ProGuardObfuscator.java">
+      <compilerarg value = "-Xlint:none"/>
+    </javac>
+
+    <copy todir = "${classes}">
+      <fileset dir = "${src}">
+        <include name = "proguard/wtk/*.properties"/>
+        <include name = "proguard/wtk/*.png"/>
+        <include name = "proguard/wtk/*.gif"/>
+        <include name = "proguard/wtk/*.pro"/>
+      </fileset>
+    </copy>
+
+    <jar jarfile  = "${lib}/proguard.jar"
+         update   = "true"
+         basedir  = "${classes}"
+         includes = "proguard/wtk/**"/>
+  </target>
+
+  <target name = "classes">
+    <mkdir dir = "${classes}"/>
+  </target>
+
+  <target name = "lib">
+    <mkdir dir = "${lib}"/>
+  </target>
+
+  <target name = "clean">
+    <delete>
+      <fileset dir = "${classes}"/>
+      <fileset dir = "${lib}"/>
+    </delete>
+  </target>
+</project>
diff --git a/build/makefile b/build/makefile
new file mode 100644
index 0000000..0508eb4
--- /dev/null
+++ b/build/makefile
@@ -0,0 +1,94 @@
+# GNU/Linux makefile for ProGuard.
+
+ANT_HOME = /usr/local/java/ant
+WTK_HOME = /usr/local/java/wtk
+
+PROGUARD_HOME := $(subst ./..,..,$(subst /build/..,/,$(dir $(MAKEFILE_LIST))..))
+SRC     = $(PROGUARD_HOME)/src
+CLASSES = $(PROGUARD_HOME)/classes
+LIB     = $(PROGUARD_HOME)/lib
+
+ANT_JAR = $(ANT_HOME)/lib/ant.jar
+WTK_JAR = $(WTK_HOME)/wtklib/kenv.zip
+
+CLASSPATH = $(ANT_JAR):$(WTK_JAR)
+
+PROGUARD     = proguard/ProGuard
+PROGUARD_GUI = proguard/gui/ProGuardGUI
+RETRACE      = proguard/retrace/ReTrace
+ANT_TASK     = proguard/ant/ProGuardTask
+WTK_PLUGIN   = proguard/wtk/ProGuardObfuscator
+
+TARGETS = $(PROGUARD) $(PROGUARD_GUI) $(RETRACE) $(ANT_TASK) $(WTK_PLUGIN)
+
+JAVAC_OPTIONS = -nowarn -Xlint:none -classpath $(CLASSPATH) -sourcepath $(SRC) -d $(CLASSES)
+
+# Command sequence definitions for creating jars.
+
+define createjar
+  jar -cfm $(LIB)/$@.jar $(SRC)/$(dir $<)MANIFEST.MF \
+    -C $(CLASSES) $(dir $<)
+endef
+
+define updatejar
+  jar -uf $(LIB)/proguard.jar \
+    -C $(CLASSES) $(dir $<)
+endef
+
+# The various targets.
+
+all:     basic options
+basic:   proguard proguardgui retrace
+options: anttask wtkplugin
+
+proguard: $(PROGUARD)
+	$(createjar)
+
+proguardgui: proguard
+proguardgui: $(PROGUARD_GUI)
+	$(createjar)
+
+retrace: $(RETRACE)
+	$(createjar)
+
+anttask: $(ANT_JAR)
+anttask: $(PROGUARD)
+anttask: $(ANT_TASK)
+	$(updatejar)
+
+wtkplugin: $(WTK_JAR)
+wtkplugin: $(PROGUARD)
+wtkplugin: $(WTK_PLUGIN)
+	$(updatejar)
+
+clean:
+	-rm -fr $(CLASSES) $(LIB)
+
+
+define RESOURCES
+  $(shell find $(SRC)/$(dir $(1)) -maxdepth 1 \( -name \*.properties -o -name \*.png -o -name \*.gif -o -name \*.pro \) -printf $(CLASSES)/$(dir $(1))%P\\n)
+endef
+
+define TARGETRULE
+  $(1): $(CLASSES) $(CLASSES)/$(1).class $(call RESOURCES,$(1)) $(LIB)
+endef
+
+$(foreach TARGET,$(TARGETS),$(eval $(call TARGETRULE,$(TARGET))))
+
+$(CLASSES) $(LIB):
+	-mkdir -p $@
+
+$(CLASSES)/%.class: $(SRC)/%.java
+	javac $(JAVAC_OPTIONS) $^
+
+$(CLASSES)/%.properties $(CLASSES)/%.png $(CLASSES)/%.gif $(CLASSES)/%.pro:
+	cp $(subst $(CLASSES),$(SRC),$@) $@
+
+%.jar %.zip:
+	echo "Please make sure the path to $@ is set"
+	echo "correctly in this $(strip $(MAKEFILE_LIST))."
+	echo "Alternatively, if you don't need the corresponding option,"
+	echo "you can run `make' with the option -k."
+	find $@
+
+.PHONY: all basic options proguard proguardgui retrace anttask wtkplugin clean $(TARGETS) $(OPTIONAL_TARGETS)
diff --git a/docs/FAQ.html b/docs/FAQ.html
new file mode 100644
index 0000000..8f19b15
--- /dev/null
+++ b/docs/FAQ.html
@@ -0,0 +1,254 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard FAQ</title>
+</head>
+<body>
+
+<h2>Frequently Asked Questions</h2>
+
+<h3>Contents</h3>
+
+<ol>
+<li><a href="#shrinking">What is shrinking?</a>
+<li><a href="#obfuscation">What is obfuscation?</a>
+<li><a href="#preverification">What is preverification?</a>
+<li><a href="#optimization">What kind of optimizations does <b>ProGuard</b>
+    support?</a>
+<li><a href="#commercial">Can I use <b>ProGuard</b> to process my commercial
+    application?</a>
+<li><a href="#jdk1.4">Does <b>ProGuard</b> work with Java 2? Java 5? Java
+    6?</a>
+<li><a href="#jme">Does <b>ProGuard</b> work with Java Micro Edition?</a>
+<li><a href="#android">Does <b>ProGuard</b> work for Google Android code?</a>
+<li><a href="#blackberry">Does <b>ProGuard</b> work for Blackberry code?</a>
+<li><a href="#ant">Does <b>ProGuard</b> have support for Ant?</a>
+<li><a href="#gui">Does <b>ProGuard</b> come with a GUI?</a>
+<li><a href="#forname">Does <b>ProGuard</b> handle <code>Class.forName</code>
+    calls?</a>
+<li><a href="#resource">Does <b>ProGuard</b> handle resource files?</a>
+<li><a href="#encrypt">Does <b>ProGuard</b> encrypt strings constants?</a>
+<li><a href="#flow">Does <b>ProGuard</b> perform control flow obfuscation?</a>
+<li><a href="#incremental">Does <b>ProGuard</b> support incremental
+    obfuscation?</a>
+<li><a href="#keywords">Can <b>ProGuard</b> obfuscate using reserved
+    keywords?</a>
+<li><a href="#stacktrace">Can <b>ProGuard</b> reconstruct obfuscated stack
+    traces?</a>
+</ol>
+
+<a name="shrinking">&nbsp;</a>
+<h3>What is shrinking?</h3>
+
+Java source code (.java files) is typically compiled to bytecode (.class
+files). Bytecode is more compact than Java source code, but it may still
+contain a lot of unused code, especially if it includes program libraries.
+Shrinking programs such as <b>ProGuard</b> can analyze bytecode and remove
+unused classes, fields, and methods. The program remains functionally
+equivalent, including the information given in exception stack traces.
+
+<a name="obfuscation">&nbsp;</a>
+<h3>What is obfuscation?</h3>
+
+By default, compiled bytecode still contains a lot of debugging information:
+source file names, line numbers, field names, method names, argument names,
+variable names, etc. This information makes it straightforward to decompile
+the bytecode and reverse-engineer entire programs. Sometimes, this is not
+desirable. Obfuscators such as <b>ProGuard</b> can remove the debugging
+information and replace all names by meaningless character sequences, making
+it much harder to reverse-engineer the code. It further compacts the code as a
+bonus. The program remains functionally equivalent, except for the class
+names, method names, and line numbers given in exception stack traces.
+
+<a name="preverification">&nbsp;</a>
+<h3>What is preverification?</h3>
+
+When loading class files, the class loader performs some sophisticated
+verification of the byte code. This analysis makes sure the code can't
+accidentally or intentionally break out of the sandbox of the virtual machine.
+Java Micro Edition and Java 6 introduced split verification. This means that
+the JME preverifier and the Java 6 compiler add preverification information to
+the class files (StackMap and StackMapTable attributes, respectively), in order
+to simplify the actual verification step for the class loader. Class files can
+then be loaded faster and in a more memory-efficient way. <b>ProGuard</b> can
+perform the preverification step too, for instance allowing to retarget older
+class files at Java 6.
+
+<a name="optimization">&nbsp;</a>
+<h3>What kind of optimizations does <b>ProGuard</b> support?</h3>
+
+Apart from removing unused classes, fields, and methods in the shrinking step,
+<b>ProGuard</b> can also perform optimizations at the bytecode level, inside
+and across methods. Thanks to techniques like control flow analysis, data flow
+analysis, partial evaluation, static single assignment, global value numbering,
+and liveness analysis, <b>ProGuard</b> can:
+
+<ul>
+<li>Evaluate constant expressions.
+<li>Remove unnecessary field accesses and method calls.
+<li>Remove unnecessary branches.
+<li>Remove unnecessary comparisons and instanceof tests.
+<li>Remove unused code blocks.
+<li>Merge identical code blocks.
+<li>Reduce variable allocation.
+<li>Remove write-only fields and unused method parameters.
+<li>Inline constant fields, method parameters, and return values.
+<li>Inline methods that are short or only called once.
+<li>Simplify tail recursion calls.
+<li>Merge classes and interfaces.
+<li>Make methods private, static, and final when possible.
+<li>Make classes static and final when possible.
+<li>Replace interfaces that have single implementations.
+<li>Perform over 200 peephole optimizations, like replacing ...*2 by
+    ...&lt;&lt;1.
+<li>Optionally remove logging code.
+</ul>
+The positive effects of these optimizations will depend on your code and on
+the virtual machine on which the code is executed. Simple virtual machines may
+benefit more than advanced virtual machines with sophisticated JIT compilers.
+At the very least, your bytecode may become a bit smaller.
+<p>
+Some notable optimizations that aren't supported yet:
+<ul>
+<li>Moving constant expressions out of loops.
+<li>Optimizations that require escape analysis.
+</ul>
+
+<a name="commercial">&nbsp;</a>
+<h3>Can I use <b>ProGuard</b> to process my commercial application?</h3>
+
+Yes, you can. <b>ProGuard</b> itself is distributed under the GPL, but this
+doesn't affect the programs that you process. Your code remains yours, and
+its license can remain the same.
+
+<a name="jdk1.4">&nbsp;</a>
+<h3>Does <b>ProGuard</b> work with Java 2? Java 5? Java 6?</h3>
+
+Yes, <b>ProGuard</b> supports all JDKs from 1.1 up to and including 6.0. Java 2
+introduced some small differences in the class file format. Java 5 added
+attributes for generics and for annotations. Java 6 introduced preverification
+attributes. <b>ProGuard</b> handles all versions correctly.
+
+<a name="jme">&nbsp;</a>
+<h3>Does <b>ProGuard</b> work with Java Micro Edition?</h3>
+
+Yes. <b>ProGuard</b> itself runs in Java Standard Edition, but you can freely
+specify the run-time environment at which your programs are targeted,
+including Java Micro Edition. <b>ProGuard</b> then also performs the required
+preverification, producing more compact results than the traditional external
+preverifier.
+<p>
+<b>ProGuard</b> also comes with an obfuscator plug-in for the JME Wireless
+Toolkit.
+
+<a name="android">&nbsp;</a>
+<h3>Does <b>ProGuard</b> work for Google Android code?</h3>
+
+Yes. Google's <code>dx</code> compiler converts ordinary jar files into files
+that run on Android devices. By preprocessing the original jar files,
+<b>ProGuard</b> can significantly reduce the file sizes and boost the run-time
+performance of the code.
+
+<a name="blackberry">&nbsp;</a>
+<h3>Does <b>ProGuard</b> work for Blackberry code?</h3>
+
+It should. RIM's proprietary <code>rapc</code> compiler converts ordinary JME
+jar files into cod files that run on Blackberry devices. The compiler performs
+quite a few optimizations, but preprocessing the jar files with
+<b>ProGuard</b> can generally still reduce the final code size by a few
+percent. However, the <code>rapc</code> compiler also seems to contain some
+bugs. It sometimes fails on obfuscated code that is valid and accepted by other
+JME tools and VMs. Your mileage may therefore vary.
+
+<a name="ant">&nbsp;</a>
+<h3>Does <b>ProGuard</b> have support for Ant?</h3>
+
+Yes. <b>ProGuard</b> provides an Ant task, so that it integrates seamlessly
+into your Ant build processes. You can still use configurations in
+<b>ProGuard</b>'s own readable format. Alternatively, if you prefer XML, you
+can specify the equivalent XML configuration.
+
+<a name="gui">&nbsp;</a>
+<h3>Does <b>ProGuard</b> come with a GUI?</h3>
+
+Yes. First of all, <b>ProGuard</b> is perfectly usable as a command-line tool
+that can easily be integrated into any automatic build process. For casual
+users, there's also a graphical user interface that simplifies creating,
+loading, editing, executing, and saving ProGuard configurations.
+
+<a name="forname">&nbsp;</a>
+<h3>Does <b>ProGuard</b> handle <code>Class.forName</code> calls?</h3>
+
+Yes. <b>ProGuard</b> automatically handles constructs like
+<code>Class.forName("SomeClass")</code> and <code>SomeClass.class</code>. The
+referenced classes are preserved in the shrinking phase, and the string
+arguments are properly replaced in the obfuscation phase.
+<p>
+With variable string arguments, it's generally not possible to determine their
+possible values. They might be read from a configuration file, for instance.
+However, <b>ProGuard</b> will note a number of constructs like
+"<code>(SomeClass)Class.forName(variable).newInstance()</code>". These might
+be an indication that the class or interface <code>SomeClass</code> and/or its
+implementations may need to be preserved. The user can adapt his configuration
+accordingly.
+
+<a name="resource">&nbsp;</a>
+<h3>Does <b>ProGuard</b> handle resource files?</h3>
+
+Yes. <b>ProGuard</b> copies all non-class resource files, optionally adapting
+their names and their contents to the obfuscation that has been applied.
+
+<a name="encrypt">&nbsp;</a>
+<h3>Does <b>ProGuard</b> encrypt strings constants?</h3>
+
+No. Storing encrypted string constants in program code is fairly futile, since
+the encryption has to be perfectly reversible by definition. Moreover, the
+decryption costs additional memory and computation at run-time. If this feature
+is ever incorporated, I'll provide a tool to decrypt the strings as well.
+
+<a name="flow">&nbsp;</a>
+<h3>Does <b>ProGuard</b> perform flow obfuscation?</h3>
+
+Not explicitly. Control flow obfuscation injects additional branches into the
+bytecode, in an attempt to fool decompilers. <b>ProGuard</b> does not do this,
+in order to avoid any negative effects on performance and size. However, the
+optimization step often already restructures the code to the point where most
+decompilers get confused.
+
+<a name="incremental">&nbsp;</a>
+<h3>Does <b>ProGuard</b> support incremental obfuscation?</h3>
+
+Yes. This feature allows you to specify a previous obfuscation mapping file in
+a new obfuscation step, in order to produce add-ons or patches for obfuscated
+code.
+
+<a name="keywords">&nbsp;</a>
+<h3>Can <b>ProGuard</b> obfuscate using reserved keywords?</h3>
+
+Yes. You can specify your own obfuscation dictionary, such as a list of
+reserved key words, identifiers with foreign characters, random source files,
+or a text by Shakespeare. Note that this hardly improves the obfuscation.
+Decent decompilers can automatically replace reserved keywords, and the effect
+can be undone fairly easily, by obfuscating again with simpler names.
+
+<a name="stacktrace">&nbsp;</a>
+<h3>Can <b>ProGuard</b> reconstruct obfuscated stack traces?</h3>
+
+Yes. <b>ProGuard</b> comes with a companion tool, <b>ReTrace</b>, that can
+'de-obfuscate' stack traces produced by obfuscated applications. The
+reconstruction is based on the mapping file that <b>ProGuard</b> can write
+out. If line numbers have been obfuscated away, a list of alternative method
+names is presented for each obfuscated method name that has an ambiguous
+reverse mapping. Please refer to the <a href="manual/index.html">ProGuard User
+Manual</a> for more details.
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/GPL.html b/docs/GPL.html
new file mode 100644
index 0000000..c7a2458
--- /dev/null
+++ b/docs/GPL.html
@@ -0,0 +1,406 @@
+<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN">
+<HTML>
+<HEAD>
+<TITLE>GNU General Public License</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#1F00FF" ALINK="#FF0000" VLINK="#9900DD">
+<H1>GNU General Public License</H1>
+<H2>Table of Contents</H2>
+<UL>
+
+  <LI><A NAME="TOC1" HREF="#SEC1">GNU GENERAL PUBLIC LICENSE</A>
+<UL>
+<LI><A NAME="TOC2" HREF="#SEC2">Preamble</A>
+<LI><A NAME="TOC3" HREF="#SEC3">TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION</A>
+
+</UL>
+</UL>
+
+<P>
+
+<HR>
+
+<P>
+
+
+
+<H2><A NAME="SEC1" HREF="#TOC1">GNU GENERAL PUBLIC LICENSE</A></H2>
+<P>
+Version 2, June 1991
+
+</P>
+
+<PRE>
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+</PRE>
+
+
+
+<H2><A NAME="SEC2" HREF="#TOC2">Preamble</A></H2>
+
+<P>
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+</P>
+<P>
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+</P>
+<P>
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+</P>
+<P>
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+</P>
+<P>
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+</P>
+<P>
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+</P>
+<P>
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+</P>
+<P>
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+</P>
+
+
+<H2><A NAME="SEC3" HREF="#TOC3">TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION</A></H2>
+
+
+<P>
+
+<STRONG>0.</STRONG>
+ This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+<P>
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+<P>
+
+<STRONG>1.</STRONG>
+ You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+<P>
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+<P>
+
+<STRONG>2.</STRONG>
+ You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+<P>
+
+<UL>
+
+<LI><STRONG>a)</STRONG>
+     You must cause the modified files to carry prominent notices
+     stating that you changed the files and the date of any change.
+
+<P>
+<LI><STRONG>b)</STRONG>
+     You must cause any work that you distribute or publish, that in
+     whole or in part contains or is derived from the Program or any
+     part thereof, to be licensed as a whole at no charge to all third
+     parties under the terms of this License.
+
+<P>
+<LI><STRONG>c)</STRONG>
+     If the modified program normally reads commands interactively
+     when run, you must cause it, when started running for such
+     interactive use in the most ordinary way, to print or display an
+     announcement including an appropriate copyright notice and a
+     notice that there is no warranty (or else, saying that you provide
+     a warranty) and that users may redistribute the program under
+     these conditions, and telling the user how to view a copy of this
+     License.  (Exception: if the Program itself is interactive but
+     does not normally print such an announcement, your work based on
+     the Program is not required to print an announcement.)
+</UL>
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+<P>
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+<P>
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+<P>
+
+<STRONG>3.</STRONG>
+ You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+
+<!-- we use this doubled UL to get the sub-sections indented, -->
+<!-- while making the bullets as unobvious as possible. -->
+<UL>
+
+<LI><STRONG>a)</STRONG>
+     Accompany it with the complete corresponding machine-readable
+     source code, which must be distributed under the terms of Sections
+     1 and 2 above on a medium customarily used for software interchange; or,
+
+<P>
+<LI><STRONG>b)</STRONG>
+     Accompany it with a written offer, valid for at least three
+     years, to give any third party, for a charge no more than your
+     cost of physically performing source distribution, a complete
+     machine-readable copy of the corresponding source code, to be
+     distributed under the terms of Sections 1 and 2 above on a medium
+     customarily used for software interchange; or,
+
+<P>
+<LI><STRONG>c)</STRONG>
+     Accompany it with the information you received as to the offer
+     to distribute corresponding source code.  (This alternative is
+     allowed only for noncommercial distribution and only if you
+     received the program in object code or executable form with such
+     an offer, in accord with Subsection b above.)
+</UL>
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+<P>
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+<P>
+
+<STRONG>4.</STRONG>
+ You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+<P>
+
+<STRONG>5.</STRONG>
+ You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+<P>
+
+<STRONG>6.</STRONG>
+ Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+<P>
+
+<STRONG>7.</STRONG>
+ If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+<P>
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+<P>
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+<P>
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+<P>
+
+<STRONG>8.</STRONG>
+ If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+<P>
+
+<STRONG>9.</STRONG>
+ The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+<P>
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+<P>
+
+
+<STRONG>10.</STRONG>
+ If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+
+
+<P><STRONG>NO WARRANTY</STRONG></P>
+
+<P>
+
+<STRONG>11.</STRONG>
+ BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+<P>
+
+<STRONG>12.</STRONG>
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+<P>
+
+
+<H2>END OF TERMS AND CONDITIONS</H2>
+</BODY>
+</HTML>
diff --git a/docs/GPL_exception.html b/docs/GPL_exception.html
new file mode 100644
index 0000000..0a44d66
--- /dev/null
+++ b/docs/GPL_exception.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN">
+<HTML>
+<HEAD>
+<TITLE>Special Exception to the GNU General Public License</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#1F00FF" ALINK="#FF0000" VLINK="#9900DD">
+<H1>Special Exception to the GNU General Public License</H1>
+
+<P>
+Copyright &copy; 2002-2009 Eric Lafortune
+</P>
+
+<P>
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+</P>
+
+<P>
+This program 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 for more details.
+</P>
+
+<P>
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place, Suite 330, Boston, MA 02111-1307 USA
+</P>
+
+<P>
+In addition, as a special exception, Eric Lafortune gives permission to link
+the code of this program with the following stand-alone applications:
+<ul>
+<li>Apache Ant,
+<li>Apache Maven,
+<li>the Eclipse ProGuardDT GUI,
+<li>the EclipseME JME IDE,
+<li>the Sun NetBeans Java IDE,
+<li>the Sun JME Wireless Toolkit, and
+<li>the Javaground Tools,
+</ul>
+and distribute linked combinations including the two. You must obey the GNU
+General Public License in all respects for all of the code used other than
+these programs. If you modify this file, you may extend this exception to your
+version of the file, but you are not obligated to do so. If you do not wish to
+do so, delete this exception statement from your version.
+</P>
+
+</BODY>
+</HTML>
diff --git a/docs/acknowledgements.html b/docs/acknowledgements.html
new file mode 100644
index 0000000..af19461
--- /dev/null
+++ b/docs/acknowledgements.html
@@ -0,0 +1,68 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Acknowledgements</title>
+</head>
+<body>
+
+<h2>Acknowledgements</h2>
+
+The first versions of <b>ProGuard</b> grew out of <b>RetroGuard</b>, which its
+author Mark Welsh kindly made available under the GNU Lesser General Public
+License. <b>RetroGuard</b> is a very nice piece of code, but it only performed
+obfuscation. I started from the class file parsing code and wrote my own
+shrinker, optimizer, obfuscator, and preverifier. As of version 4.0, all of the
+original code has been rewritten, so the most obvious remaining similarity are
+the program names.
+<p>
+
+Dirk Schnelle has contributed and maintained the first versions of the Ant
+task. I have rewritten the implementation for version 3.0, but the XML schema
+is still based on his work.
+<p>
+
+Since its first public release, many people have expressed their enthusiasm and
+have chimed in with interesting ideas, bug reports, and bug fixes: Thorsten
+Heit, Oliver Retzl, Jonathan Knudsen, Tarcisio Camara, Bob Drury, Dave Jarvis,
+Marc Chapman, Dave Morehouse, Richard Osbaldeston, Peter Hawkins, Mark
+Sherington, David Sitsky, James Manning, Ptolemy Oberin, Frank-Michael Moser,
+QZ Shines, Thomas Singer, Michele Puccini, Roman Bednarek, Natalia Pujol,
+Daniel Sj&ouml;blom, Jan Filipsky, Charles Smith, Gerrit Telkamp, Noel
+Grandin, Torbj&ouml;rn S&ouml;derstedt, Clemens Eisserer, Clark Bassett,
+Eduard Welch, Dawid Weiss, Andrew Wilson, Sean Owen, Niels Gron, Ishan Mehta,
+Steven Adams, Xavier Kral,
+and many others. Thanks! Your feedback has been invaluable.
+<p>
+
+I am developing ProGuard in my spare time, which is possible thanks to my
+day-time job at <a href="http://www.luciad.com/" target="other">Luciad</a>.
+<p>
+
+<a href="http://sourceforge.net/projects/proguard/"
+target="other">SourceForge</a> is generously providing the resources for
+hosting this project and many other projects.
+<p>
+
+JetBrains is kindly providing a license for its IntelliJ IDEA development
+environment.
+<p>
+
+The code and these web pages were written using Sun's JDKs, Linux, IntelliJ
+IDEA, GNU emacs, bash, sed, awk, and a whole host of other tools that continue
+to make programming interesting.
+<p>
+
+And finally, I'm a great fan of the <a
+href="http://www.javadocking.com/" target="other">Java Docking Library</a>.
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+
+</body>
+</html>
diff --git a/docs/alternatives.html b/docs/alternatives.html
new file mode 100644
index 0000000..fa5db0c
--- /dev/null
+++ b/docs/alternatives.html
@@ -0,0 +1,657 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Alternatives</title>
+</head>
+<body>
+
+<h2>Alternatives</h2>
+
+There are quite a few Java class file shrinkers, optimizers, obfuscators, and
+preverifiers out there. Users of <b>ProGuard</b> tell me it easily compares
+with the best of them. However, you may want to check that out yourself.
+<p>
+This is a list of the programs of which I'm aware. Obviously, I've never
+personally tested all of them. Many programs, even commercial ones, have been
+abandoned. Please drop me a note if you know of any other shrinkers,
+optimizers, obfuscators, or preverifiers, or if some information provided
+below is incorrect.
+<p>
+
+<table>
+
+<tr>
+<th>Author/Company</th>
+<th>Program</th>
+<th>Shrink.</th>
+<th>Optim.</th>
+<th>Obfusc.</th>
+<th>Preverif.</th>
+<th>License</th>
+</tr>
+
+<tr>
+<td><a target="other" href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a></td>
+<td><a target="_top" href="http://proguard.sourceforge.net/">ProGuard</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td>Free (GPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.informatik.uni-oldenburg.de/leute/hoenicke.html">Jochen Hoenicke</a></td>
+<td><a target="other" href="http://jode.sourceforge.net/">Jode</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (GPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.nq4.de/">NQ4</a></td>
+<td><a target="other" href="http://www.nq4.de/">Joga</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (no source)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.cs.cornell.edu/nystrom/">Nate Nystrom</a></td>
+<td><a target="other" href="http://www.cs.purdue.edu/homes/hosking/bloat/">Bloat</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://sourceforge.net/users/hchacha/">Hidetoshi Ohuchi</a></td>
+<td><a target="other" href="http://jarg.sourceforge.net/">Jarg</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (BSD)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.geocities.com/CapeCanaveral/Hall/2334/resume.html">Alexander Shvets</a></td>
+<td><a target="other" href="http://www.geocities.com/CapeCanaveral/Hall/2334/Programs/cafebabe.html">CafeBabe</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.yworks.com/">yWorks</a></td>
+<td><a target="other" href="http://www.yworks.com/en/products_yguard_about.htm">yGuard</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (no source)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.cs.purdue.edu/homes/grothoff/">Christian Grothoff</a></td>
+<td><a target="other" href="http://www.ovmj.org/jamit/">Jamit</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (GPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://mojo.codehaus.org/">Mojo</a></td>
+<td><a target="other" href="http://mojo.codehaus.org/minijar-maven-plugin/">Minijar</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (Apache)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.riggshill.com/">RiggsHill Software</a></td>
+<td><a target="other" href="http://genjar.sourceforge.net/">GenJar</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (Apache)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://ant.apache.org/">Apache</a></td>
+<td><a target="other" href="http://ant.apache.org/manual/OptionalTypes/classfileset.html">Ant Classfileset</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (Apache)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.curious-creature.org/">Romain Guy</a></td>
+<td><a target="other" href="http://www.jroller.com/gfx/entry/get_what_you_need_from">Harvester</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (BSD)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="https://dcd.dev.java.net/">Emeric Vernat</a></td>
+<td><a target="other" href="https://dcd.dev.java.net/">DCD</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (LGPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://sadun-util.sourceforge.net/">Cristiano Sadun</a></td>
+<td><a target="other" href="http://sadun-util.sourceforge.net/pack.html">Pack</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (LGPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://darcs.brianweb.net/">Brian Alliet</a></td>
+<td><a target="other" href="http://darcs.brianweb.net/gcclass/">Gcclass</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (LGPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.sable.mcgill.ca/">Sable</a></td>
+<td><a target="other" href="http://www.sable.mcgill.ca/soot/">Soot</a></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free (LGPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.garret.ru/~knizhnik/">Konstantin Knizhnik</a></td>
+<td><a target="other" href="http://www.garret.ru/~knizhnik/javago/ReadMe.htm">JavaGO</a></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Free</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://donquixote.cafebabe.jp/">Haruaki Tamada</a></td>
+<td><a target="other" href="http://donquixote.cafebabe.jp/">DonQuixote</a></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.sable.mcgill.ca/">Sable</a></td>
+<td><a target="other" href="http://www.sable.mcgill.ca/JBCO/">JBCO</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (LGPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://sourceforge.net/users/glurk/">Thorsten Heit</a></td>
+<td><a target="other" href="http://sourceforge.net/projects/javaguard/">JavaGuard</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (LGPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://mwobfu.sourceforge.net/">Patrick Mueller</a></td>
+<td><a target="other" href="http://mwobfu.sourceforge.net/">Mwobfu</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (GPL)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.elegant-software.com/">Elegant Software</a></td>
+<td><a target="other" href="http://www.elegant-software.com/software/jmangle/">JMangle</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.bebbosoft.de/">BebboSoft</a></td>
+<td><a target="other" href="http://www.bebbosoft.de/index.html#java/mug/index.html">Bb_mug</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (no source)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.drjava.de/">Dr. Java</a></td>
+<td><a target="other" href="http://www.drjava.de/obfuscator/">Marvin Obfuscator</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (no source)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.ibm.com/">IBM</a></td>
+<td><a target="other" href="http://www-306.ibm.com/software/wireless/wsdd/">WSDD</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.preemptive.com/">PreEmptive</a></td>
+<td><a target="other" href="http://www.preemptive.com/products/dasho/index.html">DashOPro</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.zelix.com/">Zelix</a></td>
+<td><a target="other" href="http://www.zelix.com/klassmaster/index.html">KlassMaster</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.s5systems.com/">S5 Systems</a></td>
+<td><a target="other" href="http://www.s5systems.com/jPresto.htm">jPresto</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.s-cradle.com/english/index.html">Sophia Cradle</a></td>
+<td><a target="other" href="http://www.s-cradle.com/english/products/sophiacompress_java/index.html">SophiaCompress</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.retrologic.com/">RetroLogic</a></td>
+<td><a target="other" href="http://www.retrologic.com/">RetroGuard</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.codingart.com/">CodingArt</a></td>
+<td><a target="other" href="http://www.codingart.com/codeshield.html">CodeShield</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.e-t.com/">Eastridge Technology</a></td>
+<td><a target="other" href="http://www.e-t.com/jshrink.html">Jshrink</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.helseth.com/">Helseth</a></td>
+<td><a target="other" href="http://www.helseth.com/HJO.htm">JObfuscator</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.leesw.com/">LeeSoftware</a></td>
+<td><a target="other" href="http://www.leesw.com/">Smokescreen Obfuscator</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.vegatech.com/">Vega Technologies</a></td>
+<td><a target="other" href="http://www.vegatech.com/jzipper/">JZipper</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.innaworks.com/">Innaworks</a></td>
+<td><a target="other" href="http://www.innaworks.com/">mBooster</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.uni-vologda.ac.ru/~c3c/">Sergey Sverdlov</a></td>
+<td><a target="other" href="http://www.uni-vologda.ac.ru/~c3c/jco/">J.Class Optimizer</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.smardec.com/">Smardec</a></td>
+<td><a target="other" href="//www.allatori.com/">Allatori</a></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://cs.arizona.edu/">U. of Arizona</a></td>
+<td><a target="other" href="http://sandmark.cs.arizona.edu/">SandMark</a></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.force5.com/">Force 5</a></td>
+<td><a target="other" href="http://www.force5.com/">JCloak</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.semdesigns.com/">Semantic Designs</a></td>
+<td><a target="other" href="http://www.semdesigns.com/Products/Obfuscators/JavaObfuscator.html">Obfuscator</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.duckware.com/">Duckware</a></td>
+<td><a target="other" href="http://www.duckware.com/jobfuscate/">Jobfuscate</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.jproof.com/">JProof</a></td>
+<td><a target="other" href="http://www.jproof.com/">JProof</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.solutia.ro/">GITS</a></td>
+<td><a target="other" href="http://www.solutia.ro/pages/javadc/">Blurfuscator</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.chainkey.com/">ChainKey</a></td>
+<td><a target="other" href="http://www.chainkey.com/en/jcp/">Java Code Protector</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://sourceforge.net/projects/flmobf/">Alain Moran</a></td>
+<td><a target="other" href="http://sourceforge.net/projects/flmobf/">flmObf</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free (BSD)</td>
+</tr>
+
+<tr>
+<td><a target="other" rel="nofollow" href="http://www.chez.com/vasile/">Vasile Calmatui</a></td>
+<td><a target="other" href="http://www.chez.com/vasile/obfu/VasObfuLite.html">VasObfuLite</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Free</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.alphaworks.ibm.com/">IBM AlphaWorks</a></td>
+<td><a target="other" href="http://www.research.ibm.com/jax/">JAX</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>(discontinued)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www-i2.informatik.rwth-aachen.de/~markusj/">Markus Jansen</a></td>
+<td><a target="other" rel="nofollow" href="http://www-i2.informatik.rwth-aachen.de/~markusj/jopt/">Jopt</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>(disappeared?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.primenet.com/~ej">Eron Jokipii</a></td>
+<td><a target="other" rel="nofollow" href="http://www.primenet.com/~ej">Jobe</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>(disappeared?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://jrc.krdl.org.sg/">JRC</a></td>
+<td><a target="other" rel="nofollow" href="http://jrc.krdl.org.sg/decaf/">DeCaf</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>(disappeared?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.utdallas.edu/~gxz014000/">Bajie</a></td>
+<td><a target="other" rel="nofollow" href="http://www.utdallas.edu/~gxz014000/jcmp/">JCMP</a></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>(disappeared?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.plumbdesign.com/">Plumb Design</a></td>
+<td><a target="other" rel="nofollow" href="http://www.condensity.com/">Condensity</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (discontinued)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.4thpass.com/">4th Pass</a></td>
+<td><a target="other" rel="nofollow" href="http://www.4thpass.com/">SourceGuard</a></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (discontinued?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.software4j.com/">Software4j</a></td>
+<td><a target="other" rel="nofollow" href="http://www.software4j.com/obfuscate4j/">Obfuscate4j</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (discontinued?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.jammconsulting.com/">JAMM Consulting</a></td>
+<td><a target="other" rel="nofollow" href="http://www.jammconsulting.com/jamm/servlet/com.jammconsulting.servlet.JAMMServlet?pageId=ObfuscateProPage">ObfuscatePro</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (discontinued?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.jdevelop.com/">JDevelop</a></td>
+<td><a target="other" rel="nofollow" href="http://www.jdevelop.com/best-java-obfuscator.html">JSCO</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (discontinued?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.4fang.net/">4Fang</a></td>
+<td><a target="other" rel="nofollow" href="http://www.4fang.net/jmix/">JMix</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (discontinued?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.2lkit.com/">2LKit</a></td>
+<td><a target="other" rel="nofollow" href="http://www.2lkit.com/products/2LKitObf/index.htm">2LKit Obfuscator</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (disappeared?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.wingsoft.com/">WingSoft</a></td>
+<td><a target="other" rel="nofollow" href="http://www.wingsoft.com/wingguard.html">WingGuard</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (disappeared?)</td>
+</tr>
+
+<tr class="disappeared">
+<td><a target="other" rel="nofollow" href="http://www.sbktech.org/">HashJava</a></td>
+<td><a target="other" rel="nofollow" href="http://www.sbktech.org/">HashJava</a></td>
+<td align="center"><br></td>
+<td align="center"><br></td>
+<td align="center"><img src="checkmark.gif" width="11" height="11" alt="x"></td>
+<td align="center"><br></td>
+<td>Commercial (disappeared?)</td>
+</tr>
+
+</table>
+<p>
+All trademarks are property of their respective holders.
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+
+</body>
+</html>
diff --git a/docs/checkmark.gif b/docs/checkmark.gif
new file mode 100644
index 0000000..8afa677
--- /dev/null
+++ b/docs/checkmark.gif
Binary files differ
diff --git a/docs/downloads.html b/docs/downloads.html
new file mode 100644
index 0000000..ebd1b28
--- /dev/null
+++ b/docs/downloads.html
@@ -0,0 +1,521 @@
+
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Downloads</title>
+</head>
+<body>
+
+<h2>Downloads</h2>
+
+<b>ProGuard</b> is distributed under the terms of the GNU General Public
+License. Please consult the <a href="license.html">license page</a> for more
+details.
+<p>
+<b>ProGuard</b> is written in Java, so it requires a Java Runtime Environment
+   (JRE 1.4 or higher).
+<p>
+You can download the latest release (containing the program jar, the
+documentation you're reading now, examples, and the source code) from this
+location:
+<p>
+<center><a href="http://sourceforge.net/project/showfiles.php?group_id=54750"
+target="other">Download section</a> (at <a
+href="http://sourceforge.net/projects/proguard/"
+target="other">SourceForge</a>)</center>
+<p>
+
+If you're still working with an older version of <b>ProGuard</b>, check out
+the summary of changes below, to see if you're missing something essential.
+Better look at the up-to-date <a
+href="http://proguard.sourceforge.net/downloads.html">on-line version</a> if
+you're reading a local copy of this page.
+<p>
+The download section may also contain updates with sub-minor version numbers.
+These versions are typically released shortly after their parent versions, for
+applying emergency fixes. Please make sure to look at those if you are
+encountering any problems with recent releases.
+<p>
+Finally, there may be beta versions of upcoming releases. They may be of
+interest too, because they typically contain any less urgent bug fixes
+collected since the previous release.
+<p>
+
+<h3><div>Jul 2009</div> Version 4.4</h3>
+<ul>
+<li>Added new peephole optimizations.
+<li>Added option <code>-optimizations</code> for fine-grained configuration of
+    optimizations.
+<li>Added option <code>-adaptclassstrings</code> for adapting string constants
+    that correspond to obfuscated classes.
+<li>Added option <code>-keeppackagenames</code> for keeping specified package
+    names from being obfuscated.
+<li>Added option <code>-keepdirectories</code> for keeping specified directory
+    entries in output jars.
+<li>Extended options <code>-dontnote</code> and <code>-dontwarn</code> for
+    fine-grained configuration of notes and warnings. 
+<li>Added option <code>-regex</code> in ReTrace, for specifying alternative
+    regular expressions to parse stack traces.
+<li>Extended renaming of resource files based on obfuscation.
+<li>Improved inlining of constant parameters and removal of unused parameters.
+<li>Avoiding bug in IBM's JVM for JSE, in optimization step.
+<li>Avoiding ArrayIndexOutOfBoundsException in optimization step.
+<li>Fixed configuration with annotations that are not preserved themselves.
+<li>Fixed preverification of invocations of super constructors with arguments
+    containing ternary operators.
+<li>Fixed processing of unreachable exception handlers.
+<li>Fixed merging of exception classes.
+<li>Fixed repeated method inlining.
+<li>Fixed inlining of finally blocks surrounded by large try blocks, compiled
+    with JDK 1.4 or earlier.
+<li>Fixed optimization of complex finally blocks, compiled with JDK 1.4 or
+    earlier.
+<li>Fixed obfuscation of anonymous class names, if <code>EnclosingMethod</code>
+    attributes are being kept.
+<li>Fixed obfuscation of inner class names in generic types.
+<li>Fixed decoding of UTF-8 strings containing special characters.
+<li>Fixed copying of debug information and annotations when merging classes.
+<li>Fixed writing out of unknown attributes.
+<li>Fixed updating manifest files with split lines.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Dec 2008</div> Version 4.3</h3>
+<ul>
+<li>Added class merging.
+<li>Added static single assignment analysis.
+<li>Added support for annotation and enumeration class types in configuration.
+<li>Refined shrinking of fields in case of unusual
+    <code>-keepclassmembers</code> options.
+<li>Added simplification of tail recursion calls.
+<li>Added new peephole optimizations.
+<li>Fixed optimization of unused variable initializations causing negative
+    stack sizes.
+<li>Fixed optimization of unusual initialization code causing
+    NullPointerExceptions.
+<li>Fixed optimization of half-used long and double parameters.
+<li>Fixed processing of complex generics signatures.
+<li>Working around suspected java compiler bug with parameter annotations on
+    constructors of non-static inner classes.
+<li>Fixed obfuscation of classes with inner classes whose names are preserved.
+<li>Fixed access of protected methods in repackaged classes.
+<li>Added options <code>-classobfuscationdictionary</code> and
+    <code>-packageobfuscationdictionary</code>.
+<li>Adapting more types of resource file names based on obfuscation.
+<li>Extended warnings about incorrect dependencies.
+<li>Added start-up scripts and build scripts.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Mar 2008</div> Version 4.2</h3>
+<ul>
+<li>Refined data flow analysis in optimization step.
+<li>Fixed handling of exceptions when inlining subroutines.
+<li>Fixed inlining of incompatible code constructs between different java
+    versions.
+<li>Fixed computation of local variable frame size.
+<li>Fixed optimization of infinite loops.
+<li>Fixed optimization of subroutine invocations.
+<li>Fixed optimization of floating point remainder computations.
+<li>Fixed removal of unused parameters in method descriptors containing arrays
+    of longs or doubles.
+<li>Added undocumented java system properties
+    <code>maximum.inlined.code.length</code> (default is 8) and
+    <code>maximum.resulting.code.length</code> (defaults are 8000 for JSE and
+    2000 for JME), for expert users who read release notes.
+<li>Fixed processing of generic types in Signature attributes in shrinking and
+    optimization steps.
+<li>Fixed processing of inner class names in Signature attributes in obfuscation
+    step.
+<li>Improved adapting resource file names following obfuscated class names.
+<li>Fixed interpretation of package names in GUI.
+<li>Fixed default settings for Xlets in GUI.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Dec 2007</div> Version 4.1</h3>
+<ul>
+<li>Fixed shrinking of default annotation element values.
+<li>Fixed optimization of invocations of methods in same class that are
+    accessed through extensions.
+<li>Fixed optimization of invocations of synchronized methods without other
+    side-effects.
+<li>Fixed optimization of some non-returning subroutines.
+<li>Fixed handling of local variable debug information when inlining methods.
+<li>Avoiding StackOverflowErrors during optimization of complex methods.
+<li>Fixed obfuscation of potentially ambiguous non-primitive constants in
+    interfaces.
+<li>Fixed preverification of some code constructs involving String, Class, and
+    exception types.
+<li>The Ant task now allows empty <code>&lt;injars&gt;</code> and
+    <code>&lt;libraryjars&gt;</code> elements.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Sep 2007</div> Version 4.0</h3>
+<ul>
+<li>Added preverifier for Java 6 and Java Micro Edition, with new options
+    <code>-microedition</code> and <code>-dontpreverify</code>.
+<li>Added new option <code>-target</code> to modify java version of processed
+    class files.
+<li>Made <code>-keep</code> options more orthogonal and flexible, with option
+    modifiers <code>allowshrinking</code>, <code>allowoptimization</code>, and
+    <code>allowobfuscation</code>.
+<li>Added new wildcards for class member descriptors: "<code>***</code>",
+    matching any type, and "<code>...</code>", matching any number of
+    arguments.
+<li>Added support for configuration by means of annotations.
+<li>Improved shrinking of unused annotations.
+<li>Added check on modification times of input and output, to avoid unnecessary
+    processing, with new option <code>-forceprocessing</code>.
+<li>Added new options <code>-flattenpackagehierarchy</code> and
+    <code>-repackageclasses</code> (replacing <code>-defaultpackage</code>) to
+    control obfuscation of package names.
+<li>Added new options <code>-adaptresourcefilenames</code> and
+    <code>-adaptresourcefilecontents</code>, with file filters, to update
+    resource files corresponding to obfuscated class names.
+<li>Added detection of dynamically accessed fields and methods.
+<li>Now treating <code>Exceptions</code> attributes as optional.
+<li>Now respecting naming rule for nested class names
+    (<code>EnclosingClass$InnerClass</code>) in obfuscation step, if
+    <code>InnerClasses</code> attributes or <code>EnclosingMethod</code>
+    attributes are being kept.
+<li>Added new inter-procedural optimizations: method inlining and propagation
+    of constant fields, constant arguments, and constant return values.
+<li>Added optimized local variable allocation.
+<li>Added more than 250 new peephole optimizations.
+<li>Improved making classes and class members public or protected.
+<li>Now printing notes on suspiciously unkept classes in parameters of
+    specified methods.
+<li>Now printing notes for class names that don't seem to be fully qualified.
+<li>Added support for uppercase filename extensions.
+<li>Added tool tips to the GUI.
+<li>Rewritten class file I/O code.
+<li>Updated documentation and examples.
+</ul>
+Upgrade considerations:
+<ul>
+
+<li>Since ProGuard now treats the <code>Exceptions</code> attribute as
+    optional, you may have to specify <code>-keepattributes Exceptions</code>,
+    notably when processing code that is to be used as a library.
+
+<li>ProGuard now preverifies code for Java Micro Edition, if you specify the
+    option <code>-microedition</code>. You then no longer need to process the
+    code with an external preverifier.
+
+<li>You should preferably specify <code>-repackageclasses</code> instead of the
+    old option name <code>-defaultpackage</code>.
+</ul>
+
+<h3><div>Dec 2007</div> Version 3.11</h3>
+<ul>
+<li>Fixed optimization of invocations of methods in same class that are
+    accessed through extensions.
+<li>Fixed optimization of invocations of synchronized methods without other
+    side-effects.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Aug 2007</div> Version 3.10</h3>
+<ul>
+<li>Now handling mixed-case input class names when
+    <code>-dontusemixedcaseclassnames</code> is specified.
+<li>Fixed optimization of synchronization on classes, as compiled by Eclipse
+    and Jikes.
+<li>Fixed optimization of switch statements with unreachable cases.
+<li>Avoiding merging subsequent identically named files.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Jun 2007</div> Version 3.9</h3>
+<ul>
+<li>Fixed processing of .class constructs in Java 6.
+<li>Fixed repeated processing of .class constructs.
+<li>Fixed possible division by 0 in optimization step.
+<li>Fixed handling of variable instructions with variable indices larger than
+    255.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Mar 2007</div> Version 3.8</h3>
+<ul>
+<li>Fixed optimization of parameters used as local variables.
+<li>Fixed obfuscation with conflicting class member names.
+<li>Fixed incremental obfuscation with incomplete mapping file for library jars.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Dec 2006</div> Version 3.7</h3>
+<ul>
+<li>Now accepting Java 6 class files.
+<li>Fixed shrinking of partially used annotations.
+<li>Improved incremental obfuscation, with new option
+    <code>-useuniqueclassmembernames</code>.
+<li>Printing more information in case of conflicting configuration and input.
+<li>Fixed optimization of repeated array length instruction.
+<li>Fixed optimization of subsequent try/catch/finally blocks with return
+    statements.
+<li>Fixed optimization of complex stack operations.
+<li>Fixed optimization of simple infinite loops.
+<li>Fixed optimization of expressions with constant doubles.
+<li>Tuned optimization to improve size reduction after preverification.
+<li>Fixed overflows of offsets in long code blocks.
+<li>Now allowing class names containing dashes.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>May 2006</div> Version 3.6</h3>
+<ul>
+<li>No longer automatically keeping classes in parameters of specified methods
+    from obfuscation and optimization (introduced in version 3.4).
+<li>Fixed inlining of interfaces that are used in .class constructs.
+<li>Fixed removal of busy-waiting loops reading volatile fields.
+<li>Fixed optimization of comparisons of known integers.
+<li>Fixed optimization of known branches.
+<li>Fixed optimization of method calls on arrays of interfaces.
+<li>Fixed optimization of method calls without side-effects.
+<li>Fixed optimization of nested try/catch/finally blocks with return
+    statements.
+<li>Fixed initialization of library classes that only appear in descriptors.
+<li>Fixed matching of primitive type wildcards in configuration.
+<li>Fixed the boilerplate specification for enumerations in the GUI.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Jan 2006</div> Version 3.5</h3>
+<ul>
+<li>Fixed obfuscation of class members with complex visibility.
+<li>Fixed optimization bugs causing stack verification errors.
+<li>Fixed optimization bug causing overridden methods to be finalized.
+<li>Fixed optimization bug causing abstract method errors for retro-fitted
+    library methods.
+<li>Fixed optimization bug evaluating code with constant long values.
+<li>Fixed bug in updating of optional local variable table attributes and local
+    variable type table attributes after optimization.
+<li>Fixed interpretation of comma-separated class names without wildcards.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Oct 2005</div> Version 3.4</h3>
+<ul>
+<li>Extended optimizations: removing duplicate code within methods.
+<li>Extended regular expressions for class names to comma-separated lists.
+<li>Now automatically keeping classes in descriptors of kept class members.
+<li>Added verbose statistics for optimizations.
+<li>Added boilerplate Number optimizations in GUI.
+<li>Fixed <code>Class.forName</code> detection.
+<li>Fixed incremental obfuscation bug.
+<li>Fixed optimization bug causing stack verification errors.
+<li>Fixed optimization bugs related to removal of unused parameters.
+<li>Fixed exception when optimizing code with many local variables.
+<li>Fixed exception when saving configuration with initializers in GUI.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Jun 2005</div> Version 3.3</h3>
+<ul>
+<li>Extended optimizations: making methods private and static when possible,
+    making classes static when possible, removing unused parameters.
+<li>Made file names relative to the configuration files in which they are
+    specified. Added <code>-basedirectory</code> option.
+<li>Added <code>-whyareyoukeeping</code> option to get details on why given
+    classes and class members are being kept.
+<li>Added warnings for misplaced class files.
+<li>Improved printing of notes for <code>Class.forName</code> constructs.
+<li>Implemented '<code>assumenosideeffects</code>' nested element in Ant task.
+<li>Improved processing of annotations.
+<li>Fixed reading and writing of parameter annotations.
+<li>Fixed various optimization bugs.
+<li>Fixed wildcards not matching '-' character.
+<li>Fixed wildcard bug and checkbox bugs in GUI.
+<li>Setting file chooser defaults in GUI.
+<li>Leaving room for growBox in GUI on Mac OS X.
+<li>Properly closing configuration files.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Dec 2004</div> Version 3.2</h3>
+<ul>
+<li>Fixed JDK5.0 processing bugs.
+<li>Fixed optimization bugs.
+<li>Fixed relative paths in Ant task.
+<li>Improved speed of shrinking step.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Nov 2004</div> Version 3.1</h3>
+<ul>
+<li>Improved obfuscation and shrinking of private class members.
+<li>Added inlining of interfaces with single implementations.
+<li>Added option to specify obfuscation dictionary.
+<li>Added option to read package visible library class members.
+<li>Extended support for JDK5.0 attributes.
+<li>Fixed various optimization bugs.
+<li>Modified Ant task to accept paths instead of filesets.
+<li>Fixed two Ant task bugs.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Aug 2004</div> Version 3.0</h3>
+<ul>
+<li>Added bytecode optimization step, between shrinking step and obfuscation
+    step.
+<li>Generalized filtered recursive reading and writing of jars, wars, ears,
+    zips, and directories.
+<li>Added support for grouping input and output jars, wars, ears, zips, and
+    directories.
+<li>Added support for applying mapping files to library classes.
+<li>Removed <code>-resourcejars</code> option. Resources should now be read
+    using regular <code>-injars</code> options, using filters if necessary.
+<li>Rewrote Ant task. Input and output modification dates are not checked at
+    the moment. Minor changes in XML schema:
+    <ul>
+    <li>Filters now specified using attributes.
+    <li>'<code>outjars</code>' now nested element instead of attribute.
+    <li>'<code>type</code>' attribute of <code>&lt;method&gt;</code> element no
+        longer defaults to '<code>void</code>'.
+    <li><code>&lt;</code> and <code>&gt;</code> characters now have to be
+        encoded in embedded configurations.
+    <li><code>&lt;proguardconfiguration&gt;</code> task no longer accepts
+        attributes.
+    </ul>
+<li>Updated J2ME WTK plugin, now customizable through configuration file.
+<li>Updated GUI.
+<li>Fixed various processing bugs.
+<li>Fixed ReTrace parsing bugs.
+<li>Improved jar compression.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Mar 2004</div> Version 2.1</h3>
+<ul>
+<li>Added support for JDK1.5 classes.
+<li>Added additional wildcard for matching primitive types.
+<li>Added possibility to switch off notes about duplicate class definitions.
+<li>Fixed use of multiple filters on output jars.
+<li>Fixed option to keep all attributes.
+<li>Fixed various Ant task bugs.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Dec 2003</div> Version 2.0</h3>
+<ul>
+<li>Added a graphical user interface for ProGuard and ReTrace.
+<li>Added <code>-applymapping</code> option for incremental obfuscation.
+<li>Added support for filtering input and output files.
+<li>Added support for the J++ <code>SourceDir</code> attribute.
+<li>Improved detection of <code>.class</code> constructs.
+<li>Improved handling of misplaced manifest files.
+<li>Improved implementation of ReTrace.
+<li>Worked around String UTF-8 encoding bug affecting foreign characters.
+<li>Fixed exception when ignoring warnings.
+<li>Fixed various Ant task bugs.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Aug 2003</div> Version 1.7</h3>
+<ul>
+<li>Fixed various Ant task bugs.
+<li>Fixed ClassCastException due to explicitly used abstract classes with
+    implicitly used interfaces targeted at JRE1.2 (the default in JDK1.4).
+<li>Fixed <code>-defaultpackage</code> bug for protected classes and class
+    members.
+<li>Fixed ReTrace bug when retracing without line number tables.
+<li>Worked around zip package problems with duplicate out entries and rogue
+    manifest files.
+<li>Added work-around for handling malformed legacy interface class files.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>May 2003</div> Version 1.6</h3>
+<ul>
+<li>Added support for Ant.
+<li>Added support for the J2ME Wireless Toolkit.
+<li>Added support for reading and writing directory hierarchies.
+<li>Added option for specifying resource jars and directories.
+<li>Added support for wildcards in class member specifications.
+<li>Improved handling of the <code>-defaultpackage</code> option.
+<li>Improved stack trace parsing in ReTrace tool.
+<li>Fixed processing of libraries containing public as well as non-public
+    extensions of non-public classes.
+<li>Fixed examples for processing libraries, midlets, and serializable code.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Jan 2003</div> Version 1.5</h3>
+<ul>
+<li>Fixed processing of retrofitted library interfaces.
+<li>Fixed processing of <code>.class</code> constructs in internal classes
+    targeted at JRE1.2 (the default in JDK1.4).
+<li>Fixed <code>-dump</code> option when <code>-outjar</code> option is not
+    present.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Nov 2002</div> Version 1.4</h3>
+<ul>
+<li>Now copying resource files over from the input jars to the output jar.
+<li>Added option to obfuscate using lower-case class names only.
+<li>Added better option for obfuscating native methods.
+<li>Added option not to ignore non-public library classes.
+<li>Added automatic <code>.class</code> detection for classes compiled with
+    Jikes.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Sep 2002</div> Version 1.3</h3>
+<ul>
+<li>Added support for wildcards in class names.
+<li>Added tool to de-obfuscate stack traces.
+<li>Added options to print processing information to files.
+<li>Added option to rename source file attributes.
+<li>Fixed processing of implicitly used interfaces targeted at JRE1.2 (the
+    default in JDK1.4)
+<li>Fixed processing of configurations with negated access modifiers.
+<li>Fixed duplicate class entry bug.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Aug 2002</div> Version 1.2</h3>
+<ul>
+<li>Improved speed.
+<li>Fixed processing of classes targeted at JRE1.2 (the default in JDK1.4)
+    with references to their own subclasses.
+<li>Fixed processing of static initializers in J2ME MIDP applications.
+<li>Fixed processing of retrofitted interfaces (again).
+<li>Added more flexible handling of white space in configuration.
+<li>Updated documentation.
+</ul>
+
+<h3><div>Jul 2002</div> Version 1.1</h3>
+<ul>
+<li>Added automatic detection of <code>Class.forName("MyClass")</code>,
+    <code>MyClass.class</code>, and
+    <code>(MyClass)Class.forName(variable).newInstance()</code> constructs.
+    This greatly simplifies configuration.
+<li>Added options to keep class names and class member names without affecting
+    any shrinking. They are mostly useful for native methods and serializable
+    classes.
+<li>Fixed processing of retrofitted interfaces.
+<li>Added handling of missing/invalid manifest file in input jar.
+<li>Updated documentation and examples.
+</ul>
+
+<h3><div>Jun 2002</div> Version 1.0</h3>
+<ul>
+<li>First public release, based on class parsing code from Mark Welsh's
+    <b>RetroGuard</b>.
+</ul>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+
+</body>
+</html>
diff --git a/docs/drop1.gif b/docs/drop1.gif
new file mode 100644
index 0000000..426d856
--- /dev/null
+++ b/docs/drop1.gif
Binary files differ
diff --git a/docs/drop2.gif b/docs/drop2.gif
new file mode 100644
index 0000000..b607542
--- /dev/null
+++ b/docs/drop2.gif
Binary files differ
diff --git a/docs/drop3.gif b/docs/drop3.gif
new file mode 100644
index 0000000..11ce424
--- /dev/null
+++ b/docs/drop3.gif
Binary files differ
diff --git a/docs/favicon.ico b/docs/favicon.ico
new file mode 100644
index 0000000..3923ec1
--- /dev/null
+++ b/docs/favicon.ico
Binary files differ
diff --git a/docs/feedback.html b/docs/feedback.html
new file mode 100644
index 0000000..a7e770b
--- /dev/null
+++ b/docs/feedback.html
@@ -0,0 +1,106 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-script-type" content="text/javascript">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Feedback</title>
+</head>
+<body>
+
+<h2>Feedback</h2>
+
+By now, I've invested a fair amount of time in <b>ProGuard</b>. You can help
+by providing feedback! If you have problems, bugs, bug fixes, ideas,
+encouragements, etc., please let me know:
+<p>
+<ul>
+<li>The <a href="http://sourceforge.net/forum/forum.php?forum_id=182456"
+    target="other">help forum</a> (at SourceForge) is the right place to ask
+    questions about any problems you might have configuring and running
+    <b>ProGuard</b>.
+    <p>
+
+<li>The <a href="http://sourceforge.net/forum/forum.php?forum_id=182455"
+    target="other">open discussion forum</a> (at SourceForge) offers a place
+    to share your thoughts and discuss new ideas.
+    <p>
+
+<li>The <a
+
+    href="http://sourceforge.net/tracker/?atid=474704&group_id=54750&func=browse"
+    target="other">bug tracking page</a> (at SourceForge) allows you to submit
+    and consult bug reports. Please make sure the reports are complete and
+    concise. If I can't reproduce the problem, I most likely can't fix it
+    either.
+    <p>
+
+<li>The <a
+
+    href="http://sourceforge.net/tracker/?atid=474707&group_id=54750&func=browse"
+    target="other">feature request page</a> (at SourceForge) allows you to
+    submit and consult feature requests. I generally have my own road map in
+    mind, but this is the place express your interest in new and existing
+    ideas.
+    <p>
+
+<li>The <a href="http://sourceforge.net/project/showfiles.php?group_id=54750"
+    target="other">download section</a> at SourceForge and the <a
+    href="http://software.freshmeat.net/projects/proguard/"
+    target="other">project page</a> at FreshMeat offer the possibility to
+    subscribe to the announcements of new releases. They are the most
+    efficient way to stay abreast of the latest developments.
+    <p>
+
+<li>For anything that doesn't fall in the above categories, you can mail me
+    directly at
+
+<script type="text/javascript" language="JavaScript">
+<!--
+document.write("<a href=\"ma");
+document.write("ilto:");
+document.write("lafortune");
+document.write("&#64;");
+document.write("users.sourceforge.net\">");
+document.write("lafortune");
+document.write("&#64;");
+document.write("users.sourceforge.net");
+document.write("</a>");
+document.write(" <em>or</em> at ");
+document.write("<a href=\"ma");
+document.write("ilto:");
+document.write("eric");
+document.write("&#64;");
+document.write("graphics.cornell.edu\">");
+document.write("eric");
+document.write("&#64;");
+document.write("graphics.cornell.edu");
+document.write("</a>");
+//-->
+</script>
+<noscript>
+&lt;&nbsp;lafortune&nbsp;&#64;&nbsp;users&nbsp;.&nbsp;sourceforge&nbsp;.&nbsp;net&nbsp;&gt;
+<em>or</em> at
+&lt;&nbsp;eric&nbsp;&#64;&nbsp;graphics&nbsp;.&nbsp;cornell&nbsp;.&nbsp;edu&nbsp;&gt; (please remove the spaces)
+</noscript>
+.
+</ul>
+<p>
+I can't promise a swift answer, or any answer at all, for that matter, but I
+like seeing any constructive comments.
+<p>
+
+<b>ProGuard</b> isn't a typical open source project, in the sense that I am
+<em>not</em> looking for code contributions. Developing on my own allows me to
+do things my way, without the overhead and compromises associated with larger
+projects.
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+
+</body>
+</html>
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..771bb6a
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,72 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-script-type" content="text/javascript">
+<meta http-equiv="content-style-type" content="text/css">
+<meta name="author" content="Eric Lafortune">
+<meta name="description" content="ProGuard: java shrinker, optimizer, obfuscator, and preverifier">
+<meta name="keywords" content="java obfuscator, optimizer, shrinker, preverfier">
+<link rel="stylesheet" type="text/css" href="style.css">
+<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
+<title>ProGuard</title>
+</head>
+<frameset
+ rows="50,*"
+ framespacing="0"
+ frameborder="no">
+
+<frame
+ name="title"
+ src="title.html"
+ scrolling="no"
+ marginwidth="0"
+ marginheight="0"
+ noresize>
+
+<frameset
+ cols="120,*"
+ framespacing="0"
+ frameborder="no">
+
+<frame
+ name="sections"
+ src="sections.html"
+ scrolling="no"
+ marginwidth="0"
+ marginheight="0"
+ noresize>
+
+<frame
+ name="main"
+ src="main.html"
+ scrolling="auto"
+ marginwidth="10"
+ marginheight="10"
+ noresize>
+
+</frameset>
+</frameset>
+
+<noframes>
+<body>
+<p class="intro">
+<b>ProGuard</b> is a free Java class file shrinker, optimizer, and obfuscator.
+It can detect and remove unused classes, fields, methods, and attributes. It
+can then optimize bytecode and remove unused instructions. Finally, it can
+rename the remaining classes, fields, and methods using short meaningless
+names. The resulting jars are smaller and harder to reverse-engineer.
+</p>
+<p>
+Your browser doesn't support frames, but that's cool.
+<p>
+You can go straight to the <a href="main.html">main page</a>.
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</noframes>
+</html>
diff --git a/docs/license.html b/docs/license.html
new file mode 100644
index 0000000..b4654fd
--- /dev/null
+++ b/docs/license.html
@@ -0,0 +1,47 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard License</title>
+</head>
+<body>
+
+<h2>License</h2>
+
+<b>ProGuard</b> is free. You can use it freely for processing your
+applications, commercial or not. Your code obviously remains yours after
+having been processed, and its license can remain the same.
+<p>
+
+<b>ProGuard</b> itself is copyrighted, but its distribution license provides
+you with some rights for modifying and redistributing its code and its
+documentation. More specifically, <b>ProGuard</b> is distributed under the
+terms of the <a href="GPL.html">GNU General Public License</a> (GPL), version
+2, as published by the <a href="http://www.fsf.org/" target="other">Free
+Software Foundation</a> (FSF). In short, this means that you may freely
+redistribute the program, modified or as is, on the condition that you make
+the complete source code available as well. If you develop a program that is
+linked with
+<b>ProGuard</b>, the program as a whole has to be distributed at no charge
+under the GPL. I am granting a <a href="GPL_exception.html">special
+exception</a> to the latter clause (in wording suggested by
+the <a href="http://www.gnu.org/copyleft/gpl-faq.html#GPLIncompatibleLibs"
+target="other">FSF</a>), for combinations with the following stand-alone
+applications: Apache Ant, Apache Maven, the Eclipse ProGuardDT GUI, the
+EclipseME JME IDE, the Sun NetBeans Java IDE, the Sun JME Wireless Toolkit,
+and the Javaground Tools.
+
+<p>
+The <b>ProGuard user documentation</b> represents an important part of this
+work. It may only be redistributed without changes, along with the unmodified
+version of the code.
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/luciadlogo.png b/docs/luciadlogo.png
new file mode 100644
index 0000000..0147ce3
--- /dev/null
+++ b/docs/luciadlogo.png
Binary files differ
diff --git a/docs/main.html b/docs/main.html
new file mode 100644
index 0000000..e988c79
--- /dev/null
+++ b/docs/main.html
@@ -0,0 +1,92 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<meta name="author" content="Eric Lafortune">
+<meta name="description" content="ProGuard: java shrinker, optimizer, obfuscator, and preverifier">
+<meta name="keywords" content="java obfuscator, optimizer, shrinker, preverfier">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Main</title>
+</head>
+<body>
+
+<h2>Main</h2>
+
+<p class="intro">
+<b>ProGuard</b> is a free Java class file shrinker, optimizer, obfuscator, and
+preverifier. It detects and removes unused classes, fields, methods, and
+attributes. It optimizes bytecode and removes unused instructions. It renames
+the remaining classes, fields, and methods using short meaningless names.
+Finally, it preverifies the processed code for Java 6 or for Java Micro
+Edition.
+</p>
+Some uses of <b>ProGuard</b> are:
+<ul>
+
+<li>Creating more compact code, for smaller code archives, faster transfer
+    across networks, faster loading, and smaller memory footprints.
+
+<li>Making programs and libraries harder to reverse-engineer.
+
+<li>Listing dead code, so it can be removed from the source code.
+
+<li>Retargeting and preverifying existing class files for Java 6, to take full
+    advantage of Java 6's faster class loading.
+
+</ul>
+<p>
+<b>ProGuard</b>'s main advantage compared to other Java obfuscators is
+probably its compact template-based configuration. A few intuitive command
+line options or a simple configuration file are usually sufficient. For
+instance, the following configuration option preserves all applets in a jar:
+<pre>
+    -keep public class * extends java.applet.Applet
+</pre>
+The user manual explains all available options and shows more examples of this
+powerful configuration style.
+<p>
+<b>ProGuard</b> is fast. It only takes seconds to process programs and
+libraries of several megabytes. The results section presents actual figures
+for a number of applications.
+<p>
+<b>ProGuard</b> is a command-line tool with an optional graphical user
+interface. It also comes with plugins for Ant and for the JME Wireless
+Toolkit.
+<p>
+<p class="intro">
+Version 4.0 introduced preverification and more bytecode optimizations. Please
+report any problems, so they can be fixed soon.
+</p>
+The following sections provide more detailed information:
+<ul>
+<li><a href="main.html">Main</a>: this overview page.
+<li><a href="results.html">Results</a>: some results obtained with
+    <b>ProGuard</b>, including timings and memory usage.
+<li><a href="FAQ.html">FAQ</a>: answers to some Frequently Asked Questions.
+<li><a href="manual/index.html">Manual</a>: the complete <b>ProGuard</b> user
+    manual, with examples and troubleshooting tips.
+<li><a href="quality.html">Quality</a>: a discussion of the (excellent) quality
+    of <b>ProGuard</b>'s code.
+<li><a href="screenshots.html">Screenshots</a>: some impressions of what            <b>ProGuard</b> looks like.
+<li><a href="testimonials.html">Testimonials</a>: what users think of
+    <b>ProGuard</b>.
+<li><a href="license.html">License</a>: <b>ProGuard</b> is free, under a GPL
+    license.
+<li><a href="downloads.html">Downloads</a>: download the <b>ProGuard</b>
+    package yourself.
+<li><a href="feedback.html">Feedback</a>: tell me about your experiences, or
+    learn from others on our forums.
+<li><a href="acknowledgements.html">Acknowledgements</a>: people who have been
+    helpful.
+<li><a href="alternatives.html">Alternatives</a>: other Java obfuscators,
+    optimizers, and shrinkers.
+</ul>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/ant.html b/docs/manual/ant.html
new file mode 100644
index 0000000..bc3a3b4
--- /dev/null
+++ b/docs/manual/ant.html
@@ -0,0 +1,610 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>Ant Task</title>
+</head>
+<body>
+
+<h2>Ant Task</h2>
+
+<b>ProGuard</b> can be run as a task in the Java-based build tool Ant (version
+1.6.0 or higher).
+<p>
+
+Before you can use the <code>proguard</code> task, you have to tell Ant about
+this new task. The easiest way is to add the following line to your
+<code>build.xml</code> file:
+<p>
+
+<pre>
+&lt;taskdef resource="proguard/ant/task.properties"
+         classpath="/usr/local/java/proguard/lib/proguard.jar" /&gt;
+</pre>
+<p>
+
+Please make sure the class path is set correctly for your system.
+<p>
+
+There are three ways to configure the ProGuard task: using an external
+configuration file, using embedded ProGuard configuration options, or using
+the equivalent XML configuration tags. These three ways can be combined,
+depending on practical circumstances and personal preference.
+<p>
+
+<h3>1. An external ProGuard configuration file</h3>
+
+The simplest way to use the ProGuard task in an Ant build file is to keep your
+ProGuard configuration file, and include it from Ant. You can include your
+ProGuard configuration file by setting
+the <a href="#configuration_attribute"><code>configuration</code></a>
+attribute of your
+<code>proguard</code> task. Your ant build file will then look like this:
+<p>
+
+<pre>
+&lt;taskdef resource="proguard/ant/task.properties"
+         classpath="/usr/local/java/proguard/lib/proguard.jar" /&gt;
+&lt;proguard configuration="myconfigfile.pro"/&gt;
+</pre>
+<p>
+
+This is a convenient option if you prefer ProGuard's configuration style over
+XML, if you want to keep your build file small, or if you have to share your
+configuration with developers who don't use Ant.
+<p>
+
+<h3>2. Embedded ProGuard configuration options</h3>
+
+Instead of keeping an external ProGuard configuration file, you can also copy
+the contents of the file into the nested text of the <code>proguard</code> task
+(the PCDATA area). Your Ant build file will then look like this:
+<p>
+
+<pre>
+&lt;taskdef resource="proguard/ant/task.properties"
+         classpath="/usr/local/java/proguard/lib/proguard.jar" /&gt;
+&lt;proguard&gt;
+  -libraryjars ${java.home}/lib/rt.jar
+  -injars      in.jar
+  -outjars     out.jar
+
+  -keepclasseswithmembers public class * {
+      public static void main(java.lang.String[]);
+  }
+&lt;/proguard&gt;
+</pre>
+<p>
+
+Some minor syntactical changes are required in order to conform with the XML
+standard.
+<p>
+
+Firstly, the <code>#</code> character cannot be used for comments in an XML
+file. Comments must be enclosed by an opening <code>&lt;!--</code> and a
+closing <code>--&gt;</code>. All occurrences of the <code>#</code> character
+can be removed.
+<p>
+
+Secondly, the use of <code>&lt;</code> and <code>&gt;</code> characters would
+upset the structure of the XML build file. Environment variables are now
+enclosed by an opening <code>${</code> and a closing <code>}</code>. This
+syntax also allows you to use Ant properties within the ProGuard
+configuration. Other occurrences of <code>&lt;</code> and <code>&gt;</code>
+have to be encoded as <code>&amp;lt;</code> and <code>&amp;gt;</code>.
+<p>
+
+<h3>3. XML configuration tags</h3>
+
+If you really prefer a full-blown XML configuration, you can replace the
+ProGuard configuration options by XML configuration tags. The resulting
+configuration will be equivalent, but much more verbose and difficult to read,
+as XML goes. The remainder of this page presents the supported tags. For a
+more extensive discussion of their meaning, please consult the traditional <a
+href="usage.html">Usage</a> section. You can find some sample configuration
+files in the <code>examples/ant</code> directory of the ProGuard distribution.
+<p>
+
+<a name="proguard">&nbsp;</a>
+<h2>Task Attributes and Nested Elements</h2>
+
+The <code><b>&lt;proguard&gt;</b></code> task and the
+<code><b>&lt;proguardconfiguration&gt;</b></code> task can have the following
+attributes (only for <code>&lt;proguard&gt;</code>) and nested
+elements:
+
+<dl>
+
+<dt><a name="configuration_attribute"><code><b>configuration</b></code></a>
+    = "<i>filename</i>"</dt>
+<dd>Read and merge options from the given ProGuard-style configuration
+    file. Note: for reading XML-style configurations, use the <a
+    href="#configuration_element"><code>configuration</code></a>
+    <i>element</i>.</dd>
+
+<dt><a href="usage.html#dontskipnonpubliclibraryclasses"><code><b>skipnonpubliclibraryclasses</b></code></a>
+    = "<i>boolean</i>"
+    (default = true)</dt>
+<dd>Ignore non-public library classes.</dd>
+
+<dt><a href="usage.html#dontskipnonpubliclibraryclassmembers"><code><b>skipnonpubliclibraryclassmembers</b></code></a>
+    = "<i>boolean</i>"
+    (default = true)</dt>
+<dd>Ignore package visible library class members.</dd>
+
+<dt><a href="usage.html#target"><code><b>target</b></code></a>
+    = "<i>version</i>"
+    (default = none)</dt>
+<dd>Set the given version number in the processed classes.</dd>
+
+<dt><a href="usage.html#forceprocessing"><code><b>forceprocessing</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Process the input, even if the output seems up to date.</dd>
+
+<dt><a href="usage.html#printseeds"><code><b>printseeds</b></code></a>
+    = "<i>boolean or filename</i>"
+    (default = false)</dt>
+<dd>List classes and class members matched by the various <code>keep</code>
+    commands, to the standard output or to the given file.</dd>
+
+<dt><a href="usage.html#dontshrink"><code><b>shrink</b></code></a>
+    = "<i>boolean</i>"
+    (default = true)</dt>
+<dd>Shrink the input class files.</dd>
+
+<dt><a href="usage.html#printusage"><code><b>printusage</b></code></a>
+    = "<i>boolean or filename</i>"
+    (default = false)</dt>
+<dd>List dead code of the input class files, to the standard output or to the
+    given file.</dd>
+
+<dt><a href="usage.html#dontoptimize"><code><b>optimize</b></code></a>
+    = "<i>boolean</i>"
+    (default = true)</dt>
+<dd>Optimize the input class files.</dd>
+
+<dt><a href="usage.html#optimizationpasses"><code><b>optimizationpasses</b></code></a>
+    = "<i>n</i>"
+    (default = 1)</dt>
+<dd>The number of optimization passes to be performed.</dd>
+
+<dt><a href="usage.html#allowaccessmodification"><code><b>allowaccessmodification</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Allow the access modifiers of classes and class members to be modified,
+    while optimizing.</dd>
+
+<dt><a href="usage.html#mergeinterfacesaggressively"><code><b>mergeinterfacesaggressively</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Allow any interfaces to be merged, while optimizing.</dd>
+
+<dt><a href="usage.html#dontobfuscate"><code><b>obfuscate</b></code></a>
+    = "<i>boolean</i>"
+   (default = true)</dt>
+<dd>Obfuscate the input class files.</dd>
+
+<dt><a href="usage.html#printmapping"><code><b>printmapping</b></code></a>
+    = "<i>boolean or filename</i>"
+    (default = false)</dt>
+<dd>Print the mapping from old names to new names for classes and class members
+    that have been renamed, to the standard output or to the given file.</dd>
+
+<dt><a href="usage.html#applymapping"><code><b>applymapping</b></code></a>
+    = "<i>filename</i>"
+    (default = none)</dt>
+<dd>Reuse the given mapping, for incremental obfuscation.</dd>
+
+<dt><a href="usage.html#obfuscationdictionary"><code><b>obfuscationdictionary</b></code></a>
+    = "<i>filename</i>"
+    (default = none)</dt>
+<dd>Use the words in the given text file as obfuscated field names and method
+    names.</dd>
+
+<dt><a href="usage.html#classobfuscationdictionary"><code><b>classobfuscationdictionary</b></code></a>
+    = "<i>filename</i>"
+    (default = none)</dt>
+<dd>Use the words in the given text file as obfuscated class names.</dd>
+
+<dt><a href="usage.html#packageobfuscationdictionary"><code><b>packageobfuscationdictionary</b></code></a>
+    = "<i>filename</i>"
+    (default = none)</dt>
+<dd>Use the words in the given text file as obfuscated package names.</dd>
+
+<dt><a href="usage.html#overloadaggressively"><code><b>overloadaggressively</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Apply aggressive overloading while obfuscating.</dd>
+
+<dt><a href="usage.html#useuniqueclassmembernames"><code><b>useuniqueclassmembernames</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Ensure uniform obfuscated class member names for subsequent incremental
+    obfuscation.</dd>
+
+<dt><a href="usage.html#dontusemixedcaseclassnames"><code><b>usemixedcaseclassnames</b></code></a>
+    = "<i>boolean</i>"
+    (default = true)</dt>
+<dd>Generate mixed-case class names while obfuscating.</dd>
+
+<dt><a href="usage.html#flattenpackagehierarchy"><code><b>flattenpackagehierarchy</b></code></a>
+    = "<i>package_name</i>"
+    (default = none)</dt>
+<dd>Repackage all packages that are renamed into the single given parent
+    package.</dd>
+
+<dt><a href="usage.html#repackageclasses"><code><b>repackageclasses</b></code></a>
+    = "<i>package_name</i>"
+    (default = none)</dt>
+<dd>Repackage all class files that are renamed into the single given
+    package.</dd>
+
+<dt><a href="usage.html#renamesourcefileattribute"><code><b>renamesourcefileattribute</b></code></a>
+    = "<i>string</i>"
+    (default = none)</dt>
+<dd>Put the given constant string in the <code>SourceFile</code>
+    attributes.</dd>
+
+<dt><a href="usage.html#dontpreverify"><code><b>preverify</b></code></a>
+    = "<i>boolean</i>"
+    (default = true)</dt>
+<dd>Preverify the processed class files if they are targeted at Java Micro
+    Edition or at Java 6 or higher.</dd>
+
+<dt><a href="usage.html#microedition"><code><b>microedition</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Targets the processed class files at Java Micro Edition.</dd>
+
+<dt><a href="usage.html#verbose"><code><b>verbose</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Write out some more information during processing.</dd>
+
+<dt><a href="usage.html#dontnote"><code><b>note</b></code></a>
+    = "<i>boolean</i>"
+   (default = true)</dt>
+<dd>Print notes about potential mistakes or omissions in the configuration.
+    Use the nested element <a href="#dontnote">dontnote</a> for more
+    fine-grained control.</dd>
+
+<dt><a href="usage.html#dontwarn"><code><b>warn</b></code></a>
+    = "<i>boolean</i>"
+    (default = true)</dt>
+<dd>Print warnings about unresolved references. Use the nested
+    element <a href="#dontwarn">dontwarn</a> for more fine-grained
+    control. <i>Only use this option if you know what you're doing!</i></dd>
+
+<dt><a href="usage.html#ignorewarnings"><code><b>ignorewarnings</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Print warnings about unresolved references, but continue processing
+    anyhow. <i>Only use this option if you know what you're doing!</i></dd>
+
+<dt><a href="usage.html#printconfiguration"><code><b>printconfiguration</b></code></a>
+    = "<i>boolean or filename</i>"
+    (default = false)</dt>
+<dd>Write out the entire configuration in traditional ProGuard style, to the
+    standard output or to the given file. Useful to replace unreadable
+    XML configurations.</dd>
+
+<dt><a href="usage.html#dump"><code><b>dump</b></code></a>
+    = "<i>boolean or filename</i>"
+    (default = false)</dt>
+<dd>Write out the internal structure of the processed class files, to the
+    standard output or to the given file.</dd>
+
+<dt><a href="usage.html#injars"><code><b>&lt;injar</b></code></a>
+    <a href="#classpath"><i>class_path</i></a>
+    <code><b>/&gt;</b></code></dt>
+<dd>Specifies the program jars (or wars, ears, zips, or directories).</dd>
+
+<dt><a href="usage.html#outjars"><code><b>&lt;outjar</b></code></a>
+    <a href="#classpath"><i>class_path</i></a>
+    <code><b>/&gt;</b></code></dt>
+<dd>Specifies the name of the output jars (or wars, ears, zips, or
+    directories).</dd>
+
+<dt><a href="usage.html#libraryjars"><code><b>&lt;libraryjar</b></code></a>
+    <a href="#classpath"><i>class_path</i></a>
+    <code><b>/&gt;</b></code></dt>
+<dd>Specifies the library jars (or wars, ears, zips, or directories).</dd>
+
+<dt><a href="usage.html#keepdirectories"><code><b>&lt;keepdirectory name = </b></code></a>"<i>directory_name</i>"
+    <code><b>/&gt;</b></code><br/>
+    <a href="usage.html#keepdirectories"><code><b>&lt;keepdirectories filter = </b></code></a>"<a href="usage.html#filefilters"><i>directory_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Keep the specified directories in the output jars (or wars, ears, zips, or
+    directories).</dd>
+
+<dt><a href="usage.html#keep"><code><b>&lt;keep</b></code></a>
+    <a href="#keepmodifier"><i>modifiers</i></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <code><b>&gt;</b></code>
+    <a href="#classmemberspecification"><i>class_member_specifications</i></a>
+    <code><b>&lt;/keep&gt;</b></code></dt>
+<dd>Preserve the specified classes <i>and</i> class members.</dd>
+
+<dt><a href="usage.html#keepclassmembers"><code><b>&lt;keepclassmembers</b></code></a>
+    <a href="#keepmodifier"><i>modifiers</i></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <code><b>&gt;</b></code>
+    <a href="#classmemberspecification"><i>class_member_specifications</i></a>
+    <code><b>&lt;/keepclassmembers&gt;</b></code></dt>
+<dd>Preserve the specified class members, if their classes are preserved as
+    well.</dd>
+
+<dt><a href="usage.html#keepclasseswithmembers"><code><b>&lt;keepclasseswithmembers</b></code></a>
+    <a href="#keepmodifier"><i>modifiers</i></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <code><b>&gt;</b></code>
+    <a href="#classmemberspecification"><i>class_member_specifications</i></a>
+    <code><b>&lt;/keepclasseswithmembers&gt;</b></code></dt>
+<dd>Preserve the specified classes <i>and</i> class members, if all of the
+    specified class members are present.</dd>
+
+<dt><a href="usage.html#keepnames"><code><b>&lt;keepnames</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <code><b>&gt;</b></code>
+    <a href="#classmemberspecification"><i>class_member_specifications</i></a>
+    <code><b>&lt;/keepnames&gt;</b></code></dt>
+<dd>Preserve the names of the specified classes <i>and</i> class members (if
+    they aren't removed in the shrinking step).</dd>
+
+<dt><a href="usage.html#keepclassmembernames"><code><b>&lt;keepclassmembernames</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <code><b>&gt;</b></code>
+    <a href="#classmemberspecification"><i>class_member_specifications</i></a>
+    <code><b>&lt;/keepclassmembernames&gt;</b></code></dt>
+<dd>Preserve the names of the specified class members (if they aren't removed
+    in the shrinking step).</dd>
+
+<dt><a href="usage.html#keepclasseswithmembernames"><code><b>&lt;keepclasseswithmembernames</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <code><b>&gt;</b></code>
+    <a href="#classmemberspecification"><i>class_member_specifications</i></a>
+    <code><b>&lt;/keepclasseswithmembernames&gt;</b></code></dt>
+<dd>Preserve the names of the specified classes <i>and</i> class members, if
+    all of the specified class members are present (after the shrinking
+    step).</dd>
+
+<dt><a href="usage.html#whyareyoukeeping"><code><b>&lt;whyareyoukeeping</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <code><b>&gt;</b></code>
+    <a href="#classmemberspecification"><i>class_member_specifications</i></a>
+    <code><b>&lt;/whyareyoukeeping&gt;</b></code></dt>
+<dd>Print details on why the given classes and class members are being kept in
+    the shrinking step.</dd>
+
+<dt><a href="usage.html#assumenosideeffects"><code><b>&lt;assumenosideeffects</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <code><b>&gt;</b></code>
+    <a href="#classmemberspecification"><i>class_member_specifications</i></a>
+    <code><b>&lt;/assumenosideeffects&gt;</b></code></dt>
+<dd>Assume that the specified methods don't have any side effects, while
+    optimizing. <i>Only use this option if you know what you're
+    doing!</i></dd>
+
+<dt><a href="usage.html#optimizations"><code><b>&lt;optimization name = </b></code></a>"<a href="optimizations.html"><i>optimization_name</i></a>"
+    <code><b>/&gt;</b></code><br/>
+    <a href="usage.html#optimizations"><code><b>&lt;optimizations filter = </b></code></a>""<a href="optimizations.html"><i>optimization_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Perform only the specified optimizations.</dd>
+
+<dt><a href="usage.html#keeppackagenames"><code><b>&lt;keeppackagename name = </b></code></a>"<i>package_name</i>"
+    <code><b>/&gt;</b></code><br/>
+    <a href="usage.html#keeppackagenames"><code><b>&lt;keeppackagenames filter = </b></code></a>"<a href="usage.html#filters"><i>package_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Keep the specified package names from being obfuscated. If no name is
+    given, all package names are preserved.</dd>
+
+<dt><a href="usage.html#keepattributes"><code><b>&lt;keepattribute name = </b></code></a>"<i>attribute_name</i>"
+    <code><b>/&gt;</b></code><br/>
+    <a href="usage.html#keepattributes"><code><b>&lt;keepattributes filter = </b></code></a>"<a href="usage.html#filters"><i>attribute_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Preserve the specified optional Java bytecode attributes, with optional
+    wildcards. If no name is given, all attributes are preserved.</dd>
+
+<dt><a href="usage.html#adaptclassstrings"><code><b>&lt;adaptclassstrings filter = </b></code></a>"<a href="usage.html#filters"><i>class_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Adapt string constants in the specified classes, based on the obfuscated
+    names of any corresponding classes.</dd>
+
+<dt><a href="usage.html#adaptresourcefilenames"><code><b>&lt;adaptresourcefilenames filter = </b></code></a>"<a href="usage.html#filefilters"><i>file_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Rename the specified resource files, based on the obfuscated names of the
+    corresponding class files.</dd>
+
+<dt><a href="usage.html#adaptresourcefilecontents"><code><b>&lt;adaptresourcefilecontents filter = </b></code></a>"<a href="usage.html#filefilters"><i>file_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Update the contents of the specified resource files, based on the
+    obfuscated names of the processed classes.</dd>
+
+<dt><a name="dontnote" />
+    <a href="usage.html#dontnote"><code><b>&lt;dontnote filter = </b></code></a>"<a href="usage.html#filters"><i>class_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Don't print notes about classes matching the specified class name
+    filter.</dd>
+
+<dt><a name="dontwarn" />
+    <a href="usage.html#dontwarn"><code><b>&lt;dontwarn filter = </b></code></a>"<a href="usage.html#filters"><i>class_filter</i></a>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Don't print warnings about classes matching the specified class name
+    filter. <i>Only use this option if you know what you're doing!</i></dd>
+
+<dt><a name="configuration_element"><code><b>&lt;configuration refid = </b></code></a>"<i>ref_id</i>"
+    <code><b>/&gt;</b></code></dt>
+<dd>Includes the configuration specified in the
+    <code>&lt;proguardconfiguration&gt;</code> task (or
+    <code>&lt;proguard&gt;</code> task) with the attribute <code>id</code> =
+    "<i>ref_id</i>". Note that only the nested elements of this configuration
+    are considered, not the attributes. Also note: for reading ProGuard-style
+    configuration files, use the <a
+    href="#configuration_attribute"><code>configuration</code></a>
+    <i>attribute</i>.</dd>
+
+</dl>
+
+<a name="classpath">&nbsp;</a>
+<h2>Class Path Attributes and Nested Elements</h2>
+
+The jar tags are path tags, so they can have any of the path attributes (or
+nested elements). The most common attributes are:
+
+<dl>
+
+<dt><code><b>path</b></code> = "<i>path</i>"</dt>
+<dd>The names of the jars (or wars, ears, zips, or directories), separated by
+    the path separator.</dd>
+
+<dt><code><b>location</b></code> = "<i>name</i>" (or <code><b>file</b></code>
+    = "<i>name</i>", or <code><b>dir</b></code> = "<i>name</i>", or
+    <code><b>name</b></code> = "<i>name</i>")</dt>
+<dd>Alternatively, the name of a single jar (or war, ear, zip, or
+    directory).</dd>
+
+<dt><code><b>refid</b></code> = "<i>ref_id</i>"</dt>
+<dd>Alternatively, a reference to the path or file set with the attribute
+    <code>id</code> = "<i>ref_id</i>".</dd>
+
+</dl>
+
+In addition, the jar tags can have ProGuard-style filter attributes:
+
+<dl>
+
+<dt><code><b>filter</b></code> =
+    "<a href="usage.html#filefilters"><i>file_filter</i></a>"</dt>
+<dd>An optional filter for all class file names and resource file names that
+    are encountered.</dd>
+
+<dt><code><b>jarfilter</b></code> =
+    "<a href="usage.html#filefilters"><i>file_filter</i></a>"</dt>
+<dd>An optional filter for all jar names that are encountered.</dd>
+
+<dt><code><b>warfilter</b></code> =
+    "<a href="usage.html#filefilters"><i>file_filter</i></a>"</dt>
+<dd>An optional filter for all war names that are encountered.</dd>
+
+<dt><code><b>earfilter</b></code> =
+    "<a href="usage.html#filefilters"><i>file_filter</i></a>"</dt>
+<dd>An optional filter for all ear names that are encountered.</dd>
+
+<dt><code><b>zipfilter</b></code> =
+    "<a href="usage.html#filefilters"><i>file_filter</i></a>"</dt>
+<dd>An optional filter for all zip names that are encountered.</dd>
+
+</dl>
+
+<a name="keepmodifier">&nbsp;</a>
+<h2>Keep Modifier Attributes</h2>
+
+The keep tags can have the following <i>modifier</i> attributes:
+
+<dl>
+
+<dt><a href="usage.html#allowshrinking"><code><b>allowshrinking</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Specifies whether the entry points specified in the keep tag may be
+    shrunk.</dd>
+
+<dt><a href="usage.html#allowoptimization"><code><b>allowoptimization</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Specifies whether the entry points specified in the keep tag may be
+    optimized.</dd>
+
+<dt><a href="usage.html#allowobfuscation"><code><b>allowobfuscation</b></code></a>
+    = "<i>boolean</i>"
+    (default = false)</dt>
+<dd>Specifies whether the entry points specified in the keep tag may be
+    obfuscated.</dd>
+
+</dl>
+
+<a name="classspecification">&nbsp;</a>
+<h2>Class Specification Attributes and Nested Elements</h2>
+
+The keep tags can have the following <i>class_specification</i> attributes and
+<i>class_member_specifications</i> nested elements:
+
+<dl>
+
+<dt><code><b>access</b></code> = "<i>access_modifiers</i>"</dt>
+<dd>The optional access modifiers of the class. Any space-separated list of
+    "public", "final", and "abstract", with optional negators "!".</dd>
+
+<dt><code><b>type</b></code> = "<i>type</i>"</dt>
+<dd>The optional type of the class: one of "class", "interface", or
+    "!interface".</dd>
+
+<dt><code><b>name</b></code> = "<i>class_name</i>"</dt>
+<dd>The optional fully qualified name of the class, with optional
+    wildcards.</dd>
+
+<dt><code><b>extends</b></code> = "<i>class_name</i>"</dt>
+<dd>The optional fully qualified name of the class the specified classes
+    must extend, with optional wildcards.</dd>
+
+<dt><code><b>implements</b></code> = "<i>class_name</i>"</dt>
+<dd>The optional fully qualified name of the class the specified classes
+    must implement, with optional wildcards.</dd>
+
+<dt><code><b>&lt;field</b></code>
+    <a href="#classmemberspecification"><i>class_member_specification</i></a>
+    <code><b>/&gt;</b></code></dt>
+<dd>Specifies a field.</dd>
+
+<dt><code><b>&lt;method</b></code>
+    <a href="#classmemberspecification"><i>class_member_specification</i></a>
+    <code><b>/&gt;</b></code></dt>
+<dd>Specifies a method.</dd>
+
+<dt><code><b>&lt;constructor</b></code>
+    <a href="#classmemberspecification"><i>class_member_specification</i></a>
+    <code><b>/&gt;</b></code></dt>
+<dd>Specifies a constructor.</dd>
+
+</dl>
+
+<a name="classmemberspecification">&nbsp;</a>
+<h2>Class Member Specification Attributes</h2>
+
+The class member tags can have the following <i>class_member_specification</i>
+attributes:
+
+<dl>
+
+<dt><code><b>access</b></code> = "<i>access_modifiers</i>"</dt>
+<dd>The optional access modifiers of the class. Any space-separated list of
+    "public", "protected", "private", "static", etc., with optional negators
+    "!".</dd>
+
+<dt><code><b>type</b></code> = "<i>type</i>"</dt>
+<dd>The optional fully qualified type of the class member, with optional
+    wildcards. Not applicable for constructors, but required for methods for
+    which the <code>parameters</code> attribute is specified.</dd>
+
+<dt><code><b>name</b></code> = "<i>name</i>"</dt>
+<dd>The optional name of the class member, with optional wildcards. Not
+    applicable for constructors.</dd>
+
+<dt><code><b>parameters</b></code> = "<i>parameters</i>"</dt>
+<dd>The optional comma-separated list of fully qualified method parameters,
+    with optional wildcards. Not applicable for fields, but required for
+    constructors, and for methods for which the <code>type</code> attribute is
+    specified.</dd>
+
+</dl>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/attention.gif b/docs/manual/attention.gif
new file mode 100644
index 0000000..1a0c712
--- /dev/null
+++ b/docs/manual/attention.gif
Binary files differ
diff --git a/docs/manual/examples.html b/docs/manual/examples.html
new file mode 100644
index 0000000..3f47fca
--- /dev/null
+++ b/docs/manual/examples.html
@@ -0,0 +1,1302 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Examples</title>
+</head>
+<body>
+
+<h2>Examples</h2>
+
+Some typical useful configurations:
+<ol>
+<li><a href="#application">A typical application</a>
+<li><a href="#applet">A typical applet</a>
+<li><a href="#midlet">A typical midlet</a>
+<li><a href="#jcapplet">A typical Java Card applet</a>
+<li><a href="#xlet">A typical xlet</a>
+<li><a href="#androidapplication">A typical Android application</a>
+<li><a href="#library">A typical library</a>
+<li><a href="#applications">All possible applications in the input jars</a>
+<li><a href="#applets">All possible applets in the input jars</a>
+<li><a href="#midlets">All possible midlets in the input jars</a>
+<li><a href="#jcapplets">All possible Java Card applets in the input jars</a>
+<li><a href="#xlets">All possible xlets in the input jars</a>
+<li><a href="#androidapplications">All possible Android applications in the input jars</a>
+<li><a href="#servlets">All possible servlets in the input jars</a>
+<li><a href="#native">Processing native methods</a>
+<li><a href="#callback">Processing callback methods</a>
+<li><a href="#enumerations">Processing enumeration classes</a>
+<li><a href="#serializable">Processing serializable classes</a>
+<li><a href="#beans">Processing bean classes</a>
+<li><a href="#annotations">Processing annotations</a>
+<li><a href="#database">Processing database drivers</a>
+<li><a href="#componentui">Processing ComponentUI classes</a>
+<li><a href="#rmi">Processing RMI code</a>
+<li><a href="#resourcefiles">Processing resource files</a>
+<li><a href="#stacktrace">Producing useful obfuscated stack traces</a>
+<li><a href="#repackaging">Obfuscating package names</a>
+<li><a href="#restructuring">Restructuring the output archives</a>
+<li><a href="#filtering">Filtering the input and the output</a>
+<li><a href="#multiple">Processing multiple applications at once</a>
+<li><a href="#incremental">Incremental obfuscation</a>
+<li><a href="#microedition">Preverifying class files for Java Micro Edition</a>
+<li><a href="#upgrade">Upgrading class files to Java 6</a>
+<li><a href="#deadcode">Finding dead code</a>
+<li><a href="#structure">Printing out the internal structure of class files</a>
+<li><a href="#annotated">Using annotations to configure ProGuard</a>
+</ol>
+
+You can find some sample configuration files in the <code>examples</code>
+directory of the ProGuard distribution.
+
+<a name="application">&nbsp;</a>
+<h3>A typical application</h3>
+To shrink, optimize, and obfuscate the ProGuard application itself, one would
+typically create a configuration file <code>proguard.pro</code> and then type:
+<pre>
+java -jar proguard.jar @proguard.pro
+</pre>
+<p>
+The configuration file would contain the following options:
+<pre>
+-injars       proguard.jar
+-outjars      proguard_out.jar
+-libraryjars  &lt;java.home&gt;/lib/rt.jar
+-printmapping proguard.map
+
+-keep public class proguard.ProGuard {
+    public static void main(java.lang.String[]);
+}
+</pre>
+<p>
+Note the use of the <code>&lt;java.home&gt;</code> system property; it is
+replaced automatically.
+<p>
+Also note that all type names are fully specified:
+<code>proguard.ProGuard</code> and <code>java.lang.String[]</code>.
+<p>
+The access modifiers <code>public</code> and <code>static</code> are not
+really required in this case, since we know a priori that the specified class
+and method have the proper access flags. It just looks more familiar this way.
+<p>
+We're writing out an obfuscation mapping file with <a
+href="usage.html#printmapping"><code>-printmapping</code></a>, for
+de-obfuscating any stack traces later on, or for incremental obfuscation of
+extensions.
+<p>
+We can further improve the results with a few additional options:
+<pre>
+-optimizationpasses 3
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+</pre>
+These options are not required; they just shave off some extra bytes from the
+output jar, by performing up to 3 optimization passes, and by aggressively
+obfuscating class members and <a href="#repackaging">package names</a>.
+<p>
+In general, you might need a few additional options for processing <a
+href="#native">native methods</a>, <a href="#callback">callback methods</a>,
+<a href="#enumerations">enumerations</a>, <a href="#serializable">serializable
+classes</a>, <a href="#beans">bean classes</a>, <a
+href="#annotations">annotations</a>, and <a href="#resourcefiles">resource
+files</a>. For processing 'simple' applications like ProGuard, that is not
+required.
+
+<a name="applet">&nbsp;</a>
+<h3>A typical applet</h3>
+These options shrink, optimize, and obfuscate the applet
+<code>mypackage.MyApplet</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars &lt;java.home&gt;/lib/rt.jar
+
+-keep public class mypackage.MyApplet
+</pre>
+<p>
+The typical applet methods will be preserved automatically, since
+<code>mypackage.MyApplet</code> is an extension of the <code>Applet</code>
+class in the library <code>rt.jar</code>.
+<p>
+If applicable, you should add options for processing <a href="#native">native
+methods</a>, <a href="#callback">callback methods</a>, <a
+href="#enumerations">enumerations</a>, <a href="#serializable">serializable
+classes</a>, <a href="#beans">bean classes</a>, <a
+href="#annotations">annotations</a>, and <a href="#resourcefiles">resource
+files</a>.
+
+<a name="midlet">&nbsp;</a>
+<h3>A typical midlet</h3>
+These options shrink, optimize, obfuscate, and preverify the midlet
+<code>mypackage.MyMIDlet</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/wtk2.1/lib/midpapi20.jar
+-libraryjars /usr/local/java/wtk2.1/lib/cldcapi11.jar
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+-microedition
+
+-keep public class mypackage.MyMIDlet
+</pre>
+<p>
+Note how we're now targeting the Java Micro Edition run-time environment of
+<code>midpapi20.jar</code> and <code>cldcapi11.jar</code>, instead of the Java
+Standard Edition run-time environment <code>rt.jar</code>. You can target
+other JME environments by picking the appropriate jars.
+<p>
+The typical midlet methods will be preserved automatically, since
+<code>mypackage.MyMIDlet</code> is an extension of the <code>MIDlet</code>
+class in the library <code>midpapi20.jar</code>.
+<p>
+The <a href="usage.html#microedition"><code>-microedition</code></a> option
+makes sure the class files are preverified for Java Micro Edition, producing
+compact <code>StackMap</code> attributes. It is no longer necessary to run an
+external preverifier.
+<p>
+Be careful if you do use the external <code>preverify</code> tool on a platform
+with a case-insensitive filing system, such as Windows. Because this tool
+unpacks your processed jars, you should then use ProGuard's <a
+href="usage.html#dontusemixedcaseclassnames"><code>-dontusemixedcaseclassnames</code></a>
+option.
+<p>
+If applicable, you should add options for processing <a href="#native">native
+methods</a> and <a href="#resourcefiles">resource files</a>.
+<p>
+Note that you will still have to adapt the midlet jar size in the
+corresponding jad file; ProGuard doesn't do that for you.
+
+<a name="jcapplet">&nbsp;</a>
+<h3>A typical Java Card applet</h3>
+These options shrink, optimize, and obfuscate the Java Card applet
+<code>mypackage.MyApplet</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/javacard2.2.2/lib/api.jar
+-dontwarn    java.lang.Class
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+
+-keep public class mypackage.MyApplet
+</pre>
+<p>
+The configuration is very similar to the configuration for midlets, except that
+it now targets the Java Card run-time environment. This environment doesn't
+have java.lang.Class, so we're telling ProGuard not to worry about it.
+
+<a name="xlet">&nbsp;</a>
+<h3>A typical xlet</h3>
+These options shrink, optimize, and obfuscate the xlet
+<code>mypackage.MyXlet</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/jtv1.1/javatv.jar
+-libraryjars /usr/local/java/cdc1.1/lib/cdc.jar
+-libraryjars /usr/local/java/cdc1.1/lib/btclasses.zip
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+
+-keep public class mypackage.MyXlet
+</pre>
+<p>
+The configuration is very similar to the configuration for midlets, except that
+it now targets the CDC run-time environment with the Java TV API.
+
+<a name="androidapplication">&nbsp;</a>
+<h3>A typical Android application</h3>
+These options shrink, optimize, and obfuscate the simple Android application
+based on a single activity <code>mypackage.MyActivity</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/android-1.5_r1/platforms/android-1.5/android.jar
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+-optimizations !code/simplification/arithmetic
+
+-keep public class mypackage.MyActivity
+</pre>
+<p>
+The configuration is very similar to the configuration for midlets, except that
+it now targets the Android run-time environment.
+<p>
+The <a href="usage.html#optimizations"><code>-optimizations</code></a> option
+disables some arithmetic simplifications that Dalvik 1.0 and 1.5 can't handle.
+<p>
+If applicable, you should add options for processing <a href="#native">native
+methods</a>, <a href="#callback">callback methods</a>, and <a
+href="#resourcefiles">resource files</a>.
+
+<a name="library">&nbsp;</a>
+<h3>A typical library</h3>
+These options shrink, optimize, and obfuscate an entire library, keeping all
+public and protected classes and class members, native method names, and
+serialization code:
+<pre>
+-injars       in.jar
+-outjars      out.jar
+-libraryjars  &lt;java.home&gt;/lib/rt.jar
+-printmapping out.map
+
+-renamesourcefileattribute SourceFile
+-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
+                SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
+
+-keep public class * {
+    public protected *;
+}
+
+-keepclassmembernames class * {
+    java.lang.Class class$(java.lang.String);
+    java.lang.Class class$(java.lang.String, boolean);
+}
+
+-keepclasseswithmembernames class * {
+    native &lt;methods&gt;;
+}
+
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+</pre>
+<p>
+This configuration should preserve everything we'll ever want to access in the
+library. Only if there are any other non-public classes or methods that are
+invoked dynamically, they should be specified using additional <a
+href="usage.html#keep"><code>-keep</code></a> options.
+<p>
+The <a
+href="usage.html#keepclassmembernames"><code>-keepclassmembernames</code></a>
+option for the <code>class$</code> methods is not strictly necessary. These
+methods are inserted by the <code>javac</code> compiler and the
+<code>jikes</code> compiler respectively, to implement the <code>.class</code>
+construct. ProGuard will automatically detect them and deal with them, even
+when their names have been obfuscated. However, older versions of ProGuard and
+other obfuscators may rely on the original method names. It may therefore be
+helpful to preserve them, in case these other obfuscators are ever used for
+further obfuscation of the library.
+<p>
+The "Exceptions" attribute has to be preserved, so the compiler knows which
+exceptions methods may throw.
+<p>
+The "InnerClasses" attribute (or more precisely, its source name part) has to
+be preserved too, for any inner classes that can be referenced from outside the
+library. The <code>javac</code> compiler would be unable to find the inner
+classes otherwise.
+<p>
+The "Signature" attribute is required to be able to access generic types when
+compiling in JDK 5.0 and higher.
+<p>
+Finally, we're keeping the "Deprecated" attribute and the attributes for
+producing <a href="#stacktrace">useful stack traces</a>.
+<p>
+We've also added some options for for processing <a href="#native">native
+methods</a>, <a href="#enumerations">enumerations</a>, <a
+href="#serializable">serializable classes</a>, and <a
+href="#annotations">annotations</a>, which are all discussed in their
+respective examples.
+
+<a name="applications">&nbsp;</a>
+<h3>All possible applications in the input jars</h3>
+These options shrink, optimize, and obfuscate all public applications in
+<code>in.jar</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars &lt;java.home&gt;/lib/rt.jar
+-printseeds
+
+-keepclasseswithmembers public class * {
+    public static void main(java.lang.String[]);
+}
+</pre>
+<p>
+Note the use of <a
+href="usage.html#keepclasseswithmembers"><code>-keepclasseswithmembers</code></a>.
+We don't want to preserve all classes, just all classes that have main
+methods, and those methods.
+<p>
+The <a href="usage.html#printseeds"><code>-printseeds</code></a> option prints
+out which classes exactly will be preserved, so we know for sure we're getting
+what we want.
+<p>
+If applicable, you should add options for processing <a href="#native">native
+methods</a>, <a href="#callback">callback methods</a>, <a
+href="#enumerations">enumerations</a>, <a href="#serializable">serializable
+classes</a>, <a href="#beans">bean classes</a>, <a
+href="#annotations">annotations</a>, and <a href="#resourcefiles">resource
+files</a>.
+
+<a name="applets">&nbsp;</a>
+<h3>All possible applets in the input jars</h3>
+These options shrink, optimize, and obfuscate all public applets in
+<code>in.jar</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars &lt;java.home&gt;/lib/rt.jar
+-printseeds
+
+-keep public class * extends java.applet.Applet
+</pre>
+<p>
+We're simply keeping all classes that extend the <code>Applet</code> class.
+<p>
+Again, the <a href="usage.html#printseeds"><code>-printseeds</code></a> option
+prints out which applets exactly will be preserved.
+<p>
+If applicable, you should add options for processing <a href="#native">native
+methods</a>, <a href="#callback">callback methods</a>, <a
+href="#enumerations">enumerations</a>, <a href="#serializable">serializable
+classes</a>, <a href="#beans">bean classes</a>, <a
+href="#annotations">annotations</a>, and <a href="#resourcefiles">resource
+files</a>.
+
+<a name="midlets">&nbsp;</a>
+<h3>All possible midlets in the input jars</h3>
+These options shrink, optimize, obfuscate, and preverify all public midlets in
+<code>in.jar</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/wtk2.1/lib/midpapi20.jar
+-libraryjars /usr/local/java/wtk2.1/lib/cldcapi11.jar
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+-microedition
+-printseeds
+
+-keep public class * extends javax.microedition.midlet.MIDlet
+</pre>
+<p>
+We're simply keeping all classes that extend the <code>MIDlet</code> class.
+<p>
+The <a href="usage.html#microedition"><code>-microedition</code></a> option
+makes sure the class files are preverified for Java Micro Edition, producing
+compact <code>StackMap</code> attributes. It is no longer necessary to run an
+external preverifier.
+<p>
+Be careful if you do use the external <code>preverify</code> tool on a platform
+with a case-insensitive filing system, such as Windows. Because this tool
+unpacks your processed jars, you should then use ProGuard's <a
+href="usage.html#dontusemixedcaseclassnames"><code>-dontusemixedcaseclassnames</code></a>
+option.
+<p>
+The <a href="usage.html#printseeds"><code>-printseeds</code></a> option prints
+out which midlets exactly will be preserved.
+<p>
+If applicable, you should add options for processing <a href="#native">native
+methods</a> and <a href="#resourcefiles">resource files</a>.
+<p>
+Note that you will still have to adapt the midlet jar size in the
+corresponding jad file; ProGuard doesn't do that for you.
+
+<a name="jcapplets">&nbsp;</a>
+<h3>All possible Java Card applets in the input jars</h3>
+These options shrink, optimize, and obfuscate all public Java Card applets in
+<code>in.jar</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/javacard2.2.2/lib/api.jar
+-dontwarn    java.lang.Class
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+-printseeds
+
+-keep public class * implements javacard.framework.Applet
+</pre>
+<p>
+We're simply keeping all classes that implement the <code>Applet</code>
+interface.
+<p>
+The <a href="usage.html#printseeds"><code>-printseeds</code></a> option prints
+out which applets exactly will be preserved.
+
+<a name="xlets">&nbsp;</a>
+<h3>All possible xlets in the input jars</h3>
+These options shrink, optimize, and obfuscate all public xlets in
+<code>in.jar</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/jtv1.1/javatv.jar
+-libraryjars /usr/local/java/cdc1.1/lib/cdc.jar
+-libraryjars /usr/local/java/cdc1.1/lib/btclasses.zip
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+-printseeds
+
+-keep public class * implements javax.tv.xlet.Xlet
+</pre>
+<p>
+We're simply keeping all classes that implement the <code>Xlet</code> interface.
+<p>
+The <a href="usage.html#printseeds"><code>-printseeds</code></a> option prints
+out which xlets exactly will be preserved.
+
+<a name="androidapplications">&nbsp;</a>
+<h3>All possible Android applications in the input jars</h3>
+These options shrink, optimize, and obfuscate all public activities, services,
+broadcast receivers, and content providers in <code>in.jar</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/android-1.5_r1/platforms/android-1.5/android.jar
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+-optimizations !code/simplification/arithmetic
+-printseeds
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+</pre>
+<p>
+We're keeping all classes that extend the base classes that may be referenced
+by the <code>AndroidManifest.xml</code> file of the application.
+<p>
+The <a href="usage.html#printseeds"><code>-printseeds</code></a> option prints
+out which implementations exactly will be preserved.
+<p>
+If applicable, you should add options for processing <a href="#native">native
+methods</a>, <a href="#callback">callback methods</a>, and <a
+href="#resourcefiles">resource files</a>.
+
+<a name="servlets">&nbsp;</a>
+<h3>All possible servlets in the input jars</h3>
+These options shrink, optimize, and obfuscate all public servlets in
+<code>in.jar</code>:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars &lt;java.home&gt;/lib/rt.jar
+-libraryjars /usr/local/java/servlet/servlet.jar
+-printseeds
+
+-keep public class * implements javax.servlet.Servlet
+</pre>
+<p>
+Keeping all servlets is very similar to keeping all applets. The servlet API
+is not part of the standard run-time jar, so we're specifying it as a library.
+Don't forget to use the right path name.
+<p>
+We're then keeping all classes that implement the <code>Servlet</code>
+interface. We're using the <code>implements</code> keyword because it looks
+more familiar in this context, but it is equivalent to <code>extends</code>,
+as far as ProGuard is concerned.
+<p>
+The <a href="usage.html#printseeds"><code>-printseeds</code></a> option prints
+out which servlets exactly will be preserved.
+<p>
+If applicable, you should add options for processing <a href="#native">native
+methods</a>, <a href="#callback">callback methods</a>, <a
+href="#enumerations">enumerations</a>, <a href="#serializable">serializable
+classes</a>, <a href="#beans">bean classes</a>, <a
+href="#annotations">annotations</a>, and <a href="#resourcefiles">resource
+files</a>.
+
+<a name="native">&nbsp;</a>
+<h3>Processing native methods</h3>
+If your application, applet, servlet, library, etc., contains native methods,
+you'll want to preserve their names and their classes' names, so they can
+still be linked to the native library. The following additional option will
+ensure that:
+<pre>
+-keepclasseswithmembernames class * {
+    native &lt;methods&gt;;
+}
+</pre>
+<p>
+Note the use of <a
+href="usage.html#keepclasseswithmembernames"><code>-keepclasseswithmembernames</code></a>.
+We don't want to preserve all classes or all native methods; we just want to
+keep the relevant names from being obfuscated.
+<p>
+ProGuard doesn't look at your native code, so it won't automatically preserve
+the classes or class members that are invoked by the native code. These are
+entry points, which you'll have to specify explicitly.  <a
+href="callback">Callback methods</a> are discussed below as a typical example.
+
+<a name="callback">&nbsp;</a>
+<h3>Processing callback methods</h3>
+If your application, applet, servlet, library, etc., contains callback
+methods, which are called from external code (native code, scripts,...),
+you'll want to preserve them, and probably their classes too. They are just
+entry points to your code, much like, say, the main method of an application.
+If they aren't preserved by other <code>-keep</code> options, something like
+the following option will keep the callback class and method:
+<pre>
+-keep class mypackage.MyCallbackClass {
+    void myCallbackMethod(java.lang.String);
+}
+</pre>
+<p>
+This will preserve the given class and method from being removed or renamed.
+
+<a name="enumerations">&nbsp;</a>
+<h3>Processing enumeration classes</h3>
+If your application, applet, servlet, library, etc., contains enumeration
+classes, you'll have to preserve some special methods. Enumerations were
+introduced in Java 5. The java compiler translates enumerations into classes
+with a special structure. Notably, the classes contain implementations of some
+static methods that the run-time environment accesses by introspection (Isn't
+that just grand? Introspection is the self-modifying code of a new
+generation). You have to specify these explicitly, to make sure they aren't
+removed or obfuscated:
+<pre>
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+</pre>
+
+<a name="serializable">&nbsp;</a>
+<h3>Processing serializable classes</h3>
+More complex applications, applets, servlets, libraries, etc., may contain
+classes that are serialized. Depending on the way in which they are used, they
+may require special attention:
+<ul>
+
+<li>Often, serialization is simply a means of transporting data, without
+    long-term storage. Classes that are shrunk and obfuscated should then
+    continue to function fine with the following additional options:
+
+<pre>
+-keepclassmembers class * implements java.io.Serializable {
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+</pre>
+<p>
+
+    The <a
+    href="usage.html#keepclassmembers"><code>-keepclassmembers</code></a>
+    option makes sure that any serialization methods are kept. By using this
+    option instead of the basic <code>-keep</code> option, we're not
+    forcing preservation of <i>all</i> serializable classes, just preservation
+    of the listed members of classes that are actually used.
+    <p>
+
+<li>Sometimes, the serialized data are stored, and read back later into newer
+    versions of the serializable classes. One then has to take care the classes
+    remain compatible with their unprocessed versions and with future
+    processed versions. In such cases, the relevant classes will most likely
+    have <code>serialVersionUID</code> fields. The following options should
+    then be sufficient to ensure compatibility over time:
+
+<pre>
+-keepnames class * implements java.io.Serializable
+
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    static final java.io.ObjectStreamField[] serialPersistentFields;
+    !static !transient &lt;fields&gt;;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+</pre>
+<p>
+
+    The <code>serialVersionUID</code> and <code>serialPersistentFields</code>
+    lines makes sure those fields are preserved, if they are present.
+    The <code>&lt;fields&gt;</code> line preserves all non-static,
+    non-transient fields, with their original names. The introspection of the
+    serialization process and the de-serialization process will then find
+    consistent names.
+
+<li>Occasionally, the serialized data have to remain compatible, but the
+    classes involved lack <code>serialVersionUID</code> fields. I imagine the
+    original code will then be hard to maintain, since the serial version UID
+    is then computed from a list of features the serializable class. Changing
+    the class ever so slightly may change the computed serial version UID. The
+    list of features is specified in the section on <a
+    href="http://java.sun.com/javase/6/docs/platform/serialization/spec/class.html#4100">Stream
+    Unique Identifiers</a> of Sun's <a
+    href="http://java.sun.com/javase/6/docs/platform/serialization/spec/serialTOC.html">Java
+    Object Serialization Specification</a>. The following directives should at
+    least partially ensure compatibility with the original classes:
+
+<pre>
+-keepnames class * implements java.io.Serializable
+
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    static final java.io.ObjectStreamField[] serialPersistentFields;
+    !static !transient &lt;fields&gt;;
+    !private &lt;fields&gt;;
+    !private &lt;methods&gt;;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+</pre>
+<p>
+
+    The new options force preservation of the elements involved in the UID
+    computation. In addition, the user will have to manually specify all
+    interfaces of the serializable classes (using something like "<code>-keep
+    interface MyInterface</code>"), since these names are also used when
+    computing the UID. A fast but sub-optimal alternative would be simply
+    keeping all interfaces with "<code>-keep interface *</code>".
+
+</ul>
+<p>
+
+Note that the above options may preserve more classes and class members
+than strictly necessary. For instance, a large number of classes may implement
+the <code>Serialization</code> interface, yet only a small number may actually
+ever be serialized. Knowing your application and tuning the configuration
+often produces more compact results.
+
+<a name="beans">&nbsp;</a>
+<h3>Processing bean classes</h3>
+If your application, applet, servlet, library, etc., makes extensive use of
+introspection on bean classes to find bean editor classes, or getter and
+setter methods, then configuration may become painful. There's not much else
+you can do than making sure the bean class names, or the getter and setter
+names don't change. For instance:
+<pre>
+-keep public class mypackage.MyBean {
+    public void setMyProperty(int);
+    public int getMyProperty();
+}
+
+-keep public class mypackage.MyBeanEditor
+</pre>
+<p>
+If there are too many elements to list explicitly, wildcards in class names
+and method signatures might be helpful. This example should encompasses all
+possible setters and getters in classes in the package <code>mybeans</code>:
+<pre>
+-keep class mybeans.** {
+    void set*(***);
+    void set*(int, ***);
+
+    boolean is*(); 
+    boolean is*(int);
+
+    *** get*();
+    *** get*(int);
+}
+</pre>
+<p>
+The '<code>***</code>' wildcard matches any type (primitive or non-primitive,
+array or non-array). The methods with the '<code>int</code>' arguments matches
+properties that are lists.
+
+<a name="annotations">&nbsp;</a>
+<h3>Processing annotations</h3>
+If your application, applet, servlet, library, etc., uses annotations, you may
+want to preserve them in the processed output. Annotations are represented by
+attributes that have no direct effect on the execution of the code. However,
+their values can be retrieved through introspection, allowing developers to
+adapt the execution behavior accordingly. By default, ProGuard treats
+annotation attributes as optional, and removes them in the obfuscation step.
+If they are required, you'll have to specify this explicitly:
+<pre>
+-keepattributes *Annotation*
+</pre>
+<p>
+For brevity, we're specifying a wildcarded attribute name, which will match
+<code>RuntimeVisibleAnnotations</code>,
+<code>RuntimeInvisibleAnnotations</code>,
+<code>RuntimeVisibleParameterAnnotations</code>,
+<code>RuntimeInvisibleParameterAnnotations</code>, and
+<code>AnnotationDefault</code>. Depending on the purpose of the processed
+code, you could refine this selection, for instance not keeping the run-time
+invisible annotations (which are only used at compile-time).
+<p>
+Some code may make further use of introspection to figure out the enclosing
+methods of anonymous inner classes. In that case, the corresponding attribute
+has to be preserved as well:
+<pre>
+-keepattributes EnclosingMethod
+</pre>
+
+<a name="database">&nbsp;</a>
+<h3>Processing database drivers</h3>
+Database drivers are implementations of the <code>Driver</code> interface.
+Since they are often created dynamically, you may want to preserve any
+implementations that you are processing as entry points:
+<pre>
+-keep class * implements java.sql.Driver
+</pre>
+<p>
+This option also gets rid of the note that ProGuard prints out about
+<code>(java.sql.Driver)Class.forName</code> constructs, if you are
+instantiating a driver in your code (without necessarily implementing any
+drivers yourself).
+
+<a name="componentui">&nbsp;</a>
+<h3>Processing ComponentUI classes</h3>
+Swing UI look and feels are implemented as extensions of the
+<code>ComponentUI</code> class. For some reason, these have to contain a
+static method <code>createUI</code>, which the Swing API invokes using
+introspection. You should therefore always preserve the method as an entry
+point, for instance like this:
+<pre>
+-keep class * extends javax.swing.plaf.ComponentUI {
+    public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);
+}
+</pre>
+<p>
+This option also keeps the classes themselves.
+
+<a name="rmi">&nbsp;</a>
+<h3>Processing RMI code</h3>
+Reportedly, the easiest way to handle RMI code is to process the code with
+ProGuard first and then invoke the <code>rmic</code> tool. If that is not
+possible, you may want to try something like this:
+<pre>
+-keepattributes Exceptions
+
+-keep interface * extends java.rmi.Remote {
+    &lt;methods&gt;;
+}
+
+-keep class * implements java.rmi.Remote {
+    &lt;init&gt;(java.rmi.activation.ActivationID, java.rmi.MarshalledObject);
+}
+</pre>
+<p>
+The first <code>-keep</code> option keeps all your Remote interfaces and their
+methods. The second one keeps all the implementations, along with their
+particular RMI constructors, if any.
+<p>
+The <code>Exceptions</code> attribute has to be kept too, because the RMI
+handling code performs introspection to check whether the method signatures
+are compatible.
+
+<a name="resourcefiles">&nbsp;</a>
+<h3>Processing resource files</h3>
+If your application, applet, servlet, library, etc., contains resource files,
+it may be necessary to adapt their names and/or their contents when the
+application is obfuscated. The following two options can achieve this
+automatically:
+<pre>
+-adaptresourcefilenames    **.properties,**.gif,**.jpg
+-adaptresourcefilecontents **.properties,META-INF/MANIFEST.MF
+</pre>
+<p>
+The <a href="usage.html#adaptresourcefilenames">-adaptresourcefilenames</a>
+option in this case renames properties files and image files in the processed
+output, based on the obfuscated names of their corresponding class files (if
+any). The <a
+href="usage.html#adaptresourcefilecontents">-adaptresourcefilecontents</a>
+option looks for class names in properties files and in the manifest file, and
+replaces these names by the obfuscated names (if any). You'll probably want to
+adapt the filters to suit your application.
+
+<a name="stacktrace">&nbsp;</a>
+<h3>Producing useful obfuscated stack traces</h3>
+These options let obfuscated applications or libraries produce stack traces
+that can still be deciphered later on:
+<pre>
+-printmapping out.map
+
+-renamesourcefileattribute SourceFile
+-keepattributes SourceFile,LineNumberTable
+</pre>
+<p>
+We're keeping all source file attributes, but we're replacing their values by
+the string "SourceFile". We could use any string. This string is already
+present in all class files, so it doesn't take up any extra space. If you're
+working with J++, you'll want to keep the "SourceDir" attribute as well.
+<p>
+We're also keeping the line number tables of all methods.
+<p>
+Whenever both of these attributes are present, the Java run-time environment
+will include line number information when printing out exception stack traces.
+<p>
+The information will only be useful if we can map the obfuscated names back to
+their original names, so we're saving the mapping to a file
+<code>out.map</code>. The information can then be used by the <a
+href="retrace/index.html">ReTrace</a> tool to restore the original stack trace.
+
+<a name="repackaging">&nbsp;</a>
+<h3>Obfuscating package names</h3>
+Package names can be obfuscated in various ways, with increasing levels of
+obfuscation and compactness. For example, consider the following classes:
+<pre>
+mycompany.myapplication.MyMain
+mycompany.myapplication.Foo
+mycompany.myapplication.Bar
+mycompany.myapplication.extra.FirstExtra
+mycompany.myapplication.extra.SecondExtra
+mycompany.util.FirstUtil
+mycompany.util.SecondUtil
+</pre>
+<p>
+Let's assume the class name <code>mycompany.myapplication.MyMain</code> is the
+main application class that is kept by the configuration. All other class names
+can be obfuscated.
+<p>
+By default, packages that contain classes that can't be renamed aren't renamed
+either, and the package hierarchy is preserved. This results in obfuscated
+class names like these:
+<pre>
+mycompany.myapplication.MyMain
+mycompany.myapplication.a
+mycompany.myapplication.b
+mycompany.myapplication.a.a
+mycompany.myapplication.a.b
+mycompany.a.a
+mycompany.a.b
+</pre>
+<p>
+The <a
+href="usage.html#flattenpackagehierarchy"><code>-flattenpackagehierarchy</code></a>
+option obfuscates the package names further, by flattening the package
+hierarchy of obfuscated packages:
+<pre>
+-flattenpackagehierarchy 'myobfuscated'
+</pre>
+<p>
+The obfuscated class names then look as follows:
+<pre>
+mycompany.myapplication.MyMain
+mycompany.myapplication.a
+mycompany.myapplication.b
+myobfuscated.a.a
+myobfuscated.a.b
+myobfuscated.b.a
+myobfuscated.b.b
+</pre>
+<p>
+Alternatively, the <a
+href="usage.html#repackageclasses"><code>-repackageclasses</code></a> option
+obfuscates the entire packaging, by combining obfuscated classes into a single
+package:
+<pre>
+-repackageclasses 'myobfuscated'
+</pre>
+The obfuscated class names then look as follows:
+<pre>
+mycompany.myapplication.MyMain
+mycompany.myapplication.a
+mycompany.myapplication.b
+myobfuscated.a
+myobfuscated.b
+myobfuscated.c
+myobfuscated.d
+</pre>
+<p>
+Additionally specifying the <a
+href="usage.html#allowaccessmodification"><code>-allowaccessmodification</code></a>
+option allows access permissions of classes and class members to
+be broadened, opening up the opportunity to repackage all obfuscated classes:
+<pre>
+-repackageclasses 'myobfuscated'
+-allowaccessmodification
+</pre>
+The obfuscated class names then look as follows:
+<pre>
+mycompany.myapplication.MyMain
+myobfuscated.a
+myobfuscated.b
+myobfuscated.c
+myobfuscated.d
+myobfuscated.e
+myobfuscated.f
+</pre>
+<p>
+The specified target package can always be the root package. For instance:
+<pre>
+-repackageclasses ''
+-allowaccessmodification
+</pre>
+The obfuscated class names are then the shortest possible names:
+<pre>
+mycompany.myapplication.MyMain
+a
+b
+c
+d
+e
+f
+</pre>
+<p>
+Note that not all levels of obfuscation of package names may be acceptable for
+all code. Notably, you may have to take into account that your application may
+contain <a href="#resourcefiles">resource files</a> that have to be adapted.
+
+<a name="restructuring">&nbsp;</a>
+<h3>Restructuring the output archives</h3>
+In simple applications, all output classes and resources files are merged into
+a single jar. For example:
+<pre>
+-injars  classes
+-injars  in1.jar
+-injars  in2.jar
+-injars  in3.jar
+-outjars out.jar
+</pre>
+<p>
+This configuration merges the processed versions of the files in the
+<code>classes</code> directory and the three jars into a single output jar
+<code>out.jar</code>.
+<p>
+If you want to preserve the structure of your input jars (and/or wars, ears,
+zips, or directories), you can specify an output directory (or a war, an ear,
+or a zip). For example:
+<pre>
+-injars  in1.jar
+-injars  in2.jar
+-injars  in3.jar
+-outjars out
+</pre>
+<p>
+The input jars will then be reconstructed in the directory <code>out</code>,
+with their original names.
+<p>
+You can also combine archives into higher level archives. For example:
+<pre>
+-injars  in1.jar
+-injars  in2.jar
+-injars  in3.jar
+-outjars out.war
+</pre>
+<p>
+The other way around, you can flatten the archives inside higher level
+archives into simple archives:
+<pre>
+-injars  in.war
+-outjars out.jar
+</pre>
+<p>
+This configuration puts the processed contents of all jars inside
+<code>in.war</code> (plus any other contents of <code>in.war</code>) into
+<code>out.jar</code>.
+<p>
+If you want to combine input jars (and/or wars, ears, zips, or directories)
+into output jars (and/or wars, ears, zips, or directories), you can group the
+<a href="usage.html#injars"><code>-injars</code></a> and <a
+href="usage.html#outjars"><code>-outjars</code></a> options. For example:
+<pre>
+-injars base_in1.jar
+-injars base_in2.jar
+-injars base_in3.jar
+-outjars base_out.jar
+
+-injars  extra_in.jar
+-outjars extra_out.jar
+</pre>
+<p>
+This configuration puts the processed results of all <code>base_in*.jar</code>
+jars into <code>base_out.jar</code>, and the processed results of the
+<code>extra_in.jar</code> into <code>extra_out.jar</code>. Note that only the
+order of the options matters; the additional whitespace is just for clarity.
+<p>
+This grouping, archiving, and flattening can be arbitrarily complex. ProGuard
+always tries to package output archives in a sensible way, reconstructing the
+input entries as much as required.
+
+<a name="filtering">&nbsp;</a>
+<h3>Filtering the input and the output</h3>
+
+If you want even greater control, you can add filters to the input and the
+output, filtering out zips, ears, wars, jars, and/or ordinary files. For
+example, if you want to disregard certain files from an input jar:
+<pre>
+-injars  in.jar(!images/**)
+-outjars out.jar
+</pre>
+<p>
+This configuration removes any files in the <code>images</code> directory and
+its subdirectories.
+<p>
+Such filters can be convenient for avoiding warnings about duplicate files in
+the output. For example, only keeping the manifest file from a first input jar:
+<pre>
+-injars  in1.jar
+-injars  in2.jar(!META-INF/MANIFEST.MF)
+-injars  in3.jar(!META-INF/MANIFEST.MF)
+-outjars out.jar
+</pre>
+<p>
+Another useful application is speeding up the processing by ProGuard, by
+disregarding a large number of irrelevant classes in the runtime library jar:
+<pre>
+-libraryjars &lt;java.home&gt;/lib/rt.jar(java/**,javax/**)
+</pre>
+<p>
+The filter makes ProGuard disregard <code>com.sun.**</code> classes, for
+instance , which don't affect the processing of ordinary applications.
+<p>
+It is also possible to filter the jars (and/or wars, ears, zips) themselves,
+based on their names. For example:
+<pre>
+-injars  in(**/acme_*.jar;)
+-outjars out.jar
+</pre>
+<p>
+Note the semi-colon in the filter; the filter in front of it applies to jar
+names. In this case, only <code>acme_*.jar</code> jars are read from the
+directory <code>in</code> and its subdirectories. Filters for war names, ear
+names, and zip names can be prefixed with additional semi-colons. All types of
+filters can be combined. They are orthogonal.
+<p>
+On the other hand, you can also filter the output, in order to control what
+content goes where. For example:
+<pre>
+-injars  in.jar
+-outjars code_out.jar(**.class)
+-outjars resources_out.jar
+</pre>
+<p>
+This configuration splits the processed output, sending <code>**.class</code>
+files to <code>code_out.jar</code>, and all remaining files to
+<code>resources_out.jar</code>.
+<p>
+Again, the filtering can be arbitrarily complex, especially when combined with
+the grouping of input and output.
+
+<a name="multiple">&nbsp;</a>
+<h3>Processing multiple applications at once</h3>
+You can process several dependent or independent applications (or applets,
+midlets,...) in one go, in order to save time and effort. ProGuard's input and
+output handling offers various ways to keep the output nicely structured.
+<p>
+The easiest way is to specify your input jars (and/or wars, ears, zips, and
+directories) and a single output directory. ProGuard will then reconstruct the
+input in this directory, using the original jar names. For example, showing
+just the input and output options:
+<pre>
+-injars  application1.jar
+-injars  application2.jar
+-injars  application3.jar
+-outjars processed_applications
+</pre>
+<p>
+After processing, the directory <code>processed_applications</code> will
+contain the processed application jars, with their original names.
+
+<a name="incremental">&nbsp;</a>
+<h3>Incremental obfuscation</h3>
+After having <a href="#application">processed an application</a>, e.g.
+ProGuard itself, you can still incrementally add other pieces of code that
+depend on it, e.g. the ProGuard GUI:
+<pre>
+-injars       proguardgui.jar
+-outjars      proguardgui_out.jar
+-injars       proguard.jar
+-outjars      proguard_out.jar
+-libraryjars  &lt;java.home&gt;/lib/rt.jar
+-applymapping proguard.map
+
+-keep public class proguard.gui.ProGuardGUI {
+    public static void main(java.lang.String[]);
+}
+</pre>
+<p>
+We're reading both unprocessed jars as input. Their processed contents will go
+to the respective output jars. The <a
+href="usage.html#applymapping"><code>-applymapping</code></a> option then
+makes sure the ProGuard part of the code gets the previously produced
+obfuscation mapping. The final application will consist of the obfuscated
+ProGuard jar and the additional obfuscated GUI jar.
+<p>
+The added code in this example is straightforward; it doesn't affect the
+original code. The <code>proguard_out.jar</code> will be identical to the one
+produced in the initial processing step. If you foresee adding more complex
+extensions to your code, you should specify the options <a
+href="usage.html#useuniqueclassmembernames"><code>-useuniqueclassmembernames</code></a>,
+<a href="usage.html#dontshrink"><code>-dontshrink</code></a>, and <a
+href="usage.html#dontoptimize"><code>-dontoptimize</code></a> <i>in the
+original processing step</i>. These options ensure that the obfuscated base
+jar will always remain usable without changes. You can then specify the base
+jar as a library jar:
+<pre>
+-injars       proguardgui.jar
+-outjars      proguardgui_out.jar
+-libraryjars  proguard.jar
+-libraryjars  &lt;java.home&gt;/lib/rt.jar
+-applymapping proguard.map
+
+-keep public class proguard.gui.ProGuardGUI {
+    public static void main(java.lang.String[]);
+}
+</pre>
+
+<a name="microedition">&nbsp;</a>
+<h3>Preverifying class files for Java Micro Edition</h3>
+Even if you're not interested in shrinking, optimizing, and obfuscating your
+midlets, as shown in the <a href="#midlets">midlets example</a>, you can still
+use ProGuard to preverify the class files for Java Micro Edition. ProGuard
+produces slightly more compact results compared to the traditional external
+preverifier.
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars /usr/local/java/wtk2.1/lib/midpapi20.jar
+-libraryjars /usr/local/java/wtk2.1/lib/cldcapi11.jar
+
+-dontshrink
+-dontoptimize
+-dontobfuscate
+
+-microedition
+</pre>
+<p>
+We're not processing the input, just making sure the class files are
+preverified by targeting them at Java Micro Edition with the <a
+href="usage.html#microedition"><code>-microedition</code></a> option. Note
+that we don't need any <code>-keep</code> options to specify entry points; all
+class files are simply preverified.
+
+<a name="upgrade">&nbsp;</a>
+<h3>Upgrading class files to Java 6</h3>
+The following options upgrade class files to Java 6, by updating their
+internal version numbers and preverifying them. The class files can then be
+loaded more efficiently by the Java 6 Virtual Machine.
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars &lt;java.home&gt;/lib/rt.jar
+
+-dontshrink
+-dontoptimize
+-dontobfuscate
+
+-target 1.6
+</pre>
+<p>
+We're not processing the input, just retargeting the class files with the <a
+href="usage.html#target"><code>-target</code></a> option. They will
+automatically be preverified for Java 6 as a result. Note that we don't need
+any <code>-keep</code> options to specify entry points; all class files are
+simply updated and preverified.
+
+<a name="deadcode">&nbsp;</a>
+<h3>Finding dead code</h3>
+These options list unused classes, fields, and methods in the application
+<code>mypackage.MyApplication</code>:
+<pre>
+-injars      in.jar
+-libraryjars &lt;java.home&gt;/lib/rt.jar
+
+-dontoptimize
+-dontobfuscate
+-dontpreverify
+-printusage
+
+-keep public class mypackage.MyApplication {
+    public static void main(java.lang.String[]);
+}
+</pre>
+<p>
+We're not specifying an output jar, just printing out some results. We're
+saving some processing time by skipping the other processing steps.
+<p>
+The java compiler inlines primitive constants and String constants
+(<code>static final</code> fields). ProGuard would therefore list such fields
+as not being used in the class files that it analyzes, even if they <i>are</i>
+used in the source files. We can add a <a
+href="usage.html#keepclassmembers"><code>-keepclassmembers</code></a> option
+that keeps those fields a priori, in order to avoid having them listed:
+<pre>
+-keepclassmembers class * {
+    static final %                *;
+    static final java.lang.String *;
+}
+</pre>
+
+<a name="structure">&nbsp;</a>
+<h3>Printing out the internal structure of class files</h3>
+These options print out the internal structure of all class files in the input
+jar:
+<pre>
+-injars in.jar
+
+-dontshrink
+-dontoptimize
+-dontobfuscate
+-dontpreverify
+
+-dump
+</pre>
+<p>
+Note how we don't need to specify the Java run-time jar, because we're not
+processing the input jar at all.
+
+<a name="annotated">&nbsp;</a>
+<h3>Using annotations to configure ProGuard</h3>
+
+The traditional ProGuard configuration allows to keep a clean separation
+between the code and the configuration for shrinking, optimization, and
+obfuscation. However, it is also possible to define specific annotations,
+and then annotate the code to configure the processing.
+<p>
+You can find a set of such predefined annotations in the directory
+<code>examples/annotations/lib</code> in the ProGuard distribution.
+The annotation classes are defined in <code>annotations.jar</code>. The
+corresponding ProGuard configuration (or meta-configuration, if you prefer)
+is specified in <code>annotations.pro</code>. With these files, you can start
+annotating your code. For instance, a java source file
+<code>Application.java</code> can be annotated as follows:
+<pre>
+@KeepApplication
+public class Application {
+  ....
+}
+</pre>
+<p>
+The ProGuard configuration file for the application can then be simplified by
+leveraging off these annotations:
+<pre>
+-injars      in.jar
+-outjars     out.jar
+-libraryjars &lt;java.home&gt;/lib/rt.jar
+
+-include lib/annotations.pro
+</pre>
+<p>
+The annotations are effectively replacing the application-dependent
+<code>-keep</code> options. You may still wish to add traditional
+<code>-keep</code> options for processing <a href="#native">native
+methods</a>, <a href="#enumerations">enumerations</a>, <a
+href="#serializable">serializable classes</a>, and <a
+href="#annotations">annotations</a>.
+<p>
+The directory <code>examples/annotations</code> contains more examples that
+illustrate some of the possibilities.
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/gui.html b/docs/manual/gui.html
new file mode 100644
index 0000000..37684a6
--- /dev/null
+++ b/docs/manual/gui.html
@@ -0,0 +1,474 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard GUI</title>
+</head>
+<body>
+
+<h2>Graphical User Interface</h2>
+
+You can find the ProGuard GUI jar in the <code>lib</code> directory of the
+ProGuard distribution. To run the ProGuard graphical user interface, just type:
+<p class="code">
+<code><b>java -jar proguardgui.jar</b> [-nosplash] </code>[<i>configuration_file</i>]
+</p>
+The GUI will pop up in a window. With the <code>-nosplash</code> option, you
+can switch off the short opening animation. If you have specified a ProGuard
+configuration file, it will be loaded. The GUI works like a wizard. You can
+edit the configuration and execute ProGuard through a few tabs:
+<p>
+
+<table cellspacing="5" cellpadding="5">
+<tr><td class="button"><a href="#proguard">ProGuard</a></td>
+    <td>Optionally load an existing configuration file.</td></tr>
+<tr><td class="button"><a href="#inputoutput">Input/Output</a></td>
+    <td>Specify the program jars and library jars.</td></tr>
+<tr><td class="button"><a href="#shrinking">Shrinking</a></td>
+    <td>Specify the shrinking options.</td></tr>
+<tr><td class="button"><a href="#obfuscation">Obfuscation</a></td>
+    <td>Specify the obfuscation options.</td></tr>
+<tr><td class="button"><a href="#optimization">Optimization</a></td>
+    <td>Specify the optimization options.</td></tr>
+<tr><td class="button"><a href="#information">Information</a></td>
+    <td>Specify some options to get information.</td></tr>
+<tr><td class="button"><a href="#process">Process</a></td>
+    <td>View and save the resulting configuration, and run ProGuard.</td></tr>
+</table>
+<p>
+
+In addition, there is a tab to execute ReTrace interactively:
+<p>
+
+<table cellspacing="5" cellpadding="5">
+<tr><td class="button"><a href="#retrace">ReTrace</a></td>
+    <td>Set up and run ReTrace, to de-obfuscate stack traces.</td></tr>
+</table>
+<p>
+
+You can freely toggle between the tabs by means of the buttons on the
+left-hand side of the window, or by means of the <b>Previous</b> and
+<b>Next</b> buttons at the bottom of the tabs. Tool tips briefly explain the
+purpose of the numerous options and text fields, although a basic
+understanding of the shrinking/optimization/obfuscation/preverification
+process is assumed. Please refer to the <a
+href="introduction.html">Introduction</a> of this manual.
+<p>
+
+<a name="proguard">&nbsp;</a>
+<h2>The ProGuard Tab</h2>
+
+The <i>ProGuard</i> tab presents a welcome message and one important button at
+the bottom:
+<p>
+
+<table cellspacing="5" cellpadding="5">
+<tr><td class="button">Load configuration...</td>
+    <td>opens a file chooser to load an existing ProGuard configuration
+    file.</td></tr>
+</table>
+<p>
+
+If you don't want to load an existing configuration, you can just continue
+creating a new configuration from scratch.
+<p>
+
+<a name="inputoutput">&nbsp;</a>
+<h2>The Input/Output Tab</h2>
+
+The <i>Input/Output</i> tab contains two lists, respectively to specify the
+program jars (or wars, ears, zips, or directories), and the library jars (or
+wars, ears, zips, or directories).
+
+<ul>
+<li>The list of program jars contains input entries and output entries. Input
+    entries contain the class files and resource files to be processed. Output
+    entries specify the destinations to which the processed results will be
+    written. They are preceded by arrows, to distinguish them from input
+    entries. The results of each consecutive list of input entries will be
+    written to the subsequent consecutive list of output entries.
+
+<li>The library jars are not copied to the output jars; they contain class
+    files that are used by class files in the program jars and that are
+    necessary for correct processing. This list typically at least contains the
+    targeted Java runtime jar.
+</ul>
+<p>
+
+Each of these lists can be edited by means of a couple of buttons on the
+right-hand side:
+<p>
+
+<table cellspacing="5" cellpadding="5">
+<tr><td class="button">Add input...</td> <td>opens a file chooser to add an
+    input entry to the list of program jars.</td></tr>
+<tr><td class="button">Add output...</td> <td>opens a file chooser to add an
+    output entry to the list of program jars.</td></tr>
+<tr><td class="button">Add...</td>
+    <td>opens a file chooser to add an entry to the list of library
+    jars.</td></tr>
+<tr><td class="button">Edit...</td>
+    <td>opens a file chooser to edit the selected entry in the list.</td></tr>
+<tr><td class="button">Filter...</td>
+    <td>opens a text entry field to add or edit the filters of the selected
+    entries in the list.</td></tr>
+<tr><td class="button">Remove</td>
+    <td>removes the selected entries from the list.</td></tr>
+<tr><td class="button">Move up</td>
+    <td>moves the selected entries one position up the list.</td></tr>
+<tr><td class="button">Move down</td>
+    <td>moves the selected entries one position down the list.</td></tr>
+<tr><td class="button">Move to libraries</td>
+    <td>moves the selected entries in the list of program jars to the list of
+    library jars.</td></tr>
+<tr><td class="button">Move to program</td>
+    <td>moves the selected entries in the list of library jars to the list of
+    program jars.</td></tr>
+</table>
+<p>
+
+Filters allow to filter files based on their names. One can specify filters
+for class file names and resource file names, for jar file names, for war file
+names, for ear file names, and for zip file names. Multiple entries in the
+program list only make sense when combined with filters; each output file is
+written to the first entry with a matching filter.
+<p>
+
+Input entries that are currently not readable are colored red.
+<p>
+
+The order of the entries in each list may matter, as the first occurrence of
+any duplicate entries gets precedence, just as in conventional class paths.
+<p>
+
+Corresponding configuration options:
+<ul type="none">
+<li>-<a href="usage.html#injars">injars</a>
+<li>-<a href="usage.html#outjars">outjars</a>
+<li>-<a href="usage.html#libraryjars">libraryjars</a>
+<li><a href="usage.html#classpath"><i>class_path</i></a>
+<li><a href="usage.html#filters"><i>filters</i></a>
+</ul>
+<p>
+
+<a name="shrinking">&nbsp;</a>
+<h2>The Shrinking Tab</h2>
+
+The <i>Shrinking</i> tab presents a number of options that affect the
+shrinking step. The basic options are followed by a few lists of classes and
+class members (fields and methods) that must be protected from shrinking (and
+implicitly from obfuscation as well).
+<p>
+
+The fixed lists contain predefined entries that are typically useful for many
+applications. Each of these entries can be toggled by means of a check box.
+The text field following each entry allows to constrain the applicable classes
+by means of a comma-separated list of wildcarded, fully-qualified class
+names. The default is "*", which means that all input classes of the
+corresponding type are considered.
+<p>
+
+For example, checking the <b>Applications</b> entry and filling in
+"myapplications.**" after it would mean: keep all classes that have main
+methods in the "myapplications" package and all of its subpackages.
+<p>
+
+The variable list at the bottom allows to define additional entries
+yourself. The list can be edited by means of a couple of buttons on the
+right-hand side:
+<p>
+
+<table cellspacing="5" cellpadding="5">
+<tr><td class="button">Add...</td>
+    <td>opens a window to add a new entry to the list.</td></tr>
+<tr><td class="button">Edit...</td>
+    <td>opens a window to edit the selected entry in the list.</td></tr>
+<tr><td class="button">Remove</td>
+    <td>removes the selected entries from the list.</td></tr>
+<tr><td class="button">Move up</td>
+    <td>moves the selected entries one position up the list.</td></tr>
+<tr><td class="button">Move down</td>
+    <td>moves the selected entries one position down the list.</td></tr>
+</table>
+<p>
+
+The interface windows allow to specify classes, fields, and methods. They
+contain text fields and check boxes to constrain these items. They have
+<b>Ok</b> and <b>Cancel</b> buttons to apply or to cancel the operation.
+<p>
+
+For example, your application may be creating some classes dynamically using
+<code>Class.forName</code>. You should then specify them here, so they are kept
+by their original names. Press the <b>Add...</b> button to open the class
+window. Fill out the fully-qualified class name in the <b>Code</b> text field,
+and press the <b>Ok</b> button. Repeat this for all required classes. Wildcards
+can be helpful to specify a large number of related classes in one go. If you
+want to specify all implementations of a certain interface, fill out the
+fully qualified interface name in the <b>Extends/implements class</b> instead.
+<p>
+
+For more advanced settings, it is advisable to become familiar with ProGuard's
+configuration options through the <a href="usage.html">Usage section</a> and
+the <a href="examples.html">Examples section</a>.  We'll suffice with a brief
+overview of the three dialogs provided by the GUI.
+<p>
+
+The <i>keep class</i> dialog appears when adding or editing new special keep
+entries. It has text fields and selections for specifying and constraining
+classes and class members to keep. The <b>Advanced options</b> / <b>Basic
+options</b> button at the bottom of the dialog allows to toggle showing the
+advanced options.
+
+<ul>
+<li>The <b>Comments</b> text field allows to add optional comments to this
+    entry. The comments will identify the entry in the list and they will
+    appear as comments in the configuration file.
+
+<li>The <b>Keep</b> selection allows to specify whether you want to protect
+    the specified classes and their specified class members, or just the
+    specified class members from the specified classes, or the specified
+    classes and the specified class members, if the class members are present.
+    Note that class members will only be protected if they are explicitly
+    specified, even if only by means of a wildcard.
+
+<li>The <b>Allow</b> selection allows to specify whether you want to allow the
+    the specified classes and their specified class members to be shrunk,
+    optimized and/or obfuscated.
+
+<li>The <b>Access</b> selections allows to specify constraints on the class or
+    classes, based on their access modifiers.
+
+<li>The <b>Annotation</b> text field takes the fully-qualified name of an
+    annotation that is required for matching classes. The annotation name can
+    contain wildcards. This is an advanced option for defining <i>keep</i>
+    annotations.
+
+<li>The <b>Class</b> text field takes the fully-qualified name of the class or
+    classes. The class name can contain wildcards.
+
+<li>The <b>Annotation</b> text field takes the fully-qualified name of an
+    annotation that is required for the class or interface that the above
+    class must extend. The annotation name can contain wildcards. This is an
+    advanced option for defining <i>keep</i> annotations.
+
+<li>The <b>Extends/implements class</b> text field takes the fully-qualified
+    name of the class or interface that the above classes must extend.
+
+<li>The <b>Class members</b> list allows to specify a list of fields and
+    methods to keep. It can be edited by means of a list of buttons on the
+    right-hand side.
+</ul>
+<p>
+
+The <i>keep field</i> dialog appears when adding or editing fields within the
+above dialog. It has text fields and selections for specifying and
+constraining fields to keep. Again, the <b>Advanced options</b> / <b>Basic
+options</b> button at the bottom of the dialog allows to toggle showing the
+advanced options.
+
+<ul>
+<li>The <b>Access</b> selections allows to specify constraints on the field or
+    fields, based on their access modifiers.
+
+<li>The <b>Annotation</b> text field takes the fully-qualified name of an
+    annotation that is required for matching fields. The annotation name can
+    contain wildcards. This is an advanced option for defining <i>keep</i>
+    annotations.
+
+<li>The <b>Return type</b> text field takes the fully-qualified type of the
+    field or fields. The type can contain wildcards.
+
+<li>The <b>Name</b> text field takes the name of the field or fields. The field
+    name can contain wildcards.
+</ul>
+<p>
+
+Similarly, the <i>keep method</i> dialog appears when adding or editing
+methods within the keep class dialog. It has text fields and selections for
+specifying and constraining methods to keep. Again, the <b>Advanced
+options</b> / <b>Basic options</b> button at the bottom of the dialog allows
+to toggle showing the advanced options.
+
+<ul>
+<li>The <b>Access</b> selections allows to specify constraints on the method or
+    methods, based on their access modifiers.
+
+<li>The <b>Annotation</b> text field takes the fully-qualified name of an
+    annotation that is required for matching methods. The annotation name can
+    contain wildcards. This is an advanced option for defining <i>keep</i>
+    annotations.
+
+<li>The <b>Return type</b> text field takes the fully-qualified type of the         method or methods. The type can contain wildcards.
+
+<li>The <b>Name</b> text field takes the name of the method or methods. The
+    method name can contain wildcards.
+
+<li>The <b>Arguments</b> text field takes the comma-separated list of
+    fully-qualified method arguments. Each of these arguments can contain
+    wildcards.
+</ul>
+<p>
+
+Corresponding configuration options:
+<ul type="none">
+<li>-<a href="usage.html#dontshrink">dontshrink</a>
+<li>-<a href="usage.html#printusage">printusage</a>
+<li>-<a href="usage.html#keep">keep</a>
+<li>-<a href="usage.html#keepclassmembers">keepclassmembers</a>
+<li>-<a href="usage.html#keepclasseswithmembers">keepclasseswithmembers</a>
+</ul>
+<p>
+
+<a name="obfuscation">&nbsp;</a>
+<h2>The Obfuscation Tab</h2>
+
+The <i>Obfuscation</i> tab presents a number of options that affect the
+obfuscation step. The basic options are followed by a few lists of classes and
+class members (fields and methods) that must be protected from obfuscation
+(but not necessarily from shrinking).
+<p>
+
+The lists are manipulated in the same way as in the <a
+href="#shrinking">Shrinking Tab</a>.
+<p>
+
+Corresponding configuration options:
+<ul type="none">
+<li>-<a href="usage.html#dontobfuscate">dontobfuscate</a>
+<li>-<a href="usage.html#printmapping">printmapping</a>
+<li>-<a href="usage.html#applymapping">applymapping</a>
+<li>-<a href="usage.html#obfuscationdictionary">obfuscationdictionary</a>
+<li>-<a href="usage.html#classobfuscationdictionary">classobfuscationdictionary</a>
+<li>-<a href="usage.html#packageobfuscationdictionary">packageobfuscationdictionary</a>
+<li>-<a href="usage.html#overloadaggressively">overloadaggressively</a>
+<li>-<a href="usage.html#useuniqueclassmembernames">useuniqueclassmembernames</a>
+<li>-<a href="usage.html#dontusemixedcaseclassnames">dontusemixedcaseclassnames</a>
+<li>-<a href="usage.html#keeppackagenames">keeppackagenames</a>
+<li>-<a href="usage.html#flattenpackagehierarchy">flattenpackagehierarchy</a>
+<li>-<a href="usage.html#repackageclasses">repackageclasses</a>
+<li>-<a href="usage.html#keepattributes">keepattributes</a>
+<li>-<a href="usage.html#renamesourcefileattribute">renamesourcefileattribute</a>
+<li>-<a href="usage.html#adaptclassstrings">adaptclassstrings</a>
+<li>-<a href="usage.html#adaptresourcefilenames">adaptresourcefilenames</a>
+<li>-<a href="usage.html#adaptresourcefilecontents">adaptresourcefilecontents</a>
+<li>-<a href="usage.html#keepnames">keepnames</a>
+<li>-<a href="usage.html#keepclassmembernames">keepclassmembernames</a>
+<li>-<a href="usage.html#keepclasseswithmembernames">keepclasseswithmembernames</a>
+<li><a href="usage.html#classspecification"><i>class_specification</i></a>
+</ul>
+<p>
+
+<a name="optimization">&nbsp;</a>
+<h2>The Optimization Tab</h2>
+
+The <i>Optimization</i> tab presents a number of options that affect the
+optimization step. The basic options are followed by a few lists of class
+method calls that can be removed if ProGuard can determine that their results
+are not being used.
+<p>
+
+The lists are manipulated in much the same way as in the <a
+href="#shrinking">Shrinking Tab</a>.
+<p>
+
+Corresponding configuration options:
+<ul type="none">
+<li>-<a href="usage.html#dontoptimize">dontoptimize</a>
+<li>-<a href="usage.html#optimizations">optimizations</a>
+<li>-<a href="usage.html#optimizationpasses">optimizationpasses</a>
+<li>-<a href="usage.html#allowaccessmodification">allowaccessmodification</a>
+<li>-<a href="usage.html#mergeinterfacesaggressively">mergeinterfacesaggressively</a>
+<li>-<a href="usage.html#assumenosideeffects">assumenosideeffects</a>
+<li><a href="usage.html#classspecification"><i>class_specification</i></a>
+</ul>
+<p>
+
+<a name="information">&nbsp;</a>
+<h2>The Information Tab</h2>
+
+The <i>Information</i> tab presents a number of options for preverification
+and targeting, and for the information that ProGuard returns when processing
+your code. The bottom list allows you to query ProGuard about why given
+classes and class members are being kept in the shrinking step.
+<p>
+
+Corresponding configuration options:
+<ul type="none">
+<li>-<a href="usage.html#dontpreverify">dontpreverify</a>
+<li>-<a href="usage.html#microedition">microedition</a>
+<li>-<a href="usage.html#target">target</a>
+<li>-<a href="usage.html#verbose">verbose</a>
+<li>-<a href="usage.html#dontnote">dontnote</a>
+<li>-<a href="usage.html#dontwarn">dontwarn</a>
+<li>-<a href="usage.html#ignorewarnings">ignorewarnings</a>
+<li>-<a href="usage.html#dontskipnonpubliclibraryclasses">dontskipnonpubliclibraryclasses</a>
+<li>-<a href="usage.html#dontskipnonpubliclibraryclassmembers">dontskipnonpubliclibraryclassmembers</a>
+<li>-<a href="usage.html#keepdirectories">keepdirectories</a>
+<li>-<a href="usage.html#forceprocessing">forceprocessing</a>
+<li>-<a href="usage.html#printseeds">printseeds</a>
+<li>-<a href="usage.html#printconfiguration">printconfiguration</a>
+<li>-<a href="usage.html#dump">dump</a>
+<li>-<a href="usage.html#whyareyoukeeping">whyareyoukeeping</a>
+</ul>
+<p>
+
+<a name="process">&nbsp;</a>
+<h2>The Process Tab</h2>
+
+The <i>Process</i> tab has an output console for displaying the configuration
+and the messages while processing. There are three important buttons at the
+bottom:
+<p>
+
+<table cellspacing="5" cellpadding="5">
+<tr><td class="button">View configuration</td>
+    <td>displays the current ProGuard configuration in the console.</td></tr>
+<tr><td class="button">Save configuration...</td>
+    <td>opens a file chooser to save the current ProGuard
+    configuration.</td></tr>
+<tr><td class="button">Process!</td>
+    <td>executes ProGuard with the current configuration.</td></tr>
+</table>
+<p>
+
+<a name="retrace">&nbsp;</a>
+<h2>The ReTrace Tab</h2>
+
+The <i>ReTrace</i> tab has a panel with a few settings, an input text area for
+the obfuscated stack trace, and an output console to view the de-obfuscated
+stack trace:
+
+<ul>
+<li>The <b>Verbose</b> check box in the settings panel allows to toggle between
+    normal mode and verbose mode.
+
+<li>The <b>Mapping file</b> text field takes the name of the required mapping
+    file that ProGuard wrote while processing the original code. The file name
+    can be entered manually or by means of the <b>Browse...</b> button that
+    opens a file chooser.
+
+<li>The <b>Obfuscated stack trace</b> text area allows to enter the stack
+    trace, typically by copying and pasting it from elsewhere. Alternatively,
+    it can be loaded from a file by means of the load button below.
+</ul>
+
+There are two buttons at the bottom:
+<p>
+
+<table cellspacing="5" cellpadding="5">
+<tr><td class="button">Load stack trace...</td>
+    <td>opens a file chooser to load an obfuscated stack trace.</td></tr>
+<tr><td class="button">ReTrace!</td>
+    <td>executes ReTrace with the current settings.</td></tr>
+</table>
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/index.html b/docs/manual/index.html
new file mode 100644
index 0000000..397b910
--- /dev/null
+++ b/docs/manual/index.html
@@ -0,0 +1,39 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Manual</title>
+</head>
+<body>
+
+<h2>ProGuard</h2>
+
+<ol>
+<li><a href="introduction.html">Introduction</a>
+<li><a href="usage.html">Usage</a>
+<li><a href="limitations.html">Limitations</a>
+<li><a href="examples.html">Examples</a>
+<li><a href="troubleshooting.html">Troubleshooting</a>
+<li><a href="refcard.html">Reference Card</a>
+<li><a href="gui.html">Graphical User Interface</a>
+<li><a href="ant.html">Ant Task</a>
+<li><a href="wtk.html">JME Wireless Toolkit Integration</a>
+</ol>
+
+<h2>ReTrace</h2>
+
+<ol>
+<li><a href="retrace/introduction.html">Introduction</a>
+<li><a href="retrace/usage.html">Usage</a>
+<li><a href="retrace/examples.html">Examples</a>
+</ol>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/introduction.html b/docs/manual/introduction.html
new file mode 100644
index 0000000..cdab330
--- /dev/null
+++ b/docs/manual/introduction.html
@@ -0,0 +1,156 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Introduction</title>
+</head>
+<body>
+
+<h2>Introduction</h2>
+
+<b>ProGuard</b> is a Java class file shrinker, optimizer, obfuscator, and
+preverifier. The shrinking step detects and removes unused classes, fields,
+methods, and attributes. The optimization step analyzes and optimizes the
+bytecode of the methods. The obfuscation step renames the remaining classes,
+fields, and methods using short meaningless names. These first steps make the
+code base smaller, more efficient, and harder to reverse-engineer. The final
+preverification step adds preverification information to the classes, which is
+required for Java Micro Edition or which improves the start-up time for Java
+6.
+<p>
+Each of these steps is optional. For instance, ProGuard can also be used to
+just list dead code in an application, or to preverify class files for
+efficient use in Java 6.
+<p>
+
+<table class="diagram" align="center">
+
+<tr>
+<td rowspan="4" class="lightblock">Input jars</td>
+<td colspan="8" class="transparentblock"></td>
+</tr>
+
+<tr>
+<td rowspan="2" class="transparentblock"></td>
+<td rowspan="3" class="lightblock">Shrunk code</td>
+<td colspan="6" class="transparentblock"></td>
+</tr>
+
+<tr>
+<td             class="transparentblock"></td>
+<td rowspan="2" class="lightblock">Optim. code</td>
+<td colspan="3" class="transparentblock"></td>
+<td rowspan="2" class="lightblock">Output jars</td>
+</tr>
+
+<tr>
+<td             class="transparentblock">- shrink &rarr;</td>
+<td             class="transparentblock">- optimize &rarr;</td>
+<td             class="transparentblock">- obfuscate &rarr;</td>
+<td             class="lightblock">Obfusc. code</td>
+<td             class="transparentblock">- preverify &rarr;</td>
+</tr>
+
+<tr>
+<td             class="darkblock">Library jars</td>
+<td colspan="7" class="transparentblock">------------------------------- (unchanged) -------------------------------&rarr;</td>
+<td             class="darkblock">Library jars</td>
+</tr>
+
+</table>
+<p>
+
+ProGuard typically reads the <b>input jars</b> (or wars, ears, zips, or
+directories). It then shrinks, optimizes, obfuscates, and preverifies them.
+Optionally, multiple optimization passes can be performed, each typically
+followed by another shrinking step. ProGuard writes the processed results to
+one or more <b>output jars</b> (or wars, ears, zips, or directories). The
+input may contain resource files, whose names and contents can optionally be
+updated to reflect the obfuscated class names.
+<p>
+ProGuard requires the <b>library jars</b> (or wars, ears, zips, or
+directories) of the input jars to be specified. These are essentially the
+libraries that you would need for compiling the code. ProGuard uses them to
+reconstruct the class dependencies that are necessary for proper processing.
+The library jars themselves always remain unchanged. You should still put them
+in the class path of your final application.
+<p>
+In order to determine which code has to be preserved and which code can be
+discarded or obfuscated, you have to specify one or more <i>entry points</i> to
+your code. These entry points are typically classes with main methods, applets,
+midlets, etc.
+<ul>
+<li>In the <b>shrinking step</b>, ProGuard starts from these seeds and
+    recursively determines which classes and class members are used. All other
+    classes and class members are discarded.
+
+<li>In the <b>optimization step</b>, ProGuard further optimizes the code.
+    Among other optimizations, classes and methods that are not entry points
+    can be made private, static, or final, unused parameters can be removed,
+    and some methods may be inlined.
+
+<li>In the <b>obfuscation step</b>, ProGuard renames classes and class members
+    that are not entry points. In this entire process, keeping the entry
+    points ensures that they can still be accessed by their original names.
+
+<li>The <b>preverification step</b> is the only step that doesn't have to know
+    the entry points.
+</ul>
+<p>
+The <a href="usage.html">Usage section</a> of this manual describes the
+necessary <a href="usage.html#keepoptions"><code>-keep</code> options</a> and
+the <a href="examples.html">Examples section</a> provides plenty of examples.
+
+<h3>Introspection</h3>
+
+Introspection presents particular problems for any automatic processing of
+code. In ProGuard, classes or class members in your code that are created or
+invoked dynamically (that is, by name) have to be specified as entry points
+too. For example, <code>Class.forName()</code> constructs may refer to any
+class at run-time. It is generally impossible to foresee which classes have to
+be preserved (with their original names), since the class names might be read
+from a configuration file, for instance. You therefore have to specify them in
+your ProGuard configuration, with the same simple <code>-keep</code> options.
+<p>
+However, ProGuard will already detect and handle the following cases for you:
+
+<ul>
+<li><code>Class.forName("SomeClass")</code>
+<li><code>SomeClass.class</code>
+<li><code>SomeClass.class.getField("someField")</code>
+<li><code>SomeClass.class.getDeclaredField("someField")</code>
+<li><code>SomeClass.class.getMethod("someMethod", new Class[] {})</code>
+<li><code>SomeClass.class.getMethod("someMethod", new Class[] { A.class })</code>
+<li><code>SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })</code>
+<li><code>SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})</code>
+<li><code>SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })</code>
+<li><code>SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })</code>
+</ul>
+
+The names of the classes and class members may of course be different, but the
+constructs should be literally the same for ProGuard to recognize them. The
+referenced classes and class members are preserved in the shrinking phase, and
+the string arguments are properly replaced in the obfuscation phase.
+<p>
+Furthermore, ProGuard will offer some suggestions if keeping some classes or
+class members appears necessary. For example, ProGuard will note constructs
+like "<code>(SomeClass)Class.forName(variable).newInstance()</code>". These
+might be an indication that the class or interface <code>SomeClass</code>
+and/or its implementations may need to be preserved. You can then adapt your
+configuration accordingly.
+<p>
+For proper results, you should at least be somewhat familiar with the code
+that you are processing. Obfuscating code that performs a lot of introspection
+may require trial and error, especially without the necessary information
+about the internals of the code.
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/limitations.html b/docs/manual/limitations.html
new file mode 100644
index 0000000..cfe0ff5
--- /dev/null
+++ b/docs/manual/limitations.html
@@ -0,0 +1,64 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Limitations</title>
+</head>
+<body>
+
+<h2>Limitations</h2>
+
+When using ProGuard, you should be aware of a few technical issues, all of
+which are easily avoided or resolved:
+<p>
+<ul>
+
+<li>For efficiency, ProGuard always ignores any <b>private or package visible
+    library classes</b> while reading library jars. If any of them are
+    extended by public library classes, and then extended again by input
+    classes, ProGuard will complain it can't find them. In that case, you'll
+    have to use the <code>-dontskipnonpubliclibraryclasses</code> option, and
+    maybe even the <code>-dontskipnonpubliclibraryclassmembers</code> option.
+    The graphical user interface has checkboxes for these settings.
+    <p>
+
+<li>For best results, ProGuard's optimization algorithms assume that the
+    processed code never <b>intentionally throws NullPointerExceptions</b> or
+    ArrayIndexOutOfBoundsExceptions, or even OutOfMemoryErrors or
+    StackOverflowErrors, in order to achieve something useful. For instance,
+    it may remove a method call <code>myObject.myMethod()</code> if that call
+    wouldn't have any effect. It ignores the possibility that
+    <code>myObject</code> might be null, causing a NullPointerException. In
+    some way this is a good thing: optimized code may throw fewer exceptions.
+    Should this entire assumption be false, you'll have to switch off
+    optimization using the <code>-dontoptimize</code> option.
+    <p>
+
+<li>If an input jar and a library jar contain classes in the <b>same
+    package</b>, the obfuscated output jar may contain class names that
+    overlap with class names in the library jar. This is most likely if the
+    library jar has been obfuscated before, as it will then probably contain
+    classes named 'a', 'b', etc. Packages should therefore never be split
+    across input jars and library jars.
+    <p>
+
+<li>When obfuscating, ProGuard will write out class files named
+    "<code>a.class</code>", "<code>b.class</code>", etc. If there is a large
+    numbers of classes in the same package, it may also write out
+    <b>"<code>aux.class</code>"</b>. Windows doesn't allow creating files with
+    this reserved name (among a few other names), so it's generally better to
+    write the output to a jar, in order to avoid such problems.
+    <p>
+
+</ul>
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/optimizations.html b/docs/manual/optimizations.html
new file mode 100644
index 0000000..9c20571
--- /dev/null
+++ b/docs/manual/optimizations.html
@@ -0,0 +1,158 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>Optimizations</title>
+</head>
+<body>
+
+<h2>Optimizations</h2>
+
+The optimization step of ProGuard can be switched off with the
+<a href="usage.html#dontoptimize"><code>-dontoptimize</code></a> option. For
+more fine-grained control over individual optimizations, experts can use the
+<a href="usage.html#optimizations"><code>-optimizations</code></a> option,
+with a filter based on the optimization names listed below. The filter works
+like any <a href="usage.html#filters">filter</a> in ProGuard.
+<p>
+
+The following wildcards are supported:
+
+<table cellspacing="10">
+<tr><td valign="top"><code><b>?</b></code></td>
+    <td>matches any single character in an optimization name.</td></tr>
+<tr><td valign="top"><code><b>*</b></code></td>
+    <td>matches any part of an optimization name.</td></tr>
+</table>
+
+An optimization that is preceded by an exclamation mark '<b>!</b>' is
+<i>excluded</i> from further attempts to match with <i>subsequent</i>
+optimization names in the filter. Make sure to specify filters correctly,
+since they are not checked for potential typos.
+<p>
+
+For example,
+"<code>code/simplification/variable,code/simplification/arithmetic</code>"
+only performs the two specified peephole optimizations.
+<p>
+
+For example, "<code>!method/propagation/*</code>" performs all optimizations,
+except the ones that propagate values between methods.
+<p>
+
+For example,
+"<code>!code/simplification/advanced,code/simplification/*</code>" only
+performs all peephole optimizations.
+<p>
+Some optimizations necessarily imply other optimizations. These are then
+indicated. Note that the list is likely to change over time, as optimizations
+are added and reorganized.
+<p>
+
+<dl>
+<dt><code><b>class/marking/final</b></code></dt>
+<dd>Marks classes as final, whenever possible.</dd>
+
+<dt><code><b>class/merging/vertical</b></code></dt>
+<dd>Merges classes vertically in the class hierarchy, whenever possible.</dd>
+
+<dt><code><b>class/merging/horizontal</b></code></dt>
+<dd>Merges classes horizontally in the class hierarchy, whenever possible.</dd>
+
+<dt><div>(&rArr; <code>code/removal/advanced</code>)</div>
+    <code><b>field/removal/writeonly</b></code></dt>
+<dd>Removes write-only fields.</dd>
+
+<dt><code><b>field/marking/private</b></code></dt>
+<dd>Marks fields as private, whenever possible.</dd>
+
+<dt><div>(&rArr; <code>code/simplification/advanced</code>)</div>
+    <code><b>field/propagation/value</b></code></dt>
+<dd>Propagates the values of fields across methods.</dd>
+
+<dt><code><b>method/marking/private</b></code></dt>
+<dd>Marks methods as private, whenever possible (<i>devirtualization</i>).</dd>
+
+<dt><div>(&rArr; <code>code/removal/advanced</code>)</div>
+    <code><b>method/marking/static</b></code></dt>
+<dd>Marks methods as static, whenever possible (<i>devirtualization</i>).</dd>
+
+<dt><code><b>method/marking/final</b></code></dt>
+<dd>Marks methods as final, whenever possible.</dd>
+
+<dt><div>(&rArr; <code>code/removal/advanced</code>)</div>
+    <code><b>method/removal/parameter</b></code></dt>
+<dd>Removes unused method parameters.</dd>
+
+<dt><div>(&rArr; <code>code/simplification/advanced</code>)</div>
+    <code><b>method/propagation/parameter</b></code></dt>
+<dd>Propagates the values of method parameters from method invocations to
+    the invoked methods.</dd>
+
+<dt><div>(&rArr; <code>code/simplification/advanced</code>)</div>
+    <code><b>method/propagation/returnvalue</b></code></dt>
+<dd>Propagates the values of method return values from methods to their
+    invocations.</dd>
+
+<dt><code><b>method/inlining/short</b></code></dt>
+<dd>Inlines short methods.</dd>
+
+<dt><code><b>method/inlining/unique</b></code></dt>
+<dd>Inlines methods that are only called once.</dd>
+
+<dt><code><b>method/inlining/tailrecursion</b></code></dt>
+<dd>Simplifies tail recursion calls, whenever possible.</dd>
+
+<dt><code><b>code/merging</b></code></dt>
+<dd>Merges identical blocks of code by modifying branch targets.</dd>
+
+<dt><code><b>code/simplification/variable</b></code></dt>
+<dd>Performs peephole optimizations for variable loading and storing.</dd>
+
+<dt><code><b>code/simplification/arithmetic</b></code></dt>
+<dd>Performs peephole optimizations for arithmetic instructions.</dd>
+
+<dt><code><b>code/simplification/cast</b></code></dt>
+<dd>Performs peephole optimizations for casting operations.</dd>
+
+<dt><code><b>code/simplification/field</b></code></dt>
+<dd>Performs peephole optimizations for field loading and storing.</dd>
+
+<dt><div>(&rArr; <code>code/removal/simple</code>)</div>
+    <code><b>code/simplification/branch</b></code></dt>
+<dd>Performs peephole optimizations for branch instructions.</dd>
+
+<dt><div>(<i>best used with</i> <code>code/removal/advanced</code>)</div>
+    <code><b>code/simplification/advanced</b></code></dt>
+<dd>Simplifies code based on control flow analysis and data flow
+    analysis.</dd>
+
+<dt><div>(&rArr; <code>code/removal/exception</code>)</div>
+    <code><b>code/removal/advanced</b></code></dt>
+<dd>Removes dead code based on control flow analysis and data flow
+    analysis.</dd>
+
+<dt><div>(&rArr; <code>code/removal/exception</code>)</div>
+    <code><b>code/removal/simple</b></code></dt>
+<dd>Removes dead code based on a simple control flow analysis.</dd>
+
+<dt><code><b>code/removal/variable</b></code></dt>
+<dd>Removes unused variables from the local variable frame.</dd>
+
+<dt><code><b>code/removal/exception</b></code></dt>
+<dd>Removes exceptions with empty catch blocks.</dd>
+
+<dt><code><b>code/allocation/variable</b></code></dt>
+<dd>Optimizes variable allocation on the local variable frame.</dd>
+</dl>
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/refcard.html b/docs/manual/refcard.html
new file mode 100644
index 0000000..236f049
--- /dev/null
+++ b/docs/manual/refcard.html
@@ -0,0 +1,465 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Reference Card</title>
+</head>
+<body>
+
+<h1>ProGuard Reference Card</h1>
+
+<h2>Usage</h2>
+
+<code><b>java -jar proguard.jar </b></code><i>options</i> ...
+<p>
+&nbsp;&nbsp;Typically:
+<p>
+<code><b>java -jar proguard.jar @myconfig.pro</b></code>
+<p>
+
+<h2>Options</h2>
+
+<table cellspacing="10">
+
+<tr>
+<td valign="top"><a href="usage.html#at"><code><b>@</b></code></a><a href="usage.html#filename"><i>filename</i></a></td>
+
+<td>Short for '<code>-include</code> <i>filename</i>'.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#include"><code><b>-include</b></code></a>
+                 <a href="usage.html#filename"><i>filename</i></a></td>
+
+<td>Read configuration options from the given file.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#basedirectory"><code><b>-basedirectory</b></code></a>
+                 <a href="usage.html#filename"><i>directoryname</i></a></td>
+
+<td>Specifies the base directory for subsequent relative file names.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#injars"><code><b>-injars</b></code></a>
+                 <a href="usage.html#classpath"><i>class_path</i></a></td>
+<td>Specifies the program jars (or wars, ears, zips, or directories).</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#outjars"><code><b>-outjars</b></code></a>
+                 <a href="usage.html#classpath"><i>class_path</i></a></td>
+<td>Specifies the name of the output jars (or wars, ears, zips, or
+    directories).</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#libraryjars"><code><b>-libraryjars</b></code></a>
+                 <a href="usage.html#classpath"><i>class_path</i></a></td>
+<td>Specifies the library jars (or wars, ears, zips, or directories).</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontskipnonpubliclibraryclasses"><code><b>-dontskipnonpubliclibraryclasses</b></code></a></td>
+<td>Don't ignore non-public library classes.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontskipnonpubliclibraryclassmembers"><code><b>-dontskipnonpubliclibraryclassmembers</b></code></a></td>
+<td>Don't ignore package visible library class members.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#keepdirectories"><code><b>-keepdirectories</b></code></a>
+                 [<a href="usage.html#filters"><i>directory_filter</i></a>]</td>
+<td>Keep the specified directories in the output jars (or wars, ears, zips, or
+    directories).</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#target"><code><b>-target</b></code></a>
+                 <i>version</i></td>
+<td>Set the given version number in the processed classes.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#forceprocessing"><code><b>-forceprocessing</b></code></a></td>
+<td>Process the input, even if the output seems up to date.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#keep"><code><b>-keep</b></code></a>
+                 [<a href="usage.html#keepoptionmodifiers">,<i>modifier</i></a>,...]
+                 <a href="usage.html#classspecification"><i>class_specification</i></a></td>
+<td>Preserve the specified classes <i>and</i> class members.</td>
+
+</tr>
+<tr>
+<td valign="top"><a href="usage.html#keepclassmembers"><code><b>-keepclassmembers</b></code></a>
+                 [<a href="usage.html#keepoptionmodifiers">,<i>modifier</i></a>,...]
+                 <a href="usage.html#classspecification"><i>class_specification</i></a></td>
+<td>Preserve the specified class members, if their classes are preserved as
+    well.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#keepclasseswithmembers"><code><b>-keepclasseswithmembers</b></code></a>
+                 [<a href="usage.html#keepoptionmodifiers">,<i>modifier</i></a>,...]
+                 <a href="usage.html#classspecification"><i>class_specification</i></a></td>
+<td>Preserve the specified classes <i>and</i> class members, if all of the
+    specified class members are present.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#keepnames"><code><b>-keepnames</b></code></a>
+                 <a href="usage.html#classspecification"><i>class_specification</i></a></td>
+<td>Preserve the names of the specified classes <i>and</i> class members (if
+    they aren't removed in the shrinking step).</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#keepclassmembernames"><code><b>-keepclassmembernames</b></code></a>
+                 <a href="usage.html#classspecification"><i>class_specification</i></a></td>
+<td>Preserve the names of the specified class members (if they aren't removed
+    in the shrinking step).</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#keepclasseswithmembernames"><code><b>-keepclasseswithmembernames</b></code></a>
+                 <a href="usage.html#classspecification"><i>class_specification</i></a></td>
+<td>Preserve the names of the specified classes <i>and</i> class members, if
+    all of the specified class members are present (after the shrinking
+    step).</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#printseeds"><code><b>-printseeds</b></code></a>
+                 [<a href="usage.html#filename"><i>filename</i></a>]</td>
+<td>List classes and class members matched by the various <code>-keep</code>
+    options, to the standard output or to the given file.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontshrink"><code><b>-dontshrink</b></code></a></td>
+<td>Don't shrink the input class files.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#printusage"><code><b>-printusage</b></code></a>
+                 [<a href="usage.html#filename"><i>filename</i></a>]</td>
+<td>List dead code of the input class files, to the standard output or to the
+    given file.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#whyareyoukeeping"><code><b>-whyareyoukeeping</b></code></a>
+                 <a href="usage.html#classspecification"><i>class_specification</i></a></td>
+<td>Print details on why the given classes and class members are being kept in
+    the shrinking step.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontoptimize"><code><b>-dontoptimize</b></code></a></td>
+<td>Don't optimize the input class files.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#optimizations"><code><b>-optimizations</b></code></a>
+                 <a href="optimizations.html"><i>optimization_filter</i></a></td>
+<td>The optimizations to be enabled and disabled.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#optimizationpasses"><code><b>-optimizationpasses</b></code></a>
+                 <i>n</i></td>
+<td>The number of optimization passes to be performed.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#assumenosideeffects"><code><b>-assumenosideeffects</b></code></a>
+                 <a href="usage.html#classspecification"><i>class_specification</i></a></td>
+<td>Assume that the specified methods don't have any side effects, while
+    optimizing.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#allowaccessmodification"><code><b>-allowaccessmodification</b></code></a></td>
+<td>Allow the access modifiers of classes and class members to be modified,
+    while optimizing.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#mergeinterfacesaggressively"><code><b>-mergeinterfacesaggressively</b></code></a></td>
+<td>Allow any interfaces to be merged, while optimizing.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontobfuscate"><code><b>-dontobfuscate</b></code></a></td>
+<td>Don't obfuscate the input class files.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#printmapping"><code><b>-printmapping</b></code></a>
+                 [<a href="usage.html#filename"><i>filename</i></a>]</td>
+<td>Print the mapping from old names to new names for classes and class members
+    that have been renamed, to the standard output or to the given file.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#applymapping"><code><b>-applymapping</b></code></a>
+                 <a href="usage.html#filename"><i>filename</i></a></td>
+<td>Reuse the given mapping, for incremental obfuscation.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#obfuscationdictionary"><code><b>-obfuscationdictionary</b></code></a>
+                 <a href="usage.html#filename"><i>filename</i></a></td>
+<td>Use the words in the given text file as obfuscated field names and method names.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#classobfuscationdictionary"><code><b>-classobfuscationdictionary</b></code></a>
+                 <a href="usage.html#filename"><i>filename</i></a></td>
+<td>Use the words in the given text file as obfuscated class names.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#packageobfuscationdictionary"><code><b>-packageobfuscationdictionary</b></code></a>
+                 <a href="usage.html#filename"><i>filename</i></a></td>
+<td>Use the words in the given text file as obfuscated package names.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#overloadaggressively"><code><b>-overloadaggressively</b></code></a></td>
+<td>Apply aggressive overloading while obfuscating.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#useuniqueclassmembernames"><code><b>-useuniqueclassmembernames</b></code></a></td>
+<td>Ensure uniform obfuscated class member names for subsequent incremental
+    obfuscation.</td> </tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontusemixedcaseclassnames"><code><b>-dontusemixedcaseclassnames</b></code></a></td>
+<td>Don't generate mixed-case class names while obfuscating.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#keeppackagenames"><code><b>-keeppackagenames</b></code></a>
+                 [<i><a href="usage.html#filters">package_filter</a></i>]</td>
+<td>Keep the specified package names from being obfuscated.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#flattenpackagehierarchy"><code><b>-flattenpackagehierarchy</b></code></a>
+                 [<i>package_name</i>]</td>
+<td>Repackage all packages that are renamed into the single given parent
+    package.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#repackageclasses"><code><b>-repackageclasses</b></code></a>
+                 [<i>package_name</i>]</td>
+<td>Repackage all class files that are renamed into the single given
+    package.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#keepattributes"><code><b>-keepattributes</b></code></a>
+                 [<i><a href="usage.html#filters">attribute_filter</a></i>]</td>
+<td>Preserve the given optional attributes; typically
+    <code>Exceptions</code>, <code>InnerClasses</code>,
+    <code>Signature</code>, <code>Deprecated</code>,
+    <code>SourceFile</code>, <code>SourceDir</code>,
+    <code>LineNumberTable</code>,
+    <code>LocalVariableTable</code>, <code>LocalVariableTypeTable</code>,
+    <code>Synthetic</code>, <code>EnclosingMethod</code>, and
+    <code>*Annotation*</code>.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#renamesourcefileattribute"><code><b>-renamesourcefileattribute</b></code></a>
+                 [<i>string</i>]</td>
+<td>Put the given constant string in the <code>SourceFile</code>
+    attributes.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#adaptclassstrings"><code><b>-adaptclassstrings</b></code></a>
+                 [<a href="usage.html#filters"><i>class_filter</i></a>]</td>
+<td>Adapt string constants in the specified classes, based on the obfuscated
+    names of any corresponding classes.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#adaptresourcefilenames"><code><b>-adaptresourcefilenames</b></code></a>
+                 [<a href="usage.html#filefilters"><i>file_filter</i></a>]</td>
+<td>Rename the specified resource files, based on the obfuscated names of the
+    corresponding class files.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#adaptresourcefilecontents"><code><b>-adaptresourcefilecontents</b></code></a>
+                 [<a href="usage.html#filefilters"><i>file_filter</i></a>]</td>
+<td>Update the contents of the specified resource files, based on the
+    obfuscated names of the processed classes.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontpreverify"><code><b>-dontpreverify</b></code></a></td>
+<td>Don't preverify the processed class files.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#microedition"><code><b>-microedition</b></code></a></td>
+<td>Target the processed class files at Java Micro Edition.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#verbose"><code><b>-verbose</b></code></a></td>
+<td>Write out some more information during processing.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontnote"><code><b>-dontnote</b></code></a>
+                 [<a href="usage.html#filters"><i>class_filter</i></a>]</td>
+<td>Don't print notes about potential mistakes or omissions in the
+    configuration.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dontwarn"><code><b>-dontwarn</b></code></a>
+                 [<a href="usage.html#filters"><i>class_filter</i></a>]</td>
+<td>Don't warn about unresolved references at all.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#ignorewarnings"><code><b>-ignorewarnings</b></code></a></td>
+<td>Print warnings about unresolved references, but continue processing
+    anyhow.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#printconfiguration"><code><b>-printconfiguration</b></code></a>
+                 [<a href="usage.html#filename"><i>filename</i></a>]</td>
+<td>Write out the internal structure of the processed class files, to the
+    standard output or to the given file.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#dump"><code><b>-dump</b></code></a>
+                 [<a href="usage.html#filename"><i>filename</i></a>]</td>
+<td>Write out the entire configuration in traditional ProGuard style, to the
+    standard output or to the given file.</td>
+</tr>
+
+</table>
+<p>
+Notes:
+<ul>
+
+<li><i>class_path</i> is a list of jars, wars, ears, zips, and directories,
+    with optional filters, separated by path separators.
+<li><i>filename</i> can contain Java system properties delimited by
+    '<b>&lt;</b>' and '<b>&gt;</b>'.
+<li>If <i>filename</i> contains special characters, the entire name
+    should be quoted with single or double quotes.
+</ul>
+<p>
+
+<h2>Overview of <code>Keep</code> Options</h2>
+
+<table cellpadding="5">
+
+<tr>
+<th>Keep</th>
+<td>From being removed or renamed</td>
+<td>From being renamed</td>
+</tr>
+
+<tr>
+<td>Classes and class members</td>
+<td bgcolor="#E0E0E0"><a href="usage.html#keep"><code>-keep</code></a></td>
+<td bgcolor="#E0E0E0"><a href="usage.html#keepnames"><code>-keepnames</code></a></td>
+</tr>
+
+<tr>
+<td>Class members only</td>
+<td bgcolor="#E0E0E0"><a href="usage.html#keepclassmembers"><code>-keepclassmembers</code></a></td>
+<td bgcolor="#E0E0E0"><a href="usage.html#keepclassmembernames"><code>-keepclassmembernames</code></a></td>
+</tr>
+
+<tr>
+<td>Classes and class members, if class members present</td>
+<td bgcolor="#E0E0E0"><a href="usage.html#keepclasseswithmembers"><code>-keepclasseswithmembers</code></a></td>
+<td bgcolor="#E0E0E0"><a href="usage.html#keepclasseswithmembernames"><code>-keepclasseswithmembernames</code></a></td>
+</tr>
+
+</table>
+<p>
+
+<h2>Keep Option Modifiers</h2>
+
+<table cellspacing="10">
+
+<tr>
+<td valign="top"><a href="usage.html#allowshrinking"><code><b>allowshrinking</b></code></a></td>
+<td>The entry points specified in the keep tag may be shrunk.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#allowoptimization"><code><b>allowoptimization</b></code></a></td>
+<td>The entry points specified in the keep tag may be optimized.</td>
+</tr>
+
+<tr>
+<td valign="top"><a href="usage.html#allowobfuscation"><code><b>allowobfuscation</b></code></a></td>
+<td>The entry points specified in the keep tag may be obfuscated.</td>
+</tr>
+
+</table>
+<p>
+
+<h2>Class Specifications</h2>
+
+<pre>
+[<b>@</b><i>annotationtype</i>] [[<b>!</b>]<b>public</b>|<b>final</b>|<b>abstract</b> ...] [<b>!</b>]<b>interface</b>|<b>class</b> <i>classname</i>
+    [<b>extends</b>|<b>implements</b> [<b>@</b><i>annotationtype</i>] <i>classname</i>]
+[<b>{</b>
+    [<b>@</b><i>annotationtype</i>] [[<b>!</b>]<b>public</b>|<b>private</b>|<b>protected</b>|<b>static</b>|<b>volatile</b>|<b>transient</b> ...] <b>&lt;fields&gt;</b> |
+                                                                      (<i>fieldtype fieldname</i>)<b>;</b>
+    [<b>@</b><i>annotationtype</i>] [[<b>!</b>]<b>public</b>|<b>private</b>|<b>protected</b>|<b>static</b>|<b>synchronized</b>|<b>native</b>|<b>abstract</b>|<b>strictfp</b> ...] <b>&lt;methods&gt;</b> |
+                                                                                           <b>&lt;init&gt;(</b><i>argumenttype,...</i><b>)</b> |
+                                                                                           <i>classname</i><b>(</b><i>argumenttype,...</i><b>)</b> |
+                                                                                           (<i>returntype methodname</i><b>(</b><i>argumenttype,...</i><b>)</b>)<b>;</b>
+    [<b>@</b><i>annotationtype</i>] [[<b>!</b>]<b>public</b>|<b>private</b>|<b>protected</b>|<b>static</b> ... ] <b>*;</b>
+    ...
+<b>}</b>]
+</pre>
+<p>
+Notes:
+<ul>
+<li>Class names must always be fully qualified, i.e. including their package
+    names.
+<li>Types in <i>classname</i>, <i>annotationtype</i>, <i>returntype</i>, and
+    <i>argumenttype</i> can contain wildcards: '<code><b>?</b></code>' for a
+    single character, '<code><b>*</b></code>' for any number of characters
+    (but not the package separator), '<code><b>**</b></code>' for any number
+    of (any) characters, '<code><b>%</b></code>' for any primitive type,
+    '<code><b>***</b></code>' for any type, and '<code><b>...</b></code>' for       any number of arguments..
+<li><i>fieldname</i> and <i>methodname</i> can contain wildcards as well:
+    '<code><b>?</b></code>' for a single character and '<code><b>*</b></code>'
+    for any number of characters.
+</ul>
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/retrace/examples.html b/docs/manual/retrace/examples.html
new file mode 100644
index 0000000..211017b
--- /dev/null
+++ b/docs/manual/retrace/examples.html
@@ -0,0 +1,336 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="../style.css">
+<title>ReTrace Examples</title>
+</head>
+<body>
+
+<h2>Examples</h2>
+
+Some typical example uses:
+<ol>
+<li><a href="#with">Restoring a stack trace with line numbers</a>
+<li><a href="#withverbose">Restoring a stack trace with line numbers (verbose)</a>
+<li><a href="#without">Restoring a stack trace without line numbers</a>
+</ol>
+
+<a name="with">&nbsp;</a>
+<h3>Restoring a stack trace with line numbers</h3>
+
+Assume for instance ProGuard itself has been obfuscated using the following
+extra options:
+<pre>
+-printmapping proguard.map
+
+-renamesourcefileattribute ProGuard
+-keepattributes SourceFile,LineNumberTable
+</pre>
+<p>
+
+Now assume the processed application throws an exception, and we have saved the
+stack trace in <code>proguard.trace</code>, shown below. Of course, in real
+life ProGuard rarely throws exceptions, so this is a purposely generated
+exception. :)
+
+<pre>
+Exception in thread "main" java.lang.Error: Random exception
+        at pro.bY.a(ProGuard:576)
+        at pro.bO.a(ProGuard:431)
+        at pro.bj.a(ProGuard:145)
+        at pro.bY.a(ProGuard:522)
+        at pro.bj.a(ProGuard:129)
+        at pro.bN.a(ProGuard:125)
+        at pro.bY.a(ProGuard:251)
+        at pro.bY.a(ProGuard:229)
+        at pro.l.a(ProGuard:55)
+        at pro.bo.b(ProGuard:405)
+        at pro.ci.a(ProGuard:51)
+        at pro.bo.a(ProGuard:356)
+        at pro.be.a(ProGuard:109)
+        at pro.bo.a(ProGuard:356)
+        at pro.be.a(ProGuard:186)
+        at pro.bg.a(ProGuard:369)
+        at pro.bY.a(ProGuard:286)
+        at pro.bh.a(ProGuard:55)
+        at pro.bg.b(ProGuard:408)
+        at pro.bY.a(ProGuard:190)
+        at pro.bg.a(ProGuard:369)
+        at pro.M.a(ProGuard:110)
+        at pro.bY.a(ProGuard:449)
+        at pro.M.a(ProGuard:99)
+        at pro.bo.a(ProGuard:372)
+        at pro.bY.a(ProGuard:649)
+        at pro.bY.a(ProGuard:112)
+        at pro.P.a(ProGuard:66)
+        at pro.p.a(ProGuard:83)
+        at pro.bU.a(ProGuard:69)
+        at pro.bo.a(ProGuard:356)
+        at pro.J.a(ProGuard:149)
+        at pro.I.a(ProGuard:49)
+        at pro.J.a(ProGuard:105)
+        at pro.cf.c(ProGuard:370)
+        at pro.cf.a(ProGuard:317)
+        at pro.bc.a(ProGuard:55)
+        at proguard.ProGuard.a(ProGuard:363)
+        at proguard.ProGuard.c(ProGuard:187)
+        at proguard.ProGuard.b(ProGuard:385)
+        at proguard.ProGuard.main(ProGuard:429)
+</pre>
+<p>
+
+We can then use the following command to recover the stack trace:
+<pre>
+<b>java -jar retrace.jar proguard.map proguard.trace</b>
+</pre>
+<p>
+
+The output will look as follows:
+<pre>
+Exception in thread "main" java.lang.Error: Random exception
+        at proguard.shrink.UsageMarker.visitInstruction(ProGuard:576)
+        at proguard.classfile.instruction.GenericInstruction.accept(ProGuard:431)
+        at proguard.classfile.CodeAttrInfo.instructionsAccept(ProGuard:145)
+        at proguard.shrink.UsageMarker.visitCodeAttrInfo(ProGuard:522)
+        at proguard.classfile.CodeAttrInfo.accept(ProGuard:129)
+        at proguard.classfile.ProgramMemberInfo.attributesAccept(ProGuard:125)
+        at proguard.shrink.UsageMarker.visitMemberInfo(ProGuard:251)
+        at proguard.shrink.UsageMarker.visitProgramMethodInfo(ProGuard:229)
+        at proguard.classfile.ProgramMethodInfo.accept(ProGuard:55)
+        at proguard.classfile.ProgramClassFile.methodAccept(ProGuard:405)
+        at proguard.classfile.visitor.NamedMethodVisitor.visitProgramClassFile(ProGuard:51)
+        at proguard.classfile.ProgramClassFile.accept(ProGuard:356)
+        at proguard.classfile.visitor.ClassFileUpDownTraveler.visitProgramClassFile(ProGuard:109)
+        at proguard.classfile.ProgramClassFile.accept(ProGuard:356)
+        at proguard.classfile.visitor.ClassFileUpDownTraveler.visitLibraryClassFile(ProGuard:186)
+        at proguard.classfile.LibraryClassFile.accept(ProGuard:369)
+        at proguard.shrink.UsageMarker.visitLibraryMethodInfo(ProGuard:286)
+        at proguard.classfile.LibraryMethodInfo.accept(ProGuard:55)
+        at proguard.classfile.LibraryClassFile.methodsAccept(ProGuard:408)
+        at proguard.shrink.UsageMarker.visitLibraryClassFile(ProGuard:190)
+        at proguard.classfile.LibraryClassFile.accept(ProGuard:369)
+        at proguard.classfile.ClassCpInfo.referencedClassAccept(ProGuard:110)
+        at proguard.shrink.UsageMarker.visitClassCpInfo(ProGuard:449)
+        at proguard.classfile.ClassCpInfo.accept(ProGuard:99)
+        at proguard.classfile.ProgramClassFile.constantPoolEntryAccept(ProGuard:372)
+        at proguard.shrink.UsageMarker.markCpEntry(ProGuard:649)
+        at proguard.shrink.UsageMarker.visitProgramClassFile(ProGuard:112)
+        at proguard.classfile.visitor.VariableClassFileVisitor.visitProgramClassFile(ProGuard:66)
+        at proguard.classfile.visitor.MultiClassFileVisitor.visitProgramClassFile(ProGuard:83)
+        at proguard.classfile.visitor.FilteredClassFileVisitor.visitProgramClassFile(ProGuard:69)
+        at proguard.classfile.ProgramClassFile.accept(ProGuard:356)
+        at proguard.classfile.ClassPool.classFileAccept(ProGuard:149)
+        at proguard.classfile.visitor.NamedClassFileVisitor.visitClassPool(ProGuard:49)
+        at proguard.classfile.ClassPool.accept(ProGuard:105)
+        at proguard.KeepCommand.executeShrinkingPhase(ProGuard:370)
+        at proguard.KeepCommand.execute(ProGuard:317)
+        at proguard.CompoundCommand.execute(ProGuard:55)
+        at proguard.ProGuard.executeCommands(ProGuard:363)
+        at proguard.ProGuard.shrink(ProGuard:187)
+        at proguard.ProGuard.execute(ProGuard:385)
+        at proguard.ProGuard.main(ProGuard:429)
+</pre>
+
+<a name="withverbose">&nbsp;</a>
+<h3>Restoring a stack trace with line numbers (verbose)</h3>
+
+In the previous example, we could also use the verbose flag:
+<pre>
+<b>java -jar retrace.jar -verbose proguard.map proguard.trace</b>
+</pre>
+<p>
+
+The output will then look as follows:
+<pre>
+Exception in thread "main" java.lang.Error: Random exception
+        at proguard.shrink.UsageMarker.void visitInstruction(proguard.classfile.ClassFile,proguard.classfile.instruction.Instruction)(ProGuard:576)
+        at proguard.classfile.instruction.GenericInstruction.void accept(proguard.classfile.ClassFile,proguard.classfile.instruction.InstructionVisitor)(ProGuard:431)
+        at proguard.classfile.CodeAttrInfo.void instructionsAccept(proguard.classfile.ClassFile,proguard.classfile.instruction.InstructionVisitor)(ProGuard:145)
+        at proguard.shrink.UsageMarker.void visitCodeAttrInfo(proguard.classfile.ClassFile,proguard.classfile.CodeAttrInfo)(ProGuard:522)
+        at proguard.classfile.CodeAttrInfo.void accept(proguard.classfile.ClassFile,proguard.classfile.visitor.AttrInfoVisitor)(ProGuard:129)
+        at proguard.classfile.ProgramMemberInfo.void attributesAccept(proguard.classfile.ProgramClassFile,proguard.classfile.visitor.AttrInfoVisitor)(ProGuard:125)
+        at proguard.shrink.UsageMarker.void visitMemberInfo(proguard.classfile.ProgramClassFile,proguard.classfile.ProgramMemberInfo)(ProGuard:251)
+        at proguard.shrink.UsageMarker.void visitProgramMethodInfo(proguard.classfile.ProgramClassFile,proguard.classfile.ProgramMethodInfo)(ProGuard:229)
+        at proguard.classfile.ProgramMethodInfo.void accept(proguard.classfile.ProgramClassFile,proguard.classfile.visitor.MemberInfoVisitor)(ProGuard:55)
+        at proguard.classfile.ProgramClassFile.void methodAccept(proguard.classfile.visitor.MemberInfoVisitor,java.lang.String,java.lang.String)(ProGuard:405)
+        at proguard.classfile.visitor.NamedMethodVisitor.void visitProgramClassFile(proguard.classfile.ProgramClassFile)(ProGuard:51)
+        at proguard.classfile.ProgramClassFile.void accept(proguard.classfile.visitor.ClassFileVisitor)(ProGuard:356)
+        at proguard.classfile.visitor.ClassFileUpDownTraveler.void visitProgramClassFile(proguard.classfile.ProgramClassFile)(ProGuard:109)
+        at proguard.classfile.ProgramClassFile.void accept(proguard.classfile.visitor.ClassFileVisitor)(ProGuard:356)
+        at proguard.classfile.visitor.ClassFileUpDownTraveler.void visitLibraryClassFile(proguard.classfile.LibraryClassFile)(ProGuard:186)
+        at proguard.classfile.LibraryClassFile.void accept(proguard.classfile.visitor.ClassFileVisitor)(ProGuard:369)
+        at proguard.shrink.UsageMarker.void visitLibraryMethodInfo(proguard.classfile.LibraryClassFile,proguard.classfile.LibraryMethodInfo)(ProGuard:286)
+        at proguard.classfile.LibraryMethodInfo.void accept(proguard.classfile.LibraryClassFile,proguard.classfile.visitor.MemberInfoVisitor)(ProGuard:55)
+        at proguard.classfile.LibraryClassFile.void methodsAccept(proguard.classfile.visitor.MemberInfoVisitor)(ProGuard:408)
+        at proguard.shrink.UsageMarker.void visitLibraryClassFile(proguard.classfile.LibraryClassFile)(ProGuard:190)
+        at proguard.classfile.LibraryClassFile.void accept(proguard.classfile.visitor.ClassFileVisitor)(ProGuard:369)
+        at proguard.classfile.ClassCpInfo.void referencedClassAccept(proguard.classfile.visitor.ClassFileVisitor)(ProGuard:110)
+        at proguard.shrink.UsageMarker.void visitClassCpInfo(proguard.classfile.ClassFile,proguard.classfile.ClassCpInfo)(ProGuard:449)
+        at proguard.classfile.ClassCpInfo.void accept(proguard.classfile.ClassFile,proguard.classfile.visitor.CpInfoVisitor)(ProGuard:99)
+        at proguard.classfile.ProgramClassFile.void constantPoolEntryAccept(proguard.classfile.visitor.CpInfoVisitor,int)(ProGuard:372)
+        at proguard.shrink.UsageMarker.void markCpEntry(proguard.classfile.ClassFile,int)(ProGuard:649)
+        at proguard.shrink.UsageMarker.void visitProgramClassFile(proguard.classfile.ProgramClassFile)(ProGuard:112)
+        at proguard.classfile.visitor.VariableClassFileVisitor.void visitProgramClassFile(proguard.classfile.ProgramClassFile)(ProGuard:66)
+        at proguard.classfile.visitor.MultiClassFileVisitor.void visitProgramClassFile(proguard.classfile.ProgramClassFile)(ProGuard:83)
+        at proguard.classfile.visitor.FilteredClassFileVisitor.void visitProgramClassFile(proguard.classfile.ProgramClassFile)(ProGuard:69)
+        at proguard.classfile.ProgramClassFile.void accept(proguard.classfile.visitor.ClassFileVisitor)(ProGuard:356)
+        at proguard.classfile.ClassPool.void classFileAccept(proguard.classfile.visitor.ClassFileVisitor,java.lang.String)(ProGuard:149)
+        at proguard.classfile.visitor.NamedClassFileVisitor.void visitClassPool(proguard.classfile.ClassPool)(ProGuard:49)
+        at proguard.classfile.ClassPool.void accept(proguard.classfile.visitor.ClassPoolVisitor)(ProGuard:105)
+        at proguard.KeepCommand.void executeShrinkingPhase(proguard.classfile.ClassPool,proguard.classfile.ClassPool)(ProGuard:370)
+        at proguard.KeepCommand.void execute(int,proguard.classfile.ClassPool,proguard.classfile.ClassPool)(ProGuard:317)
+        at proguard.CompoundCommand.void execute(int,proguard.classfile.ClassPool,proguard.classfile.ClassPool)(ProGuard:55)
+        at proguard.ProGuard.void executeCommands(int)(ProGuard:363)
+        at proguard.ProGuard.void shrink()(ProGuard:187)
+        at proguard.ProGuard.void execute(java.lang.String[])(ProGuard:385)
+        at proguard.ProGuard.void main(java.lang.String[])(ProGuard:429)
+</pre>
+
+
+<a name="without">&nbsp;</a>
+<h3>Restoring a stack trace without line numbers</h3>
+
+Assume for instance ProGuard itself has been obfuscated using the following
+extra options, this time without preserving the line number tables:
+<pre>
+-printmapping proguard.map
+</pre>
+<p>
+
+A stack trace <code>proguard.trace</code> will then lack line number
+information:
+<pre>
+Exception in thread "main" java.lang.Error: Random exception
+        at pro.bY.a(Unknown Source)
+        at pro.bO.a(Unknown Source)
+        at pro.bj.a(Unknown Source)
+        at pro.bY.a(Unknown Source)
+        at pro.bj.a(Unknown Source)
+        at pro.bN.a(Unknown Source)
+        at pro.bY.a(Unknown Source)
+        at pro.bY.a(Unknown Source)
+        at pro.l.a(Unknown Source)
+        at pro.bo.b(Unknown Source)
+        at pro.ci.a(Unknown Source)
+        at pro.bo.a(Unknown Source)
+        at pro.be.a(Unknown Source)
+        at pro.bo.a(Unknown Source)
+        at pro.be.a(Unknown Source)
+        at pro.bg.a(Unknown Source)
+        at pro.bY.a(Unknown Source)
+        at pro.bh.a(Unknown Source)
+        at pro.bg.b(Unknown Source)
+        at pro.bY.a(Unknown Source)
+        at pro.bg.a(Unknown Source)
+        at pro.M.a(Unknown Source)
+        at pro.bY.a(Unknown Source)
+        at pro.M.a(Unknown Source)
+        at pro.bo.a(Unknown Source)
+        at pro.bY.a(Unknown Source)
+        at pro.bY.a(Unknown Source)
+        at pro.P.a(Unknown Source)
+        at pro.p.a(Unknown Source)
+        at pro.bU.a(Unknown Source)
+        at pro.bo.a(Unknown Source)
+        at pro.J.a(Unknown Source)
+        at pro.I.a(Unknown Source)
+        at pro.J.a(Unknown Source)
+        at pro.cf.c(Unknown Source)
+        at pro.cf.a(Unknown Source)
+        at pro.bc.a(Unknown Source)
+        at proguard.ProGuard.a(Unknown Source)
+        at proguard.ProGuard.c(Unknown Source)
+        at proguard.ProGuard.b(Unknown Source)
+        at proguard.ProGuard.main(Unknown Source)
+</pre>
+<p>
+
+We can still use the same command to recover the stack trace:
+<pre>
+<b>java -jar retrace.jar proguard.map proguard.trace</b>
+</pre>
+<p>
+
+The output will now give a list of alternative original method names for each
+ambiguous obfuscated method name:
+<pre>
+Exception in thread "main" java.lang.Error: Random exception
+        at proguard.shrink.UsageMarker.visitProgramClassFile(Unknown Source)
+                                       visitLibraryClassFile
+                                       visitProgramFieldInfo
+                                       visitProgramMethodInfo
+                                       visitMemberInfo
+                                       visitLibraryFieldInfo
+                                       visitLibraryMethodInfo
+                                       visitIntegerCpInfo
+                                       visitLongCpInfo
+                                       visitFloatCpInfo
+                                       visitDoubleCpInfo
+                                       visitStringCpInfo
+                                       visitUtf8CpInfo
+                                       visitFieldrefCpInfo
+                                       visitInterfaceMethodrefCpInfo
+                                       visitMethodrefCpInfo
+                                       visitClassCpInfo
+                                       visitNameAndTypeCpInfo
+                                       visitUnknownAttrInfo
+                                       visitInnerClassesAttrInfo
+                                       visitConstantValueAttrInfo
+                                       visitExceptionsAttrInfo
+                                       visitCodeAttrInfo
+                                       visitLineNumberTableAttrInfo
+                                       visitLocalVariableTableAttrInfo
+                                       visitSourceFileAttrInfo
+                                       visitDeprecatedAttrInfo
+                                       visitSyntheticAttrInfo
+                                       visitInstruction
+                                       visitCpInstruction
+                                       visitExceptionInfo
+                                       visitInnerClassesInfo
+                                       visitLocalVariableInfo
+                                       markCpEntry
+                                       markAsUnused
+                                       isUsed
+        at proguard.classfile.instruction.GenericInstruction.create(Unknown Source)
+                                                             isWide
+                                                             getLength
+                                                             accept
+        at proguard.classfile.CodeAttrInfo.getAttribute(Unknown Source)
+                                           getAttrInfoLength
+                                           readInfo
+                                           accept
+                                           instructionsAccept
+                                           exceptionsAccept
+        [...]
+        at proguard.KeepCommand.executeShrinkingPhase(Unknown Source)
+                                access$100
+        at proguard.KeepCommand.keepField(Unknown Source)
+                                ensureMultiClassFileVisitorForMembers
+                                execute
+                                executeObfuscationPhase
+                                access$002
+                                access$000
+                                access$102
+                                access$108
+        at proguard.CompoundCommand.addCommand(Unknown Source)
+                                    execute
+        at proguard.ProGuard.readCommands(Unknown Source)
+                             obfuscate
+                             executeCommands
+        at proguard.ProGuard.shrink(Unknown Source)
+        at proguard.ProGuard.check(Unknown Source)
+                             execute
+        at proguard.ProGuard.main(Unknown Source)
+</pre>
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
+
diff --git a/docs/manual/retrace/index.html b/docs/manual/retrace/index.html
new file mode 100644
index 0000000..ebb23ac
--- /dev/null
+++ b/docs/manual/retrace/index.html
@@ -0,0 +1,25 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="../style.css">
+<title>ReTrace Manual</title>
+</head>
+<body>
+
+<h2>ReTrace</h2>
+
+<ol>
+<li><a href="introduction.html">Introduction</a>
+<li><a href="usage.html">Usage</a>
+<li><a href="examples.html">Examples</a>
+</ol>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/retrace/introduction.html b/docs/manual/retrace/introduction.html
new file mode 100644
index 0000000..19f9471
--- /dev/null
+++ b/docs/manual/retrace/introduction.html
@@ -0,0 +1,68 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="../style.css">
+<title>ReTrace Introduction</title>
+</head>
+<body>
+
+<h2>Introduction</h2>
+
+<b>ReTrace</b> is a companion tool for <b>ProGuard</b> that 'de-obfuscates'
+stack traces.
+<p>
+When an obfuscated program throws an exception, the resulting stack trace
+typically isn't very informative. Class names and method names have been
+replaced by short meaningless strings. Source file names and line numbers are
+missing altogether. While this may be intentional, it can also be inconvenient
+when debugging problems.
+<p>
+
+<table class="diagram" align="center">
+
+<tr>
+<td rowspan="1" class="lightblock">Original code</td>
+<td             class="transparentblock">- <b>ProGuard</b> &rarr;</td>
+<td rowspan="1" class="lightblock">Obfuscated code</td>
+</tr>
+
+<tr>
+<td rowspan="3" class="transparentblock"></td>
+<td             class="transparentblock">&darr;</td>
+<td             class="transparentblock">&darr;</td>
+</tr>
+
+<tr>
+<td             class="whiteblock">Mapping file</td>
+<td             class="transparentblock">&darr;</td>
+</tr>
+
+<tr>
+<td             class="transparentblock">&darr;</td>
+<td             class="transparentblock">&darr;</td>
+</tr>
+
+<tr>
+<td             class="whiteblock">Readable stack trace</td>
+<td             class="transparentblock">&larr; <b>ReTrace</b> -</td>
+<td             class="whiteblock">Obfuscated stack trace</td>
+</tr>
+
+</table>
+<p>
+ReTrace can read an obfuscated stack trace and restore it to what it would
+look like without obfuscation. The restoration is based on the mapping file
+that ProGuard can write out during obfuscation. The mapping file links the
+original class names and class member names to their obfuscated names.
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
+
diff --git a/docs/manual/retrace/usage.html b/docs/manual/retrace/usage.html
new file mode 100644
index 0000000..88587ff
--- /dev/null
+++ b/docs/manual/retrace/usage.html
@@ -0,0 +1,117 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="../style.css">
+<title>ReTrace Usage</title>
+</head>
+<body>
+
+<h2>Usage</h2>
+
+You can find the ReTrace jar in the <code>lib</code> directory of the
+ProGuard distribution. To run ReTrace, just type:
+<p>
+<p class="code">
+<code><b>java -jar retrace.jar </b></code>[<i>options...</i>]
+         <i>mapping_file</i> [<i>stacktrace_file</i>]
+</p>
+These are the arguments:
+
+<dl>
+<dt><i>mapping_file</i></dt>
+
+<dd>Specifies the name of the mapping file, produced by ProGuard with the
+    option
+    "<a href="../usage.html#printmapping"><code>-printmapping</code></a> <i>mapping_file</i>",
+    while obfuscating the application that produced the stack trace.</dd>
+
+<dt><i>stacktrace_file</i></dt>
+
+<dd>Optionally specifies the name of the file containing the stack trace. If
+    no file is specified, a stack trace is read from the standard input. Blank
+    lines and unrecognized lines are ignored, as far as possible.</dd>
+</dl>
+
+The following options are supported:
+<dl>
+<dt><code><b>-verbose</b></code></dt>
+
+<dd>Specifies to print out more informative stack traces that include not only
+    method names, but also method return types and arguments.</dd>
+
+<dt><code><b>-regex</b></code> <i>regular_expression</i></dt>
+
+<dd>Specifies the regular expression that is used to parse the lines in the
+    stack trace. Specifying a different regular expression allows to
+    de-obfuscate more general types of input than just stack traces. The
+    default is suitable for stack traces produced by most JVMs:
+    <pre>
+    (?:\s*%c:.*)|(?:\s*at\s+%c.%m\s*\(.*?(?::%l)?\)\s*)
+    </pre>
+    The regular expression is a Java regular expression (cfr. the documentation
+    of <code>java.util.regex.Pattern</code>), with a few additional wildcards:
+    <table cellspacing="10">
+    <tr><td valign="top"><code><b>%c</b></code></td>
+        <td>matches a class name (e.g.
+            "<code>myapplication.MyClass</code>").</td></tr>
+    <tr><td valign="top"><code><b>%C</b></code></td>
+        <td>matches a class name with slashes (e.g.
+            "<code>myapplication/MyClass</code>").</td></tr>
+    <tr><td valign="top"><code><b>%t</b></code></td>
+        <td>matches a field type or method return type (e.g.
+            "<code>myapplication.MyClass[]</code>").</td></tr>
+    <tr><td valign="top"><code><b>%f</b></code></td>
+        <td>matches a field name (e.g.
+            "<code>myField</code>").</td></tr>
+    <tr><td valign="top"><code><b>%m</b></code></td>
+        <td>matches a method name (e.g.
+            "<code>myMethod</code>").</td></tr>
+    <tr><td valign="top"><code><b>%a</b></code></td>
+        <td>matches a list of method arguments (e.g.
+            "<code>boolean,int</code>").</td></tr>
+    <tr><td valign="top"><code><b>%l</b></code></td>
+        <td>matches a line number inside a method (e.g.
+            "<code>123</code>").</td></tr>
+    </table>
+    Elements that match these wildcards are de-obfuscated, when possible. Note
+    that regular expressions must not contain any capturing groups. Use
+    non-capturing groups instead: <code>(?:</code>...<code>)</code>
+    </dd>
+</dl>
+
+The restored stack trace is printed to the standard output. The completeness
+of the restored stack trace depends on the presence of line number tables in
+the obfuscated class files:
+
+<ul>
+<li>If all line numbers have been preserved while obfuscating the application,
+    ReTrace will be able to restore the stack trace completely.
+    <p>
+
+<li>If the line numbers have been removed, mapping obfuscated method names
+    back to their original names has become ambiguous. Retrace will list all
+    possible original method names for each line in the stack trace. The user
+    can then try to deduce the actual stack trace manually, based on the logic
+    of the program.
+
+</ul>
+<p>
+
+Preserving line number tables is explained in detail in this <a
+href="../examples.html#stacktrace">example</a> in the ProGuard User Manual.
+<p>
+
+Unobfuscated elements and obfuscated elements for which no mapping is available
+will be left unchanged.
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
+
diff --git a/docs/manual/sections.html b/docs/manual/sections.html
new file mode 100644
index 0000000..62df9c3
--- /dev/null
+++ b/docs/manual/sections.html
@@ -0,0 +1,64 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-script-type" content="text/javascript">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="../style.css">
+<title>Sections</title>
+</head>
+<body class="navigation">
+
+<ul class="navigation">
+<li><a href="../sections.html">&lt;&lt; Main menu</a></li>
+
+<li class="title">ProGuard Manual</li>
+<li><a target="main" href="introduction.html">Introduction</a></li>
+<li><a target="main" href="usage.html">Usage</a></li>
+<li><a target="main" href="limitations.html">Limitations</a></li>
+<li><a target="main" href="examples.html">Examples</a></li>
+<li><a target="main" href="troubleshooting.html">Troubleshooting</a></li>
+<li><a target="main" href="refcard.html">Ref Card</a></li>
+<li><a target="main" href="gui.html">GUI</a></li>
+<li><a target="main" href="ant.html">Ant Task</a></li>
+<li><a target="main" href="wtk.html">JME WTK</a></li>
+
+<li class="title">ReTrace Manual</li>
+<li><a target="main" href="retrace/introduction.html">Introduction</a></li>
+<li><a target="main" href="retrace/usage.html">Usage</a></li>
+<li><a target="main" href="retrace/examples.html">Examples</a></li>
+</ul>
+
+<p>
+<center>
+<small>With support of</small>
+<p>
+
+<a href="http://sourceforge.net/projects/proguard/" target="other">
+
+<script type="text/javascript" language="JavaScript">
+<!--
+document.write("<img src=\"");
+document.write(document.location.hostname == "proguard.sourceforge.net" ?
+  "http://sourceforge.net/sflogo.php?group_id=54750&amp;type=1" :
+  "../sflogo.png");
+document.write("\" width=\"88\" height=\"31\" alt=\"SourceForge\">");
+//-->
+</script>
+<noscript>
+<img src="../sflogo.png" width="88" height="31" alt="SourceForge">
+</noscript>
+
+</a>
+
+<p>
+<a href="http://www.luciad.com/" target="other">
+<img src="../luciadlogo.png" width="88" height="24" alt="Luciad"></a>
+
+<p>
+<a href="http://www.javadocking.com/" target="other">
+<img src="../sanawarelogo.png" width="88" height="24" alt="Luciad"></a>
+</center>
+
+</body>
+</html>
diff --git a/docs/manual/style.css b/docs/manual/style.css
new file mode 100644
index 0000000..28fc112
--- /dev/null
+++ b/docs/manual/style.css
@@ -0,0 +1,92 @@
+@charset "iso-8859-1";
+
+/* Global settings. */
+
+body {
+  background: #FFFFFF;
+}
+
+h1 {
+  text-align: center;
+}
+
+h2 {
+  background: #EEEEFF;
+  padding: 10px;
+}
+
+dt {
+  padding: 6px;
+}
+
+dt div 
+{
+  color: grey;
+  float: right;
+}
+
+dd {
+  padding: 6px;
+}
+
+pre {
+  padding: 10px;
+  background: #E0E0E0;
+}
+
+a
+{
+  text-decoration: none;
+}
+
+/* Settings for variable width code. */
+
+p.code {
+  padding: 10px;
+  background: #E0E0E0;
+}
+
+
+/* Settings for diagrams. */
+
+table.diagram {
+  padding: 8px;
+  border: none;
+  border-spacing: 2px;
+}
+
+td.transparentblock {
+  text-align: center;
+  padding: 10px 0px;
+}
+
+td.whiteblock {
+  width: 100px;
+  text-align: center;
+  border: solid #C0C0C0 1px;
+  background: #E0E0E0;
+  padding: 10px 0px;
+}
+
+td.lightblock {
+  width: 100px;
+  text-align: center;
+  border: solid #8888FF 1px;
+  background: #BBBBFF;
+  padding: 20px 0px;
+}
+
+td.darkblock {
+  width: 100px;
+  text-align: center;
+  background: #8888FF;
+  padding: 20px 0px;
+}
+
+/* Settings for buttons. */
+
+td.button {
+  background: #E0E0E0;
+  border: outset #FFFFFF 1px;
+  font-weight: bold;
+}
diff --git a/docs/manual/troubleshooting.html b/docs/manual/troubleshooting.html
new file mode 100644
index 0000000..d3ed14e
--- /dev/null
+++ b/docs/manual/troubleshooting.html
@@ -0,0 +1,592 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Troubleshooting</title>
+</head>
+<body>
+
+<h2>Troubleshooting</h2>
+
+While preparing a configuration for processing your code, you may bump into a
+few problems. The following sections discuss some common issues and solutions:
+
+<h3><a href="#processing">Problems while processing</a></h3>
+<ul>
+<li><a href="#dynamicalclass">Note: can't find dynamically referenced class</a></li>
+<li><a href="#dynamicalclasscast">Note: ... calls '(...)Class.forName(variable).newInstance()'</a></li>
+<li><a href="#dynamicalclassmember">Note: ... accesses a field/method '...' dynamically</a></li>
+<li><a href="#descriptorclass">Note: the configuration keeps the entry point '...', but not the descriptor class '...'</a></li>
+<li><a href="#duplicateclass">Note: duplicate definition of program/library class</a></li>
+<li><a href="#duplicatezipentry">Warning: can't write resource ... Duplicate zip entry</a></li>
+<li><a href="#unresolvedclass">Warning: can't find superclass or interface</a></li>
+<li><a href="#unresolvedclass">Warning: can't find referenced class</a></li>
+<li><a href="#unresolvedclassmember">Warning: can't find referenced field/method</a></li>
+<li><a href="#unresolvedenclosingmethod">Warning: can't find enclosing class/method</a></li>
+<li><a href="#dependency">Warning: library class ... depends on program class ...</a></li>
+<li><a href="#unexpectedclass">Warning: class file ... unexpectedly contains class ...</a></li>
+<li><a href="#mappingconflict1">Warning: ... is not being kept as ..., but remapped to ...</a></li>
+<li><a href="#mappingconflict2">Warning: field/method ... can't be mapped to ...</a></li>
+<li><a href="#keep">Error: You have to specify '-keep' options</a></li>
+<li><a href="#filename">Error: Expecting class path separator ';' before 'Files\Java\...' (in Windows)</a></li>
+<li><a href="#macosx">Error: Can't read [.../lib/rt.jar] (No such file or directory) (in MacOS X)</a></li>
+<li><a href="#outofmemoryerror">OutOfMemoryError</a></li>
+<li><a href="#stackoverflowerror">StackOverflowError</a></li>
+<li><a href="#unexpectederror">Unexpected error</a></li>
+<li><a href="#otherwise">Otherwise...</a></li>
+</ul>
+
+<h3><a href="#afterprocessing">Unexpected observations after processing</a></h3>
+<ul>
+<li><a href="#disappearingclasses">Disappearing classes</a></li>
+<li><a href="#notkept">Classes or class members not being kept</a></li>
+<li><a href="#notobfuscated">Variable names not being obfuscated</a></li>
+</ul>
+
+<h3><a href="#preverifying">Problems while preverifying for Java Micro Edition</a></h3>
+
+<ul>
+<li><a href="#invalidclassexception1">InvalidClassException, class loading error, or verification error</a></li>
+</ul>
+
+<h3><a href="#runtime">Problems at run-time</a></h3>
+<ul>
+<li><a href="#stacktraces">Stack traces without class names or line numbers</a></li>
+<li><a href="#noclassdeffounderror">NoClassDefFoundError</a></li>
+<li><a href="#classnotfoundexception">ClassNotFoundException</a></li>
+<li><a href="#nosuchmethodexception">NoSuchMethodException</a></li>
+<li><a href="#missingresourceexception">MissingResourceException or NullPointerException</a></li>
+<li><a href="#invalidjarfile">Invalid or corrupt jarfile</a></li>
+<li><a href="#invalidclassexception2">InvalidClassException, class loading error, or verification error (in Java Micro Edition)</a></li>
+<li><a href="#nosuchfieldormethod">Error: No Such Field or Method, Error verifying method (in a Java Micro Edition emulator)</a></li>
+<li><a href="#failingmidlets">Failing midlets (on a Java Micro Edition device)</a></li>
+<li><a href="#disappearingloops">Disappearing loops</a></li>
+<li><a href="#securityexception">SecurityException: SHA1 digest error</a></li>
+<li><a href="#classcastexception">ClassCastException: class not an enum</a></li><li><a href="#classcastexception">IllegalArgumentException: class not an enum type</a></li>
+<li><a href="#arraystoreexception">ArrayStoreException: sun.reflect.annotation.EnumConstantNotPresentExceptionProxy</a></li>
+<li><a href="#compilererror">CompilerError: duplicate addition</a></li>
+<li><a href="#classformaterror">ClassFormatError: repetitive field name/signature</a></li>
+<li><a href="#nosuchmethoderror">NoSuchMethodError or AbstractMethodError</a></li>
+<li><a href="#verifyerror">VerifyError</a></li>
+</ul>
+
+
+<a name="processing">&nbsp;</a>
+<h2>Problems while processing</h2>
+
+ProGuard may print out some notes and non-fatal warnings:
+
+<dl>
+<dt><a name="dynamicalclass"><b>Note: can't find dynamically referenced class</b></a></dt>
+
+<dd>ProGuard can't find a class or interface that your code is accessing by
+    means of introspection. You should check if you want to add the jar that
+    contains this class.</dd>
+
+<dt><a name="dynamicalclasscast"><b>Note: ... calls '(...)Class.forName(variable).newInstance()'</b></a></dt>
+
+<dd>ProGuard lists all class casts of dynamically created class instances,
+    like "<code>(MyClass)Class.forName(variable).newInstance()</code>".
+    Depending on your application, you may need to keep the mentioned classes
+    with an option like "<code>-keep class MyClass</code>", or their
+    implementations with an option like "<code>-keep class * implements
+    MyClass</code>". You can switch off these notes by specifying the
+    <a href="usage.html#dontnote"><code>-dontnote</code></a> option.</dd>
+
+<dt><a name="dynamicalclassmember"><b>Note: ... accesses a field/method '...' dynamically</b></a></dt>
+
+<dd>ProGuard lists a number of constructs like
+    "<code>.getField("myField")</code>". Depending on your application, you
+    may need to figure out where the mentioned class members are defined and
+    keep them with an option like "<code>-keep class MyClass { MyFieldType
+    myField; }</code>". Otherwise, ProGuard might remove or obfuscate the
+    class members, since it can't know which ones they are exactly. It does
+    list possible candidates, for your information. You can switch off these
+    notes by specifying the <a
+    href="usage.html#dontnote"><code>-dontnote</code></a> option.</dd>
+
+<dt><a name="descriptorclass"><b>Note: the configuration keeps the entry point '...', but not the descriptor class '...'</b></a></dt>
+
+<dd>Your configuration contains a <code>-keep</code> option to preserve the
+    given method (or field), but no <code>-keep</code> option for the given
+    class that is an argument type or return type in the method's descriptor.
+    You may then want to keep the class too. Otherwise, ProGuard will
+    obfuscate its name, thus changing the method's signature. The method might
+    then become unfindable as an entry point, e.g. if it is part of a public
+    API. You can switch off these notes by specifying the <a
+    href="usage.html#dontnote"><code>-dontnote</code></a> option.</dd>
+
+<dt><a name="duplicateclass"><b>Note: duplicate definition of program/library class</b></a></dt>
+
+<dd>Your program jars or library jars contain multiple definitions of the
+    listed classes. ProGuard continues processing as usual, only considering
+    the first definitions. The warning may be an indication of some problem
+    though, so it's advisable to remove the duplicates. A convenient way to do
+    so is by specifying filters on the input jars or library jars. You can
+    switch off these notes by specifying the <a
+    href="usage.html#dontnote"><code>-dontnote</code></a> option.</dd>
+
+<dt><a name="duplicatezipentry"><b>Warning: can't write resource ... Duplicate zip entry</b></a></dt>
+
+<dd>Your input jars contain multiple resource files with the same name.
+    ProGuard continues copying the resource files as usual, skipping any files
+    with previously used names. Once more, the warning may be an indication of
+    some problem though, so it's advisable to remove the duplicates. A
+    convenient way to do so is by specifying filters on the input jars. There
+    is no option to switch off these warnings.</dd>
+
+</dl>
+<p>
+
+ProGuard may terminate when it encounters parsing errors or I/O errors, or
+some more serious warnings:
+
+<dl>
+<dt><a name="unresolvedclass"><b>Warning: can't find superclass or interface</b><br/><b>Warning: can't find referenced class</b></a></dt>
+
+<dd>If there are unresolved references to classes or interfaces, you most
+    likely forgot to specify an essential library. For proper processing, all
+    libraries that are referenced by your code must be specified, including
+    the Java run-time library. For specifying libraries, use
+    the <a href="usage.html#libraryjars"><code>-libraryjars</code></a> option.
+    <p>
+    If the class that is reported as missing is a non-public library class,
+    you should specify the <a
+    href="usage.html#dontskipnonpubliclibraryclasses"><code>-dontskipnonpubliclibraryclasses</code></a>
+    option. Common examples are the classes
+    <code>javax.swing.TransferHandler$HasGetTransferHandler</code> and
+    <code>java.util.zip.ZipConstants</code>, which are used as interfaces in
+    some public classes, even though they are only package visible. This
+    option is not set by default for reasons of efficiency. Setting it increases
+    the processing time a bit, but it won't hurt the output in any way.
+    <p>
+    If you're missing a library and you're absolutely sure it isn't used
+    anyway, you can try your luck with the <a
+    href="usage.html#ignorewarnings"><code>-ignorewarnings</code></a> option,
+    or even the <a href="usage.html#dontwarn"><code>-dontwarn</code></a>
+    option. Only use these options if you really know what you're doing
+    though.</dd>
+
+<dt><a name="unresolvedclassmember"><b>Warning: can't find referenced field/method</b></a></dt>
+
+<dd>If there are unresolved references to class members in input classes, your
+    class files are most likely inconsistent. Possibly, some class file didn't
+    get recompiled properly, or some class file was left behind after its
+    source file was removed. Try removing all class files, recompiling them,
+    zipping them up, and running ProGuard again.
+    <p>
+    If the class member that is reported as missing is actually implemented in
+    a non-public library class, you should specify the
+    <a href="usage.html#dontskipnonpubliclibraryclasses"><code>-dontskipnonpubliclibraryclasses</code></a> option. A common example is
+    the method <code>setLength(int)</code> in the public class
+    <code>java.lang.StringBuilder</code>. This method is actually defined in
+    the package visible superclass
+    <code>java.lang.AbstractStringBuilder</code>, which ProGuard ignores by
+    default.
+    <p>
+    If your program classes reside in the same packages as library classes,
+    and refer to their package visible class members, then you should specify
+    the <a
+    href="usage.html#dontskipnonpubliclibraryclassmembers"><code>-dontskipnonpubliclibraryclassmembers</code></a>
+    option.</dd>
+
+<dt><a name="unresolvedenclosingmethod"><b>Warning: can't find enclosing class/method</b></a></dt>
+
+<dd>If there are unresolved references to classes that are defined inside
+    methods in your input, once more, your class files are most likely
+    inconsistent. Possibly, some class file didn't get recompiled properly, or
+    some class file was left behind after its source file was removed. Try
+    removing all class files, recompiling them, zipping them up, and running
+    ProGuard again.</dd>
+
+<dt><a name="dependency"><b>Warning: library class ... depends on program class ...</b></a></dt>
+
+<dd>If any of your library classes depend on your program classes, by
+    extending, implementing or just referencing them, your processed code will
+    generally be unusable. Program classes can depend on library classes, but
+    not the other way around. Program classes are processed, while library
+    classes always remain unchanged. It is therefore impossible to adapt
+    references from library classes to program classes, for instance if the
+    program classes are renamed. You should define a clean separation between
+    program code and library code, and try again.</dd>
+
+<dt><a name="unexpectedclass"><b>Warning: class file ... unexpectedly contains class ...</b></a></dt>
+
+<dd>The given class file contains a definition for the given class, but the
+    directory name of the file doesn't correspond to the package name of the
+    class. ProGuard will accept the class definition, but the current
+    implementation will not write out the processed version. Please make sure
+    your input classes are packaged correctly. Notably, class files that are
+    in the <code>WEB-INF/classes</code> directory in a war should be packaged
+    in a jar and put in the <code>WEB-INF/lib</code> directory. If you don't
+    mind these classes not being written to the output, you can specify the <a
+    href="usage.html#ignorewarnings"><code>-ignorewarnings</code></a> option,
+    or even the <a href="usage.html#dontwarn"><code>-dontwarn</code></a>
+    option.</dd>
+
+<dt><a name="mappingconflict1"><b>Warning: ... is not being kept as ..., but remapped to ...</b></a></dt>
+
+<dd>There is a conflict between a <code>-keep</code> option in the
+    configuration, and the mapping file, in the obfuscation step. The given
+    class name or class member name can't be kept by its original name, as
+    specified in the configuration, but it has to be mapped to the other given
+    name, as specified in the mapping file. You should adapt your
+    configuration or your mapping file to remove the conflict. Alternatively,
+    if you're sure the renaming won't hurt, you can specify the <a
+    href="usage.html#ignorewarnings"><code>-ignorewarnings</code></a> option,
+    or even the <a href="usage.html#dontwarn"><code>-dontwarn</code></a>
+    option.</dd>
+
+<dt><a name="mappingconflict2"><b>Warning: field/method ... can't be mapped to ...</b></a></dt>
+
+<dd>There is a conflict between some new program code and the mapping file, in
+    the obfuscation step. The given class member can't be mapped to the given
+    name, because it would conflict with another class member that is already
+    being mapped to the same name. This can happen if you are performing
+    incremental obfuscation, applying an obfuscation mapping file from an
+    initial obfuscation step. For instance, some new class may have been added
+    that extends two existing classes, introducing a conflict in the name
+    space of its class members. If you're sure the class member receiving
+    another name than the one specified won't hurt, you can specify the <a
+    href="usage.html#ignorewarnings"><code>-ignorewarnings</code></a> option,
+    or even the <a href="usage.html#dontwarn"><code>-dontwarn</code></a>
+    option. Note that you should always use the <a
+    href="usage.html#useuniqueclassmembernames"><code>-useuniqueclassmembernames</code></a>
+    option in the initial obfuscation step, in order to reduce the risk of
+    conflicts.</dd>
+
+<dt><a name="keep"><b>Error: You have to specify '-keep' options</b></a></dt>
+
+<dd>You either forgot to specify <a
+    href="usage.html#keep"><code>-keep</code></a> options, or you mistyped the
+    class names. ProGuard has to know exactly what you want to keep: an
+    application, an applet, a servlet, a midlet,..., or any combination of
+    these. Without the proper seed specifications, ProGuard would shrink,
+    optimize, or obfuscate all class files away.</dd>
+
+
+<dt><a name="filename"><b>Error: Expecting class path separator ';' before 'Files\Java\</b>...<b>'</b> (in Windows)</a></dt>
+
+<dd>If the path of your run-time jar contains spaces, like in "Program Files",
+    you have to enclose it with single or double quotes, as explained in the
+    section on <a href="usage.html#filename">file names</a>. This is actually
+    true for all file names containing special characters, on all
+    platforms.</dd>
+
+<dt><a name="macosx"><b>Error: Can't read [</b>...<b>/lib/rt.jar] (No such file or directory)</b> (in MacOS X)</a></dt>
+
+<dd>In MacOS X, the run-time classes may be in a different place than on most
+    other platforms. You'll then have to adapt your configuration, replacing
+    the path <code>&lt;java.home&gt;/lib/rt.jar</code> by
+    <code>&lt;java.home&gt;/../Classes/classes.jar</code>.</dd>
+
+</dl>
+<p>
+
+Should ProGuard crash while processing your application:
+
+<dl>
+<dt><a name="outofmemoryerror"><b>OutOfMemoryError</b></a></dt>
+
+<dd>You can try increasing the heap size of the Java virtual machine (with the
+    usual <code>-Xms</code> and <code>-Xmx</code> options). You can also
+    reduce the amount of memory that ProGuard needs by removing unnecessary
+    library jars from your configuration, or by filtering out unused library
+    packages and classes. Remember that only classes or interfaces that are
+    extended or implemented by classes in your input jars are required.</dd>
+
+<dt><a name="stackoverflowerror"><b>StackOverflowError</b></a></dt>
+
+<dd>This error might occur when processing a large code base on Windows
+    (surprisingly, not so easily on Linux). In theory, increasing the stack
+    size of the Java virtual machine (with the usual <code>-Xss</code> option)
+    should help too. In practice however, the <code>-Xss</code> setting
+    doesn't have any effect on the main thread, due to <a
+    href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4362291">Sun Bug
+    #4362291</a>. As a result, this solution will only work when running
+    ProGuard in a different thread, e.g. from its GUI.</dd>
+
+<dt><a name="unexpectederror"><b>Unexpected error</b></a></dt>
+
+<dd>ProGuard has encountered an unexpected condition, typically in the
+    optimization step. It may or may not recover. You should be able to avoid
+    it using the <a
+    href="usage.html#dontoptimize"><code>-dontoptimize</code></a> option. In
+    any case, please report the problem, preferably with the simplest example
+    that causes ProGuard to crash.</dd>
+
+<dt><a name="otherwise"><b>Otherwise...</b></a></dt>
+
+<dd>Maybe your class files are corrupt. See if recompiling them and trying
+    again helps. If not, please report the problem, preferably with the
+    simplest example that causes ProGuard to crash.</dd>
+
+</dl>
+<p>
+
+<a name="afterprocessing">&nbsp;</a>
+<h2>Unexpected observations after processing</h2>
+
+If ProGuard seems to run fine, but your processed code doesn't look right,
+there might be a couple of reasons:
+
+<dl>
+<dt><a name="disappearingclasses"><b>Disappearing classes</b></a></dt>
+
+<dd>If you are working on Windows and it looks like some classes have
+    disappeared from your output, you should make sure you're not writing your
+    output class files to a directory (or unpacking the output jar). On
+    platforms with case-insensitive file systems, such as Windows, unpacking
+    tools often let class files with similar lower-case and upper-case names
+    overwrite each other. If you really can't switch to a different operating
+    system, you could consider using ProGuard's <a
+    href="usage.html#dontusemixedcaseclassnames"><code>-dontusemixedcaseclassnames</code></a>
+    option.
+    <p>
+    Also, you should make sure your class files are in directories that
+    correspond to their package names. ProGuard will read misplaced class
+    files, but it will currently not write their processed versions. Notably,
+    class files that are in the <code>WEB-INF/classes</code> directory in a
+    war should be packaged in a jar and put in the <code>WEB-INF/lib</code>
+    directory.</dd>
+
+<dt><a name="notkept"><b>Classes or class members not being kept</b></a></dt>
+
+<dd>If ProGuard is not keeping the right classes or class members, make sure
+    you are using fully qualified class names. If the package name of some
+    class is missing, ProGuard won't match the elements that you might be
+    expecting. It may help to double-check for typos too. You can use the <a
+    href="usage.html#printseeds"><code>-printseeds</code></a> option to see
+    which elements are being kept exactly.
+    <p>
+    If you are using marker interfaces to keep other classes, the marker
+    interfaces themselves are probably being removed in the shrinking step.
+    You should therefore always explicitly keep any marker interfaces.</dd>
+
+<dt><a name="notobfuscated"><b>Variable names not being obfuscated</b></a></dt>
+
+<dd>If the names of the local variables and parameters in your obfuscated code
+    don't look obfuscated, because they suspiciously resemble the names of
+    their types, it's probably because the decompiler that you are using is
+    coming up with those names. ProGuard's obfuscation step does remove the
+    original names entirely, unless you explicitly keep the
+    <code>LocalVariableTable</code> or <code>LocalVariableTypeTable</code>
+    attributes.</dd>
+
+</dl>
+
+<a name="preverifying">&nbsp;</a>
+<h2>Problems while preverifying for Java Micro Edition</h2>
+
+If ProGuard seems to run fine, but the external preverifier subsequently
+produces errors, it's usually for a single reason:
+
+<dl>
+<dt><a name="invalidclassexception1"><b>InvalidClassException</b>, <b>class loading error</b>, or <b>verification error</b></a></dt>
+
+<dd>If you get any such message from the preverifier, you are probably working
+    on a platform with a case-insensitive file system, such as Windows. The
+    <code>preverify</code> tool always unpacks the jars, so class files with
+    similar lower-case and upper-case names overwrite each other. You can use
+    ProGuard's <a
+    href="usage.html#dontusemixedcaseclassnames"><code>-dontusemixedcaseclassnames</code></a>
+    option to work around this problem.
+    <p>
+    If the above doesn't help, there is probably a bug in the optimization
+    step of ProGuard. Make sure you are using the latest version. You should
+    be able to work around the problem by using the <a
+    href="usage.html#dontoptimize"><code>-dontoptimize</code></a> option. You
+    can check the bug database to see if it is a known problem (often with a
+    fix). Otherwise, please report it, preferably with the simplest example on
+    which you can find ProGuard to fail.</dd>
+
+</dl>
+
+Note that it is no longer necessary to use an external preverifier. With the
+<a href="usage.html#microedition"><code>-microedition</code></a> option,
+ProGuard will preverify the class files for Java Micro Edition.
+<p>
+
+<a name="runtime">&nbsp;</a>
+<h2>Problems at run-time</h2>
+
+If ProGuard runs fine, but your processed application doesn't work, there
+might be several reasons:
+
+<dl>
+<dt><a name="stacktraces"><b>Stack traces without class names or line numbers</b></a></dt>
+
+<dd>If your stack traces don't contain any class names or lines numbers,
+    even though you are keeping the proper attributes, make sure this debugging
+    information is present in your compiled code to start with. Notably the Ant
+    javac task has debugging information switched off by default.</dd>
+
+<dt><a name="noclassdeffounderror"><b>NoClassDefFoundError</b></a></dt>
+
+<dd>Your class path is probably incorrect. It should at least contain all
+    library jars and, of course, your processed program jar.</dd>
+
+<dt><a name="classnotfoundexception"><b>ClassNotFoundException</b></a></dt>
+
+<dd>Your code is probably calling <code>Class.forName</code>, trying to create
+    the missing class dynamically. ProGuard can only detect constant name
+    arguments, like <code>Class.forName("mypackage.MyClass")</code>. For
+    variable name arguments like <code>Class.forName(someClass)</code>, you
+    have to keep all possible classes using the appropriate <a
+    href="usage.html#keep"><code>-keep</code></a> option, e.g. "<code>-keep
+    class mypackage.MyClass</code>" or "<code>-keep class * implements
+    mypackage.MyInterface</code>".</dd>
+
+<dt><a name="nosuchmethodexception"><b>NoSuchMethodException</b></a></dt>
+
+<dd>Your code is probably calling something like
+    <code>myClass.getMethod</code>, trying to find some method dynamically.
+    Since ProGuard isn't detecting this (yet), you have to keep the missing
+    method in using the appropriate <a
+    href="usage.html#keep"><code>-keep</code></a> option, e.g. "<code>-keep
+    class mypackage.MyClass { void myMethod(); }</code>".</dd>
+
+<dt><a name="missingresourceexception"><b>MissingResourceException</b> or <b>NullPointerException</b></a></dt>
+
+<dd>Your processed code may be unable to find some resource files. ProGuard
+    simply copies resource files over from the input jars to the output jars.
+    Their names and contents remain unchanged, unless you specify the options
+    <a
+    href="usage.html#adaptresourcefilenames"><code>-adaptresourcefilenames</code></a>
+    and/or <a
+    href="usage.html#adaptresourcefilecontents"><code>-adaptresourcefilecontents</code></a>.
+    <p>
+    Furthermore, directory entries in jar files aren't copied, unless you
+    specify the option <a
+    href="usage.html#keepdirectories"><code>-keepdirectories</code></a>.</dd>
+
+<dt><a name="invalidjarfile"><b>Invalid or corrupt jarfile</b></a></dt>
+
+<dd>You are probably starting your application with the java option
+    <code>-jar</code> instead of the option <code>-classpath</code>. The java
+    virtual machine returns with this error message if your jar doesn't
+    contain a manifest file (<code>META-INF/MANIFEST.MF</code>), if the
+    manifest file doesn't specify a main class (<code>Main-Class:</code> ...),
+    or if the jar doesn't contain this main class. You should then make sure
+    that the input jar contains a valid manifest file to start with, that this
+    manifest file is the one that is copied (the first manifest file that is
+    encountered), and that the main class is kept in your configuration,</dd>
+
+<dt><a name="invalidclassexception2"><b>InvalidClassException</b>, <b>class loading error</b>, or <b>verification error</b> (in Java Micro Edition)</a></dt>
+
+<dd>If you get such an error in Java Micro Edition, you may have forgotten to
+    specify the <a
+    href="usage.html#microedition"><code>-microedition</code></a> option, so
+    the processed class files are preverified properly.</dd>
+
+<dt><a name="nosuchfieldormethod"><b>Error: No Such Field or Method</b>, <b>Error verifying method</b> (in a Java Micro Edition emulator)</a></dt>
+
+<dd>If you get such a message in a Motorola or Sony Ericsson phone emulator,
+    it's because these emulators don't like packageless classes and/or
+    overloaded fields and methods. You can work around it by not using the
+    options <code><a href="usage.html#repackageclasses">-repackageclasses</a>
+    ''</code> and <a
+    href="usage.html#overloadaggressively"><code>-overloadaggressively</code></a>.
+    If you're using the JME WTK plugin, you can adapt the configuration
+    <code>proguard/wtk/default.pro</code> that's inside the
+    <code>proguard.jar</code>.</dd>
+
+<dt><a name="failingmidlets"><b>Failing midlets</b> (on a Java Micro Edition device)</a></dt>
+
+<dd>If your midlet runs in an emulator and on some devices, but not on some
+    other devices, this is probably due to a bug in the latter devices. For
+    some older Motorola and Nokia phones, you might try specifying the <a
+    href="usage.html#useuniqueclassmembernames"><code>-useuniqueclassmembernames</code></a>
+    option. It avoids overloading class member names, which triggers a bug in
+    their java virtual machine.
+    <p>
+    You might also try using the <a
+    href="usage.html#dontusemixedcaseclassnames"><code>-dontusemixedcaseclassnames</code></a>
+    option. Even if the midlet has been properly processed and then
+    preverified on a case-sensitive file system, the device itself might not
+    like the mixed-case class names. Notably, the Nokia N-Gage emulator works
+    fine, but the actual device seems to exhibit this problem.</dd>
+
+<dt><a name="disappearingloops"><b>Disappearing loops</b></a></dt>
+
+<dd>If your code contains empty busy-waiting loops, ProGuard's optimization
+    step may remove them. More specifically, this happens if a loop
+    continuously checks the value of a non-volatile field that is changed in a
+    different thread. The specifications of the Java Virtual Machine require
+    that you always mark fields that are accessed across different threads
+    without further synchronization as <code>volatile</code>. If this is not
+    possible for some reason, you'll have to switch off optimization using the
+    <a href="usage.html#dontoptimize"><code>-dontoptimize</code></a>
+    option.</dd>
+
+<dt><a name="securityexception"><b>SecurityException: SHA1 digest error</b></a></dt>
+
+<dd>You may have forgotten to sign your program jar <i>after</i> having
+    processed it with ProGuard.</dd>
+
+<dt><a name="classcastexception"><b>ClassCastException: class not an enum</b>, or <br><b>IllegalArgumentException: class not an enum type</b></a></dt>
+
+<dd>You should make sure you're preserving the special methods of enumeration
+    types, which the run-time environment calls by introspection. The required
+    options are shown in the <a
+    href="examples.html#enumerations">examples</a>.</dd>
+
+<dt><a name="arraystoreexception"><b>ArrayStoreException: sun.reflect.annotation.EnumConstantNotPresentExceptionProxy</b></a></dt>
+
+<dd>You are probably processing annotations involving enumerations. Again, you
+    should make sure you're preserving the special methods of the enumeration
+    type, as shown in the examples.</dd>
+
+<dt><a name="compilererror"><b>CompilerError: duplicate addition</b></a></dt>
+
+<dd>You are probably compiling or running some code that has been obfuscated
+    with the <a
+    href="usage.html#overloadaggressively"><code>-overloadaggressively</code></a>
+    option. This option triggers a bug in
+    <code>sun.tools.java.MethodSet.add</code> in Sun's JDK 1.2.2, which is
+    used for (dynamic) compilation. You should then avoid this option.</dd>
+
+<dt><a name="classformaterror"><b>ClassFormatError: repetitive field name/signature</b></a></dt>
+
+<dd>You are probably processing some code that has been obfuscated before with
+    the <a
+    href="usage.html#overloadaggressively"><code>-overloadaggressively</code></a>
+    option. You should then use the same option again in the second processing
+    round.</dd>
+
+<dt><a name="nosuchmethoderror"><b>NoSuchMethodError</b> or <b>AbstractMethodError</b></a></dt>
+
+<dd>You should make sure you're not writing your output class files to a
+    directory on a platform with a case-insensitive file system, such as
+    Windows. Please refer to the section about <a
+    href="#disappearingclasses">disappearing classes</a> for details.
+    <p>
+    Furthermore, you should check whether you have specified your program jars
+    and library jars properly. Program classes can refer to library classes,
+    but not the other way around.
+    <p>
+    If all of this seems ok, perhaps there's a bug in ProGuard (gasp!). If so,
+    please report it, preferably with the simplest example on which you can
+    find ProGuard to fail.</dd>
+
+<dt><a name="verifyerror"><b>VerifyError</b></a></dt>
+
+<dd>Verification errors when executing a program are almost certainly the
+    result of a bug in the optimization step of ProGuard. Make sure you are
+    using the latest version. You should be able to work around the problem by
+    using the <a href="usage.html#dontoptimize"><code>-dontoptimize</code></a>
+    option. You can check the bug database to see if it is a known problem
+    (often with a fix). Otherwise, please report it, preferably with the
+    simplest example on which ProGuard fails.</dd>
+
+</dl>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/usage.html b/docs/manual/usage.html
new file mode 100644
index 0000000..8be8d60
--- /dev/null
+++ b/docs/manual/usage.html
@@ -0,0 +1,1231 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Usage</title>
+</head>
+<body>
+
+<h2>Usage</h2>
+
+To run ProGuard, just type:
+<p class="code">
+<code><b>java -jar proguard.jar </b></code><i>options</i> ...
+</p>
+You can find the ProGuard jar in the <code>lib</code> directory of the
+ProGuard distribution. Options can also be put in one or more configuration
+files. Typically, you'll put most options in a configuration file (say,
+<code>myconfig.pro</code>), and just call:
+<p class="code">
+<code><b>java -jar proguard.jar @myconfig.pro</b></code>
+</p>
+You can combine command line options and options from configuration files, for
+instance:
+<p class="code">
+<code><b>java -jar proguard.jar @myconfig.pro -verbose</b></code>
+</p>
+<p>
+In a configuration file, a <code><b>#</b></code> sign and all remaining
+characters on that line are ignored, allowing you to add comments.
+<p>
+Extra whitespace between words and delimiters is ignored. To specify file
+names with spaces or special characters, words can be quoted with single or
+double quotes. Note that the quotes may need to be escaped when used on the
+command line, to avoid them being gobbled by the shell.
+<p>
+Options can be grouped arbitrarily in arguments on the command line and in
+lines in configuration files. This means that you can quote any arbitrary
+section of command line options, to avoid shell expansion of special
+characters, for instance.
+<p>
+The order of the options is generally irrelevant. They can be abbreviated to
+their first unique characters.
+<p>
+
+The sections below provide more details:
+<ul>
+<li><a href="#iooptions">Input/Output Options</a>
+<li><a href="#keepoptions">Keep Options</a>
+<li><a href="#shrinkingoptions">Shrinking Options</a>
+<li><a href="#optimizationoptions">Optimization Options</a>
+<li><a href="#obfuscationoptions">Obfuscation Options</a>
+<li><a href="#preverificationoptions">Preverification Options</a>
+<li><a href="#generaloptions">General Options</a>
+<li><a href="#classpath">Class Paths</a>
+<li><a href="#filename">File Names</a>
+<li><a href="#filefilters">File Filters</a>
+<li><a href="#filters">Filters</a>
+<li><a href="#keepoverview">Overview of <code>Keep</code> Options</a>
+<li><a href="#keepoptionmodifiers">Keep Option Modifiers</a>
+<li><a href="#classspecification">Class Specifications</a>
+</ul>
+
+<a name="iooptions">&nbsp;</a>
+<h2>Input/Output Options</h2>
+
+<dl>
+<dt><a name="at"><code><b>@</b></code></a><a href="#filename"><i>filename</i></a></dt>
+
+<dd>Short for '<a href="#include"><code>-include</code></a>
+     <a href="#filename"><i>filename</i></a>'.</dd>
+
+<dt><a name="include"><code><b>-include</b></code></a>
+    <a href="#filename"><i>filename</i></a></dt>
+
+<dd>Recursively reads configuration options from the given file
+    <i>filename</i>.</dd>
+
+<dt><a name="basedirectory"><code><b>-basedirectory</b></code></a>
+    <a href="#filename"><i>directoryname</i></a></dt>
+
+<dd>Specifies the base directory for all subsequent relative file names in
+    these configuration arguments or this configuration file.</dd>
+
+<dt><a name="injars"><code><b>-injars</b></code></a>
+    <a href="#classpath"><i>class_path</i></a></dt>
+
+<dd>Specifies the input jars (or wars, ears, zips, or directories) of the
+    application to be processed. The class files in these jars will be
+    processed and written to the output jars. By default, any non-class files
+    will be copied without changes. Please be aware of any temporary files
+    (e.g. created by IDEs), especially if you are reading your input files
+    straight from directories. The entries in the class path can be filtered,
+    as explained in the <a href="#filefilters">filters</a> section. For better
+    readability, class path entries can be specified using multiple
+    <code>-injars</code> options.</dd>
+
+<dt><a name="outjars"><code><b>-outjars</b></code></a>
+    <a href="#classpath"><i>class_path</i></a></dt>
+
+<dd>Specifies the names of the output jars (or wars, ears, zips, or
+    directories). The processed input of the preceding <code>-injars</code>
+    options will be written to the named jars. This allows you to collect the
+    contents of groups of input jars into corresponding groups of output jars.
+    In addition, the output entries can be filtered, as explained in
+    the <a href="#filefilters">filters</a> section. Each processed class file
+    or resource file is then written to the first output entry with a matching
+    filter, within the group of output jars.
+    <p>
+    You must avoid letting the output files overwrite any input files. For
+    better readability, class path entries can be specified using multiple
+    <code>-outjars</code> options. Without any <code>-outjars</code> options,
+    no jars will be written.</dd>
+
+<dt><a name="libraryjars"><code><b>-libraryjars</b></code></a>
+    <a href="#classpath"><i>class_path</i></a></dt>
+
+<dd>Specifies the library jars (or wars, ears, zips, or directories) of the
+    application to be processed. The files in these jars will not be included
+    in the output jars. The specified library jars should at least contain the
+    class files that are <i>extended</i> by application class files. Library
+    class files that are only <i>called</i> needn't be present, although their
+    presence can improve the results of the optimization step. The entries in
+    the class path can be filtered, as explained in the <a
+    href="#filefilters">filters</a> section. For better readability, class path
+    entries can be specified using multiple <code>-libraryjars</code> options.
+    <p>
+    Please note that the boot path and the class path set for running ProGuard
+    are not considered when looking for library classes. This means that you
+    explicitly have to specify the run-time jar that your code will use.
+    Although this may seem cumbersome, it allows you to process applications
+    targeted at different run-time environments. For example, you can process
+    <a href="examples.html#application">J2SE applications</a> as well as <a
+    href="examples.html#midlet">JME midlets</a>, just by specifying the
+    appropriate run-time jar.</dd>
+
+<dt><a name="dontskipnonpubliclibraryclasses"><code><b>-dontskipnonpubliclibraryclasses</b></code></a></dt>
+
+<dd>Specifies not to ignore non-public library classes. By default, non-public
+    library classes are skipped while parsing library jars. The classes are
+    typically not relevant during processing, since they don't affect the
+    actual program code in the input jars. Ignoring them reduces memory usage
+    and processing time. Occasionally, a badly designed library may contain a
+    non-public library class that is extended/implemented by a public library
+    class. If the latter library class in turn is extended/implemented by a
+    program class, ProGuard will complain that it can't find the non-public
+    library class, which it had ignored during parsing. This option will
+    overcome that problem, at the cost of greater memory usage and longer
+    processing time.</dd>
+
+<dt><a name="dontskipnonpubliclibraryclassmembers"><code><b>-dontskipnonpubliclibraryclassmembers</b></code></a></dt>
+
+<dd>Specifies not to ignore package visible library class members (fields and
+    methods). By default, these class members are skipped while parsing
+    library classes, as program classes will generally not refer to them.
+    Sometimes however, program classes reside in the same packages as library
+    classes, and they do refer to their package visible class members. In
+    those cases, it can be useful to actually read the class members, in order
+    to make sure the processed code remains consistent.</dd>
+
+<dt><a name="keepdirectories"><code><b>-keepdirectories</b></code></a>
+    [<i><a href="#filefilters">directory_filter</a></i>]</dt>
+
+<dd>Specifies the directories to be kept in the output jars (or wars, ears, or
+    directories). By default, directory entries are removed. This reduces the
+    jar size, but it may be undesirable if the program code tries to find them
+    with constructs like "<code>MyClass.class.getResource("")</code>". If the
+    option is specified without a filter, all directories are kept. With a
+    filter, only matching directories are kept.</dd>
+
+<dt><a name="target"><code><b>-target</b></code></a> <i>version</i></dt>
+
+<dd>Specifies the version number to be set in the processed class files. The
+    version number can be one of <code>1.0</code>, <code>1.1</code>,
+    <code>1.2</code>, <code>1.3</code>, <code>1.4</code>, <code>1.5</code> (or
+    just <code>5</code>), or <code>1.6</code> (or just <code>6</code>). By
+    default, the version numbers of the class files are left unchanged. For
+    example, you may want to <a href="examples.html#upgrade">upgrade class
+    files to Java 6</a>, by changing their version numbers and having them
+    preverified.</dd>
+
+<dt><a name="forceprocessing"><code><b>-forceprocessing</b></code></a></dt>
+
+<dd>Specifies to process the input, even if the output seems up to date. The
+    up-to-dateness test is based on a comparison of the date stamps of the
+    specified input, output, and configuration files or directories.</dd>
+
+</dl>
+<p>
+
+<a name="keepoptions">&nbsp;</a>
+<h2>Keep Options</h2>
+
+<dl>
+<dt><a name="keep"><code><b>-keep</b></code></a>
+    [<a href="#keepoptionmodifiers">,<i>modifier</i></a>,...]
+    <a href="#classspecification"><i>class_specification</i></a></dt>
+
+<dd>Specifies classes and class members (fields and methods) to be preserved
+    as entry points to your code. For example, in order to <a
+    href="examples.html#application">keep an application</a>, you can specify
+    the main class along with its main method. In order to <a
+    href="examples.html#library">process a library</a>, you should specify all
+    publicly accessible elements.</dd>
+
+<dt><a name="keepclassmembers"><code><b>-keepclassmembers</b></code></a>
+    [<a href="#keepoptionmodifiers">,<i>modifier</i></a>,...]
+    <a href="#classspecification"><i>class_specification</i></a></dt>
+
+<dd>Specifies class members to be preserved, if their classes are preserved as
+    well. For example, you may want to <a
+    href="examples.html#serializable">keep all serialization fields and
+    methods</a> of classes that implement the <code>Serializable</code>
+    interface.</dd>
+
+<dt><a name="keepclasseswithmembers"><code><b>-keepclasseswithmembers</b></code></a>
+    [<a href="#keepoptionmodifiers">,<i>modifier</i></a>,...]
+    <a href="#classspecification"><i>class_specification</i></a></dt>
+
+<dd>Specifies classes and class members to be preserved, on the condition that
+    all of the specified class members are present. For example, you may want
+    to <a href="examples.html#applications">keep all applications</a> that
+    have a main method, without having to list them explicitly.</dd>
+
+<dt><a name="keepnames"><code><b>-keepnames</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a></dt>
+
+<dd>Short for <a href="#keep"><code>-keep</code></a>,<a href="#allowshrinking"><code>allowshrinking</code></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <p>
+    Specifies classes and class members whose names are to be preserved, if
+    they aren't removed in the shrinking phase. For example, you may want to
+    <a href="examples.html#serializable">keep all class names</a> of classes
+    that implement the <code>Serializable</code> interface, so that the
+    processed code remains compatible with any originally serialized classes.
+    Classes that aren't used at all can still be removed. Only applicable when
+    obfuscating.</dd>
+
+<dt><a name="keepclassmembernames"><code><b>-keepclassmembernames</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a></dt>
+
+<dd>Short for <a href="#keepclassmembers"><code>-keepclassmembers</code></a>,<a href="#allowshrinking"><code>allowshrinking</code></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <p>
+    Specifies class members whose names are to be preserved, if they aren't
+    removed in the shrinking phase. For example, you may want to preserve the
+    name of the synthetic <code>class$</code> methods when <a
+    href="examples.html#library">processing a library</a>, so obfuscators can
+    detect it again when processing an application that uses the processed
+    library (although ProGuard itself doesn't need this). Only applicable when
+    obfuscating.</dd>
+
+<dt><a name="keepclasseswithmembernames"><code><b>-keepclasseswithmembernames</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a></dt>
+
+<dd>Short for <a href="#keepclasseswithmembers"><code>-keepclasseswithmembers</code></a>,<a href="#allowshrinking"><code>allowshrinking</code></a>
+    <a href="#classspecification"><i>class_specification</i></a>
+    <p>
+    Specifies classes and class members whose names are to be preserved, on
+    the condition that all of the specified class members are present after
+    the shrinking phase. For example, you may want to <a
+    href="examples.html#native">keep all native method names</a> and the names
+    of their classes, so that the processed code can still link with the
+    native library code. Native methods that aren't used at all can still be
+    removed. If a class file is used, but none of its native methods are, its
+    name will still be obfuscated. Only applicable when obfuscating.</dd>
+
+<dt><a name="printseeds"><code><b>-printseeds</b></code></a>
+    [<a href="#filename"><i>filename</i></a>]</dt>
+
+<dd>Specifies to exhaustively list classes and class members matched by the
+    various <code>-keep</code> options. The list is printed to the standard
+    output or to the given file. The list can be useful to verify if the
+    intended class members are really found, especially if you're using
+    wildcards. For example, you may want to list all the <a
+    href="examples.html#applications">applications</a> or all the <a
+    href="examples.html#applets">applets</a> that you are keeping.</dd>
+
+</dl>
+<p>
+
+<a name="shrinkingoptions">&nbsp;</a>
+<h2>Shrinking Options</h2>
+
+<dl>
+<dt><a name="dontshrink"><code><b>-dontshrink</b></code></a></dt>
+
+<dd>Specifies not to shrink the input class files. By default, shrinking is
+    applied; all classes and class members are removed, except for the ones
+    listed by the various <code>-keep</code> options, and the ones on which
+    they depend, directly or indirectly. A shrinking step is also applied
+    after each optimization step, since some optimizations may open the
+    possibility to remove more classes and class members.</dd>
+
+<dt><a name="printusage"><code><b>-printusage</b></code></a>
+    [<a href="#filename"><i>filename</i></a>]</dt>
+
+<dd>Specifies to list dead code of the input class files. The list is printed
+    to the standard output or to the given file. For example, you can <a
+    href="examples.html#deadcode">list the unused code of an application</a>.
+    Only applicable when shrinking.</dd>
+
+<dt><a name="whyareyoukeeping"><code><b>-whyareyoukeeping</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a></dt>
+
+<dd>Specifies to print details on why the given classes and class members are
+    being kept in the shrinking step. This can be useful if you are wondering
+    why some given element is present in the output. In general, there can be
+    many different reasons. This option prints the shortest chain of methods
+    to a specified seed or entry point, for each specified class and class
+    member. <i>In the current implementation, the shortest chain that is
+    printed out may sometimes contain circular deductions -- these do not
+    reflect the actual shrinking process.</i> If the <a
+    href="#verbose"><code>-verbose</code></a> option if specified, the traces
+    include full field and method signatures. Only applicable when
+    shrinking.</dd>
+
+</dl>
+<p>
+
+<a name="optimizationoptions">&nbsp;</a>
+<h2>Optimization Options</h2>
+
+<dl>
+<dt><a name="dontoptimize"><code><b>-dontoptimize</b></code></a></dt>
+
+<dd>Specifies not to optimize the input class files. By default, optimization
+    is enabled; all methods are optimized at a bytecode level.</dd>
+
+<dt><a name="optimizations"><code><b>-optimizations</b></code></a>
+    <a href="optimizations.html"><i>optimization_filter</i></a></dt>
+
+<dd>Specifies the optimizations to be enabled and disabled, at a more
+    fine-grained level. Only applicable when optimizing. <i>This is an expert
+    option.</i></dd>
+
+<dt><a name="optimizationpasses"><code><b>-optimizationpasses</b></code></a> <i>n</i></dt>
+
+<dd>Specifies the number of optimization passes to be performed. By default, a
+    single pass is performed. Multiple passes may result in further
+    improvements. If no improvements are found after an optimization pass, the
+    optimization is ended. Only applicable when optimizing.</dd>
+
+<dt><a name="assumenosideeffects"><code><b>-assumenosideeffects</b></code></a>
+    <a href="#classspecification"><i>class_specification</i></a></dt>
+
+<dd>Specifies methods that don't have any side effects (other than maybe
+    returning a value). In the optimization step, ProGuard will then remove
+    calls to such methods, if it can determine that the return values aren't
+    used. Note that ProGuard will analyze your program code to find such
+    methods automatically. It will not analyze library code, for which this
+    option can thus be useful. For example, you could specify the method
+    <code>System.currentTimeMillis()</code>, so that any idle calls to it will
+    be removed. Note that ProGuard applies the option to the entire hierarchy
+    of the specified methods. Only applicable when optimizing. In general,
+    making assumptions can be dangerous; you can easily break the processed
+    code. <i>Only use this option if you know what you're doing!</i></dd>
+
+<dt><a name="allowaccessmodification"><code><b>-allowaccessmodification</b></code></a></dt>
+
+<dd>Specifies that the access modifiers of classes and class members may be
+    broadened during processing. This can improve the results of the
+    optimization step. For instance, when inlining a public getter, it may be
+    necessary to make the accessed field public too. Although Java's binary
+    compatibility specifications formally do not require this (cfr. <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/j.title.doc.html"
+    >The Java Language Specification, Second Edition</a>, <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/binaryComp.doc.html#47259"
+    >Section 13.4.6</a>), some virtual machines would have problems with the
+    processed code otherwise. Only applicable when optimizing (and when
+    obfuscating with the <a
+    href="#repackageclasses"><code>-repackageclasses</code></a> option).
+    <p>
+    <i>Counter-indication:</i> you probably shouldn't use this option when
+    processing code that is to be used as a library, since classes and class
+    members that weren't designed to be public in the API may become
+    public.</dd>
+
+<dt><a name="mergeinterfacesaggressively"><code><b>-mergeinterfacesaggressively</b></code></a></dt>
+
+<dd>Specifies that interfaces may be merged, even if their implementing
+    classes don't implement all interface methods. This can reduce the size of
+    the output by reducing the total number of classes. Note that Java's
+    binary compatibility specifications allow such constructs (cfr. <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/j.title.doc.html"
+    >The Java Language Specification, Second Edition</a>, <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/binaryComp.doc.html#45347"
+    >Section 13.5.3</a>), even if they are not allowed in the Java language
+    (cfr. <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/j.title.doc.html"
+    >The Java Language Specification, Second Edition</a>, <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#34031"
+    >Section 8.1.4</a>). Only applicable when optimizing.
+    <p>
+    <i>Counter-indication:</i> setting this option can reduce the performance
+    of the processed code on some JVMs, since advanced just-in-time
+    compilation tends to favor more interfaces with fewer implementing
+    classes. Worse, some JVMs may not be able to handle the resulting code.
+    Notably:
+    <ul>
+    <li>Sun's JRE 1.3 may throw an <code>InternalError</code> when
+        encountering more than 256 <i>Miranda</i> methods (interface methods
+        without implementations) in a class.
+    </ul></dd>
+
+</dl>
+<p>
+
+<a name="obfuscationoptions">&nbsp;</a>
+<h2>Obfuscation Options</h2>
+
+<dl>
+<dt><a name="dontobfuscate"><code><b>-dontobfuscate</b></code></a></dt>
+
+<dd>Specifies not to obfuscate the input class files. By default, obfuscation
+    is applied; classes and class members receive new short random names,
+    except for the ones listed by the various <code>-keep</code> options.
+    Internal attributes that are useful for debugging, such as source files
+    names, variable names, and line numbers are removed.</dd>
+
+<dt><a name="printmapping"><code><b>-printmapping</b></code></a>
+    [<a href="#filename"><i>filename</i></a>]</dt>
+
+<dd>Specifies to print the mapping from old names to new names for classes and
+    class members that have been renamed. The mapping is printed to the
+    standard output or to the given file. For example, it is required for
+    subsequent <a href="examples.html#incremental">incremental
+    obfuscation</a>, or if you ever want to make sense again of <a
+    href="examples.html#stacktrace">obfuscated stack traces</a>. Only
+    applicable when obfuscating.</dd>
+
+<dt><a name="applymapping"><code><b>-applymapping</b></code></a>
+    <a href="#filename"><i>filename</i></a></dt>
+
+<dd>Specifies to reuse the given name mapping that was printed out in a
+    previous obfuscation run of ProGuard. Classes and class members that are
+    listed in the mapping file receive the names specified along with them.
+    Classes and class members that are not mentioned receive new names. The
+    mapping may refer to input classes as well as library classes. This option
+    can be useful for <a href="examples.html#incremental">incremental
+    obfuscation</a>, i.e. processing add-ons or small patches to an existing
+    piece of code. In such cases, you should consider whether you also need
+    the option <a
+    href="#useuniqueclassmembernames"><code>-useuniqueclassmembernames</code></a>.
+    Only applicable when obfuscating.</dd>
+
+<dt><a name="obfuscationdictionary"><code><b>-obfuscationdictionary</b></code></a>
+    <a href="#filename"><i>filename</i></a></dt>
+
+<dd>Specifies a text file from which all valid words are used as obfuscated
+    field and method names. By default, short names like 'a', 'b', etc. are
+    used as obfuscated names. With an obfuscation dictionary, you can specify
+    a list of reserved key words, or identifiers with foreign characters, for
+    instance. White space, punctuation characters, duplicate words, and
+    comments after a <code><b>#</b></code> sign are ignored. Note that an
+    obfuscation dictionary hardly improves the obfuscation. Decent compilers
+    can automatically replace them, and the effect can fairly simply be undone
+    by obfuscating again with simpler names. The most useful application is
+    specifying strings that are typically already present in class files (such
+    as 'Code'), thus reducing the class file sizes just a little bit more.
+    Only applicable when obfuscating.</dd>
+
+<dt><a name="classobfuscationdictionary"><code><b>-classobfuscationdictionary</b></code></a>
+    <a href="#filename"><i>filename</i></a></dt>
+
+<dd>Specifies a text file from which all valid words are used as obfuscated
+    class names. The obfuscation dictionary is similar to the one of the
+    option <a
+    href="#obfuscationdictionary"><code>-obfuscationdictionary</code></a>.
+    Only applicable when obfuscating.</dd>
+
+<dt><a name="packageobfuscationdictionary"><code><b>-packageobfuscationdictionary</b></code></a>
+    <a href="#filename"><i>filename</i></a></dt>
+
+<dd>Specifies a text file from which all valid words are used as obfuscated
+    package names. The obfuscation dictionary is similar to the one of the
+    option <a
+    href="#obfuscationdictionary"><code>-obfuscationdictionary</code></a>.
+    Only applicable when obfuscating.</dd>
+
+<dt><a name="overloadaggressively"><code><b>-overloadaggressively</b></code></a></dt>
+
+<dd>Specifies to apply aggressive overloading while obfuscating. Multiple
+    fields and methods can then get the same names, as long as their arguments
+    and return types are different (not just their arguments). This option can
+    make the processed code even smaller (and less comprehensible). Only
+    applicable when obfuscating.
+    <p>
+    <i>Counter-indication:</i> the resulting class files fall within the Java
+    bytecode specification (cfr. <a href=
+    "http://java.sun.com/docs/books/vmspec/2nd-edition/html/VMSpecTOC.doc.html"
+    >The Java Virtual Machine Specification, Second Edition</a>, first
+    paragraphs of <a href=
+    "http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#2877"
+    >Section 4.5</a> and <a href=
+    "http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#1513"
+    >Section 4.6</a>), even though this kind of overloading is not allowed in
+    the Java language (cfr. <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/j.title.doc.html"
+    >The Java Language Specification, Second Edition</a>, <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#40898"
+    >Section 8.3</a> and <a href=
+    "http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#227768"
+    >Section 8.4.7</a>). Still, some tools have problems with it. Notably:
+    <ul>
+    <li>Sun's JDK 1.2.2 <code>javac</code> compiler produces an exception when
+        compiling with such a library (cfr. <a href=
+        "http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4216736">Bug
+        #4216736</a>). You probably shouldn't use this option for processing
+        libraries.
+    <li>Sun's JRE 1.4 and later fail to serialize objects with overloaded
+        primitive fields.
+    <li>Sun's JRE 1.5 <code>pack200</code> tool reportedly has problems with
+        overloaded class members.
+    </ul></dd>
+
+<dt><a name="useuniqueclassmembernames"><code><b>-useuniqueclassmembernames</b></code></a></dt>
+
+<dd>Specifies to assign the same obfuscated names to class members that have
+    the same names, and different obfuscated names to class members that have
+    different names (for each given class member signature). Without the
+    option, more class members can be mapped to the same short names like 'a',
+    'b', etc. The option therefore increases the size of the resulting code
+    slightly, but it ensures that the saved obfuscation name mapping can
+    always be respected in subsequent incremental obfuscation steps.
+    <p>
+    For instance, consider two distinct interfaces containing methods with the
+    same name and signature. Without this option, these methods may get
+    different obfuscated names in a first obfuscation step. If a patch is then
+    added containing a class that implements both interfaces, ProGuard will
+    have to enforce the same method name for both methods in an incremental
+    obfuscation step. The original obfuscated code is changed, in order to
+    keep the resulting code consistent. With this option <i>in the initial
+    obfuscation step</i>, such renaming will never be necessary.
+    <p>
+    This option is only applicable when obfuscating. In fact, if you are
+    planning on performing incremental obfuscation, you probably want to avoid
+    shrinking and optimization altogether, since these steps could remove or
+    modify parts of your code that are essential for later additions.</dd>
+
+<dt><a name="dontusemixedcaseclassnames"><code><b>-dontusemixedcaseclassnames</b></code></a></dt>
+
+<dd>Specifies not to generate mixed-case class names while obfuscating. By
+    default, obfuscated class names can contain a mix of upper-case characters
+    and lower-case characters. This creates perfectly acceptable and usable
+    jars. Only if a jar is unpacked on a platform with a case-insensitive
+    filing system (say, Windows), the unpacking tool may let similarly named
+    class files overwrite each other. Code that self-destructs when it's
+    unpacked! Developers who really want to unpack their jars on Windows can
+    use this option to switch off this behavior. Note that the obfuscated jars
+    will become larger as a result. Only applicable when obfuscating.</dd>
+
+<dt><a name="keeppackagenames"><code><b>-keeppackagenames</b></code></a>
+    [<i><a href="#filters">package_filter</a></i>]</dt>
+
+<dd>Specifies not obfuscate the given package names. The optional filter is a
+    comma-separated list of package names. Package names can contain <b>?</b>,
+    <b>*</b>, and <b>**</b> wildcards, and they can be preceded by the
+    <b>!</b> negator. Only applicable when obfuscating.</dd>
+
+<dt><a name="flattenpackagehierarchy"><code><b>-flattenpackagehierarchy</b></code></a>
+    [<i>package_name</i>]</dt>
+
+<dd>Specifies to repackage all packages that are renamed, by moving them into
+    the single given parent package. Without argument or with an empty string
+    (''), the packages are moved into the root package. This option is one
+    example of further <a href="examples.html#repackaging">obfuscating package
+    names</a>. It can make the processed code smaller and less comprehensible.
+    Only applicable when obfuscating.</dd>
+
+<dt><a name="repackageclasses"><code><b>-repackageclasses</b></code></a>
+    [<i>package_name</i>]</dt>
+
+<dd>Specifies to repackage all class files that are renamed, by moving them
+    into the single given package. Without argument or with an empty string
+    (''), the package is removed completely. This option option overrides the
+    <a
+    href="#flattenpackagehierarchy"><code>-flattenpackagehierarchy</code></a>
+    option. It is another example of further <a
+    href="examples.html#repackaging">obfuscating package names</a>. It can
+    make the processed code even smaller and less comprehensible. Its
+    deprecated name is <code>-defaultpackage</code>. Only applicable when
+    obfuscating.
+    <p>
+    <i>Counter-indication:</i> classes that look for resource files in their
+    package directories will no longer work properly if they are moved
+    elsewhere. When in doubt, just leave the packaging untouched by not using
+    this option.</dd>
+
+<dt><a name="keepattributes"><code><b>-keepattributes</b></code></a>
+    [<i><a href="#filters">attribute_filter</a></i>]</dt>
+
+<dd>Specifies any optional attributes to be preserved. The attributes can be
+    specified with one or more <code>-keepattributes</code> directives. The
+    optional filter is a comma-separated list of attribute names. Attribute
+    names can contain <b>?</b>, <b>*</b>, and <b>**</b> wildcards, and they
+    can be preceded by the <b>!</b> negator. Typical optional attributes are
+    <code>Exceptions</code>, <code>Signature</code>, <code>Deprecated</code>,
+    <code>SourceFile</code>, <code>SourceDir</code>,
+    <code>LineNumberTable</code>, <code>LocalVariableTable</code>,
+    <code>LocalVariableTypeTable</code>, <code>Synthetic</code>,
+    <code>EnclosingMethod</code>, <code>RuntimeVisibleAnnotations</code>,
+    <code>RuntimeInvisibleAnnotations</code>,
+    <code>RuntimeVisibleParameterAnnotations</code>,
+    <code>RuntimeInvisibleParameterAnnotations</code>, and
+    <code>AnnotationDefault</code>. The <code>InnerClasses</code> attribute
+    name can be specified as well, referring to the source name part of this
+    attribute. For example, you should at least keep the
+    <code>Exceptions</code>, <code>InnerClasses</code>, and
+    <code>Signature</code> attributes when <a
+    href="examples.html#library">processing a library</a>. As another example,
+    you should keep the <code>SourceFile</code> and
+    <code>LineNumberTable</code> attributes for <a
+    href="examples.html#stacktrace">producing useful obfuscated stack
+    traces</a>. Only applicable when obfuscating.</dd>
+
+<dt><a name="renamesourcefileattribute"><code><b>-renamesourcefileattribute</b></code></a>
+    [<i>string</i>]</dt>
+
+<dd>Specifies a constant string to be put in the <code>SourceFile</code>
+    attributes (and <code>SourceDir</code> attributes) of the class files.
+    Note that the attribute has to be present to start with, so it also has to
+    be preserved explicitly using the <code>-keepattributes</code> directive.
+    For example, you may want to have your processed libraries and
+    applications produce <a href="examples.html#stacktrace">useful obfuscated
+    stack traces</a>. Only applicable when obfuscating.</dd>
+
+<dt><a name="adaptclassstrings"><code><b>-adaptclassstrings</b></code></a>
+    [<i><a href="#filters">class_filter</a></i>]</dt>
+
+<dd>Specifies that string constants that correspond to class names should be
+    obfuscated as well. Without a filter, all string constants that correspond
+    to class names are adapted. With a filter, only string constants in
+    classes that match the filter are adapted. For example, if your code
+    contains a large number of hard-coded strings that refer to classes, and
+    you prefer not to keep their names, you may want to use this option.
+    Primarily applicable when obfuscating, although corresponding classes are
+    automatically kept in the shrinking step too.</dd>
+
+<dt><a name="adaptresourcefilenames"><code><b>-adaptresourcefilenames</b></code></a>
+    [<i><a href="#filefilters">file_filter</a></i>]</dt>
+
+<dd>Specifies the resource files to be renamed, based on the obfuscated names
+    of the corresponding class files (if any). Without a filter, all resource
+    files that correspond to class files are renamed. With a filter, only
+    matching files are renamed. For example, see <a
+    href="examples.html#resourcefiles">processing resource files</a>. Only
+    applicable when obfuscating.</dd>
+
+<dt><a name="adaptresourcefilecontents"><code><b>-adaptresourcefilecontents</b></code></a>
+    [<i><a href="#filefilters">file_filter</a></i>]</dt>
+
+<dd>Specifies the resource files whose contents are to be updated. Any class
+    names mentioned in the resource files are renamed, based on the obfuscated
+    names of the corresponding classes (if any). Without a filter, the
+    contents of all resource files updated. With a filter, only matching files
+    are updated. The resource files are parsed and written using the
+    platform's default character set. You can change this default character set
+    by setting the environment variable <code>LANG</code> or the Java system
+    property <code>file.encoding</code>. For an example,
+    see <a href="examples.html#resourcefiles">processing resource files</a>.
+    Only applicable when obfuscating.</dd>
+
+</dl>
+<p>
+
+<a name="preverificationoptions">&nbsp;</a>
+<h2>Preverification Options</h2>
+
+<dl>
+<dt><a name="dontpreverify"><code><b>-dontpreverify</b></code></a></dt>
+
+<dd>Specifies not to preverify the processed class files. By default, class
+    files are preverified if they are targeted at Java Micro Edition or at
+    Java 6 or higher. For Java Micro Edition, preverification is required, so
+    you will need to run an external preverifier on the processed code if you
+    specify this option. For Java 6, preverification is not required (yet),
+    but it improves the efficiency of the class loading in the Java Virtual
+    Machine.</dd>
+
+<dt><a name="microedition"><code><b>-microedition</b></code></a></dt>
+
+<dd>Specifies that the processed class files are targeted at Java Micro
+    Edition. The preverifier will then add the appropriate StackMap
+    attributes, which are different from the default StackMapTable attributes
+    for Java Standard Edition. For example, you will need this option if you
+    are <a href="examples.html#midlets">processing midlets</a>.</dd>
+
+</dl>
+<p>
+
+<a name="generaloptions">&nbsp;</a>
+<h2>General Options</h2>
+
+<dl>
+<dt><a name="verbose"><code><b>-verbose</b></code></a></dt>
+
+<dd>Specifies to write out some more information during processing. If the
+    program terminates with an exception, this option will print out the entire
+    stack trace, instead of just the exception message.</dd>
+
+<dt><a name="dontnote"><code><b>-dontnote</b></code></a>
+    [<i><a href="#filters">class_filter</a></i>]</dt>
+
+<dd>Specifies not to print notes about potential mistakes or omissions in the
+    configuration, like typos in class names, or like missing options that
+    might be useful. The optional filter is a regular expression; ProGuard
+    doesn't print notes about classes with matching names.</dd>
+
+<dt><a name="dontwarn"><code><b>-dontwarn</b></code></a>
+    [<i><a href="#filters">class_filter</a></i>]</dt>
+
+<dd>Specifies not to warn about unresolved references and other important
+    problems at all. The optional filter is a regular expression; ProGuard
+    doesn't print warnings about classes with matching names. Ignoring
+    warnings can be dangerous. For instance, if the unresolved classes or
+    class members are indeed required for processing, the processed code will
+    not function properly. <i>Only use this option if you know what you're
+    doing!</i></dd>
+
+<dt><a name="ignorewarnings"><code><b>-ignorewarnings</b></code></a></dt>
+
+<dd>Specifies to print any warnings about unresolved references and other
+    important problems, but to continue processing in any case. Ignoring
+    warnings can be dangerous. For instance, if the unresolved classes or
+    class members are indeed required for processing, the processed code will
+    not function properly. <i>Only use this option if you know what you're
+    doing!</i></dd>
+
+<dt><a name="printconfiguration"><code><b>-printconfiguration</b></code></a>
+    [<a href="#filename"><i>filename</i></a>]</dt>
+
+<dd>Specifies to write out the entire configuration that has been parsed, with
+    included files and replaced variables. The structure is printed to the
+    standard output or to the given file. This can sometimes be useful for
+    debugging configurations, or for converting XML configurations into a more
+    readable format.</dd>
+
+<dt><a name="dump"><code><b>-dump</b></code></a>
+    [<a href="#filename"><i>filename</i></a>]</dt>
+
+<dd>Specifies to write out the internal structure of the class files, after
+    any processing. The structure is printed to the standard output or to the
+    given file. For example, you may want to <a
+    href="examples.html#structure">write out the contents of a given jar
+    file</a>, without processing it at all.</dd>
+
+</dl>
+<p>
+
+<a name="classpath">&nbsp;</a>
+<h2>Class Paths</h2>
+
+ProGuard accepts a generalization of class paths to specify input files and
+output files. A class path consists of entries, separated by the traditional
+path separator (e.g. '<b>:</b>' on Unix, or '<b>;</b>' on Windows platforms).
+The order of the entries determines their priorities, in case of duplicates.
+<p>
+Each input entry can be:
+<ul>
+<li>A class file or resource file.
+<li>A jar file, containing any of the above,
+<li>A war file, containing any of the above,
+<li>An ear file, containing any of the above,
+<li>A zip file, containing any of the above,
+<li>A directory (structure), containing any of the above.
+</ul>
+<p>
+The paths of directly specified class files and resource files is ignored, so
+class files should generally be part of a jar file, a war file, an ear file, a
+zip file, or a directory. In addition, the paths of class files should not have
+any additional directory prefixes inside the archives or directories.
+
+<p>
+Each output entry can be:
+<ul>
+<li>A jar file, in which all processed class files and resource files will be
+    collected.
+<li>A war file, in which any and all of the above will be collected,
+<li>An ear file, in which any and all of the above will be collected,
+<li>A zip file, in which any and all of the above will be collected,
+<li>A directory, in which any and all of the above will be collected.
+</ul>
+<p>
+When writing output entries, ProGuard will generally package the results in a
+sensible way, reconstructing the input entries as much as required. Writing
+everything to an output directory is the most straightforward option: the
+output directory will contain a complete reconstruction of the input entries.
+The packaging can be almost arbitrarily complex though: you could process an
+entire application, packaged in a zip file along with its documentation,
+writing it out as a zip file again. The Examples section shows a few ways
+to <a href="examples.html#restructuring">restructure output archives</a>.
+<p>
+Files and directories can be specified as discussed in the section on <a
+href="#filename">file names</a> below.
+<p>
+In addition, ProGuard provides the possibility to filter the class path
+entries and their contents, based on their full relative file names. Each
+class path entry can be followed by up to 5 types of <a
+href="#filefilters">file filters</a> between parentheses, separated by
+semi-colons:
+<ul>
+<li>A filter for all zip names that are encountered,
+<li>A filter for all ear names that are encountered,
+<li>A filter for all war names that are encountered,
+<li>A filter for all jar names that are encountered,
+<li>A filter for all class file names and resource file names that are
+    encountered.
+</ul>
+<p>
+If fewer than 5 filters are specified, they are assumed to be the latter
+filters. Any empty filters are ignored. More formally, a filtered class path
+entry looks like this:
+<pre>
+<i>classpathentry</i><b>(</b>[[[[<i>zipfilter</i><b>;</b>]<i>earfilter</i><b>;</b>]<i>warfilter</i><b>;</b>]<i>jarfilter</i><b>;</b>]<i>filefilter</i><b>)</b>
+</pre>
+<p>
+Square brackets "[]" mean that their contents are optional.
+<p>
+For example, "<code>rt.jar(java/**.class,javax/**.class)</code>" matches all
+class files in the <code>java</code> and <code>javax</code> directories inside
+the <code>rt</code> jar.
+<p>
+For example, "<code>input.jar(!**.gif,images/**)</code>" matches all files in
+the <code>images</code> directory inside the <code>input</code> jar, except
+gif files.
+<p>
+Note that the different filters are applied to all corresponding file types,
+irrespective of their nesting levels in the input; they are orthogonal.
+<p>
+For example,
+"<code>input.war(lib/**.jar,support/**.jar;**.class,**.gif)</code>" only
+considers jar files in the <code>lib</code> and <code>support</code>
+directories in the <code>input</code> war, not any other jar files. It then
+matches all class files and gif files that are encountered.
+<p>
+The filters allow for an almost infinite number of packaging and repackaging
+possibilities. The Examples section provides a few more examples
+for <a href="examples.html#filtering">filtering input and output</a>.
+<p>
+
+<a name="filename">&nbsp;</a>
+<h2>File Names</h2>
+
+ProGuard accepts absolute paths and relative paths for the various file names
+and directory names. A relative path is interpreted as follows:
+<ul>
+<li>relative to the base directory, if set, or otherwise
+<li>relative to the configuration file in which it is specified, if any, or
+    otherwise
+<li>relative to the working directory.
+</ul>
+<p>
+The names can contain Java system properties delimited by '<b>&lt;</b>' and
+'<b>&gt;</b>'. The system properties
+are automatically replaced by their respective values.
+<p>
+For example, <code>&lt;java.home&gt;/lib/rt.jar</code> will automatically be
+expanded to something like <code>/usr/local/java/jdk/jre/lib/rt.jar</code>.
+Similarly, <code>&lt;user.home&gt;</code> will be expanded to the user's home
+directory, and <code>&lt;user.dir&gt;</code> will be expanded to the current
+working directory.
+<p>
+Names with special characters like spaces and parentheses must be quoted with
+single or double quotes. Note that each file name in a list of names has to be
+quoted individually. Also note that the quotes themselves may need to be
+escaped when used on the command line, to avoid them being gobbled by the
+shell.
+<p>
+For example, on the command line, you could use an option like <code>'-injars
+"my program.jar":"/your directory/your program.jar"'</code>.
+<p>
+
+<a name="filefilters">&nbsp;</a>
+<h2>File Filters</h2>
+
+Like general <a href="#filters">filters</a>, a file filter is a
+comma-separated list of file names that can contain wildcards. Only files with
+matching file names are read (in the case of input jars), or written (in the
+case of output jars). The following wildcards are supported:
+
+<table cellspacing="10">
+<tr><td valign="top"><code><b>?</b></code></td>
+    <td>matches any single character in a file name.</td></tr>
+<tr><td valign="top"><code><b>*</b></code></td>
+    <td>matches any part of a filename not containing the directory
+        separator.</td></tr>
+<tr><td valign="top"><code><b>**</b></code></td>
+    <td>matches any part of a filename, possibly containing any number of
+        directory separators.</td></tr>
+</table>
+
+For example, "<code>java/**.class,javax/**.class</code>" matches all
+class files in the <code>java</code> and <code>javax</code>.
+<p>
+
+Furthermore, a file name can be preceded by an exclamation mark '<b>!</b>' to
+<i>exclude</i> the file name from further attempts to match with
+<i>subsequent</i> file names.
+<p>
+For example, "<code>!**.gif,images/**</code>" matches all files in the
+<code>images</code> directory, except gif files.
+<p>
+The Examples section provides a few more examples for <a
+href="examples.html#filtering">filtering input and output</a>.
+
+<a name="filters">&nbsp;</a>
+<h2>Filters</h2>
+
+ProGuard offers options with filters for many different aspects of the
+configuration: names of files, directories, classes, packages, attributes,
+optimizations, etc. 
+<p>
+A filter is a list of comma-separated names that can contain wildcards. Only
+names that match an item on the list pass the filter. The supported wildcards
+depend on the type of names for which the filter is being used, but the
+following wildcards are typical:
+
+<table cellspacing="10">
+<tr><td valign="top"><code><b>?</b></code></td>
+    <td>matches any single character in a name.</td></tr>
+<tr><td valign="top"><code><b>*</b></code></td>
+    <td>matches any part of a name not containing the package separator or
+        directory separator.</td></tr>
+<tr><td valign="top"><code><b>**</b></code></td>
+    <td>matches any part of a name, possibly containing any number of
+        package separators or directory separators.</td></tr>
+</table>
+
+For example, "<code>foo,*bar</code>" matches the name <code>foo</code> and
+all names ending with <code>bar</code>.
+<p>
+
+Furthermore, a name can be preceded by a negating exclamation mark '<b>!</b>'
+to <i>exclude</i> the name from further attempts to match
+with <i>subsequent</i> names. So, if a name matches an item in the filter, it
+is accepted or rejected right away, depending on whether the item has a
+negator. If the name doesn't match the item, it is tested against the next
+item, and so on. It if doesn't match any items, it is accepted or rejected,
+depending on the whether the last item has a negator or not.
+<p>
+For example, "<code>!foobar,*bar</code>" matches all names ending with
+<code>bar</code>, except <code>foobar</code>.
+<p>
+
+<a name="keepoverview">&nbsp;</a>
+<h2>Overview of <code>Keep</code> Options</h2>
+
+The various <code>-keep</code> options for shrinking and obfuscation may seem
+a bit confusing at first, but there's actually a pattern behind them. The
+following table summarizes how they are related:
+<p>
+
+<table cellpadding="5">
+
+<tr>
+<th>Keep</th>
+<td>From being removed or renamed</td>
+<td>From being renamed</td>
+</tr>
+
+<tr>
+<td>Classes and class members</td>
+<td bgcolor="#E0E0E0"><a href="#keep"><code>-keep</code></a></td>
+<td bgcolor="#E0E0E0"><a href="#keepnames"><code>-keepnames</code></a></td>
+</tr>
+
+<tr>
+<td>Class members only</td>
+<td bgcolor="#E0E0E0"><a href="#keepclassmembers"><code>-keepclassmembers</code></a></td>
+<td bgcolor="#E0E0E0"><a href="#keepclassmembernames"><code>-keepclassmembernames</code></a></td>
+</tr>
+
+<tr>
+<td>Classes and class members, if class members present</td>
+<td bgcolor="#E0E0E0"><a href="#keepclasseswithmembers"><code>-keepclasseswithmembers</code></a></td>
+<td bgcolor="#E0E0E0"><a href="#keepclasseswithmembernames"><code>-keepclasseswithmembernames</code></a></td>
+</tr>
+
+</table>
+<p>
+
+Each of these <code>-keep</code> options is of course followed by a
+<a href="#classspecification">specification</a> of the classes and class
+members (fields and methods) to which it should be applied.
+<p>
+If you're not sure which option you need, you should probably simply use
+<code>-keep</code>. It will make sure the specified classes and class members
+are not removed in the shrinking step, and not renamed in the obfuscation step.
+<p>
+<table>
+<tr><td valign="top">
+<img src="attention.gif" width="64" height="64"alt="attention">
+</td><td>
+Always remember:
+<ul>
+<li>Specifying a class without class members only preserves the class as an
+    entry point &mdash; any class members may then still be removed, optimized,
+    or obfuscated.</li>
+<li>Specifying a class member only preserves the class member as an entry
+    point &mdash; any associated code may still be optimized and adapted.</li>
+</ul>
+</td></tr>
+</table>
+<p>
+
+<a name="keepoptionmodifiers">&nbsp;</a>
+<h2>Keep Option Modifiers</h2>
+
+<dl>
+<dt><a name="allowshrinking"><code><b>allowshrinking</b></code></a></dt>
+
+<dd>Specifies that the entry points specified in the <a href="#keep">-keep</a>
+    option may be shrunk, even if they have to be preserved otherwise. That
+    is, the entry points may be removed in the shrinking step, but if they are
+    necessary after all, they may not be optimized or obfuscated.</dd>
+
+<dt><a name="allowoptimization"><code><b>allowoptimization</b></code></a></dt>
+
+<dd>Specifies that the entry points specified in the <a href="#keep">-keep</a>
+    option may be optimized, even if they have to be preserved otherwise. That
+    is, the entry points may be altered in the optimization step, but they may
+    not be removed or obfuscated. This modifier is only useful for achieving
+    unusual requirements.</dd>
+
+<dt><a name="allowobfuscation"><code><b>allowobfuscation</b></code></a></dt>
+
+<dd>Specifies that the entry points specified in the <a href="#keep">-keep</a>
+    option may be obfuscated, even if they have to be preserved otherwise. That
+    is, the entry points may be renamed in the obfuscation step, but they may
+    not be removed or optimized. This modifier is only useful for achieving
+    unusual requirements.</dd>
+
+</dl>
+<p>
+
+<a name="classspecification">&nbsp;</a>
+<h2>Class Specifications</h2>
+
+A class specification is a template of classes and class members (fields and
+methods). It is used in the various <code>-keep</code> options and in the
+<code>-assumenosideeffects</code> option. The corresponding option is only
+applied to classes and class members that match the template.
+<p>
+The template was designed to look very Java-like, with some extensions for
+wildcards. To get a feel for the syntax, you should probably look at the <a
+href="examples.html">examples</a>, but this is an attempt at a complete formal
+definition:
+<p>
+
+<pre>
+[<b>@</b><i>annotationtype</i>] [[<b>!</b>]<b>public</b>|<b>final</b>|<b>abstract</b>|<b>@</b> ...] [<b>!</b>]<b>interface</b>|<b>class</b>|<b>enum</b> <i>classname</i>
+    [<b>extends</b>|<b>implements</b> [<b>@</b><i>annotationtype</i>] <i>classname</i>]
+[<b>{</b>
+    [<b>@</b><i>annotationtype</i>] [[<b>!</b>]<b>public</b>|<b>private</b>|<b>protected</b>|<b>static</b>|<b>volatile</b>|<b>transient</b> ...] <b>&lt;fields&gt;</b> |
+                                                                      (<i>fieldtype fieldname</i>)<b>;</b>
+    [<b>@</b><i>annotationtype</i>] [[<b>!</b>]<b>public</b>|<b>private</b>|<b>protected</b>|<b>static</b>|<b>synchronized</b>|<b>native</b>|<b>abstract</b>|<b>strictfp</b> ...] <b>&lt;methods&gt;</b> |
+                                                                                           <b>&lt;init&gt;(</b><i>argumenttype,...</i><b>)</b> |
+                                                                                           <i>classname</i><b>(</b><i>argumenttype,...</i><b>)</b> |
+                                                                                           (<i>returntype methodname</i><b>(</b><i>argumenttype,...</i><b>)</b>)<b>;</b>
+    [<b>@</b><i>annotationtype</i>] [[<b>!</b>]<b>public</b>|<b>private</b>|<b>protected</b>|<b>static</b> ... ] <b>*;</b>
+    ...
+<b>}</b>]
+</pre>
+<p>
+Square brackets "[]" mean that their contents are optional. Ellipsis dots
+"..." mean that any number of the preceding items may be specified. A vertical
+bar "|" delimits two alternatives. Non-bold parentheses "()" just group parts
+of the specification that belong together. The indentation tries to clarify
+the intended meaning, but white-space is irrelevant in actual configuration
+files.
+<p>
+<ul>
+
+<li>The <code><b>class</b></code> keyword refers to any interface or class.
+    The <code><b>interface</b></code> keyword restricts matches to interface
+    classes. The <code><b>enum</b></code> keyword restricts matches to
+    enumeration classes. Preceding the <code><b>interface</b></code> or
+    <code><b>enum</b></code> keywords by a <code><b>!</b></code> restricts
+    matches to classes that are not interfaces or enumerations, respectively.
+    <p>
+
+<li>Every <i>classname</i> must be fully qualified, e.g.
+    <code>java.lang.String</code>. Class names may be specified as regular
+    expressions containing the following wildcards:
+
+<table cellspacing="10">
+
+<tr><td valign="top"><code><b>?</b></code></td>
+
+<td>matches any single character in a class name, but not the package
+    separator. For example, "<code>mypackage.Test?</code>" matches
+    "<code>mypackage.Test1</code>" and "<code>mypackage.Test2</code>", but not
+    "<code>mypackage.Test12</code>".</td></tr>
+
+<tr><td valign="top"><code><b>*</b></code></td>
+
+<td>matches any part of a class name not containing the package separator. For
+    example, "<code>mypackage.*Test*</code>" matches
+    "<code>mypackage.Test</code>" and
+    "<code>mypackage.YourTestApplication</code>", but not
+    "<code>mypackage.mysubpackage.MyTest</code>". Or, more generally,
+    "<code>mypackage.*</code>" matches all classes in
+    "<code>mypackage</code>", but not in its subpackages.</td></tr>
+
+<tr><td valign="top"><code><b>**</b></code></td>
+
+<td>matches any part of a class name, possibly containing any number of
+    package separators. For example, "<code>**.Test</code>" matches all
+    <code>Test</code> classes in all packages except the root package. Or,
+    "<code>mypackage.**</code>" matches all classes in
+    "<code>mypackage</code>" and in its subpackages.</td></tr>
+
+</table>
+
+    For additional flexibility, class names can actually be comma-separated
+    lists of class names, with optional <code><b>!</b></code> negators, just
+    like file name filters. This notation doesn't look very Java-like, so it
+    should be used with moderation.
+    <p>
+    For convenience and for backward compatibility, the class name
+    <code><b>*</b></code> refers to any class, irrespective of its package.
+    <p>
+
+<li>The <code><b>extends</b></code> and <code><b>implements</b></code>
+    specifications are typically used to restrict classes with wildcards. They
+    are currently equivalent, specifying that only classes extending or
+    implementing the given class qualify. Note that the given class itself is
+    not included in this set. If required, it should be specified in a
+    separate option.
+    <p>
+
+<li>The <code><b>@</b></code> specifications can be used to restrict classes
+    and class members to the ones that are annotated with the specified
+    annotation types. An <i>annotationtype</i> is specified just like a
+    <i>classname</i>.
+    <p>
+
+<li>Fields and methods are specified much like in Java, except that method
+    argument lists don't contain argument names (just like in other tools
+    like <code>javadoc</code> and <code>javap</code>). The specifications can
+    also contain the following catch-all wildcards:
+
+<table cellspacing="10">
+
+<tr><td valign="top"><code><b>&lt;init&gt;</b></code></td>
+<td>matches any constructor.</td></tr>
+
+<tr><td valign="top"><code><b>&lt;fields&gt;</b></code></td>
+<td>matches any field.</td></tr>
+
+<tr><td valign="top"><code><b>&lt;methods&gt;</b></code></td>
+<td>matches any method.</td></tr>
+
+<tr><td valign="top"><code><b>*</b></code></td>
+<td>matches any field or method.</td></tr>
+
+</table>
+
+    Note that the above wildcards don't have return types. Only the
+    <code><b>&lt;init&gt;</b></code> wildcard has an argument list.
+    <p>
+
+    Fields and methods may also be specified using regular expressions. Names
+    can contain the following wildcards:
+
+<table cellspacing="10">
+<tr><td valign="top"><code><b>?</b></code></td>
+    <td>matches any single character in a method name.</td></tr>
+<tr><td valign="top"><code><b>*</b></code></td>
+    <td>matches any part of a method name.</td></tr>
+</table>
+
+    Types in descriptors can contain the following wildcards:
+
+<table cellspacing="10">
+<tr><td valign="top"><code><b>%</b></code></td>
+    <td>matches any primitive type ("<code>boolean</code>", "<code>int</code>",
+        etc, but not "<code>void</code>").</td></tr>
+<tr><td valign="top"><code><b>?</b></code></td>
+    <td>matches any single character in a class name.</td></tr>
+<tr><td valign="top"><code><b>*</b></code></td>
+    <td>matches any part of a class name not containing the package                     separator.</td></tr>
+<tr><td valign="top"><code><b>**</b></code></td>
+    <td>matches any part of a class name, possibly containing any number of
+        package separators.</td></tr>
+<tr><td valign="top"><code><b>***</b></code></td>
+    <td>matches any type (primitive or non-primitive, array or
+        non-array).</td></tr>
+<tr><td valign="top"><code><b>...</b></code></td>
+    <td>matches any number of arguments of any type.</td></tr>
+
+</table>
+
+    Note that the <code>?</code>, <code>*</code>, and <code>**</code>
+    wildcards will never match primitive types. Furthermore, only the
+    <code>***</code> wildcards will match array types of any dimension. For
+    example, "<code>** get*()</code>" matches "<code>java.lang.Object
+    getObject()</code>", but not "<code>float getFloat()</code>", nor
+    "<code>java.lang.Object[] getObjects()</code>".
+    <p>
+
+<li>Constructors can also be specified using their short class names (without
+    package) or using their full class names. As in the Java language, the
+    constructor specification has an argument list, but no return type.
+    <p>
+
+<li>The class access modifiers and class member access modifiers are typically
+    used to restrict wildcarded classes and class members. They specify that
+    the corresponding access flags have to be set for the member to match. A
+    preceding <code><b>!</b></code> specifies that the corresponding access
+    flag should be unset.
+    <p>
+    Combining multiple flags is allowed (e.g. <code>public static</code>). It
+    means that both access flags have to be set (e.g. <code>public</code>
+    <i>and</i> <code>static</code>), except when they are conflicting, in
+    which case at least one of them has to be set (e.g. at least
+    <code>public</code>
+    <i>or</i> <code>protected</code>).
+
+</ul>
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/manual/wtk.html b/docs/manual/wtk.html
new file mode 100644
index 0000000..b8c70b5
--- /dev/null
+++ b/docs/manual/wtk.html
@@ -0,0 +1,58 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard JME Wireless Toolkit Integration</title>
+</head>
+<body>
+
+<h2>JME Wireless Toolkit Integration</h2>
+
+<b>ProGuard</b> can be seamlessly integrated in Sun's Wireless Toolkit (WTK)
+for Java Micro Edition (JME).
+<p>
+
+The WTK already comes with a plug-in for ProGuard. Alternatively, ProGuard
+offers its own plug-in. This latter implementation is recommended, as it more
+up to date and it solves some problems. It is also somewhat more efficient,
+invoking the ProGuard engine directly, instead of writing out a configuration
+file and running ProGuard in a separate virtual machine.
+<p>
+
+In order to integrate this plug-in in the toolkit, you'll have to put the
+following lines in the file
+{j2mewtk.dir}<code>/wtklib/Linux/ktools.properties</code> or
+{j2mewtk.dir}<code>\wtklib\Windows\ktools.properties</code> (whichever is
+applicable).
+<p>
+
+<pre>
+obfuscator.runner.class.name: proguard.wtk.ProGuardObfuscator
+obfuscator.runner.classpath: /usr/local/java/proguard/lib/proguard.jar
+</pre>
+<p>
+
+Please make sure the class path is set correctly for your system.
+<p>
+
+Once ProGuard has been set up, you can apply it to your projects as part of
+the build process. The build process is started from the WTK menu bar:
+<p>
+<center><b>Project -> Package -> Create Obfuscated Package</b></center>
+<p>
+This option will compile, shrink, obfuscate, verify, and install your midlets
+for testing.
+<p>
+Should you ever need to customize your ProGuard configuration for the JME WTK,
+you can adapt the configuration file <code>proguard/wtk/default.pro</code>
+that's inside the <code>proguard.jar</code>.
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/quality.html b/docs/quality.html
new file mode 100644
index 0000000..29889e3
--- /dev/null
+++ b/docs/quality.html
@@ -0,0 +1,44 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Quality</title>
+</head>
+<body>
+
+<h2>Quality</h2>
+
+In order to get a feel for the quality of the <b>ProGuard</b> code, it is run
+through a regular automatic build process. This process produces numerous
+statistics on the source code, Java lint comments, Java documentation
+comments, the Java documentation itself, html lint comments on the Java
+documentation, spell checks, compilation results, an output jar, dead code
+analysis, a shrunk and obfuscated jar (using ProGuard itself!), test runs with
+memory and performance analyses, etc. Most analyses are produced using freely
+available tools. The results are poured into a convenient set of web pages
+using bash/sed/awk scripts. You're welcome to have a look at an uploaded
+snapshot of one of these runs:
+<p>
+<center><a href="http://proguard.sourceforge.net/quality/"
+target="other">Automated Code Analysis and Testing Pages</a> (at <a
+href="http://sourceforge.net/projects/proguard/"
+target="other">SourceForge</a>)</center>
+<p>
+The pages will appear in a new window, which you probably want to view at
+full-screen size.
+<p>
+
+In addition, <b>ProGuard</b> is tested against a constantly growing test suite
+(more than 500 tests at this time of writing). These small programs contain a
+wide range of common and uncommon constructs, in order to detect any regression
+problems as soon as possible.
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/results.html b/docs/results.html
new file mode 100644
index 0000000..480cd4f
--- /dev/null
+++ b/docs/results.html
@@ -0,0 +1,155 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Results</title>
+</head>
+<body>
+
+<h2>Results</h2>
+
+<b>ProGuard</b> successfully processes any Java bytecode, ranging from small
+midlets to entire run-time libraries. It primarily reduces the size of the
+processed code, with some potential increase in efficiency as an added bonus.
+The improvements obviously depend on the original code. The table below
+presents some typical results:
+<p>
+
+<table>
+
+<tr>
+<th width="28%">Input Program</th>
+<th width="12%">Original size</th>
+<th width="12%">After shrinking</th>
+<th width="12%">After optim.</th>
+<th width="12%">After obfusc.</th>
+<th width="12%">Total reduction</th>
+<th width="12%">Time</th>
+<th width="12%">Memory usage</th>
+</tr>
+
+<tr>
+<td><a target="other" href="http://java.sun.com/j2me/">Worm</a>, a sample midlet from Sun's JME</td>
+<td align="center">10.3 K</td>
+<td align="center">9.8 K</td>
+<td align="center">9.6 K</td>
+<td align="center">8.5 K</td>
+<td align="center">18 %</td>
+<td align="center">2 s</td>
+<td align="center">19 M</td>
+</tr>
+
+<tr>
+<td><a target="other" href="http://www.javadocking.com/">Javadocking</a>, a docking library</td>
+<td align="center">290 K</td>
+<td align="center">281 K</td>
+<td align="center">270 K</td>
+<td align="center">201 K</td>
+<td align="center">30 %</td>
+<td align="center">12 s</td>
+<td align="center">32 M</td>
+</tr>
+
+<tr>
+<td><b>ProGuard</b> itself</td>
+<td align="center">648 K</td>
+<td align="center">579 K</td>
+<td align="center">557 K</td>
+<td align="center">348 K</td>
+<td align="center">46 %</td>
+<td align="center">28 s</td>
+<td align="center">66 M</td>
+</tr>
+
+<tr>
+<td><a target="other" href="http://www.clarkware.com/software/JDepend.html">JDepend</a>, a Java quality metrics tool</td>
+<td align="center">57 K</td>
+<td align="center">36 K</td>
+<td align="center">33 K</td>
+<td align="center">28 K</td>
+<td align="center">51 %</td>
+<td align="center">6 s</td>
+<td align="center">24 M</td>
+</tr>
+
+<tr>
+<td><a target="other" href="http://java.sun.com/javase/6/">the run-time classes</a> from Sun's Java 6</td>
+<td align="center">53 M</td>
+<td align="center">23 M</td>
+<td align="center">22 M</td>
+<td align="center">18 M</td>
+<td align="center">66 %</td>
+<td align="center">16 min</td>
+<td align="center">270 M</td>
+</tr>
+
+<tr>
+<td><a target="other" href="http://jakarta.apache.org/tomcat/index.html">Tomcat</a>, the Apache servlet container</td>
+<td align="center">1.1 M</td>
+<td align="center">466 K</td>
+<td align="center">426 K</td>
+<td align="center">295 K</td>
+<td align="center">74 %</td>
+<td align="center">17 s</td>
+<td align="center">44 M</td>
+</tr>
+
+<tr>
+<td><a target="other" href="http://www.kclee.com/clemens/java/javancss/">JavaNCSS</a>, a Java source metrics tool</td>
+<td align="center">632 K</td>
+<td align="center">242 K</td>
+<td align="center">212 K</td>
+<td align="center">152 K</td>
+<td align="center">75 %</td>
+<td align="center">20 s</td>
+<td align="center">36 M</td>
+</tr>
+
+<tr>
+<td><a target="other" href="http://ant.apache.org/">Ant</a>, the Apache build tool</td>
+<td align="center">2.4 M</td>
+<td align="center">401 K</td>
+<td align="center">325 K</td>
+<td align="center">242 K</td>
+<td align="center">90 %</td>
+<td align="center">23 s</td>
+<td align="center">61 M</td>
+</tr>
+
+</table>
+<p>
+Results were measured with ProGuard 4.0 on a 2.6 GHz Pentium 4 with 512 MB
+of memory, using Sun JDK 1.5.0 in Fedora Core 3 Linux.
+<p>
+The program sizes include companion libraries. The shrinking step produces the
+best results for programs that use only small parts of their libraries. The
+obfuscation step can significantly shrink large programs even further, since
+the identifiers of their many internal references can be replaced by short
+identifiers.
+<p>
+The Java 6 run-time classes are the most complex example. The classes perform
+a lot of introspection, interacting with the native code of the virtual
+machine. The 1500+ lines of configuration were largely composed by automated
+analysis, complemented by a great deal of trial and error. The configuration
+is probably not complete, but the resulting library successfully serves as a
+run-time environment for running applications like ProGuard and the ProGuard
+GUI.
+<p>
+For small inputs, timings are governed by the reading and parsing of the jars.
+For large inputs, the optimization step becomes more important. For instance,
+processing the Java 6 run-time classes without optimization only takes 2
+minutes.
+<p>
+Memory usage (the amount of physical memory used by ProGuard while processing)
+is governed by the basic java virtual machine and by the total size of the
+library jars and program jars.
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+
+</body>
+</html>
diff --git a/docs/sanawarelogo.png b/docs/sanawarelogo.png
new file mode 100644
index 0000000..bf7218b
--- /dev/null
+++ b/docs/sanawarelogo.png
Binary files differ
diff --git a/docs/screenshot_console.gif b/docs/screenshot_console.gif
new file mode 100644
index 0000000..8aea61e
--- /dev/null
+++ b/docs/screenshot_console.gif
Binary files differ
diff --git a/docs/screenshot_console_small.gif b/docs/screenshot_console_small.gif
new file mode 100644
index 0000000..3f55f5b
--- /dev/null
+++ b/docs/screenshot_console_small.gif
Binary files differ
diff --git a/docs/screenshot_gui1.gif b/docs/screenshot_gui1.gif
new file mode 100644
index 0000000..233e180
--- /dev/null
+++ b/docs/screenshot_gui1.gif
Binary files differ
diff --git a/docs/screenshot_gui2.gif b/docs/screenshot_gui2.gif
new file mode 100644
index 0000000..aecdb35
--- /dev/null
+++ b/docs/screenshot_gui2.gif
Binary files differ
diff --git a/docs/screenshot_gui3.gif b/docs/screenshot_gui3.gif
new file mode 100644
index 0000000..17950ee
--- /dev/null
+++ b/docs/screenshot_gui3.gif
Binary files differ
diff --git a/docs/screenshot_gui4.gif b/docs/screenshot_gui4.gif
new file mode 100644
index 0000000..31e60fa
--- /dev/null
+++ b/docs/screenshot_gui4.gif
Binary files differ
diff --git a/docs/screenshot_gui5.gif b/docs/screenshot_gui5.gif
new file mode 100644
index 0000000..1313db2
--- /dev/null
+++ b/docs/screenshot_gui5.gif
Binary files differ
diff --git a/docs/screenshot_gui6.gif b/docs/screenshot_gui6.gif
new file mode 100644
index 0000000..c200b97
--- /dev/null
+++ b/docs/screenshot_gui6.gif
Binary files differ
diff --git a/docs/screenshot_gui7.gif b/docs/screenshot_gui7.gif
new file mode 100644
index 0000000..bc5d6ed
--- /dev/null
+++ b/docs/screenshot_gui7.gif
Binary files differ
diff --git a/docs/screenshot_gui8.gif b/docs/screenshot_gui8.gif
new file mode 100644
index 0000000..95a1a30
--- /dev/null
+++ b/docs/screenshot_gui8.gif
Binary files differ
diff --git a/docs/screenshots.html b/docs/screenshots.html
new file mode 100644
index 0000000..8718839
--- /dev/null
+++ b/docs/screenshots.html
@@ -0,0 +1,56 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Screenshots</title>
+</head>
+<body>
+
+<h2>Screenshots</h2>
+
+<table><tr><td>
+<a href="screenshot_gui1.gif" target="other">
+<img src="screenshots_gui_small.gif" width="320" height="223" align="right"
+  alt="GUI screenshot" usemap="#guimap"></a>
+
+<map id="guimap" name="guimap">
+<area shape="rect" coords="2,7,46,16"  alt="ProGuard"     href="screenshot_gui1.gif" target="other">
+<area shape="rect" coords="2,17,46,27" alt="Input/Output" href="screenshot_gui2.gif" target="other">
+<area shape="rect" coords="2,28,46,37" alt="Shrinking"    href="screenshot_gui3.gif" target="other">
+<area shape="rect" coords="2,38,46,48" alt="Optimization" href="screenshot_gui4.gif" target="other">
+<area shape="rect" coords="2,49,46,58" alt="Obfuscation"  href="screenshot_gui5.gif" target="other">
+<area shape="rect" coords="2,59,46,69" alt="Information"  href="screenshot_gui6.gif" target="other">
+<area shape="rect" coords="2,70,46,79" alt="Process"      href="screenshot_gui7.gif" target="other">
+<area shape="rect" coords="2,80,46,90" alt="ReTrace"      href="screenshot_gui8.gif" target="other">
+</map>
+
+The graphical user interface to <b>ProGuard</b> works like a wizard. It allows
+you to browse through the presented tabs and fill them out.
+<p>
+You can click on the small tab buttons to see the full-size versions of the
+tabs.
+
+</td></tr>
+<tr><td>
+<a href="screenshot_console.gif" target="other">
+<img src="screenshot_console_small.gif" width="320" height="268" align="left"
+  alt="Console screenshot"></a>
+
+Of course, real developers don't need all this point-and-click fluff. They
+write a short configuration file using their favorite text editor and invoke
+<b>ProGuard</b> from the command-line.
+<p>
+You can click on the image to see the full-size version.
+
+</td></tr></table>
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+</body>
+</html>
diff --git a/docs/screenshots_gui_small.gif b/docs/screenshots_gui_small.gif
new file mode 100644
index 0000000..bc32ccb
--- /dev/null
+++ b/docs/screenshots_gui_small.gif
Binary files differ
diff --git a/docs/sections.html b/docs/sections.html
new file mode 100644
index 0000000..d955d33
--- /dev/null
+++ b/docs/sections.html
@@ -0,0 +1,62 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-script-type" content="text/javascript">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>Sections</title>
+</head>
+<body class="navigation">
+
+<ul class="navigation">
+<li><a target="main" href="main.html">Main</a></li>
+<li><a target="main" href="results.html">Results</a></li>
+<li><a target="main" href="FAQ.html">FAQ</a></li>
+<li><a               href="manual/sections.html">Manual</a></li>
+<li><a target="main" href="quality.html">Quality</a></li>
+<li><a target="main" href="screenshots.html">Screenshots</a></li>
+<li><a target="main" href="testimonials.html">Testimonials</a></li>
+<li><a target="main" href="license.html">License</a></li>
+<li><a target="main" href="downloads.html">Downloads</a></li>
+<li><a target="main" href="feedback.html">Feedback</a></li>
+<li><a target="main" href="acknowledgements.html">Ack'ments</a></li>
+<li><a target="main" href="alternatives.html">Alternatives</a></li>
+</ul>
+
+<p>
+<center>
+<small>With support of</small>
+<p>
+
+<a href="http://sourceforge.net/projects/proguard/" target="other">
+
+<script type="text/javascript" language="JavaScript">
+<!--
+document.write("<img src=\"");
+document.write(document.location.hostname == "proguard.sourceforge.net" ?
+  "http://sourceforge.net/sflogo.php?group_id=54750&amp;type=1" :
+  "sflogo.png");
+document.write("\" width=\"88\" height=\"31\" alt=\"SourceForge\">");
+if (document.location.hostname == "proguard.sourceforge.net") {
+  document.write("<script type=\"text/javascript\" src=\"http://sourceforge.net/apps/piwik/proguard/piwik.js\"></scri"+"pt>");
+  document.write("<script type=\"text/javascript\">piwik_log(\"\", 1, \"http://sourceforge.net/apps/piwik/proguard/piwik.php\");</scri"+"pt>");
+}
+//-->
+</script>
+<noscript>
+<img src="sflogo.png" width="88" height="31" alt="SourceForge">
+</noscript>
+
+</a>
+
+<p>
+<a href="http://www.luciad.com/" target="other">
+<img src="luciadlogo.png" width="88" height="24" alt="Luciad"></a>
+<p>
+<a href="http://www.javadocking.com/" target="other">
+<img src="sanawarelogo.png" width="88" height="24" alt="Sanaware"></a>
+</center>
+
+</body>
+</html>
diff --git a/docs/sflogo.png b/docs/sflogo.png
new file mode 100644
index 0000000..142a6f9
--- /dev/null
+++ b/docs/sflogo.png
Binary files differ
diff --git a/docs/steel.gif b/docs/steel.gif
new file mode 100644
index 0000000..307b57a
--- /dev/null
+++ b/docs/steel.gif
Binary files differ
diff --git a/docs/style.css b/docs/style.css
new file mode 100644
index 0000000..c9798db
--- /dev/null
+++ b/docs/style.css
@@ -0,0 +1,210 @@
+
+@charset "iso-8859-1";
+
+/* Global settings. */
+
+body
+{
+  background: #FFFFFF;
+}
+
+h1
+{
+  text-align: center;
+}
+
+h2
+{
+  text-align: center;
+}
+
+h3
+{
+  background: #EEEEFF;
+  padding: 10px;
+}
+
+h3 div
+{
+  font-weight: normal;
+  font-size: 80%;
+  float: right;
+}
+
+table
+{
+  width: 100%;
+}
+
+th
+{
+  padding: 4px;
+}
+
+tr.disappeared td
+{
+  background: #EEEEEE;
+}
+
+td
+{
+  background: #EEEEFF;
+  padding: 8px;
+}
+
+a
+{
+  text-decoration: none;
+}
+
+img
+{
+  border: none;
+}
+
+/* Settings for the introductory paragraph. */
+
+p.intro
+{
+  background: #EEEEFF;
+  padding: 10px;
+  border: solid #000000 1px
+}
+
+/* Settings for the title frame. */
+
+body.title
+{
+  margin: 0px;
+  padding: 0px;
+  background: #C0C0C0;
+}
+
+div.title
+{
+  height: 48px;
+  margin: 0px;
+  padding: 0px;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #FFFFFF #808080 #808080 #FFFFFF;
+  background: url("steel.gif"); 
+}
+
+div.title h1
+{
+  margin: 0px;
+  padding: 0px;
+  padding-top: 8px;
+  padding-left: 40%;
+  float: left;
+}
+
+div.title div
+{
+  margin: 0px;
+  padding: 0px;
+  padding-top: 12px;
+  padding-right: 20px;
+  float: right;
+}
+
+/* Settings for the section frames. */
+
+body.navigation
+{
+  margin: 0px;
+  padding: 0px;
+}
+
+ul.navigation
+{
+  margin: 0px;
+  padding: 0px;
+  list-style: none;
+  text-align: center;
+  background: url("steel.gif");
+}
+
+ul.navigation li
+{
+  margin: 0px;
+  padding: 0px;
+  border-width: 1px;
+  border-style: solid;
+  border-color: #FFFFFF #808080 #808080 #FFFFFF;
+  color: #000000;
+  font-weight: bold;
+}
+
+ul.navigation li.title
+{
+  margin: 0px;
+  padding: 4px 10px;
+  background: #E0E0E0;
+}
+
+ul.navigation li a
+{
+  margin: 0px;
+  padding: 6px 0px;
+  background: transparent;
+  color: #000000;
+  text-decoration: none;
+  display: block;
+}
+
+ul.navigation li a:hover,
+ul.navigation li a:focus
+{
+  background: #FFFFFF;
+}
+
+/* Settings for the yellow note tables. */
+
+table.note
+{
+  width: 408px;
+  border: none;
+  border-spacing: 0px;
+}
+
+td.shadow8
+{
+  width: 8px;
+  padding: 0px;
+  margin: 0px;
+  vertical-align: bottom;
+  background: transparent;
+}
+
+td.shadow400
+{
+  width: 400px;
+  padding: 0px;
+  margin: 0px;
+  text-align: right;
+  background: transparent;
+}
+
+td.note
+{
+  width: 380px;
+  background: #FFFFC0;
+  padding: 0px;
+  margin: 0px;
+}
+
+p.note
+{
+  padding: 0px;
+  margin: 0px 10px;
+  text-align: center;
+}
+
+p.author
+{
+  padding: 0px;
+  margin: 0px 10px;
+  text-align: right;
+}
diff --git a/docs/testimonials.html b/docs/testimonials.html
new file mode 100644
index 0000000..6971617
--- /dev/null
+++ b/docs/testimonials.html
@@ -0,0 +1,122 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard Testimonials</title>
+</head>
+<body>
+
+<h2>Testimonials</h2>
+
+And now for some shameless self-glorification and name-dropping...
+<p>
+<b>ProGuard</b> is probably the most popular java shrinker, optimizer, and
+obfuscator world-wide. It is being used by developers at companies and
+organizations like Sun, IBM, HP, Siemens, Nokia, Google, and NATO. It is the
+default tool in many development environments like Sun's Wireless Toolkit,
+Netbeans, EclipseME, and more. Although the quotes below probably don't
+represent official views of any kind, encouragements like these do keep me
+happy.
+<p>
+
+<center><table class="note">
+<tr><td class="note"><p class="note"><cite>
+ProGuard is <b>the</b> ultimate java obfuscator!
+</cite></p>
+<p class="author">P.S, IBM</p></td>
+<td class="shadow8"><img src="drop2.gif" width="8" height="100"></td></tr><tr>
+<td class="shadow400"><img src="drop1.gif" width="400" height="8"></td>
+<td class="shadow8"><img src="drop3.gif" width="8" height="8"></td>
+</tr></table></center>
+<p>
+
+Also:
+<center><table class="note">
+<tr><td class="note"><p class="note"><cite>
+ProGuard is pure quality - powerful and trouble-free.
+</cite></p>
+<p class="author">M.B., Statestep</p></td>
+<td class="shadow8"><img src="drop2.gif" width="8" height="100"></td></tr><tr>
+<td class="shadow400"><img src="drop1.gif" width="400" height="8"></td>
+<td class="shadow8"><img src="drop3.gif" width="8" height="8"></td>
+</tr></table></center>
+<p>
+
+And:
+<center><table class="note">
+<tr><td class="note"><p class="note"><cite>
+It is the simplest and most robust obfuscator we have ever used.
+</cite></p>
+<p class="author">I.I., Hewlett-Packard</p></td>
+<td class="shadow8"><img src="drop2.gif" width="8" height="100"></td></tr><tr>
+<td class="shadow400"><img src="drop1.gif" width="400" height="8"></td>
+<td class="shadow8"><img src="drop3.gif" width="8" height="8"></td>
+</tr></table></center>
+<p>
+
+And indeed:
+<center><table class="note">
+<tr><td class="note"><p class="note"><cite>
+ProGuard rules. Much easier to use than the commercial alternatives.
+</cite></p>
+<p class="author">B.G., Quiotix Corp.</p></td>
+<td class="shadow8"><img src="drop2.gif" width="8" height="100"></td></tr><tr>
+<td class="shadow400"><img src="drop1.gif" width="400" height="8"></td>
+<td class="shadow8"><img src="drop3.gif" width="8" height="8"></td>
+</tr></table></center>
+<p>
+
+Straight from <b>ProGuard</b>'s open discussion forum:
+<p>
+<center><table class="note">
+<tr><td class="note"><p class="note"><cite>
+After searching for, trying to trial, and futzing with numerous other
+obfuscators and shrinkers, ProGuard stands out as the simplest, most robust,
+and accurate shrinker of them all.
+</cite></p>
+<p class="author">D.J., Joot</p></td>
+<td class="shadow8"><img src="drop2.gif" width="8" height="100"></td></tr><tr>
+<td class="shadow400"><img src="drop1.gif" width="400" height="8"></td>
+<td class="shadow8"><img src="drop3.gif" width="8" height="8"></td>
+</tr></table></center>
+<p>
+
+From the article "Obfuscating MIDlet Suites with ProGuard" at <a
+target="other" href="http://developers.sun.com/">developers.sun.com</a>:
+<p>
+<center><table class="note">
+<tr><td class="note"><p class="note"><cite>
+Its friendly license, attractive price tag, compelling performance, and
+powerful configuration options make it an excellent addition to your MIDlet
+development toolbox.
+</cite></p>
+<p class="author">J.K., Sun</p></td>
+<td class="shadow8"><img src="drop2.gif" width="8" height="100"></td></tr><tr>
+<td class="shadow400"><img src="drop1.gif" width="400" height="8"></td>
+<td class="shadow8"><img src="drop3.gif" width="8" height="8"></td>
+</tr></table></center>
+<p>
+
+And, of course, the price is stunning:
+<p>
+<center><table class="note">
+<tr><td class="note"><p class="note"><cite>
+You could've been rich.
+</cite></p>
+<p class="author">My mother</p></td>
+<td class="shadow8"><img src="drop2.gif" width="8" height="100"></td></tr><tr>
+<td class="shadow400"><img src="drop1.gif" width="400" height="8"></td>
+<td class="shadow8"><img src="drop3.gif" width="8" height="8"></td>
+</tr></table></center>
+<p>
+
+<hr>
+<address>
+Copyright &copy; 2002-2009
+<a href="http://www.graphics.cornell.edu/~eric/">Eric Lafortune</a>.
+</address>
+
+</body>
+</html>
diff --git a/docs/title.gif b/docs/title.gif
new file mode 100644
index 0000000..5e6ca26
--- /dev/null
+++ b/docs/title.gif
Binary files differ
diff --git a/docs/title.html b/docs/title.html
new file mode 100644
index 0000000..33171b5
--- /dev/null
+++ b/docs/title.html
@@ -0,0 +1,17 @@
+<!doctype html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
+<meta http-equiv="content-style-type" content="text/css">
+<link rel="stylesheet" type="text/css" href="style.css">
+<title>ProGuard</title>
+</head>
+<body class="title">
+
+<div class="title">
+<h1><img src="title.gif" width="154" height="29" alt="ProGuard"></h1>
+<div>Version 4.4</div>
+</div>
+
+</body>
+</html>
diff --git a/examples/annotations/examples.pro b/examples/annotations/examples.pro
new file mode 100644
index 0000000..270092a
--- /dev/null
+++ b/examples/annotations/examples.pro
@@ -0,0 +1,61 @@
+#
+# This ProGuard configuration file illustrates how to use annotations for
+# specifying which classes and class members should be kept.
+# Usage:
+#     java -jar proguard.jar @examples.pro
+#
+
+# Specify the input, output, and library jars.
+# This is assuming the code has been compiled in the examples directory.
+
+#-injars  examples(*.class)
+-injars  classes(*.class)
+-outjars out
+
+-libraryjars <java.home>/lib/rt.jar
+
+# Some important configuration is based on the annotations in the code.
+# We have to specify what the annotations mean to ProGuard.
+
+-include lib/annotations.pro
+
+#
+# We can then still add any other options that might be useful.
+#
+
+# Print out a list of what we're preserving.
+
+-printseeds
+
+# Preserve all annotations themselves.
+
+-keepattributes *Annotation*
+
+# Preserve all native method names and the names of their classes.
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# Preserve the special static methods that are required in all enumeration
+# classes.
+
+-keepclassmembers class * extends java.lang.Enum {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+# Explicitly preserve all serialization members. The Serializable interface
+# is only a marker interface, so it wouldn't save them.
+# You can comment this out if your application doesn't use serialization.
+# If your code contains serializable classes that have to be backward 
+# compatible, please refer to the manual.
+
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    static final java.io.ObjectStreamField[] serialPersistentFields;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
diff --git a/examples/annotations/examples/Applet.java b/examples/annotations/examples/Applet.java
new file mode 100644
index 0000000..8a5874b
--- /dev/null
+++ b/examples/annotations/examples/Applet.java
@@ -0,0 +1,22 @@
+import proguard.annotation.*;
+
+/**
+ * This applet illustrates the use of annotations for configuring ProGuard.
+ *
+ * You can compile it with:
+ *     javac -classpath ../lib/annotations.jar Applet.java
+ * You can then process it with:
+ *     java -jar ../../../lib/proguard.jar @ ../examples.pro
+ *
+ * The annotation will preserve the class and its essential methods.
+ */
+@Keep
+public class Applet extends java.applet.Applet
+{
+    // Implementations for Applet.
+
+    public void init()
+    {
+        // ...
+    }
+}
diff --git a/examples/annotations/examples/Application.java b/examples/annotations/examples/Application.java
new file mode 100644
index 0000000..f8d5060
--- /dev/null
+++ b/examples/annotations/examples/Application.java
@@ -0,0 +1,20 @@
+import proguard.annotation.KeepApplication;
+
+/**
+ * This application illustrates the use of annotations for configuring ProGuard.
+ *
+ * You can compile it with:
+ *     javac -classpath ../lib/annotations.jar Application.java
+ * You can then process it with:
+ *     java -jar ../../../lib/proguard.jar @ ../examples.pro
+ *
+ * The annotation will preserve the class and its main method.
+ */
+@KeepApplication
+public class Application
+{
+    public static void main(String[] args)
+    {
+        System.out.println("The answer is 42");
+    }
+}
diff --git a/examples/annotations/examples/Bean.java b/examples/annotations/examples/Bean.java
new file mode 100644
index 0000000..0544bf3
--- /dev/null
+++ b/examples/annotations/examples/Bean.java
@@ -0,0 +1,56 @@
+import proguard.annotation.*;
+
+/**
+ * This bean illustrates the use of annotations for configuring ProGuard.
+ *
+ * You can compile it with:
+ *     javac -classpath ../lib/annotations.jar Bean.java
+ * You can then process it with:
+ *     java -jar ../../../lib/proguard.jar @ ../examples.pro
+ *
+ * The annotations will preserve the class and its public getters and setters.
+ */
+@Keep
+@KeepPublicGettersSetters
+public class Bean
+{
+    public boolean booleanProperty;
+    public int     intProperty;
+    public String  stringProperty;
+
+
+    public boolean isBooleanProperty()
+    {
+        return booleanProperty;
+    }
+
+
+    public void setBooleanProperty(boolean booleanProperty)
+    {
+        this.booleanProperty = booleanProperty;
+    }
+
+
+    public int getIntProperty()
+    {
+        return intProperty;
+    }
+
+
+    public void setIntProperty(int intProperty)
+    {
+        this.intProperty = intProperty;
+    }
+
+
+    public String getStringProperty()
+    {
+        return stringProperty;
+    }
+
+
+    public void setStringProperty(String stringProperty)
+    {
+        this.stringProperty = stringProperty;
+    }
+}
diff --git a/examples/annotations/examples/NativeCallBack.java b/examples/annotations/examples/NativeCallBack.java
new file mode 100644
index 0000000..2c72f7b
--- /dev/null
+++ b/examples/annotations/examples/NativeCallBack.java
@@ -0,0 +1,44 @@
+import proguard.annotation.*;
+
+/**
+ * This application illustrates the use of annotations for configuring ProGuard.
+ *
+ * You can compile it with:
+ *     javac -classpath ../lib/annotations.jar NativeCallBack.java
+ * You can then process it with:
+ *     java -jar ../../../lib/proguard.jar @ ../examples.pro
+ *
+ * The annotation will preserve the class and its main method.
+ */
+@KeepApplication
+public class NativeCallBack
+{
+    /**
+     * Suppose this is a native method that computes an answer.
+     *
+     * The -keep option regular ProGuard configuration will make sure it is
+     * not renamed when processing this code.
+     */
+    public native int computeAnswer();
+
+
+    /**
+     * Suppose this method is called back from the above native method.
+     *
+     * ProGuard would remove it, because it is not referenced from java.
+     * The annotation will make sure it is preserved anyhow.
+     */
+    @Keep
+    public int getAnswer()
+    {
+        return 42;
+    }
+
+
+    public static void main(String[] args)
+    {
+        int answer = new NativeCallBack().computeAnswer();
+
+        System.out.println("The answer is " + answer);
+    }
+}
diff --git a/examples/annotations/lib/annotations.jar b/examples/annotations/lib/annotations.jar
new file mode 100644
index 0000000..967d604
--- /dev/null
+++ b/examples/annotations/lib/annotations.jar
Binary files differ
diff --git a/examples/annotations/lib/annotations.pro b/examples/annotations/lib/annotations.pro
new file mode 100644
index 0000000..f704af2
--- /dev/null
+++ b/examples/annotations/lib/annotations.pro
@@ -0,0 +1,118 @@
+#
+# This ProGuard configuration file specifies how annotations can be used
+# to configure the processing of other code.
+# Usage:
+#     java -jar proguard.jar @annotations.pro -libraryjars annotations.jar ...
+#
+# Note that the other input/output options still have to be specified.
+# If you specify them in a separate file, you can simply include this file:
+#     -include annotations.pro
+#
+# You can add any other options that are required. For instance, if you are
+# processing a library, you can still include the options from library.pro.
+
+
+# The annotations are defined in the accompanying jar. For now, we'll start
+# with these. You can always define your own annotations, if necessary.
+-libraryjars annotations.jar
+
+
+# The following annotations can be specified with classes and with class
+# members.
+
+# @Keep specifies not to shrink, optimize, or obfuscate the annotated class
+# or class member as an entry point.
+
+-keep @proguard.annotation.Keep class *
+
+-keepclassmembers class * {
+    @proguard.annotation.Keep *;
+}
+
+
+# @KeepName specifies not to optimize or obfuscate the annotated class or
+# class member as an entry point.
+
+-keepnames @proguard.annotation.KeepName class *
+
+-keepclassmembernames class * {
+    @proguard.annotation.KeepName *;
+}
+
+
+# The following annotations can only be specified with classes.
+
+# @KeepImplementations and @KeepPublicImplementations specify to keep all,
+# resp. all public, implementations or extensions of the annotated class as
+# entry points. Note the extension of the java-like syntax, adding annotations
+# before the (wild-carded) interface name.
+
+-keep        class * implements @proguard.annotation.KeepImplementations       *
+-keep public class * implements @proguard.annotation.KeepPublicImplementations *
+
+# @KeepApplication specifies to keep the annotated class as an application,
+# together with its main method.
+
+-keepclasseswithmembers @proguard.annotation.KeepApplication public class * {
+    public static void main(java.lang.String[]);
+}
+
+# @KeepClassMembers, @KeepPublicClassMembers, and
+# @KeepPublicProtectedClassMembers specify to keep all, all public, resp.
+# all public or protected, class members of the annotated class from being
+# shrunk, optimized, or obfuscated as entry points.
+
+-keepclassmembers @proguard.annotation.KeepClassMembers class * {
+    *;
+}
+
+-keepclassmembers @proguard.annotation.KeepPublicClassMembers class * {
+    public *;
+}
+
+-keepclassmembers @proguard.annotation.KeepPublicProtectedClassMembers class * {
+    public protected *;
+}
+
+# @KeepClassMemberNames, @KeepPublicClassMemberNames, and
+# @KeepPublicProtectedClassMemberNames specify to keep all, all public, resp.
+# all public or protected, class members of the annotated class from being
+# optimized or obfuscated as entry points.
+
+-keepclassmembernames @proguard.annotation.KeepClassMemberNames class * {
+    *;
+}
+
+-keepclassmembernames @proguard.annotation.KeepPublicClassMemberNames class * {
+    public *;
+}
+
+-keepclassmembernames @proguard.annotation.KeepPublicProtectedClassMemberNames class * {
+    public protected *;
+}
+
+# @KeepGettersSetters and @KeepPublicGettersSetters specify to keep all, resp.
+# all public, getters and setters of the annotated class from being shrunk,
+# optimized, or obfuscated as entry points.
+
+-keepclassmembers @proguard.annotation.KeepGettersSetters class * {
+    void set*(***);
+    void set*(int, ***);
+
+    boolean is*();
+    boolean is*(int);
+
+    *** get*();
+    *** get*(int);
+}
+
+-keepclassmembers @proguard.annotation.KeepPublicGettersSetters class * {
+    public void set*(***);
+    public void set*(int, ***);
+
+    public boolean is*();
+    public boolean is*(int);
+
+    public *** get*();
+    public *** get*(int);
+}
diff --git a/examples/annotations/src/proguard/annotation/Keep.java b/examples/annotations/src/proguard/annotation/Keep.java
new file mode 100644
index 0000000..93a469f
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/Keep.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies not to optimize or obfuscate the annotated class or
+ * class member as an entry point.
+ */
+@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface Keep {}
diff --git a/examples/annotations/src/proguard/annotation/KeepApplication.java b/examples/annotations/src/proguard/annotation/KeepApplication.java
new file mode 100644
index 0000000..181f9b1
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepApplication.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep the annotated class as an application,
+ * together with its a main method.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepApplication {}
diff --git a/examples/annotations/src/proguard/annotation/KeepClassMemberNames.java b/examples/annotations/src/proguard/annotation/KeepClassMemberNames.java
new file mode 100644
index 0000000..b2f1df9
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepClassMemberNames.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all class members of the annotated class
+ * from being optimized or obfuscated as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepClassMemberNames {}
diff --git a/examples/annotations/src/proguard/annotation/KeepClassMembers.java b/examples/annotations/src/proguard/annotation/KeepClassMembers.java
new file mode 100644
index 0000000..7ed755b
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepClassMembers.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all class members of the annotated class
+ * from being shrunk, optimized, or obfuscated as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepClassMembers {}
diff --git a/examples/annotations/src/proguard/annotation/KeepGettersSetters.java b/examples/annotations/src/proguard/annotation/KeepGettersSetters.java
new file mode 100644
index 0000000..497dcb7
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepGettersSetters.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all getters and setters of the annotated
+ * class from being shrunk, optimized, or obfuscated as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepGettersSetters {}
diff --git a/examples/annotations/src/proguard/annotation/KeepImplementations.java b/examples/annotations/src/proguard/annotation/KeepImplementations.java
new file mode 100644
index 0000000..47406a3
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepImplementations.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all implementations or extensions of the
+ * annotated class as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepImplementations {}
diff --git a/examples/annotations/src/proguard/annotation/KeepName.java b/examples/annotations/src/proguard/annotation/KeepName.java
new file mode 100644
index 0000000..5dd3680
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepName.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies not to optimize or obfuscate the annotated class or
+ * class member as an entry point.
+ */
+@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepName {}
diff --git a/examples/annotations/src/proguard/annotation/KeepPublicClassMemberNames.java b/examples/annotations/src/proguard/annotation/KeepPublicClassMemberNames.java
new file mode 100644
index 0000000..f24b126
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepPublicClassMemberNames.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all public class members of the annotated
+ * class from being optimized or obfuscated as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepPublicClassMemberNames {}
diff --git a/examples/annotations/src/proguard/annotation/KeepPublicClassMembers.java b/examples/annotations/src/proguard/annotation/KeepPublicClassMembers.java
new file mode 100644
index 0000000..2be7fa4
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepPublicClassMembers.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all public class members of the annotated
+ * class from being shrunk, optimized, or obfuscated as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepPublicClassMembers {}
diff --git a/examples/annotations/src/proguard/annotation/KeepPublicGettersSetters.java b/examples/annotations/src/proguard/annotation/KeepPublicGettersSetters.java
new file mode 100644
index 0000000..6028ba9
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepPublicGettersSetters.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all public getters and setters of the
+ * annotated class from being shrunk, optimized, or obfuscated as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepPublicGettersSetters {}
diff --git a/examples/annotations/src/proguard/annotation/KeepPublicImplementations.java b/examples/annotations/src/proguard/annotation/KeepPublicImplementations.java
new file mode 100644
index 0000000..52ee5b9
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepPublicImplementations.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all public implementations or extensions
+ * of the annotated class as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepPublicImplementations {}
diff --git a/examples/annotations/src/proguard/annotation/KeepPublicProtectedClassMemberNames.java b/examples/annotations/src/proguard/annotation/KeepPublicProtectedClassMemberNames.java
new file mode 100644
index 0000000..59f0004
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepPublicProtectedClassMemberNames.java
@@ -0,0 +1,18 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all public or protected class members of
+ * the annotated class from being optimized or obfuscated as entry points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepPublicProtectedClassMemberNames {}
diff --git a/examples/annotations/src/proguard/annotation/KeepPublicProtectedClassMembers.java b/examples/annotations/src/proguard/annotation/KeepPublicProtectedClassMembers.java
new file mode 100644
index 0000000..e918a9b
--- /dev/null
+++ b/examples/annotations/src/proguard/annotation/KeepPublicProtectedClassMembers.java
@@ -0,0 +1,19 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
+ */
+package proguard.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * This annotation specifies to keep all public or protected class members of
+ * the annotated class from being shrunk, optimized, or obfuscated as entry
+ * points.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.CLASS)
+@Documented
+public @interface KeepPublicProtectedClassMembers {}
diff --git a/examples/ant/applets.xml b/examples/ant/applets.xml
new file mode 100644
index 0000000..a55b1c3
--- /dev/null
+++ b/examples/ant/applets.xml
@@ -0,0 +1,81 @@
+<!-- This Ant build file illustrates how to process applets.
+     Usage: ant -f applets.xml -->
+
+<project name="Applets" default="obfuscate" basedir="../..">
+
+<target name="obfuscate">
+  <taskdef resource="proguard/ant/task.properties"
+           classpath="lib/proguard.jar" />
+
+  <proguard printseeds="on">
+
+    <!-- Specify the input jars, output jars, and library jars. -->
+
+    <injar  file="in.jar" />
+    <outjar file="out.jar" />
+
+    <libraryjar file="${java.home}/lib/rt.jar" />
+
+    <!-- Preserve all public applets. -->
+
+    <keep access="public" extends="java.applet.Applet" />
+
+    <!-- Preserve all annotations. -->
+
+    <keepattribute name="*Annotation*" />
+
+    <!-- Preserve all native method names and the names of their classes. -->
+
+    <keepclasseswithmembernames>
+      <method access="native" />
+    </keepclasseswithmembernames>
+    
+    <!-- Preserve the methods that are required in all enumeration classes. -->
+    
+    <keepclassmembers extends="java.lang.Enum">
+      <method access="public static"
+              type="**[]"
+              name="values"
+              parameters="" />
+      <method access="public static"
+              type="**"
+              name="valueOf"
+              parameters="java.lang.String" />
+    </keepclassmembers>
+
+    <!-- Explicitly preserve all serialization members. The Serializable
+         interface is only a marker interface, so it wouldn't save them.
+         You can comment this out if your library doesn't use serialization.
+         If your code contains serializable classes that have to be backward
+         compatible, please refer to the manual. -->
+
+    <keepclassmembers implements="java.io.Serializable">
+      <field  access    ="static final"
+              type      ="long"
+              name      ="serialVersionUID" />
+      <field  access    ="static final"
+              type      ="java.io.ObjectStreamField[]"
+              name      ="serialPersistentFields" />
+      <method access    ="private"
+              type      ="void"
+              name      ="writeObject"
+              parameters="java.io.ObjectOutputStream" />
+      <method access    ="private"
+              type      ="void"
+              name      ="readObject"
+              parameters="java.io.ObjectInputStream" />
+      <method type      ="java.lang.Object"
+              name      ="writeReplace"
+              parameters="" />
+      <method type      ="java.lang.Object"
+              name      ="readResolve"
+              parameters="" />
+    </keepclassmembers>
+
+    <!-- Your application may contain more items that need to be preserved;
+         typically classes that are dynamically created using Class.forName -->
+
+   </proguard>
+</target>
+
+</project>
diff --git a/examples/ant/applications1.xml b/examples/ant/applications1.xml
new file mode 100644
index 0000000..7a3d473
--- /dev/null
+++ b/examples/ant/applications1.xml
@@ -0,0 +1,15 @@
+<!-- This Ant build file illustrates how to process applications,
+     by including a ProGuard-style configuration file.
+     Usage: ant -f applications.xml -->
+
+<project name="Applications" default="obfuscate" basedir="../..">
+
+<target name="obfuscate">
+  <taskdef resource="proguard/ant/task.properties"
+           classpath="lib/proguard.jar" />
+
+  <proguard configuration="examples/applications.pro" />
+
+</target>
+
+</project>
diff --git a/examples/ant/applications2.xml b/examples/ant/applications2.xml
new file mode 100644
index 0000000..2e28f01
--- /dev/null
+++ b/examples/ant/applications2.xml
@@ -0,0 +1,68 @@
+<!-- This Ant build file illustrates how to process applications,
+     by including ProGuard-style configuration options.
+     Usage: ant -f applications.xml -->
+
+<project name="Applications" default="obfuscate" basedir="../..">
+
+<target name="obfuscate">
+  <taskdef resource="proguard/ant/task.properties"
+           classpath="lib/proguard.jar" />
+
+  <proguard>
+
+    <!-- Specify the input jars, output jars, and library jars. -->
+
+    -injars  in.jar
+    -outjars out.jar
+
+    -libraryjars ${java.home}/lib/rt.jar
+    <!-- -libraryjars junit.jar    -->
+    <!-- -libraryjars servlet.jar  -->
+    <!-- -libraryjars jai_core.jar -->
+    <!-- ...                       -->
+
+    <!-- Preserve all public applications. -->
+
+    -keepclasseswithmembers public class * {
+        public static void main(java.lang.String[]);
+    }
+
+    <!-- Preserve all annotations. -->
+
+    -keepattributes *Annotation*
+
+    <!-- Preserve all native method names and the names of their classes. -->
+
+    -keepclasseswithmembernames class * {
+        native &lt;methods&gt;;
+    }
+
+    <!-- Preserve the methods that are required in all enumeration classes. -->
+
+    -keepclassmembers class * extends java.lang.Enum {
+        public static **[] values();
+        public static ** valueOf(java.lang.String);
+    }
+
+    <!-- Explicitly preserve all serialization members. The Serializable
+         interface is only a marker interface, so it wouldn't save them.
+         You can comment this out if your library doesn't use serialization.
+         If your code contains serializable classes that have to be backward
+         compatible, please refer to the manual. -->
+
+    -keepclassmembers class * implements java.io.Serializable {
+        static final long serialVersionUID;
+        static final java.io.ObjectStreamField[] serialPersistentFields;
+        private void writeObject(java.io.ObjectOutputStream);
+        private void readObject(java.io.ObjectInputStream);
+        java.lang.Object writeReplace();
+        java.lang.Object readResolve();
+    }
+
+    <!-- Your application may contain more items that need to be preserved;
+         typically classes that are dynamically created using Class.forName -->
+
+  </proguard>
+</target>
+
+</project>
diff --git a/examples/ant/applications3.xml b/examples/ant/applications3.xml
new file mode 100644
index 0000000..d5501e0
--- /dev/null
+++ b/examples/ant/applications3.xml
@@ -0,0 +1,92 @@
+<!-- This Ant build file illustrates how to process applications,
+     using a full-blown XML configuration.
+     Usage: ant -f applications.xml -->
+
+<project name="Applications" default="obfuscate" basedir="../..">
+
+<target name="obfuscate">
+  <taskdef resource="proguard/ant/task.properties"
+           classpath="lib/proguard.jar" />
+
+  <proguard printseeds="on">
+
+    <!-- Specify the input jars, output jars, and library jars. -->
+
+    <injar  file="in.jar" />
+    <outjar file="out.jar" />
+
+    <libraryjar file="${java.home}/lib/rt.jar" />
+    <!-- libraryjar file="junit.jar"           / -->
+    <!-- libraryjar file="servlet.jar"         / -->
+    <!-- libraryjar file="jai_core.jar"        / -->
+    <!-- ...                                   / -->
+
+
+    <!-- Preserve all public applications. -->
+
+    <keepclasseswithmembers access="public">
+      <method access    ="public static"
+              type      ="void"
+              name      ="main"
+              parameters="java.lang.String[]" />
+    </keepclasseswithmembers>
+
+    <!-- Preserve all annotations. -->
+
+    <keepattribute name="*Annotation*" />
+
+    <!-- Preserve all native method names and the names of their classes. -->
+
+    <keepclasseswithmembernames>
+      <method access="native" />
+    </keepclasseswithmembernames>
+
+    <!-- Preserve the methods that are required in all enumeration classes. -->
+
+    <keepclassmembers extends="java.lang.Enum">
+      <method access="public static"
+              type="**[]"
+              name="values"
+              parameters="" />
+      <method access="public static"
+              type="**"
+              name="valueOf"
+              parameters="java.lang.String" />
+    </keepclassmembers>
+
+    <!-- Explicitly preserve all serialization members. The Serializable
+         interface is only a marker interface, so it wouldn't save them.
+         You can comment this out if your library doesn't use serialization.
+         If your code contains serializable classes that have to be backward
+         compatible, please refer to the manual. -->
+
+    <keepclassmembers implements="java.io.Serializable">
+      <field  access    ="static final"
+              type      ="long"
+              name      ="serialVersionUID" />
+      <field  access    ="static final"
+              type      ="java.io.ObjectStreamField[]"
+              name      ="serialPersistentFields" />
+      <method access    ="private"
+              type      ="void"
+              name      ="writeObject"
+              parameters="java.io.ObjectOutputStream" />
+      <method access    ="private"
+              type      ="void"
+              name      ="readObject"
+              parameters="java.io.ObjectInputStream" />
+      <method type      ="java.lang.Object"
+              name      ="writeReplace"
+              parameters="" />
+      <method type      ="java.lang.Object"
+              name      ="readResolve"
+              parameters="" />
+    </keepclassmembers>
+
+    <!-- Your application may contain more items that need to be preserved;
+         typically classes that are dynamically created using Class.forName -->
+
+  </proguard>
+</target>
+
+</project>
diff --git a/examples/ant/library.xml b/examples/ant/library.xml
new file mode 100644
index 0000000..d87bd16
--- /dev/null
+++ b/examples/ant/library.xml
@@ -0,0 +1,102 @@
+<!-- This Ant build file illustrates how to process a program library,
+     such that it remains usable as a library.
+     Usage: ant -f library.xml -->
+
+<project name="Library" default="obfuscate" basedir="../..">
+
+<target name="obfuscate">
+  <taskdef resource="proguard/ant/task.properties"
+           classpath="lib/proguard.jar" />
+
+  <proguard printmapping="out.map"
+            renamesourcefileattribute="SourceFile">
+
+    <!-- Specify the input jars, output jars, and library jars. -->
+
+    <injar  file="library.jar" />
+    <outjar file="library_out.jar" />
+
+    <libraryjar file="${java.home}/lib/rt.jar" />
+
+    <!-- Keep some useful attributes. -->
+
+    <keepattribute name="InnerClasses" />
+    <keepattribute name="SourceFile" />
+    <keepattribute name="LineNumberTable" />
+    <keepattribute name="Deprecated" />
+    <keepattribute name="*Annotation*" />
+
+    <!-- Preserve all public classes, and their public and protected fields
+        and methods. -->
+
+    <keep access="public">
+      <field  access="public protected" />
+      <method access="public protected" />
+    </keep>
+
+    <!-- Preserve all .class method names. -->
+
+    <keepclassmembernames access="public">
+      <method type      ="java.lang.Class"
+              name      ="class$"
+              parameters="java.lang.String" />
+      <method type      ="java.lang.Class"
+              name      ="class$"
+              parameters="java.lang.String,boolean" />
+    </keepclassmembernames>
+
+    <!-- Preserve all native method names and the names of their classes. -->
+
+    <keepclasseswithmembernames>
+      <method access="native" />
+    </keepclasseswithmembernames>
+    
+    <!-- Preserve the methods that are required in all enumeration classes. -->
+    
+    <keepclassmembers extends="java.lang.Enum">
+      <method access="public static"
+              type="**[]"
+              name="values"
+              parameters="" />
+      <method access="public static"
+              type="**"
+              name="valueOf"
+              parameters="java.lang.String" />
+    </keepclassmembers>
+
+    <!-- Explicitly preserve all serialization members. The Serializable
+         interface is only a marker interface, so it wouldn't save them.
+         You can comment this out if your library doesn't use serialization.
+         If your code contains serializable classes that have to be backward
+         compatible, please refer to the manual. -->
+
+    <keepclassmembers implements="java.io.Serializable">
+      <field  access    ="final"
+              type      ="long"
+              name      ="serialVersionUID" />
+      <field  access    ="static final"
+              type      ="java.io.ObjectStreamField[]"
+              name      ="serialPersistentFields" />
+      <method access    ="private"
+              type      ="void"
+              name      ="writeObject"
+              parameters="java.io.ObjectOutputStream" />
+      <method access    ="private"
+              type      ="void"
+              name      ="readObject"
+              parameters="java.io.ObjectInputStream" />
+      <method type      ="java.lang.Object"
+              name      ="writeReplace"
+              parameters="" />
+      <method type      ="java.lang.Object"
+              name      ="readResolve"
+              parameters="" />
+    </keepclassmembers>
+
+    <!-- Your application may contain more items that need to be preserved;
+      typically classes that are dynamically created using Class.forName -->
+
+  </proguard>
+</target>
+
+</project>
diff --git a/examples/ant/midlets.xml b/examples/ant/midlets.xml
new file mode 100644
index 0000000..b17d036
--- /dev/null
+++ b/examples/ant/midlets.xml
@@ -0,0 +1,45 @@
+<!-- This Ant build file illustrates how to process J2ME midlets.
+     Usage: ant -f midlets.xml -->
+
+<project name="Midlets" default="obfuscate" basedir="../..">
+
+<target name="obfuscate">
+  <taskdef resource="proguard/ant/task.properties"
+           classpath="lib/proguard.jar" />
+
+  <proguard microedition="on"
+            overloadaggressively="on"
+            repackageclasses=""
+            allowaccessmodification="on"
+            printseeds="on">
+
+            <!-- On Windows, you can't use mixed case class names,
+                 for the sake of the preverify tool.
+            usemixedcaseclassnames="false">
+            -->
+
+    <!-- Specify the input jars, output jars, and library jars. -->
+
+    <injar  file="in.jar" />
+    <outjar file="out.jar" />
+
+    <libraryjar file="/usr/local/java/wtk2.1/lib/midpapi20.jar" />
+    <libraryjar file="/usr/local/java/wtk2.1/lib/cldcapi11.jar" />
+
+    <!-- Preserve all public midlets. -->
+
+    <keep access="public" extends="javax.microedition.midlet.MIDlet" />
+
+    <!-- Preserve all native method names and the names of their classes. -->
+
+    <keepclasseswithmembernames>
+      <method access="native" />
+    </keepclasseswithmembernames>
+    
+    <!-- Your application may contain more items that need to be preserved;
+         typically classes that are dynamically created using Class.forName -->
+
+   </proguard>
+</target>
+
+</project>
diff --git a/examples/ant/proguard.xml b/examples/ant/proguard.xml
new file mode 100644
index 0000000..315e628
--- /dev/null
+++ b/examples/ant/proguard.xml
@@ -0,0 +1,73 @@
+<!-- This Ant build file illustrates how to process ProGuard (including its
+     main application, its GUI, its Ant task, and its WTK plugin), and the
+     ReTrace tool, all in one go.
+     Usage: ant -f proguard.xml -->
+
+<project name="ProGuard" default="obfuscate" basedir="../..">
+
+<target name="obfuscate">
+  <taskdef resource="proguard/ant/task.properties"
+           classpath="lib/proguard.jar" />
+
+  <proguard skipnonpubliclibraryclasses="off"
+            printmapping="proguard.map"
+            overloadaggressively="on"
+            repackageclasses="">
+
+    <!-- Specify the input jars, output jars, and library jars. -->
+
+    <injar  file="lib/proguard.jar" />
+    <injar  file="lib/proguardgui.jar" filter="!META-INF/**" />
+    <injar  file="lib/retrace.jar"     filter="!META-INF/**" />
+
+    <outjar file="examples/ant/proguard_out.jar" />
+
+    <libraryjar file="${java.home}/lib/rt.jar" />
+    <libraryjar file="/usr/local/java/ant1.7.0/lib/ant.jar" />
+    <libraryjar file="/usr/local/java/wtk2.1/wtklib/kenv.zip" />
+
+    <!-- Adapt the resource file names, based on the corresponding obfuscated
+         class names. -->
+
+    <adaptresourcefilenames    filter="**.properties,**.gif,**.jpg"  />
+    <adaptresourcefilecontents filter="proguard/ant/task.properties" />
+
+    <!-- The main seeds: ProGuard and its companion tool ReTrace. -->
+
+    <keep access="public" name="proguard.ProGuard">
+      <method access    ="public static"
+              type      ="void"
+              name      ="main"
+              parameters="java.lang.String[]" />
+    </keep>
+    <keep access="public" name="proguard.gui.ProGuardGUI">
+      <method access    ="public static"
+              type      ="void"
+              name      ="main"
+              parameters="java.lang.String[]" />
+    </keep>
+    <keep access="public" name="proguard.retrace.ReTrace">
+      <method access    ="public static"
+              type      ="void"
+              name      ="main"
+              parameters="java.lang.String[]" />
+    </keep>
+
+    <!-- If we have ant.jar, we can properly process the Ant task. -->
+
+    <keeppackagename name="proguard.ant" />
+    <keep name="proguard.ant.*" allowobfuscation="true" />
+    <keepclassmembers access="public" name="proguard.ant.*">
+      <constructor parameters="org.apache.tools.ant.Project" />
+      <method access="public" type="void" name="set*" parameters="***" />
+      <method access="public" type="void" name="add*" parameters="***" />
+    </keepclassmembers>
+
+    <!-- If we have kenv.zip, we can process the J2ME WTK plugin. -->
+
+    <keep access="public" name="proguard.wtk.ProGuardObfuscator" />
+
+  </proguard>
+</target>
+
+</project>
diff --git a/examples/ant/servlets.xml b/examples/ant/servlets.xml
new file mode 100644
index 0000000..83af0f2
--- /dev/null
+++ b/examples/ant/servlets.xml
@@ -0,0 +1,81 @@
+<!-- This Ant build file illustrates how to process servlets.
+     Usage: ant -f servlets.xml -->
+
+<project name="Servlets" default="obfuscate" basedir="../..">
+
+<target name="obfuscate">
+  <taskdef resource="proguard/ant/task.properties"
+           classpath="lib/proguard.jar" />
+
+  <proguard printseeds="on">
+
+    <!-- Specify the input jars, output jars, and library jars. -->
+
+    <injar  file="in.jar" />
+    <outjar file="out.jar" />
+
+    <libraryjar file="${java.home}/lib/rt.jar" />
+
+    <!-- Keep all public servlets. -->
+
+    <keep access="public" implements="javax.servlet.Servlet" />
+
+    <!-- Preserve all annotations. -->
+
+    <keepattribute name="*Annotation*" />
+
+    <!-- Preserve all native method names and the names of their classes. -->
+
+    <keepclasseswithmembernames>
+      <method access="native" />
+    </keepclasseswithmembernames>
+    
+    <!-- Preserve the methods that are required in all enumeration classes. -->
+    
+    <keepclassmembers extends="java.lang.Enum">
+      <method access="public static"
+              type="**[]"
+              name="values"
+              parameters="" />
+      <method access="public static"
+              type="**"
+              name="valueOf"
+              parameters="java.lang.String" />
+    </keepclassmembers>
+
+    <!-- Explicitly preserve all serialization members. The Serializable
+         interface is only a marker interface, so it wouldn't save them.
+         You can comment this out if your library doesn't use serialization.
+         If your code contains serializable classes that have to be backward
+         compatible, please refer to the manual. -->
+
+    <keepclassmembers implements="java.io.Serializable">
+      <field  access    ="static final"
+              type      ="long"
+              name      ="serialVersionUID" />
+      <field  access    ="static final"
+              type      ="java.io.ObjectStreamField[]"
+              name      ="serialPersistentFields" />
+      <method access    ="private"
+              type      ="void"
+              name      ="writeObject"
+              parameters="java.io.ObjectOutputStream" />
+      <method access    ="private"
+              type      ="void"
+              name      ="readObject"
+              parameters="java.io.ObjectInputStream" />
+      <method type      ="java.lang.Object"
+              name      ="writeReplace"
+              parameters="" />
+      <method type      ="java.lang.Object"
+              name      ="readResolve"
+              parameters="" />
+    </keepclassmembers>
+
+    <!-- Your application may contain more items that need to be preserved;
+         typically classes that are dynamically created using Class.forName -->
+ 
+  </proguard>
+</target>
+
+</project>
diff --git a/examples/applets.pro b/examples/applets.pro
new file mode 100644
index 0000000..ee90db0
--- /dev/null
+++ b/examples/applets.pro
@@ -0,0 +1,60 @@
+#
+# This ProGuard configuration file illustrates how to process applets.
+# Usage:
+#     java -jar proguard.jar @applets.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+
+-injars  in.jar
+-outjars out.jar
+
+-libraryjars <java.home>/lib/rt.jar
+
+# Preserve all public applets.
+
+-keep public class * extends java.applet.Applet
+
+# Print out a list of what we're preserving.
+
+-printseeds
+
+# Preserve all annotations.
+
+-keepattributes *Annotation*
+
+# Preserve all native method names and the names of their classes.
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# Preserve the special static methods that are required in all enumeration
+# classes.
+
+-keepclassmembers class * extends java.lang.Enum {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+# Explicitly preserve all serialization members. The Serializable interface
+# is only a marker interface, so it wouldn't save them.
+# You can comment this out if your library doesn't use serialization.
+# If your code contains serializable classes that have to be backward
+# compatible, please refer to the manual.
+
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    static final java.io.ObjectStreamField[] serialPersistentFields;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+
+# Your application may contain more items that need to be preserved;
+# typically classes that are dynamically created using Class.forName:
+
+# -keep public class mypackage.MyClass
+# -keep public interface mypackage.MyInterface
+# -keep public class * implements mypackage.MyInterface
diff --git a/examples/applications.pro b/examples/applications.pro
new file mode 100644
index 0000000..29d8e43
--- /dev/null
+++ b/examples/applications.pro
@@ -0,0 +1,66 @@
+#
+# This ProGuard configuration file illustrates how to process applications.
+# Usage:
+#     java -jar proguard.jar @applications.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+
+-injars  in.jar
+-outjars out.jar
+
+-libraryjars <java.home>/lib/rt.jar
+#-libraryjars junit.jar
+#-libraryjars servlet.jar
+#-libraryjars jai_core.jar
+#...
+
+# Preserve all public applications.
+
+-keepclasseswithmembers public class * {
+    public static void main(java.lang.String[]);
+}
+
+# Print out a list of what we're preserving.
+
+-printseeds
+
+# Preserve all annotations.
+
+-keepattributes *Annotation*
+
+# Preserve all native method names and the names of their classes.
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# Preserve the special static methods that are required in all enumeration
+# classes.
+
+-keepclassmembers class * extends java.lang.Enum {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+# Explicitly preserve all serialization members. The Serializable interface
+# is only a marker interface, so it wouldn't save them.
+# You can comment this out if your application doesn't use serialization.
+# If your code contains serializable classes that have to be backward 
+# compatible, please refer to the manual.
+
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    static final java.io.ObjectStreamField[] serialPersistentFields;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+
+# Your application may contain more items that need to be preserved; 
+# typically classes that are dynamically created using Class.forName:
+
+# -keep public class mypackage.MyClass
+# -keep public interface mypackage.MyInterface
+# -keep public class * implements mypackage.MyInterface
diff --git a/examples/dictionaries/compact.txt b/examples/dictionaries/compact.txt
new file mode 100644
index 0000000..5636a3e
--- /dev/null
+++ b/examples/dictionaries/compact.txt
@@ -0,0 +1,18 @@
+#
+# This obfuscation dictionary contains strings that are already present
+# in many class files. Since these strings can be shared, the resulting
+# obfuscated class files will generally be a little bit more compact.
+# Usage:
+#     java -jar proguard.jar ..... -obfuscationdictionary compact.txt
+#
+
+Code
+V
+I
+Z
+B
+C
+S
+F
+D
+L
diff --git a/examples/dictionaries/keywords.txt b/examples/dictionaries/keywords.txt
new file mode 100644
index 0000000..76f5a7b
--- /dev/null
+++ b/examples/dictionaries/keywords.txt
@@ -0,0 +1,58 @@
+#
+# This obfuscation dictionary contains reserved Java keywords. They can't
+# be used in Java source files, but they can be used in compiled class files.
+# Note that this hardly improves the obfuscation. Decent decompilers can
+# automatically replace reserved keywords, and the effect can fairly simply be
+# undone by obfuscating again with simpler names.
+# Usage:
+#     java -jar proguard.jar ..... -obfuscationdictionary keywords.txt
+#
+
+do
+if
+for
+int
+new
+try
+byte
+case
+char
+else
+goto
+long
+this
+void
+break
+catch
+class
+const
+final
+float
+short
+super
+throw
+while
+double
+import
+native
+public
+return
+static
+switch
+throws
+boolean
+default
+extends
+finally
+package
+private
+abstract
+continue
+strictfp
+volatile
+interface
+protected
+transient
+implements
+instanceof
+synchronized
diff --git a/examples/dictionaries/shakespeare.txt b/examples/dictionaries/shakespeare.txt
new file mode 100755
index 0000000..28b1cd8
--- /dev/null
+++ b/examples/dictionaries/shakespeare.txt
@@ -0,0 +1,23 @@
+#
+# This obfuscation dictionary contains quotes from plays by Shakespeare.
+# It illustrates that any text can be used, for whatever flippant reasons
+# one may have.
+# Usage:
+#     java -jar proguard.jar ..... -obfuscationdictionary shakespeare.txt
+#
+
+
+"This thing of darkness I acknowledge mine."
+
+  --From The Tempest (V, i, 275-276) 
+
+
+"Though this be madness, yet there is method in 't."
+
+  --From Hamlet (II, ii, 206)
+
+
+"What's in a name? That which we call a rose
+ By any other word would smell as sweet."
+
+  --From Romeo and Juliet (II, ii, 1-2)
diff --git a/examples/dictionaries/windows.txt b/examples/dictionaries/windows.txt
new file mode 100644
index 0000000..fd65dc9
--- /dev/null
+++ b/examples/dictionaries/windows.txt
@@ -0,0 +1,209 @@
+#
+# This obfuscation dictionary contains names that are not allowed as file names
+# in Windows, not even with extensions like .class or .java. They can however
+# be used without problems in jar archives, which just begs to apply them as
+# obfuscated class names. Trying to unpack the obfuscated archives in Windows
+# will probably generate some sparks.
+# Usage:
+#     java -jar proguard.jar ..... -classobfuscationdictionary windows.txt
+#                                  -packageobfuscationdictionary windows.txt
+#
+
+aux
+Aux
+aUx
+AUx
+auX
+AuX
+aUX
+AUX
+AUX
+con
+Con
+cOn
+COn
+coN
+CoN
+cON
+CON
+CON
+nul
+Nul
+nUl
+NUl
+nuL
+NuL
+nUL
+NUL
+NUL
+prn
+Prn
+pRn
+PRn
+prN
+PrN
+pRN
+PRN
+PRN
+com1
+Com1
+cOm1
+COm1
+coM1
+CoM1
+cOM1
+COM1
+COM1
+com2
+Com2
+cOm2
+COm2
+coM2
+CoM2
+cOM2
+COM2
+COM2
+com3
+Com3
+cOm3
+COm3
+coM3
+CoM3
+cOM3
+COM3
+COM3
+com4
+Com4
+cOm4
+COm4
+coM4
+CoM4
+cOM4
+COM4
+COM4
+com5
+Com5
+cOm5
+COm5
+coM5
+CoM5
+cOM5
+COM5
+COM5
+com6
+Com6
+cOm6
+COm6
+coM6
+CoM6
+cOM6
+COM6
+COM6
+com7
+Com7
+cOm7
+COm7
+coM7
+CoM7
+cOM7
+COM7
+COM7
+com8
+Com8
+cOm8
+COm8
+coM8
+CoM8
+cOM8
+COM8
+COM8
+com9
+Com9
+cOm9
+COm9
+coM9
+CoM9
+cOM9
+COM9
+COM9
+lpt1
+Lpt1
+lPt1
+LPt1
+lpT1
+LpT1
+lPT1
+LPT1
+LPT1
+lpt2
+Lpt2
+lPt2
+LPt2
+lpT2
+LpT2
+lPT2
+LPT2
+LPT2
+lpt3
+Lpt3
+lPt3
+LPt3
+lpT3
+LpT3
+lPT3
+LPT3
+LPT3
+lpt4
+Lpt4
+lPt4
+LPt4
+lpT4
+LpT4
+lPT4
+LPT4
+LPT4
+lpt5
+Lpt5
+lPt5
+LPt5
+lpT5
+LpT5
+lPT5
+LPT5
+LPT5
+lpt6
+Lpt6
+lPt6
+LPt6
+lpT6
+LpT6
+lPT6
+LPT6
+LPT6
+lpt7
+Lpt7
+lPt7
+LPt7
+lpT7
+LpT7
+lPT7
+LPT7
+LPT7
+lpt8
+Lpt8
+lPt8
+LPt8
+lpT8
+LpT8
+lPT8
+LPT8
+LPT8
+lpt9
+Lpt9
+lPt9
+LPt9
+lpT9
+LpT9
+lPT9
+LPT9
+LPT9
diff --git a/examples/library.pro b/examples/library.pro
new file mode 100644
index 0000000..37be47e
--- /dev/null
+++ b/examples/library.pro
@@ -0,0 +1,78 @@
+#
+# This ProGuard configuration file illustrates how to process a program
+# library, such that it remains usable as a library.
+# Usage:
+#     java -jar proguard.jar @library.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+# In this case, the input jar is the program library that we want to process.
+
+-injars  in.jar
+-outjars out.jar
+
+-libraryjars  <java.home>/lib/rt.jar
+
+# Save the obfuscation mapping to a file, so we can de-obfuscate any stack
+# traces later on. Keep a fixed source file attribute and all line number
+# tables to actually get these stack traces.
+# You can comment this out if you're not interested in stack traces.
+
+-printmapping out.map
+-renamesourcefileattribute SourceFile
+-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
+                SourceFile,LineNumberTable,EnclosingMethod
+
+# Preserve all annotations.
+
+-keepattributes *Annotation*
+
+# Preserve all public classes, and their public and protected fields and
+# methods.
+
+-keep public class * {
+    public protected *;
+}
+
+# Preserve all .class method names.
+
+-keepclassmembernames class * {
+    java.lang.Class class$(java.lang.String);
+    java.lang.Class class$(java.lang.String, boolean);
+}
+
+# Preserve all native method names and the names of their classes.
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# Preserve the special static methods that are required in all enumeration
+# classes.
+
+-keepclassmembers class * extends java.lang.Enum {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+# Explicitly preserve all serialization members. The Serializable interface
+# is only a marker interface, so it wouldn't save them.
+# You can comment this out if your library doesn't use serialization.
+# If your code contains serializable classes that have to be backward
+# compatible, please refer to the manual.
+
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    static final java.io.ObjectStreamField[] serialPersistentFields;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+
+# Your library may contain more items that need to be preserved; 
+# typically classes that are dynamically created using Class.forName:
+
+# -keep public class mypackage.MyClass
+# -keep public interface mypackage.MyInterface
+# -keep public class * implements mypackage.MyInterface
diff --git a/examples/midlets.pro b/examples/midlets.pro
new file mode 100644
index 0000000..bffc38e
--- /dev/null
+++ b/examples/midlets.pro
@@ -0,0 +1,56 @@
+#
+# This ProGuard configuration file illustrates how to process J2ME midlets.
+# Usage:
+#     java -jar proguard.jar @midlets.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+
+-injars  in.jar
+-outjars out.jar
+
+-libraryjars /usr/local/java/wtk2.1/lib/midpapi20.jar
+-libraryjars /usr/local/java/wtk2.1/lib/cldcapi11.jar
+
+# Preverify the code suitably for Java Micro Edition.
+
+-microedition
+
+# Allow methods with the same signature, except for the return type,
+# to get the same obfuscation name.
+
+-overloadaggressively
+
+# Put all obfuscated classes into the nameless root package.
+
+-repackageclasses ''
+
+# Allow classes and class members to be made public.
+
+-allowaccessmodification
+
+# On Windows, you can't use mixed case class names,
+# should you still want to use the preverify tool.
+#
+# -dontusemixedcaseclassnames
+
+# Preserve all public midlets.
+
+-keep public class * extends javax.microedition.midlet.MIDlet
+
+# Print out a list of what we're preserving.
+
+-printseeds
+
+# Preserve all native method names and the names of their classes.
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# Your midlet may contain more items that need to be preserved; 
+# typically classes that are dynamically created using Class.forName:
+
+# -keep public class mypackage.MyClass
+# -keep public interface mypackage.MyInterface
+# -keep public class * implements mypackage.MyInterface
diff --git a/examples/proguard.pro b/examples/proguard.pro
new file mode 100644
index 0000000..c48b87e
--- /dev/null
+++ b/examples/proguard.pro
@@ -0,0 +1,57 @@
+#
+# This ProGuard configuration file illustrates how to process ProGuard itself.
+# Configuration files for typical applications will be very similar.
+# Usage:
+#     java -jar proguard.jar @proguard.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+# We'll filter out the Ant and WTK classes, keeping everything else.
+
+-injars  ../lib/proguard.jar(!proguard/ant/**,!proguard/wtk/**)
+-outjars proguard_out.jar
+
+-libraryjars <java.home>/lib/rt.jar
+
+# Write out an obfuscation mapping file, for de-obfuscating any stack traces
+# later on, or for incremental obfuscation of extensions.
+
+-printmapping proguard.map
+
+# Allow methods with the same signature, except for the return type,
+# to get the same obfuscation name.
+
+-overloadaggressively
+
+# Put all obfuscated classes into the nameless root package.
+
+-repackageclasses ''
+
+# Allow classes and class members to be made public.
+
+-allowaccessmodification
+
+# The entry point: ProGuard and its main method.
+
+-keep public class proguard.ProGuard {
+    public static void main(java.lang.String[]);
+}
+
+# If you want to preserve the Ant task as well, you'll have to specify the
+# main ant.jar.
+
+#-libraryjars /usr/local/java/ant/lib/ant.jar
+#-adaptresourcefilecontents proguard/ant/task.properties
+#
+#-keep,allowobfuscation class proguard.ant.*
+#-keepclassmembers public class proguard.ant.* {
+#    <init>(org.apache.tools.ant.Project);
+#    public void set*(***);
+#    public void add*(***);
+#}
+
+# If you want to preserve the WTK obfuscation plug-in, you'll have to specify
+# the kenv.zip file.
+
+#-libraryjars /usr/local/java/wtk2.1/wtklib/kenv.zip
+#-keep public class proguard.wtk.ProGuardObfuscator
diff --git a/examples/proguardall.pro b/examples/proguardall.pro
new file mode 100644
index 0000000..8dc042e
--- /dev/null
+++ b/examples/proguardall.pro
@@ -0,0 +1,62 @@
+#
+# This ProGuard configuration file illustrates how to process ProGuard
+# (including its main application, its GUI, its Ant task, and its WTK plugin),
+# and the ReTrace tool, all in one go.
+# Configuration files for typical applications will be very similar.
+# Usage:
+#     java -jar proguard.jar @proguardall.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+# We'll read all jars from the lib directory, process them, and write the
+# processed jars to a new out directory.
+
+-injars  ../lib
+-outjars out
+
+# You may have to adapt the paths below.
+
+-libraryjars <java.home>/lib/rt.jar
+-libraryjars /usr/local/java/ant1.5.0/lib/ant.jar
+-libraryjars /usr/local/java/wtk2.1/wtklib/kenv.zip
+
+# Allow methods with the same signature, except for the return type,
+# to get the same obfuscation name.
+
+-overloadaggressively
+
+# Put all obfuscated classes into the nameless root package.
+
+-repackageclasses ''
+
+# Adapt the names and contents of the resource files.
+
+-adaptresourcefilenames    **.properties,**.gif,**.jpg
+-adaptresourcefilecontents proguard/ant/task.properties
+
+# The main entry points.
+
+-keep public class proguard.ProGuard {
+    public static void main(java.lang.String[]);
+}
+
+-keep public class proguard.gui.ProGuardGUI {
+    public static void main(java.lang.String[]);
+}
+
+-keep public class proguard.retrace.ReTrace {
+    public static void main(java.lang.String[]);
+}
+
+# If we have ant.jar, we can properly process the Ant task.
+
+-keep,allowobfuscation class proguard.ant.*
+-keepclassmembers public class proguard.ant.* {
+    <init>(org.apache.tools.ant.Project);
+    public void set*(***);
+    public void add*(***);
+}
+
+# If we have kenv.zip, we can process the J2ME WTK plugin.
+
+-keep public class proguard.wtk.ProGuardObfuscator
diff --git a/examples/proguardgui.pro b/examples/proguardgui.pro
new file mode 100644
index 0000000..2cb83ed
--- /dev/null
+++ b/examples/proguardgui.pro
@@ -0,0 +1,55 @@
+#
+# This ProGuard configuration file illustrates how to process the ProGuard GUI.
+# Configuration files for typical applications will be very similar.
+# Usage:
+#     java -jar proguard.jar @proguardgui.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+# The input jars will be merged in a single output jar.
+# We'll filter out the Ant and WTK classes.
+
+-injars  ../lib/proguardgui.jar
+-injars  ../lib/proguard.jar(!META-INF/**,!proguard/ant/**,!proguard/wtk/**)
+-injars  ../lib/retrace.jar (!META-INF/**)
+-outjars proguardgui_out.jar
+
+-libraryjars <java.home>/lib/rt.jar
+
+# In recent JREs, some public Swing classes depend on package visible classes,
+# so don't skip these package visible classes while parsing the library jar.
+
+-dontskipnonpubliclibraryclasses
+
+# If we wanted to reuse the previously obfuscated proguard_out.jar, we could
+# perform incremental obfuscation based on its mapping file, and only keep the
+# additional GUI files instead of all files.
+
+#-applymapping proguard.map
+#-injars      ../lib/proguardgui.jar
+#-outjars     proguardgui_out.jar
+#-libraryjars ../lib/proguard.jar(!proguard/ant/**,!proguard/wtk/**)
+#-libraryjars ../lib/retrace.jar
+#-libraryjars <java.home>/lib/rt.jar
+
+
+# Allow methods with the same signature, except for the return type,
+# to get the same obfuscation name.
+
+-overloadaggressively
+
+# Put all obfuscated classes into the nameless root package.
+
+-repackageclasses ''
+
+# Adapt the names of resource files, based on the corresponding obfuscated
+# class names. Notably, in this case, the GUI resource properties file will
+# have to be renamed.
+
+-adaptresourcefilenames **.properties,**.gif,**.jpg
+
+# The entry point: ProGuardGUI and its main method.
+
+-keep public class proguard.gui.ProGuardGUI {
+    public static void main(java.lang.String[]);
+}
diff --git a/examples/retrace.pro b/examples/retrace.pro
new file mode 100644
index 0000000..39f5a95
--- /dev/null
+++ b/examples/retrace.pro
@@ -0,0 +1,43 @@
+#
+# This ProGuard configuration file illustrates how to process the ReTrace tool.
+# Configuration files for typical applications will be very similar.
+# Usage:
+#     java -jar proguard.jar @retrace.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+# The input jars will be merged in a single output jar.
+# We'll filter out the Ant and WTK classes.
+
+-injars  ../lib/retrace.jar
+-injars  ../lib/proguard.jar(!META-INF/MANIFEST.MF,
+                             !proguard/ant/**,!proguard/wtk/**)
+-outjars retrace_out.jar
+
+-libraryjars <java.home>/lib/rt.jar
+
+# If we wanted to reuse the previously obfuscated proguard_out.jar, we could
+# perform incremental obfuscation based on its mapping file, and only keep the
+# additional ReTrace files instead of all files.
+
+#-applymapping proguard.map
+#-outjars      retrace_out.jar(proguard/retrace/**)
+
+# Allow methods with the same signature, except for the return type,
+# to get the same obfuscation name.
+
+-overloadaggressively
+
+# Put all obfuscated classes into the nameless root package.
+
+-repackageclasses ''
+
+# Allow classes and class members to be made public.
+
+-allowaccessmodification
+
+# The entry point: ReTrace and its main method.
+
+-keep public class proguard.retrace.ReTrace {
+    public static void main(java.lang.String[]);
+}
diff --git a/examples/servlets.pro b/examples/servlets.pro
new file mode 100644
index 0000000..fdc36b0
--- /dev/null
+++ b/examples/servlets.pro
@@ -0,0 +1,61 @@
+#
+# This ProGuard configuration file illustrates how to process servlets.
+# Usage:
+#     java -jar proguard.jar @servlets.pro
+#
+
+# Specify the input jars, output jars, and library jars.
+
+-injars  in.jar
+-outjars out.jar
+
+-libraryjars <java.home>/lib/rt.jar
+-libraryjars /usr/local/java/servlet/servlet.jar
+
+# Preserve all public servlets.
+
+-keep public class * implements javax.servlet.Servlet
+
+# Print out a list of what we're preserving.
+
+-printseeds
+
+# Preserve all annotations.
+
+-keepattributes *Annotation*
+
+# Preserve all native method names and the names of their classes.
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# Preserve the special static methods that are required in all enumeration
+# classes.
+
+-keepclassmembers class * extends java.lang.Enum {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+# Explicitly preserve all serialization members. The Serializable interface
+# is only a marker interface, so it wouldn't save them.
+# You can comment this out if your library doesn't use serialization.
+# If your code contains serializable classes that have to be backward
+# compatible, please refer to the manual.
+
+-keepclassmembers class * implements java.io.Serializable {
+    static final long serialVersionUID;
+    static final java.io.ObjectStreamField[] serialPersistentFields;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+
+# Your application may contain more items that need to be preserved;
+# typically classes that are dynamically created using Class.forName:
+
+# -keep public class mypackage.MyClass
+# -keep public interface mypackage.MyInterface
+# -keep public class * implements mypackage.MyInterface
diff --git a/lib/proguard.jar b/lib/proguard.jar
new file mode 100644
index 0000000..3a9aebc
--- /dev/null
+++ b/lib/proguard.jar
Binary files differ
diff --git a/lib/proguardgui.jar b/lib/proguardgui.jar
new file mode 100644
index 0000000..5866856
--- /dev/null
+++ b/lib/proguardgui.jar
Binary files differ
diff --git a/lib/retrace.jar b/lib/retrace.jar
new file mode 100644
index 0000000..8e42e5a
--- /dev/null
+++ b/lib/retrace.jar
Binary files differ
diff --git a/src/proguard/ArgumentWordReader.java b/src/proguard/ArgumentWordReader.java
new file mode 100644
index 0000000..89f3824
--- /dev/null
+++ b/src/proguard/ArgumentWordReader.java
@@ -0,0 +1,110 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.io.*;
+
+
+/**
+ * A <code>WordReader</code> that returns words from an argument list.
+ * Single arguments are split into individual words if necessary.
+ *
+ * @author Eric Lafortune
+ */
+public class ArgumentWordReader extends WordReader
+{
+    private final String[] arguments;
+    private int      index = 0;
+
+
+//    /**
+//     * Creates a new ArgumentWordReader for the given arguments.
+//     */
+//    public ArgumentWordReader(String[] arguments)
+//    {
+//        this(arguments, null);
+//    }
+//
+//
+    /**
+     * Creates a new ArgumentWordReader for the given arguments, with the
+     * given base directory.
+     */
+    public ArgumentWordReader(String[] arguments, File baseDir)
+    {
+        super(baseDir);
+
+        this.arguments = arguments;
+    }
+
+
+    // Implementations for WordReader.
+
+    protected String nextLine() throws IOException
+    {
+        return index < arguments.length ?
+            arguments[index++] :
+            null;
+    }
+
+
+    protected String lineLocationDescription()
+    {
+        return "argument number " + index;
+    }
+
+
+    /**
+     * Test application that prints out the individual words of
+     * the argument list.
+     */
+    public static void main(String[] args) {
+
+        try
+        {
+            WordReader reader = new ArgumentWordReader(args, null);
+
+            try
+            {
+                while (true)
+                {
+                    String word = reader.nextWord();
+                    if (word == null)
+                        System.exit(-1);
+
+                    System.err.println("["+word+"]");
+                }
+            }
+            catch (Exception ex)
+            {
+                ex.printStackTrace();
+            }
+            finally
+            {
+                reader.close();
+            }
+        }
+        catch (IOException ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/ClassPath.java b/src/proguard/ClassPath.java
new file mode 100644
index 0000000..f4eeaad
--- /dev/null
+++ b/src/proguard/ClassPath.java
@@ -0,0 +1,94 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.util.*;
+
+
+/**
+ * This class represents a class path, as a list of ClassPathEntry objects.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassPath
+{
+    private final List classPathEntries = new ArrayList();
+
+
+    /**
+     * Returns whether the class path contains any output entries.
+     */
+    public boolean hasOutput()
+    {
+        for (int index = 0; index < classPathEntries.size(); index++)
+        {
+            if (((ClassPathEntry)classPathEntries.get(index)).isOutput())
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    // Delegates to List.
+
+    public void clear()
+    {
+        classPathEntries.clear();
+    }
+
+    public void add(int index, ClassPathEntry classPathEntry)
+    {
+        classPathEntries.add(index, classPathEntry);
+    }
+
+    public boolean add(ClassPathEntry classPathEntry)
+    {
+        return classPathEntries.add(classPathEntry);
+    }
+
+    public boolean addAll(ClassPath classPath)
+    {
+        return classPathEntries.addAll(classPath.classPathEntries);
+    }
+
+    public ClassPathEntry get(int index)
+    {
+        return (ClassPathEntry)classPathEntries.get(index);
+    }
+
+    public ClassPathEntry remove(int index)
+    {
+        return (ClassPathEntry)classPathEntries.remove(index);
+    }
+
+    public boolean isEmpty()
+    {
+        return classPathEntries.isEmpty();
+    }
+
+    public int size()
+    {
+        return classPathEntries.size();
+    }
+}
diff --git a/src/proguard/ClassPathEntry.java b/src/proguard/ClassPathEntry.java
new file mode 100644
index 0000000..28483be
--- /dev/null
+++ b/src/proguard/ClassPathEntry.java
@@ -0,0 +1,149 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.io.*;
+import java.util.List;
+
+
+/**
+ * This class represents an entry from a class path: a jar, a war, a zip, an
+ * ear, or a directory, with a name and a flag to indicates whether the entry is
+ * an input entry or an output entry. Optional filters can be specified for the
+ * names of the contained resource/classes, jars, wars, ears, and zips.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassPathEntry
+{
+    private File    file;
+    private boolean output;
+    private List    filter;
+    private List    jarFilter;
+    private List    warFilter;
+    private List    earFilter;
+    private List    zipFilter;
+
+
+    /**
+     * Creates a new ClassPathEntry with the given name and type.
+     */
+    public ClassPathEntry(File file, boolean isOutput)
+    {
+        this.file   = file;
+        this.output = isOutput;
+    }
+
+
+    /**
+     * Returns the path name of the entry.
+     */
+    public String getName()
+    {
+        try
+        {
+            return file.getCanonicalPath();
+        }
+        catch (IOException ex)
+        {
+            return file.getPath();
+        }
+    }
+
+
+    public File getFile()
+    {
+        return file;
+    }
+
+
+    public void setFile(File file)
+    {
+        this.file = file;
+    }
+
+
+    public boolean isOutput()
+    {
+        return output;
+    }
+
+
+    public void setOutput(boolean output)
+    {
+        this.output = output;
+    }
+
+
+    public List getFilter()
+    {
+        return filter;
+    }
+
+    public void setFilter(List filter)
+    {
+        this.filter = filter == null || filter.size() == 0 ? null : filter;
+    }
+
+
+    public List getJarFilter()
+    {
+        return jarFilter;
+    }
+
+    public void setJarFilter(List filter)
+    {
+        this.jarFilter = filter == null || filter.size() == 0 ? null : filter;
+    }
+
+
+    public List getWarFilter()
+    {
+        return warFilter;
+    }
+
+    public void setWarFilter(List filter)
+    {
+        this.warFilter = filter == null || filter.size() == 0 ? null : filter;
+    }
+
+
+    public List getEarFilter()
+    {
+        return earFilter;
+    }
+
+    public void setEarFilter(List filter)
+    {
+        this.earFilter = filter == null || filter.size() == 0 ? null : filter;
+    }
+
+
+    public List getZipFilter()
+    {
+        return zipFilter;
+    }
+
+    public void setZipFilter(List filter)
+    {
+        this.zipFilter = filter == null || filter.size() == 0 ? null : filter;
+    }
+}
diff --git a/src/proguard/ClassSpecification.java b/src/proguard/ClassSpecification.java
new file mode 100644
index 0000000..a84e0c8
--- /dev/null
+++ b/src/proguard/ClassSpecification.java
@@ -0,0 +1,259 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.util.*;
+
+/**
+ * This class stores a specification of classes and possibly class members.
+ * The specification is template-based: the class names and class member names
+ * and descriptors can contain wildcards. Classes can be specified explicitly,
+ * or as extensions or implementations in the class hierarchy.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassSpecification implements Cloneable
+{
+    public final String comments;
+    public       int    requiredSetAccessFlags;
+    public       int    requiredUnsetAccessFlags;
+    public final String annotationType;
+    public       String className;
+    public final String extendsAnnotationType;
+    public final String extendsClassName;
+
+    public       List   fieldSpecifications;
+    public       List   methodSpecifications;
+
+
+    /**
+     * Creates a new ClassSpecification for all possible classes, without
+     * comments or class members.
+     */
+    public ClassSpecification()
+    {
+        this(null,
+             0,
+             0,
+             null,
+             null,
+             null,
+             null);
+    }
+
+
+    /**
+     * Creates a new ClassSpecification that is a copy of the given specification.
+     */
+    public ClassSpecification(ClassSpecification classSpecification)
+    {
+        this(classSpecification.comments,
+             classSpecification.requiredSetAccessFlags,
+             classSpecification.requiredUnsetAccessFlags,
+             classSpecification.annotationType,
+             classSpecification.className,
+             classSpecification.extendsAnnotationType,
+             classSpecification.extendsClassName,
+             classSpecification.fieldSpecifications,
+             classSpecification.methodSpecifications);
+    }
+
+
+    /**
+     * Creates a new ClassSpecification for the specified class(es), without
+     * class members.
+     *
+     * @param comments                 provides optional comments on this
+     *                                 specification.
+     * @param requiredSetAccessFlags   the class access flags that must be set
+     *                                 in order for the class to apply.
+     * @param requiredUnsetAccessFlags the class access flags that must be
+     *                                 unset in order for the class to apply.
+     * @param annotationType           the name of the class that must be an
+     *                                 annotation of the class in order for it
+     *                                 to apply. The name may be null to
+     *                                 specify that no annotation is required.
+     * @param className                the class name. The name may be null to
+     *                                 specify any class, or it may contain
+     *                                 "**", "*", or "?" wildcards.
+     * @param extendsAnnotationType    the name of the class of that must be
+     *                                 an annotation of the class that the
+     *                                 class must extend or implement in order
+     *                                 to apply. The name may be null to
+     *                                 specify that no annotation is required.
+     * @param extendsClassName         the name of the class that the class
+     *                                 must extend or implement in order to
+     *                                 apply. The name may be null to specify
+     *                                 any class.
+     */
+    public ClassSpecification(String comments,
+                              int    requiredSetAccessFlags,
+                              int    requiredUnsetAccessFlags,
+                              String annotationType,
+                              String className,
+                              String extendsAnnotationType,
+                              String extendsClassName)
+    {
+        this(comments,
+             requiredSetAccessFlags,
+             requiredUnsetAccessFlags,
+             annotationType,
+             className,
+             extendsAnnotationType,
+             extendsClassName,
+             null,
+             null);
+    }
+
+
+    /**
+     * Creates a new ClassSpecification for the specified classes and class
+     * members.
+     *
+     * @param comments                 provides optional comments on this
+     *                                 specification.
+     * @param requiredSetAccessFlags   the class access flags that must be set
+     *                                 in order for the class to apply.
+     * @param requiredUnsetAccessFlags the class access flags that must be
+     *                                 unset in order for the class to apply.
+     * @param annotationType           the name of the class that must be an
+     *                                 annotation of the class in order for it
+     *                                 to apply. The name may be null to
+     *                                 specify that no annotation is required.
+     * @param className                the class name. The name may be null to
+     *                                 specify any class, or it may contain
+     *                                 "**", "*", or "?" wildcards.
+     * @param extendsAnnotationType    the name of the class of that must be
+     *                                 an annotation of the class that the
+     *                                 class must extend or implement in order
+     *                                 to apply. The name may be null to
+     *                                 specify that no annotation is required.
+     * @param extendsClassName         the name of the class that the class
+     *                                 must extend or implement in order to
+     *                                 apply. The name may be null to specify
+     *                                 any class.
+     * @param fieldSpecifications      the field specifications.
+     * @param methodSpecifications     the method specifications.
+     */
+    public ClassSpecification(String comments,
+                              int    requiredSetAccessFlags,
+                              int    requiredUnsetAccessFlags,
+                              String annotationType,
+                              String className,
+                              String extendsAnnotationType,
+                              String extendsClassName,
+                              List   fieldSpecifications,
+                              List   methodSpecifications)
+    {
+        this.comments                 = comments;
+        this.requiredSetAccessFlags   = requiredSetAccessFlags;
+        this.requiredUnsetAccessFlags = requiredUnsetAccessFlags;
+        this.annotationType           = annotationType;
+        this.className                = className;
+        this.extendsAnnotationType    = extendsAnnotationType;
+        this.extendsClassName         = extendsClassName;
+        this.fieldSpecifications      = fieldSpecifications;
+        this.methodSpecifications     = methodSpecifications;
+    }
+
+
+    /**
+     * Specifies to keep the specified field(s) of this option's class(es).
+     *
+     * @param fieldSpecification the field specification.
+     */
+    public void addField(MemberSpecification fieldSpecification)
+    {
+        if (fieldSpecifications == null)
+        {
+            fieldSpecifications = new ArrayList();
+        }
+
+        fieldSpecifications.add(fieldSpecification);
+    }
+
+
+    /**
+     * Specifies to keep the specified method(s) of this option's class(es).
+     *
+     * @param methodSpecification the method specification.
+     */
+    public void addMethod(MemberSpecification methodSpecification)
+    {
+        if (methodSpecifications == null)
+        {
+            methodSpecifications = new ArrayList();
+        }
+
+        methodSpecifications.add(methodSpecification);
+    }
+
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        ClassSpecification other = (ClassSpecification)object;
+        return
+//          (this.comments                 == null ? other.comments              == null : this.comments.equals(other.comments)                          ) &&
+            (this.requiredSetAccessFlags   == other.requiredSetAccessFlags                                                                               ) &&
+            (this.requiredUnsetAccessFlags == other.requiredUnsetAccessFlags                                                                             ) &&
+            (this.annotationType           == null ? other.annotationType        == null : this.annotationType.equals(other.annotationType)              ) &&
+            (this.className                == null ? other.className             == null : this.className.equals(other.className)                        ) &&
+            (this.extendsAnnotationType    == null ? other.extendsAnnotationType == null : this.extendsAnnotationType.equals(other.extendsAnnotationType)) &&
+            (this.extendsClassName         == null ? other.extendsClassName      == null : this.extendsClassName.equals(other.extendsClassName)          ) &&
+            (this.fieldSpecifications      == null ? other.fieldSpecifications   == null : this.fieldSpecifications.equals(other.fieldSpecifications)    ) &&
+            (this.methodSpecifications     == null ? other.methodSpecifications  == null : this.methodSpecifications.equals(other.methodSpecifications)  );
+    }
+
+    public int hashCode()
+    {
+        return
+//          (comments              == null ? 0 : comments.hashCode()             ) ^
+            (requiredSetAccessFlags                                              ) ^
+            (requiredUnsetAccessFlags                                            ) ^
+            (annotationType        == null ? 0 : annotationType.hashCode()       ) ^
+            (className             == null ? 0 : className.hashCode()            ) ^
+            (extendsAnnotationType == null ? 0 : extendsAnnotationType.hashCode()) ^
+            (extendsClassName      == null ? 0 : extendsClassName.hashCode()     ) ^
+            (fieldSpecifications   == null ? 0 : fieldSpecifications.hashCode()  ) ^
+            (methodSpecifications  == null ? 0 : methodSpecifications.hashCode() );
+    }
+
+    public Object clone()
+    {
+        try
+        {
+            return super.clone();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            return null;
+        }
+    }
+}
diff --git a/src/proguard/ClassSpecificationVisitorFactory.java b/src/proguard/ClassSpecificationVisitorFactory.java
new file mode 100644
index 0000000..c99ab2c
--- /dev/null
+++ b/src/proguard/ClassSpecificationVisitorFactory.java
@@ -0,0 +1,502 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.visitor.AllAttributeVisitor;
+import proguard.classfile.visitor.*;
+
+import java.util.List;
+
+/**
+ * This factory creates visitors to efficiently travel to specified classes and
+ * class members.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassSpecificationVisitorFactory
+{
+    /**
+     * Constructs a ClassPoolVisitor to efficiently travel to the specified
+     * classes and class members.
+     *
+     * @param keepClassSpecifications the list of KeepClassSpecification
+     *                                instances, defining of the classes and
+     *                                class members to visit.
+     * @param classVisitor            the ClassVisitor to be applied to matching
+     *                                classes.
+     * @param memberVisitor           the MemberVisitor to be applied to matching
+     *                                class members.
+     */
+    public static ClassPoolVisitor createClassPoolVisitor(List          keepClassSpecifications,
+                                                          ClassVisitor  classVisitor,
+                                                          MemberVisitor memberVisitor,
+                                                          boolean       shrinking,
+                                                          boolean       optimizing,
+                                                          boolean       obfuscating)
+    {
+        MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor();
+
+        if (keepClassSpecifications != null)
+        {
+            for (int index = 0; index < keepClassSpecifications.size(); index++)
+            {
+                KeepClassSpecification keepClassSpecification =
+                    (KeepClassSpecification)keepClassSpecifications.get(index);
+
+                if ((shrinking   && !keepClassSpecification.allowShrinking)    ||
+                    (optimizing  && !keepClassSpecification.allowOptimization) ||
+                    (obfuscating && !keepClassSpecification.allowObfuscation))
+                {
+                    multiClassPoolVisitor.addClassPoolVisitor(
+                        createClassPoolVisitor(keepClassSpecification,
+                                               classVisitor,
+                                               memberVisitor));
+                }
+            }
+        }
+
+        return multiClassPoolVisitor;
+    }
+
+
+    /**
+     * Constructs a ClassPoolVisitor to efficiently travel to the specified
+     * classes and class members.
+     *
+     * @param classSpecifications the list of ClassSpecification instances,
+     *                            defining of the classes and class members to
+     *                            visit.
+     * @param classVisitor        the ClassVisitor to be applied to matching
+     *                            classes.
+     * @param memberVisitor       the MemberVisitor to be applied to matching
+     *                            class members.
+     */
+    public static ClassPoolVisitor createClassPoolVisitor(List          classSpecifications,
+                                                          ClassVisitor  classVisitor,
+                                                          MemberVisitor memberVisitor)
+    {
+        MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor();
+
+        if (classSpecifications != null)
+        {
+            for (int index = 0; index < classSpecifications.size(); index++)
+            {
+                ClassSpecification classSpecification =
+                    (ClassSpecification)classSpecifications.get(index);
+
+                multiClassPoolVisitor.addClassPoolVisitor(
+                    createClassPoolVisitor(classSpecification,
+                                           classVisitor,
+                                           memberVisitor));
+            }
+        }
+
+        return multiClassPoolVisitor;
+    }
+
+
+    /**
+     * Constructs a ClassPoolVisitor to efficiently travel to the specified
+     * classes and class members.
+     *
+     * @param keepClassSpecification the specifications of the class(es) and class
+     *                          members to visit.
+     * @param classVisitor      the ClassVisitor to be applied to matching
+     *                          classes.
+     * @param memberVisitor     the MemberVisitor to be applied to matching
+     *                          class members.
+     */
+    private static ClassPoolVisitor createClassPoolVisitor(KeepClassSpecification keepClassSpecification,
+                                                           ClassVisitor      classVisitor,
+                                                           MemberVisitor     memberVisitor)
+    {
+        // Don't  visit the classes if not specified.
+        if (!keepClassSpecification.markClasses &&
+            !keepClassSpecification.markConditionally)
+        {
+            classVisitor = null;
+        }
+
+        // If specified, let the marker visit the class and its class
+        // members conditionally.
+        if (keepClassSpecification.markConditionally)
+        {
+            // Combine both visitors.
+            ClassVisitor composedClassVisitor =
+                createCombinedClassVisitor(keepClassSpecification,
+                                           classVisitor,
+                                           memberVisitor);
+
+            // Replace the class visitor.
+            classVisitor =
+                createClassMemberTester(keepClassSpecification,
+                                        composedClassVisitor);
+
+            // Discard the member visitor, because it has already been included.
+            memberVisitor = null;
+        }
+
+        return createClassPoolVisitor((ClassSpecification)keepClassSpecification,
+                                      classVisitor,
+                                      memberVisitor);
+    }
+
+
+    /**
+     * Constructs a ClassPoolVisitor to efficiently travel to the specified
+     * classes and class members.
+     *
+     * @param classSpecification the specifications of the class(es) and class
+     *                           members to visit.
+     * @param classVisitor       the ClassVisitor to be applied to matching
+     *                           classes.
+     * @param memberVisitor      the MemberVisitor to be applied to matching
+     *                           class members.
+     */
+    private static ClassPoolVisitor createClassPoolVisitor(ClassSpecification classSpecification,
+                                                           ClassVisitor       classVisitor,
+                                                           MemberVisitor      memberVisitor)
+    {
+        // Combine both visitors.
+        ClassVisitor composedClassVisitor =
+            createCombinedClassVisitor(classSpecification,
+                                       classVisitor,
+                                       memberVisitor);
+
+        // By default, start visiting from the named class name, if specified.
+        String className = classSpecification.className;
+
+        // Although we may have to start from the extended class.
+        String extendsAnnotationType = classSpecification.extendsAnnotationType;
+        String extendsClassName      = classSpecification.extendsClassName;
+
+        // If wildcarded, only visit classes with matching names.
+        if (className != null &&
+            (extendsAnnotationType != null ||
+             extendsClassName      != null ||
+             containsWildCards(className)))
+        {
+            composedClassVisitor =
+                new ClassNameFilter(className, composedClassVisitor);
+
+            // We'll have to visit all classes now.
+            className = null;
+        }
+
+        // If specified, only visit classes with the right annotation.
+        String annotationType = classSpecification.annotationType;
+
+        if (annotationType != null)
+        {
+            composedClassVisitor =
+                new AllAttributeVisitor(
+                new AllAnnotationVisitor(
+                new AnnotationTypeFilter(annotationType,
+                new AnnotatedClassVisitor(composedClassVisitor))));
+        }
+
+        // If specified, only visit classes with the right access flags.
+        if (classSpecification.requiredSetAccessFlags   != 0 ||
+            classSpecification.requiredUnsetAccessFlags != 0)
+        {
+            composedClassVisitor =
+                new ClassAccessFilter(classSpecification.requiredSetAccessFlags,
+                                      classSpecification.requiredUnsetAccessFlags,
+                                      composedClassVisitor);
+        }
+
+        // If it's specified, start visiting from the extended class.
+        if (extendsAnnotationType != null ||
+            extendsClassName      != null)
+        {
+            // Start visiting from the extended class.
+            composedClassVisitor =
+                new ClassHierarchyTraveler(false, false, false, true,
+                                           composedClassVisitor);
+
+            // If specified, only visit extended classes with the right annotation.
+            if (extendsAnnotationType != null)
+            {
+                composedClassVisitor =
+                    new AllAttributeVisitor(
+                    new AllAnnotationVisitor(
+                    new AnnotationTypeFilter(extendsAnnotationType,
+                    new AnnotatedClassVisitor(composedClassVisitor))));
+            }
+
+            // If specified, only visit extended classes with matching names.
+            if (extendsClassName != null)
+            {
+                // If wildcarded, only visit extended classes with matching names.
+                if (containsWildCards(extendsClassName))
+                {
+                    composedClassVisitor =
+                        new ClassNameFilter(extendsClassName,
+                                            composedClassVisitor);
+                }
+                else
+                {
+                    // Start visiting from the named extended class.
+                    className = extendsClassName;
+                }
+            }
+        }
+
+        // If specified, visit a single named class, otherwise visit all classes.
+        return className != null ?
+            (ClassPoolVisitor)new NamedClassVisitor(composedClassVisitor, className) :
+            (ClassPoolVisitor)new AllClassVisitor(composedClassVisitor);
+    }
+
+
+    /**
+     * Constructs a ClassVisitor to efficiently travel to the specified
+     * classes and class members.
+     *
+     * @param classSpecification the specifications of the class(es) and class
+     *                           members to visit.
+     * @param classVisitor       the ClassVisitor to be applied to matching
+     *                           classes.
+     * @param memberVisitor      the MemberVisitor to be applied to matching
+     *                           class members.
+     */
+    private static ClassVisitor createCombinedClassVisitor(ClassSpecification classSpecification,
+                                                           ClassVisitor       classVisitor,
+                                                           MemberVisitor      memberVisitor)
+    {
+        // Don't visit any members if there aren't any member specifications.
+        if (classSpecification.fieldSpecifications  == null &&
+            classSpecification.methodSpecifications == null)
+        {
+            memberVisitor = null;
+        }
+
+        // The class visitor for classes and their members.
+        MultiClassVisitor multiClassVisitor = new MultiClassVisitor();
+
+        // If specified, let the class visitor visit the class itself.
+        if (classVisitor != null)
+        {
+            // This class visitor may be the only one.
+            if (memberVisitor == null)
+            {
+                return classVisitor;
+            }
+
+            multiClassVisitor.addClassVisitor(classVisitor);
+        }
+
+        // If specified, let the member info visitor visit the class members.
+        if (memberVisitor != null)
+        {
+            ClassVisitor memberClassVisitor =
+                createClassVisitor(classSpecification, memberVisitor);
+
+            // This class visitor may be the only one.
+            if (classVisitor == null)
+            {
+                return memberClassVisitor;
+            }
+
+            multiClassVisitor.addClassVisitor(memberClassVisitor);
+        }
+
+        return multiClassVisitor;
+    }
+
+
+    /**
+     * Constructs a ClassVisitor to efficiently travel to the specified class
+     * members.
+     *
+     * @param classSpecification the specifications of the class members to visit.
+     * @param memberVisitor      the MemberVisitor to be applied to matching
+     *                           class members.
+     */
+    private static ClassVisitor createClassVisitor(ClassSpecification classSpecification,
+                                                   MemberVisitor      memberVisitor)
+    {
+        MultiClassVisitor multiClassVisitor = new MultiClassVisitor();
+
+        addMemberVisitors(classSpecification.fieldSpecifications,  true,  multiClassVisitor, memberVisitor);
+        addMemberVisitors(classSpecification.methodSpecifications, false, multiClassVisitor, memberVisitor);
+
+        // Mark the class member in this class and in super classes.
+        return new ClassHierarchyTraveler(true, true, false, false,
+                                          multiClassVisitor);
+    }
+
+
+    /**
+     * Adds elements to the given MultiClassVisitor, to apply the given
+     * MemberVisitor to all class members that match the given List
+     * of options (of the given type).
+     */
+    private static void addMemberVisitors(List              memberSpecifications,
+                                          boolean           isField,
+                                          MultiClassVisitor multiClassVisitor,
+                                          MemberVisitor     memberVisitor)
+    {
+        if (memberSpecifications != null)
+        {
+            for (int index = 0; index < memberSpecifications.size(); index++)
+            {
+                MemberSpecification memberSpecification =
+                    (MemberSpecification)memberSpecifications.get(index);
+
+                multiClassVisitor.addClassVisitor(
+                    createClassVisitor(memberSpecification,
+                                       isField,
+                                       memberVisitor));
+            }
+        }
+    }
+
+
+    /**
+     * Constructs a ClassVisitor that conditionally applies the given
+     * ClassVisitor to all classes that contain the given class members.
+     */
+    private static ClassVisitor createClassMemberTester(ClassSpecification classSpecification,
+                                                        ClassVisitor       classVisitor)
+    {
+        // Create a linked list of conditional visitors, for fields and for
+        // methods.
+        return createClassMemberTester(classSpecification.fieldSpecifications,
+                                       true,
+               createClassMemberTester(classSpecification.methodSpecifications,
+                                       false,
+                                       classVisitor));
+    }
+
+
+    /**
+     * Constructs a ClassVisitor that conditionally applies the given
+     * ClassVisitor to all classes that contain the given List of class
+     * members (of the given type).
+     */
+    private static ClassVisitor createClassMemberTester(List         memberSpecifications,
+                                                        boolean      isField,
+                                                        ClassVisitor classVisitor)
+    {
+        // Create a linked list of conditional visitors.
+        if (memberSpecifications != null)
+        {
+            for (int index = 0; index < memberSpecifications.size(); index++)
+            {
+                MemberSpecification memberSpecification =
+                    (MemberSpecification)memberSpecifications.get(index);
+
+                classVisitor =
+                    createClassVisitor(memberSpecification,
+                                       isField,
+                                       new MemberToClassVisitor(classVisitor));
+            }
+        }
+
+        return classVisitor;
+    }
+
+
+    /**
+     * Creates a new ClassVisitor to efficiently travel to the specified class
+     * members.
+     *
+     * @param memberSpecification the specification of the class member(s) to
+     *                            visit.
+     * @param memberVisitor       the MemberVisitor to be applied to matching
+     *                            class member(s).
+     */
+    private static ClassVisitor createClassVisitor(MemberSpecification memberSpecification,
+                                                   boolean             isField,
+                                                   MemberVisitor       memberVisitor)
+    {
+        String name       = memberSpecification.name;
+        String descriptor = memberSpecification.descriptor;
+
+        // If name or descriptor are not fully specified, only visit matching
+        // class members.
+        boolean fullySpecified =
+            name       != null &&
+            descriptor != null &&
+            !containsWildCards(name) &&
+            !containsWildCards(descriptor);
+
+        if (!fullySpecified)
+        {
+            if (descriptor != null)
+            {
+                memberVisitor =
+                    new MemberDescriptorFilter(descriptor, memberVisitor);
+            }
+
+            if (name != null)
+            {
+                memberVisitor =
+                    new MemberNameFilter(name, memberVisitor);
+            }
+        }
+
+        // If specified, only visit class members with the right annotation.
+        if (memberSpecification.annotationType != null)
+        {
+            memberVisitor =
+                new AllAttributeVisitor(
+                new AllAnnotationVisitor(
+                new AnnotationTypeFilter(memberSpecification.annotationType,
+                new AnnotationToMemberVisitor(memberVisitor))));
+        }
+
+        // If any access flags are specified, only visit matching class members.
+        if (memberSpecification.requiredSetAccessFlags   != 0 ||
+            memberSpecification.requiredUnsetAccessFlags != 0)
+        {
+            memberVisitor =
+                new MemberAccessFilter(memberSpecification.requiredSetAccessFlags,
+                                       memberSpecification.requiredUnsetAccessFlags,
+                                       memberVisitor);
+        }
+
+        // Depending on what's specified, visit a single named class member,
+        // or all class members, filtering the matching ones.
+        return isField ?
+            fullySpecified ?
+                (ClassVisitor)new NamedFieldVisitor(name, descriptor, memberVisitor) :
+                (ClassVisitor)new AllFieldVisitor(memberVisitor) :
+            fullySpecified ?
+                (ClassVisitor)new NamedMethodVisitor(name, descriptor, memberVisitor) :
+                (ClassVisitor)new AllMethodVisitor(memberVisitor);
+    }
+
+
+    // Small utility methods.
+
+    private static boolean containsWildCards(String string)
+    {
+        return string != null &&
+            (string.indexOf('*')   >= 0 ||
+             string.indexOf('?')   >= 0 ||
+             string.indexOf('%')   >= 0 ||
+             string.indexOf(',')   >= 0 ||
+             string.indexOf("///") >= 0);
+    }
+}
diff --git a/src/proguard/Configuration.java b/src/proguard/Configuration.java
new file mode 100644
index 0000000..d513e2c
--- /dev/null
+++ b/src/proguard/Configuration.java
@@ -0,0 +1,321 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * The ProGuard configuration.
+ *
+ * @see ProGuard
+ *
+ * @author Eric Lafortune
+ */
+public class Configuration
+{
+    ///////////////////////////////////////////////////////////////////////////
+    // Input and output options.
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * A list of input and output entries (jars, wars, ears, zips, and directories).
+     */
+    public ClassPath programJars;
+
+    /**
+     * A list of library entries (jars, wars, ears, zips, and directories).
+     */
+    public ClassPath libraryJars;
+
+    /**
+     * Specifies whether to skip non-public library classes while reading
+     * library jars.
+     */
+    public boolean   skipNonPublicLibraryClasses      = true;
+
+    /**
+     * Specifies whether to skip non-public library class members while reading
+     * library classes.
+     */
+    public boolean   skipNonPublicLibraryClassMembers = true;
+
+    /**
+     * A list of <code>String</code>s specifying directories to be kept in
+     * the output directories or the output jars. A <code>null</code> list
+     * means no directories. An empty list means all directories. The directory
+     * names may contain "**", "*", or "?" wildcards, and they may be preceded
+     * by the "!" negator.
+     */
+    public List      keepDirectories;
+
+    /**
+     * Specifies the version number of the output classes, or 0 if the version
+     * number can be left unchanged.
+     */
+    public int       targetClassVersion;
+
+    /**
+     * Specifies the last modification time of this configuration. This time
+     * is necessary to check whether the input has to be processed. Setting it
+     * to Long.MAX_VALUE forces processing, even if the modification times
+     * of the output appear more recent than the modification times of the
+     * input.
+     */
+    public long      lastModified                     = 0L;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Keep options.
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * A list of {@link KeepClassSpecification} instances, whose class names and
+     * class member names are to be kept from shrinking, optimization, and/or
+     * obfuscation.
+     */
+    public List      keep;
+
+    /**
+     * An optional output file for listing the kept seeds.
+     * An empty file name means the standard output.
+     */
+    public File      printSeeds;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Shrinking options.
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Specifies whether the code should be shrunk.
+     */
+    public boolean   shrink                           = true;
+
+    /**
+     * An optional output file for listing the unused classes and class
+     * members. An empty file name means the standard output.
+     */
+    public File      printUsage;
+
+    /**
+     * A list of {@link ClassSpecification} instances, for which an explanation
+     * is to be printed, why they are kept in the shrinking step.
+     */
+    public List      whyAreYouKeeping;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Optimization options.
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Specifies whether the code should be optimized.
+     */
+    public boolean   optimize                         = true;
+
+    /**
+     * A list of <code>String</code>s specifying the optimizations to be
+     * performed. A <code>null</code> list means all optimizations. The
+     * optimization names may contain "*" or "?" wildcards, and they may
+     * be preceded by the "!" negator.
+     */
+    public List      optimizations;
+
+    /**
+     * Specifies the number of optimization passes.
+     */
+    public int       optimizationPasses               = 1;
+
+    /**
+     * A list of {@link ClassSpecification} instances, whose methods are
+     * assumed to have no side effects.
+     */
+    public List      assumeNoSideEffects;
+
+    /**
+     * Specifies whether the access of class members can be modified.
+     */
+    public boolean   allowAccessModification          = false;
+
+    /**
+     * Specifies whether interfaces may be merged aggressively.
+     */
+    public boolean   mergeInterfacesAggressively      = false;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Obfuscation options.
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Specifies whether the code should be obfuscated.
+     */
+    public boolean   obfuscate                        = true;
+
+    /**
+     * An optional output file for listing the obfuscation mapping.
+     * An empty file name means the standard output.
+     */
+    public File      printMapping;
+
+    /**
+     * An optional input file for reading an obfuscation mapping.
+     */
+    public File      applyMapping;
+
+    /**
+     * An optional name of a file containing obfuscated class member names.
+     */
+    public File      obfuscationDictionary;
+
+    /**
+     * An optional name of a file containing obfuscated class names.
+     */
+    public File      classObfuscationDictionary;
+
+    /**
+     * An optional name of a file containing obfuscated package names.
+     */
+    public File      packageObfuscationDictionary;
+
+    /**
+     * Specifies whether to apply aggressive name overloading on class members.
+     */
+    public boolean   overloadAggressively             = false;
+
+    /**
+     * Specifies whether to generate globally unique class member names.
+     */
+    public boolean   useUniqueClassMemberNames        = false;
+
+    /**
+     * Specifies whether obfuscated packages and classes can get mixed-case names.
+     */
+    public boolean   useMixedCaseClassNames           = true;
+
+    /**
+     * A list of <code>String</code>s specifying package names to be kept.
+     * A <code>null</code> list means no names. An empty list means all
+     * names. The package names may contain "**", "*", or "?" wildcards, and
+     * they may be preceded by the "!" negator.
+     */
+    public List      keepPackageNames;
+
+    /**
+     * An optional base package if the obfuscated package hierarchy is to be
+     * flattened, <code>null</code> otherwise.
+     */
+    public String    flattenPackageHierarchy;
+
+    /**
+     * An optional base package if the obfuscated classes are to be repackaged
+     * into a single package, <code>null</code> otherwise.
+     */
+    public String    repackageClasses;
+
+    /**
+     * A list of <code>String</code>s specifying optional attributes to be kept.
+     * A <code>null</code> list means no attributes. An empty list means all
+     * attributes. The attribute names may contain "*" or "?" wildcards, and
+     * they may be preceded by the "!" negator.
+     */
+    public List      keepAttributes;
+
+    /**
+     * An optional replacement for all SourceFile attributes.
+     */
+    public String    newSourceFileAttribute;
+
+    /**
+     * A list of <code>String</code>s specifying a filter for clases whose
+     * string constants are to be adapted, based on corresponding obfuscated
+     * class names.
+     */
+    public List      adaptClassStrings;
+
+    /**
+     * A list of <code>String</code>s specifying a filter for files whose
+     * names are to be adapted, based on corresponding obfuscated class names.
+     */
+    public List      adaptResourceFileNames;
+
+    /**
+     * A list of <code>String</code>s specifying a filter for files whose
+     * contents are to be adapted, based on obfuscated class names.
+     */
+    public List      adaptResourceFileContents;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Preverification options.
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Specifies whether the code should be preverified.
+     */
+    public boolean   preverify                        = true;
+
+    /**
+     * Specifies whether the code should be preverified for Java Micro Edition
+     * (creating StackMap attributes) instead of for Java Standard Edition
+     * (creating StackMapTable attributes).
+     */
+    public boolean   microEdition                     = false;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // General options.
+    ///////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Specifies whether to print verbose messages.
+     */
+    public boolean   verbose                          = false;
+
+    /**
+     * A list of <code>String</code>s specifying a filter for the classes for
+     * which not to print notes, if there are noteworthy potential problems.
+     * A <code>null</code> list means all classes. The class names may contain
+     * "**", "*", or "?" wildcards, and they may be preceded by the "!" negator.
+     */
+    public List      note                             = null;
+
+    /**
+     * A list of <code>String</code>s specifying a filter for the classes for
+     * which not to print warnings, if there are any problems.
+     * A <code>null</code> list means all classes. The class names may contain
+     * "**", "*", or "?" wildcards, and they may be preceded by the "!" negator.
+     */
+    public List      warn                             = null;
+
+    /**
+     * Specifies whether to ignore any warnings.
+     */
+    public boolean   ignoreWarnings                   = false;
+
+    /**
+     * An optional output file for printing out the configuration that ProGuard
+     * is using (with included files and replaced variables).
+     * An empty file name means the standard output.
+     */
+    public File      printConfiguration;
+
+    /**
+     * An optional output file for printing out the processed code in a more
+     * or less readable form. An empty file name means the standard output.
+     */
+    public File      dump;
+}
diff --git a/src/proguard/ConfigurationConstants.java b/src/proguard/ConfigurationConstants.java
new file mode 100644
index 0000000..694e006
--- /dev/null
+++ b/src/proguard/ConfigurationConstants.java
@@ -0,0 +1,121 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+/**
+ * This class provides constants for parsing and writing ProGuard configurations.
+ *
+ * @author Eric Lafortune
+ */
+class ConfigurationConstants
+{
+    public static final String OPTION_PREFIX            = "-";
+    public static final String AT_DIRECTIVE             = "@";
+    public static final String INCLUDE_DIRECTIVE        = "-include";
+    public static final String BASE_DIRECTORY_DIRECTIVE = "-basedirectory";
+
+    public static final String INJARS_OPTION       = "-injars";
+    public static final String OUTJARS_OPTION      = "-outjars";
+    public static final String LIBRARYJARS_OPTION  = "-libraryjars";
+    public static final String RESOURCEJARS_OPTION = "-resourcejars";
+
+    public static final String KEEP_OPTION                           = "-keep";
+    public static final String KEEP_CLASS_MEMBERS_OPTION             = "-keepclassmembers";
+    public static final String KEEP_CLASSES_WITH_MEMBERS_OPTION      = "-keepclasseswithmembers";
+    public static final String KEEP_NAMES_OPTION                     = "-keepnames";
+    public static final String KEEP_CLASS_MEMBER_NAMES_OPTION        = "-keepclassmembernames";
+    public static final String KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION = "-keepclasseswithmembernames";
+    public static final String ALLOW_SHRINKING_SUBOPTION             = "allowshrinking";
+    public static final String ALLOW_OPTIMIZATION_SUBOPTION          = "allowoptimization";
+    public static final String ALLOW_OBFUSCATION_SUBOPTION           = "allowobfuscation";
+    public static final String PRINT_SEEDS_OPTION                    = "-printseeds";
+
+    public static final String DONT_SHRINK_OPTION         = "-dontshrink";
+    public static final String PRINT_USAGE_OPTION         = "-printusage";
+    public static final String WHY_ARE_YOU_KEEPING_OPTION = "-whyareyoukeeping";
+
+    public static final String DONT_OPTIMIZE_OPTION                 = "-dontoptimize";
+    public static final String OPTIMIZATIONS                        = "-optimizations";
+    public static final String OPTIMIZATION_PASSES                  = "-optimizationpasses";
+    public static final String ASSUME_NO_SIDE_EFFECTS_OPTION        = "-assumenosideeffects";
+    public static final String ALLOW_ACCESS_MODIFICATION_OPTION     = "-allowaccessmodification";
+    public static final String MERGE_INTERFACES_AGGRESSIVELY_OPTION = "-mergeinterfacesaggressively";
+
+    public static final String DONT_OBFUSCATE_OPTION                  = "-dontobfuscate";
+    public static final String PRINT_MAPPING_OPTION                   = "-printmapping";
+    public static final String APPLY_MAPPING_OPTION                   = "-applymapping";
+    public static final String OBFUSCATION_DICTIONARY_OPTION          = "-obfuscationdictionary";
+    public static final String CLASS_OBFUSCATION_DICTIONARY_OPTION    = "-classobfuscationdictionary";
+    public static final String PACKAGE_OBFUSCATION_DICTIONARY_OPTION  = "-packageobfuscationdictionary";
+    public static final String OVERLOAD_AGGRESSIVELY_OPTION           = "-overloadaggressively";
+    public static final String USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION   = "-useuniqueclassmembernames";
+    public static final String DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION = "-dontusemixedcaseclassnames";
+    public static final String KEEP_PACKAGE_NAMES_OPTION              = "-keeppackagenames";
+    public static final String FLATTEN_PACKAGE_HIERARCHY_OPTION       = "-flattenpackagehierarchy";
+    public static final String REPACKAGE_CLASSES_OPTION               = "-repackageclasses";
+    public static final String DEFAULT_PACKAGE_OPTION                 = "-defaultpackage";
+    public static final String KEEP_ATTRIBUTES_OPTION                 = "-keepattributes";
+    public static final String RENAME_SOURCE_FILE_ATTRIBUTE_OPTION    = "-renamesourcefileattribute";
+    public static final String ADAPT_CLASS_STRINGS_OPTION             = "-adaptclassstrings";
+    public static final String ADAPT_RESOURCE_FILE_NAMES_OPTION       = "-adaptresourcefilenames";
+    public static final String ADAPT_RESOURCE_FILE_CONTENTS_OPTION    = "-adaptresourcefilecontents";
+
+    public static final String DONT_PREVERIFY_OPTION = "-dontpreverify";
+    public static final String MICRO_EDITION_OPTION  = "-microedition";
+
+    public static final String VERBOSE_OPTION                                    = "-verbose";
+    public static final String DONT_NOTE_OPTION                                  = "-dontnote";
+    public static final String DONT_WARN_OPTION                                  = "-dontwarn";
+    public static final String IGNORE_WARNINGS_OPTION                            = "-ignorewarnings";
+    public static final String PRINT_CONFIGURATION_OPTION                        = "-printconfiguration";
+    public static final String DUMP_OPTION                                       = "-dump";
+    public static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION       = "-dontskipnonpubliclibraryclasses";
+    public static final String DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION = "-dontskipnonpubliclibraryclassmembers";
+    public static final String TARGET_OPTION                                     = "-target";
+    public static final String KEEP_DIRECTORIES_OPTION                           = "-keepdirectories";
+    public static final String FORCE_PROCESSING_OPTION                           = "-forceprocessing";
+
+    public static final String ANY_ATTRIBUTE_KEYWORD       = "*";
+    public static final String ATTRIBUTE_SEPARATOR_KEYWORD = ",";
+
+    public static final String JAR_SEPARATOR_KEYWORD   = System.getProperty("path.separator");
+
+    public static final char OPEN_SYSTEM_PROPERTY  = '<';
+    public static final char CLOSE_SYSTEM_PROPERTY = '>';
+
+    public static final String ANNOTATION_KEYWORD         = "@";
+    public static final String NEGATOR_KEYWORD            = "!";
+    public static final String CLASS_KEYWORD              = "class";
+    public static final String ANY_CLASS_KEYWORD          = "*";
+    public static final String ANY_TYPE_KEYWORD           = "***";
+    public static final String IMPLEMENTS_KEYWORD         = "implements";
+    public static final String EXTENDS_KEYWORD            = "extends";
+    public static final String OPEN_KEYWORD               = "{";
+    public static final String ANY_CLASS_MEMBER_KEYWORD   = "*";
+    public static final String ANY_FIELD_KEYWORD          = "<fields>";
+    public static final String ANY_METHOD_KEYWORD         = "<methods>";
+    public static final String OPEN_ARGUMENTS_KEYWORD     = "(";
+    public static final String ARGUMENT_SEPARATOR_KEYWORD = ",";
+    public static final String ANY_ARGUMENTS_KEYWORD      = "...";
+    public static final String CLOSE_ARGUMENTS_KEYWORD    = ")";
+    public static final String SEPARATOR_KEYWORD          = ";";
+    public static final String CLOSE_KEYWORD              = "}";
+}
diff --git a/src/proguard/ConfigurationParser.java b/src/proguard/ConfigurationParser.java
new file mode 100644
index 0000000..e01809e
--- /dev/null
+++ b/src/proguard/ConfigurationParser.java
@@ -0,0 +1,1257 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.ClassConstants;
+import proguard.classfile.util.ClassUtil;
+import proguard.util.ListUtil;
+
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+
+
+/**
+ * This class parses ProGuard configurations. Configurations can be read from an
+ * array of arguments or from a configuration file or URL.
+ *
+ * @author Eric Lafortune
+ */
+public class ConfigurationParser
+{
+    private WordReader reader;
+    private String     nextWord;
+    private String     lastComments;
+
+
+    /**
+     * Creates a new ConfigurationParser for the given String arguments.
+     */
+    public ConfigurationParser(String[] args) throws IOException
+    {
+        this(args, null);
+    }
+
+
+    /**
+     * Creates a new ConfigurationParser for the given String arguments,
+     * with the given base directory.
+     */
+    public ConfigurationParser(String[] args,
+                               File     baseDir) throws IOException
+    {
+        reader = new ArgumentWordReader(args, baseDir);
+
+        readNextWord();
+    }
+
+
+    /**
+     * Creates a new ConfigurationParser for the given file.
+     */
+    public ConfigurationParser(File file) throws IOException
+    {
+        reader = new FileWordReader(file);
+
+        readNextWord();
+    }
+
+
+    /**
+     * Creates a new ConfigurationParser for the given URL.
+     */
+    public ConfigurationParser(URL url) throws IOException
+    {
+        reader = new FileWordReader(url);
+
+        readNextWord();
+    }
+
+
+    /**
+     * Parses and returns the configuration.
+     * @param configuration the configuration that is updated as a side-effect.
+     * @throws ParseException if the any of the configuration settings contains
+     *                        a syntax error.
+     * @throws IOException if an IO error occurs while reading a configuration.
+     */
+    public void parse(Configuration configuration)
+    throws ParseException, IOException
+    {
+        while (nextWord != null)
+        {
+            lastComments = reader.lastComments();
+
+            // First include directives.
+            if      (ConfigurationConstants.AT_DIRECTIVE                                     .startsWith(nextWord) ||
+                     ConfigurationConstants.INCLUDE_DIRECTIVE                                .startsWith(nextWord)) configuration.lastModified                     = parseIncludeArgument(configuration.lastModified);
+            else if (ConfigurationConstants.BASE_DIRECTORY_DIRECTIVE                         .startsWith(nextWord)) parseBaseDirectoryArgument();
+
+            // Then configuration options with or without arguments.
+            else if (ConfigurationConstants.INJARS_OPTION                                    .startsWith(nextWord)) configuration.programJars                      = parseClassPathArgument(configuration.programJars, false);
+            else if (ConfigurationConstants.OUTJARS_OPTION                                   .startsWith(nextWord)) configuration.programJars                      = parseClassPathArgument(configuration.programJars, true);
+            else if (ConfigurationConstants.LIBRARYJARS_OPTION                               .startsWith(nextWord)) configuration.libraryJars                      = parseClassPathArgument(configuration.libraryJars, false);
+            else if (ConfigurationConstants.RESOURCEJARS_OPTION                              .startsWith(nextWord)) throw new ParseException("The '-resourcejars' option is no longer supported. Please use the '-injars' option for all input");
+            else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION      .startsWith(nextWord)) configuration.skipNonPublicLibraryClasses      = parseNoArgument(false);
+            else if (ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION.startsWith(nextWord)) configuration.skipNonPublicLibraryClassMembers = parseNoArgument(false);
+            else if (ConfigurationConstants.TARGET_OPTION                                    .startsWith(nextWord)) configuration.targetClassVersion               = parseClassVersion();
+            else if (ConfigurationConstants.FORCE_PROCESSING_OPTION                          .startsWith(nextWord)) configuration.lastModified                     = parseNoArgument(Long.MAX_VALUE);
+
+            else if (ConfigurationConstants.KEEP_OPTION                                      .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, true,  false, false);
+            else if (ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION                        .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, false, false, false);
+            else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION                 .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, false, true,  false);
+            else if (ConfigurationConstants.KEEP_NAMES_OPTION                                .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, true,  false, true);
+            else if (ConfigurationConstants.KEEP_CLASS_MEMBER_NAMES_OPTION                   .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, false, false, true);
+            else if (ConfigurationConstants.KEEP_CLASSES_WITH_MEMBER_NAMES_OPTION            .startsWith(nextWord)) configuration.keep                             = parseKeepClassSpecificationArguments(configuration.keep, false, true,  true);
+            else if (ConfigurationConstants.PRINT_SEEDS_OPTION                               .startsWith(nextWord)) configuration.printSeeds                       = parseOptionalFile();
+
+            // After '-keep'.
+            else if (ConfigurationConstants.KEEP_DIRECTORIES_OPTION                          .startsWith(nextWord)) configuration.keepDirectories                  = parseCommaSeparatedList("directory name", true, true, false, false, true, false, false, configuration.keepDirectories);
+
+            else if (ConfigurationConstants.DONT_SHRINK_OPTION                               .startsWith(nextWord)) configuration.shrink                           = parseNoArgument(false);
+            else if (ConfigurationConstants.PRINT_USAGE_OPTION                               .startsWith(nextWord)) configuration.printUsage                       = parseOptionalFile();
+            else if (ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION                       .startsWith(nextWord)) configuration.whyAreYouKeeping                 = parseClassSpecificationArguments(configuration.whyAreYouKeeping);
+
+            else if (ConfigurationConstants.DONT_OPTIMIZE_OPTION                             .startsWith(nextWord)) configuration.optimize                         = parseNoArgument(false);
+            else if (ConfigurationConstants.OPTIMIZATION_PASSES                              .startsWith(nextWord)) configuration.optimizationPasses               = parseIntegerArgument();
+            else if (ConfigurationConstants.OPTIMIZATIONS                                    .startsWith(nextWord)) configuration.optimizations                    = parseCommaSeparatedList("optimization name", true, false, false, false, false, false, false, configuration.optimizations);
+            else if (ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION                    .startsWith(nextWord)) configuration.assumeNoSideEffects              = parseClassSpecificationArguments(configuration.assumeNoSideEffects);
+            else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION                 .startsWith(nextWord)) configuration.allowAccessModification          = parseNoArgument(true);
+            else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION             .startsWith(nextWord)) configuration.mergeInterfacesAggressively      = parseNoArgument(true);
+
+            else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION                            .startsWith(nextWord)) configuration.obfuscate                        = parseNoArgument(false);
+            else if (ConfigurationConstants.PRINT_MAPPING_OPTION                             .startsWith(nextWord)) configuration.printMapping                     = parseOptionalFile();
+            else if (ConfigurationConstants.APPLY_MAPPING_OPTION                             .startsWith(nextWord)) configuration.applyMapping                     = parseFile();
+            else if (ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION                    .startsWith(nextWord)) configuration.obfuscationDictionary            = parseFile();
+            else if (ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION              .startsWith(nextWord)) configuration.classObfuscationDictionary       = parseFile();
+            else if (ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION            .startsWith(nextWord)) configuration.packageObfuscationDictionary     = parseFile();
+            else if (ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION                     .startsWith(nextWord)) configuration.overloadAggressively             = parseNoArgument(true);
+            else if (ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION             .startsWith(nextWord)) configuration.useUniqueClassMemberNames        = parseNoArgument(true);
+            else if (ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION           .startsWith(nextWord)) configuration.useMixedCaseClassNames           = parseNoArgument(false);
+            else if (ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION                        .startsWith(nextWord)) configuration.keepPackageNames                 = parseCommaSeparatedList("package name", true, true, false, true, false, true, false, configuration.keepPackageNames);
+            else if (ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION                 .startsWith(nextWord)) configuration.flattenPackageHierarchy          = ClassUtil.internalClassName(parseOptionalArgument());
+            else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION                         .startsWith(nextWord)) configuration.repackageClasses                 = ClassUtil.internalClassName(parseOptionalArgument());
+            else if (ConfigurationConstants.DEFAULT_PACKAGE_OPTION                           .startsWith(nextWord)) configuration.repackageClasses                 = ClassUtil.internalClassName(parseOptionalArgument());
+            else if (ConfigurationConstants.KEEP_ATTRIBUTES_OPTION                           .startsWith(nextWord)) configuration.keepAttributes                   = parseCommaSeparatedList("attribute name", true, true, false, true, false, false, false, configuration.keepAttributes);
+            else if (ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION              .startsWith(nextWord)) configuration.newSourceFileAttribute           = parseOptionalArgument();
+            else if (ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION                       .startsWith(nextWord)) configuration.adaptClassStrings                = parseCommaSeparatedList("class name", true, true, false, true, false, true, false, configuration.adaptClassStrings);
+            else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION                 .startsWith(nextWord)) configuration.adaptResourceFileNames           = parseCommaSeparatedList("resource file name", true, true, false, false, false, false, false, configuration.adaptResourceFileNames);
+            else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION              .startsWith(nextWord)) configuration.adaptResourceFileContents        = parseCommaSeparatedList("resource file name", true, true, false, false, false, false, false, configuration.adaptResourceFileContents);
+
+            else if (ConfigurationConstants.DONT_PREVERIFY_OPTION                            .startsWith(nextWord)) configuration.preverify                        = parseNoArgument(false);
+            else if (ConfigurationConstants.MICRO_EDITION_OPTION                             .startsWith(nextWord)) configuration.microEdition                     = parseNoArgument(true);
+
+            else if (ConfigurationConstants.VERBOSE_OPTION                                   .startsWith(nextWord)) configuration.verbose                          = parseNoArgument(true);
+            else if (ConfigurationConstants.DONT_NOTE_OPTION                                 .startsWith(nextWord)) configuration.note                             = parseCommaSeparatedList("class name", true, true, false, true, false, true, false, configuration.note);
+            else if (ConfigurationConstants.DONT_WARN_OPTION                                 .startsWith(nextWord)) configuration.warn                             = parseCommaSeparatedList("class name", true, true, false, true, false, true, false, configuration.warn);
+            else if (ConfigurationConstants.IGNORE_WARNINGS_OPTION                           .startsWith(nextWord)) configuration.ignoreWarnings                   = parseNoArgument(true);
+            else if (ConfigurationConstants.PRINT_CONFIGURATION_OPTION                       .startsWith(nextWord)) configuration.printConfiguration               = parseOptionalFile();
+            else if (ConfigurationConstants.DUMP_OPTION                                      .startsWith(nextWord)) configuration.dump                             = parseOptionalFile();
+            else
+            {
+                throw new ParseException("Unknown option " + reader.locationDescription());
+            }
+        }
+    }
+
+
+
+    /**
+     * Closes the configuration.
+     * @throws IOException if an IO error occurs while closing the configuration.
+     */
+    public void close() throws IOException
+    {
+        if (reader != null)
+        {
+            reader.close();
+        }
+    }
+
+
+    private long parseIncludeArgument(long lastModified) throws ParseException, IOException
+    {
+        // Read the configuation file name.
+        readNextWord("configuration file name");
+
+        File file = file(nextWord);
+        reader.includeWordReader(new FileWordReader(file));
+
+        readNextWord();
+
+        return Math.max(lastModified, file.lastModified());
+    }
+
+
+    private void parseBaseDirectoryArgument() throws ParseException, IOException
+    {
+        // Read the base directory name.
+        readNextWord("base directory name");
+
+        reader.setBaseDir(file(nextWord));
+
+        readNextWord();
+    }
+
+
+    private ClassPath parseClassPathArgument(ClassPath classPath,
+                                             boolean   isOutput)
+    throws ParseException, IOException
+    {
+        // Create a new List if necessary.
+        if (classPath == null)
+        {
+            classPath = new ClassPath();
+        }
+
+        while (true)
+        {
+            // Read the next jar name.
+            readNextWord("jar or directory name");
+
+            // Create a new class path entry.
+            ClassPathEntry entry = new ClassPathEntry(file(nextWord), isOutput);
+
+            // Read the opening parenthesis or the separator, if any.
+            readNextWord();
+
+            // Read the optional filters.
+            if (!configurationEnd() &&
+                ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord))
+            {
+                // Read all filters in an array.
+                List[] filters = new List[5];
+
+                int counter = 0;
+                do
+                {
+                    // Read the filter.
+                    filters[counter++] =
+                        parseCommaSeparatedList("filter", true, false, true, false, true, false, false, null);
+                }
+                while (counter < filters.length &&
+                       ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord));
+
+                // Make sure there is a closing parenthesis.
+                if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord))
+                {
+                    throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
+                                             "' or '" + ConfigurationConstants.SEPARATOR_KEYWORD +
+                                             "', or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
+                                             "' before " + reader.locationDescription());
+                }
+
+                // Set all filters from the array on the entry.
+                entry.setFilter(filters[--counter]);
+                if (counter > 0)
+                {
+                    entry.setJarFilter(filters[--counter]);
+                    if (counter > 0)
+                    {
+                        entry.setWarFilter(filters[--counter]);
+                        if (counter > 0)
+                        {
+                            entry.setEarFilter(filters[--counter]);
+                            if (counter > 0)
+                            {
+                                entry.setZipFilter(filters[--counter]);
+                            }
+                        }
+                    }
+                }
+
+                // Read the separator, if any.
+                readNextWord();
+            }
+
+            // Add the entry to the list.
+            classPath.add(entry);
+
+            if (configurationEnd())
+            {
+                return classPath;
+            }
+
+            if (!nextWord.equals(ConfigurationConstants.JAR_SEPARATOR_KEYWORD))
+            {
+                throw new ParseException("Expecting class path separator '" + ConfigurationConstants.JAR_SEPARATOR_KEYWORD +
+                                         "' before " + reader.locationDescription());
+            }
+        }
+    }
+
+
+    private int parseClassVersion()
+    throws ParseException, IOException
+    {
+        // Read the obligatory target.
+        readNextWord("java version");
+
+        int classVersion = ClassUtil.internalClassVersion(nextWord);
+        if (classVersion == 0)
+        {
+            throw new ParseException("Unsupported java version " + reader.locationDescription());
+        }
+
+        readNextWord();
+
+        return classVersion;
+    }
+
+
+    private int parseIntegerArgument()
+    throws ParseException, IOException
+    {
+        try
+        {
+            // Read the obligatory integer.
+            readNextWord("integer");
+
+            int integer = Integer.parseInt(nextWord);
+
+            readNextWord();
+
+            return integer;
+        }
+        catch (NumberFormatException e)
+        {
+            throw new ParseException("Expecting integer argument instead of '" + nextWord +
+                                     "' before " + reader.locationDescription());
+        }
+    }
+
+
+    private File parseFile()
+    throws ParseException, IOException
+    {
+        // Read the obligatory file name.
+        readNextWord("file name");
+
+        // Make sure the file is properly resolved.
+        File file = file(nextWord);
+
+        readNextWord();
+
+        return file;
+    }
+
+
+    private File parseOptionalFile()
+    throws ParseException, IOException
+    {
+        // Read the optional file name.
+        readNextWord();
+
+        // Didn't the user specify a file name?
+        if (configurationEnd())
+        {
+            return new File("");
+        }
+
+        // Make sure the file is properly resolved.
+        File file = file(nextWord);
+
+        readNextWord();
+
+        return file;
+    }
+
+
+    private String parseOptionalArgument() throws IOException
+    {
+        // Read the optional argument.
+        readNextWord();
+
+        // Didn't the user specify an argument?
+        if (configurationEnd())
+        {
+            return "";
+        }
+
+        String fileName = nextWord;
+
+        readNextWord();
+
+        return fileName;
+    }
+
+
+    private boolean parseNoArgument(boolean value) throws IOException
+    {
+        readNextWord();
+
+        return value;
+    }
+
+
+    private long parseNoArgument(long value) throws IOException
+    {
+        readNextWord();
+
+        return value;
+    }
+
+
+    private List parseKeepClassSpecificationArguments(List    keepClassSpecifications,
+                                                      boolean markClasses,
+                                                      boolean markConditionally,
+                                                      boolean allowShrinking)
+    throws ParseException, IOException
+    {
+        // Create a new List if necessary.
+        if (keepClassSpecifications == null)
+        {
+            keepClassSpecifications = new ArrayList();
+        }
+
+        //boolean allowShrinking    = false;
+        boolean allowOptimization = false;
+        boolean allowObfuscation  = false;
+
+        // Read the keep modifiers.
+        while (true)
+        {
+            readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
+                         "', '"      + ClassConstants.EXTERNAL_ACC_INTERFACE +
+                         "', or '"   + ClassConstants.EXTERNAL_ACC_ENUM + "'", true);
+
+            if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord))
+            {
+                // Not a comma. Stop parsing the keep modifiers.
+                break;
+            }
+
+            readNextWord("keyword '" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION +
+                         "', '"      + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION +
+                         "', or '"   + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION + "'");
+
+            if      (ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION   .startsWith(nextWord))
+            {
+                allowShrinking    = true;
+            }
+            else if (ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION.startsWith(nextWord))
+            {
+                allowOptimization = true;
+            }
+            else if (ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION .startsWith(nextWord))
+            {
+                allowObfuscation  = true;
+            }
+            else
+            {
+                throw new ParseException("Expecting keyword '" + ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION +
+                                         "', '"                + ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION +
+                                         "', or '"             + ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION +
+                                         "' before " + reader.locationDescription());
+            }
+        }
+
+        // Read the class configuration.
+        ClassSpecification classSpecification =
+            parseClassSpecificationArguments();
+
+        // Create and add the keep configuration.
+        keepClassSpecifications.add(new KeepClassSpecification(markClasses,
+                                                               markConditionally,
+                                                               allowShrinking,
+                                                               allowOptimization,
+                                                               allowObfuscation,
+                                                               classSpecification));
+        return keepClassSpecifications;
+    }
+
+
+    private List parseClassSpecificationArguments(List classSpecifications)
+    throws ParseException, IOException
+    {
+        // Create a new List if necessary.
+        if (classSpecifications == null)
+        {
+            classSpecifications = new ArrayList();
+        }
+
+        // Read and add the class configuration.
+        readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
+                     "', '"      + ClassConstants.EXTERNAL_ACC_INTERFACE +
+                     "', or '"   + ClassConstants.EXTERNAL_ACC_ENUM + "'", true);
+
+        classSpecifications.add(parseClassSpecificationArguments());
+
+        return classSpecifications;
+    }
+
+
+    private ClassSpecification parseClassSpecificationArguments()
+    throws ParseException, IOException
+    {
+        // Clear the annotation type.
+        String annotationType = null;
+
+        // Clear the class access modifiers.
+        int requiredSetClassAccessFlags   = 0;
+        int requiredUnsetClassAccessFlags = 0;
+
+        // Parse the class annotations and access modifiers until the class keyword.
+        while (!ConfigurationConstants.CLASS_KEYWORD.equals(nextWord))
+        {
+            // Parse the annotation type, if any.
+//            if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
+//            {
+//                annotationType =
+//                    ClassUtil.internalType(
+//                    ListUtil.commaSeparatedString(
+//                    parseCommaSeparatedList("annotation type",
+//                                            true, false, false, true, false, null)));
+//
+//                continue;
+//            }
+
+            // Strip the negating sign, if any.
+            String strippedWord = nextWord.startsWith(ConfigurationConstants.NEGATOR_KEYWORD) ?
+                nextWord.substring(1) :
+                nextWord;
+
+            // Parse the class access modifiers.
+            // TODO: Distinguish annotation from annotation modifier.
+            int accessFlag =
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_PUBLIC)     ? ClassConstants.INTERNAL_ACC_PUBLIC      :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL)      ? ClassConstants.INTERNAL_ACC_FINAL       :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE)  ? ClassConstants.INTERNAL_ACC_INTERFACE   :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT)   ? ClassConstants.INTERNAL_ACC_ABSTRACT    :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_ANNOTATION) ? ClassConstants.INTERNAL_ACC_ANNOTATTION :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)       ? ClassConstants.INTERNAL_ACC_ENUM        :
+                                                                              unknownAccessFlag();
+
+            // Is it an annotation modifier?
+            if (accessFlag == ClassConstants.INTERNAL_ACC_ANNOTATTION)
+            {
+                // Is the next word actually an annotation type?
+                readNextWord("annotation type or keyword '" + ClassConstants.EXTERNAL_ACC_INTERFACE + "'", false);
+
+                if (!nextWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) &&
+                    !nextWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)      &&
+                    !nextWord.equals(ConfigurationConstants.CLASS_KEYWORD))
+                {
+                    // Parse the annotation type.
+                    annotationType =
+                        ListUtil.commaSeparatedString(
+                        parseCommaSeparatedList("annotation type",
+                                                false, false, false, true, false, false, true, null));
+
+                    continue;
+                }
+            }
+
+            if (strippedWord.equals(nextWord))
+            {
+                requiredSetClassAccessFlags   |= accessFlag;
+            }
+            else
+            {
+                requiredUnsetClassAccessFlags |= accessFlag;
+            }
+
+
+            if ((requiredSetClassAccessFlags &
+                 requiredUnsetClassAccessFlags) != 0)
+            {
+                throw new ParseException("Conflicting class access modifiers for '" + strippedWord +
+                                         "' before " + reader.locationDescription());
+            }
+
+            if (strippedWord.equals(ClassConstants.EXTERNAL_ACC_INTERFACE) ||
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_ENUM)      ||
+                strippedWord.equals(ConfigurationConstants.CLASS_KEYWORD))
+            {
+                // The interface or enum keyword. Stop parsing the class flags.
+                break;
+            }
+
+            readNextWord("keyword '" + ConfigurationConstants.CLASS_KEYWORD +
+                         "', '"      + ClassConstants.EXTERNAL_ACC_INTERFACE +
+                         "', or '"   + ClassConstants.EXTERNAL_ACC_ENUM + "'", true);
+        }
+
+       // Parse the class name part.
+        String externalClassName =
+            ListUtil.commaSeparatedString(
+            parseCommaSeparatedList("class name or interface name",
+                                    true, false, false, true, false, false, false, null));
+
+        // For backward compatibility, allow a single "*" wildcard to match any
+        // class.
+        String className = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalClassName) ?
+            null :
+            ClassUtil.internalClassName(externalClassName);
+
+        // Clear the annotation type and the class name of the extends part.
+        String extendsAnnotationType = null;
+        String extendsClassName      = null;
+
+        if (!configurationEnd())
+        {
+            // Parse 'implements ...' or 'extends ...' part, if any.
+            if (ConfigurationConstants.IMPLEMENTS_KEYWORD.equals(nextWord) ||
+                ConfigurationConstants.EXTENDS_KEYWORD.equals(nextWord))
+            {
+                readNextWord("class name or interface name", true);
+
+                // Parse the annotation type, if any.
+                if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
+                {
+                    extendsAnnotationType =
+                        ListUtil.commaSeparatedString(
+                        parseCommaSeparatedList("annotation type",
+                                                true, false, false, true, false, false, true, null));
+                }
+
+                String externalExtendsClassName =
+                    ListUtil.commaSeparatedString(
+                    parseCommaSeparatedList("class name or interface name",
+                                            false, false, false, true, false, false, false, null));
+
+                extendsClassName = ConfigurationConstants.ANY_CLASS_KEYWORD.equals(externalExtendsClassName) ?
+                    null :
+                    ClassUtil.internalClassName(externalExtendsClassName);
+            }
+        }
+
+        // Create the basic class specification.
+        ClassSpecification classSpecification =
+            new ClassSpecification(lastComments,
+                                   requiredSetClassAccessFlags,
+                                   requiredUnsetClassAccessFlags,
+                                   annotationType,
+                                   className,
+                                   extendsAnnotationType,
+                                   extendsClassName);
+
+
+        // Now add any class members to this class specification.
+        if (!configurationEnd())
+        {
+            // Check the class member opening part.
+            if (!ConfigurationConstants.OPEN_KEYWORD.equals(nextWord))
+            {
+                throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_KEYWORD +
+                                         "' at " + reader.locationDescription());
+            }
+
+            // Parse all class members.
+            while (true)
+            {
+                readNextWord("class member description" +
+                             " or closing '" + ConfigurationConstants.CLOSE_KEYWORD + "'", true);
+
+                if (nextWord.equals(ConfigurationConstants.CLOSE_KEYWORD))
+                {
+                    // The closing brace. Stop parsing the class members.
+                    readNextWord();
+
+                    break;
+                }
+
+                parseMemberSpecificationArguments(externalClassName,
+                                                  classSpecification);
+            }
+        }
+
+        return classSpecification;
+    }
+
+
+    private void parseMemberSpecificationArguments(String             externalClassName,
+                                                   ClassSpecification classSpecification)
+    throws ParseException, IOException
+    {
+        // Clear the annotation name.
+        String annotationType = null;
+
+        // Parse the class member access modifiers, if any.
+        int requiredSetMemberAccessFlags   = 0;
+        int requiredUnsetMemberAccessFlags = 0;
+
+        while (!configurationEnd(true))
+        {
+            // Parse the annotation type, if any.
+            if (ConfigurationConstants.ANNOTATION_KEYWORD.equals(nextWord))
+            {
+                annotationType =
+                    ListUtil.commaSeparatedString(
+                    parseCommaSeparatedList("annotation type",
+                                            true, false, false, true, false, false, true, null));
+
+                continue;
+            }
+
+            String strippedWord = nextWord.startsWith("!") ?
+                nextWord.substring(1) :
+                nextWord;
+
+            // Parse the class member access modifiers.
+            int accessFlag =
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_PUBLIC)       ? ClassConstants.INTERNAL_ACC_PUBLIC       :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_PRIVATE)      ? ClassConstants.INTERNAL_ACC_PRIVATE      :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_PROTECTED)    ? ClassConstants.INTERNAL_ACC_PROTECTED    :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_STATIC)       ? ClassConstants.INTERNAL_ACC_STATIC       :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_FINAL)        ? ClassConstants.INTERNAL_ACC_FINAL        :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED) ? ClassConstants.INTERNAL_ACC_SYNCHRONIZED :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_VOLATILE)     ? ClassConstants.INTERNAL_ACC_VOLATILE     :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_TRANSIENT)    ? ClassConstants.INTERNAL_ACC_TRANSIENT    :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_NATIVE)       ? ClassConstants.INTERNAL_ACC_NATIVE       :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT)     ? ClassConstants.INTERNAL_ACC_ABSTRACT     :
+                strippedWord.equals(ClassConstants.EXTERNAL_ACC_STRICT)       ? ClassConstants.INTERNAL_ACC_STRICT       :
+                                                                                0;
+            if (accessFlag == 0)
+            {
+                // Not a class member access modifier. Stop parsing them.
+                break;
+            }
+
+            if (strippedWord.equals(nextWord))
+            {
+                requiredSetMemberAccessFlags   |= accessFlag;
+            }
+            else
+            {
+                requiredUnsetMemberAccessFlags |= accessFlag;
+            }
+
+            // Make sure the user doesn't try to set and unset the same
+            // access flags simultaneously.
+            if ((requiredSetMemberAccessFlags &
+                 requiredUnsetMemberAccessFlags) != 0)
+            {
+                throw new ParseException("Conflicting class member access modifiers for " +
+                                         reader.locationDescription());
+            }
+
+            readNextWord("class member description");
+        }
+
+        // Parse the class member type and name part.
+
+        // Did we get a special wildcard?
+        if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord) ||
+            ConfigurationConstants.ANY_FIELD_KEYWORD       .equals(nextWord) ||
+            ConfigurationConstants.ANY_METHOD_KEYWORD      .equals(nextWord))
+        {
+            // Act according to the type of wildcard..
+            if (ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD.equals(nextWord))
+            {
+                checkFieldAccessFlags(requiredSetMemberAccessFlags,
+                                      requiredUnsetMemberAccessFlags);
+                checkMethodAccessFlags(requiredSetMemberAccessFlags,
+                                       requiredUnsetMemberAccessFlags);
+
+                classSpecification.addField(
+                    new MemberSpecification(requiredSetMemberAccessFlags,
+                                            requiredUnsetMemberAccessFlags,
+                                            annotationType,
+                                            null,
+                                            null));
+                classSpecification.addMethod(
+                    new MemberSpecification(requiredSetMemberAccessFlags,
+                                            requiredUnsetMemberAccessFlags,
+                                            annotationType,
+                                            null,
+                                            null));
+            }
+            else if (ConfigurationConstants.ANY_FIELD_KEYWORD.equals(nextWord))
+            {
+                checkFieldAccessFlags(requiredSetMemberAccessFlags,
+                                      requiredUnsetMemberAccessFlags);
+
+                classSpecification.addField(
+                    new MemberSpecification(requiredSetMemberAccessFlags,
+                                            requiredUnsetMemberAccessFlags,
+                                            annotationType,
+                                            null,
+                                            null));
+            }
+            else if (ConfigurationConstants.ANY_METHOD_KEYWORD.equals(nextWord))
+            {
+                checkMethodAccessFlags(requiredSetMemberAccessFlags,
+                                       requiredUnsetMemberAccessFlags);
+
+                classSpecification.addMethod(
+                    new MemberSpecification(requiredSetMemberAccessFlags,
+                                            requiredUnsetMemberAccessFlags,
+                                            annotationType,
+                                            null,
+                                            null));
+            }
+
+            // We still have to read the closing separator.
+            readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
+
+            if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
+            {
+                throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
+                                         "' before " + reader.locationDescription());
+            }
+        }
+        else
+        {
+            // Make sure we have a proper type.
+            checkJavaIdentifier("java type");
+            String type = nextWord;
+
+            readNextWord("class member name");
+            String name = nextWord;
+
+            // Did we get just one word before the opening parenthesis?
+            if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(name))
+            {
+                // This must be a constructor then.
+                // Make sure the type is a proper constructor name.
+                if (!(type.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ||
+                      type.equals(externalClassName) ||
+                      type.equals(ClassUtil.externalShortClassName(externalClassName))))
+                {
+                    throw new ParseException("Expecting type and name " +
+                                             "instead of just '" + type +
+                                             "' before " + reader.locationDescription());
+                }
+
+                // Assign the fixed constructor type and name.
+                type = ClassConstants.EXTERNAL_TYPE_VOID;
+                name = ClassConstants.INTERNAL_METHOD_NAME_INIT;
+            }
+            else
+            {
+                // It's not a constructor.
+                // Make sure we have a proper name.
+                checkJavaIdentifier("class member name");
+
+                // Read the opening parenthesis or the separating
+                // semi-colon.
+                readNextWord("opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD +
+                             "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
+            }
+
+            // Are we looking at a field, a method, or something else?
+            if (ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
+            {
+                // It's a field.
+                checkFieldAccessFlags(requiredSetMemberAccessFlags,
+                                      requiredUnsetMemberAccessFlags);
+
+                // We already have a field descriptor.
+                String descriptor = ClassUtil.internalType(type);
+
+                // Add the field.
+                classSpecification.addField(
+                    new MemberSpecification(requiredSetMemberAccessFlags,
+                                            requiredUnsetMemberAccessFlags,
+                                            annotationType,
+                                            name,
+                                            descriptor));
+            }
+            else if (ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD.equals(nextWord))
+            {
+                // It's a method.
+                checkMethodAccessFlags(requiredSetMemberAccessFlags,
+                                       requiredUnsetMemberAccessFlags);
+
+                // Parse the method arguments.
+                String descriptor =
+                    ClassUtil.internalMethodDescriptor(type,
+                                                       parseCommaSeparatedList("argument", true, true, true, true, false, false, false, null));
+
+                if (!ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord))
+                {
+                    throw new ParseException("Expecting separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
+                                             "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
+                                             "' before " + reader.locationDescription());
+                }
+
+                // Read the separator after the closing parenthesis.
+                readNextWord("separator '" + ConfigurationConstants.SEPARATOR_KEYWORD + "'");
+
+                if (!ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord))
+                {
+                    throw new ParseException("Expecting separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
+                                             "' before " + reader.locationDescription());
+                }
+
+                // Add the method.
+                classSpecification.addMethod(
+                    new MemberSpecification(requiredSetMemberAccessFlags,
+                                            requiredUnsetMemberAccessFlags,
+                                            annotationType,
+                                            name,
+                                            descriptor));
+            }
+            else
+            {
+                // It doesn't look like a field or a method.
+                throw new ParseException("Expecting opening '" + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD +
+                                         "' or separator '" + ConfigurationConstants.SEPARATOR_KEYWORD +
+                                         "' before " + reader.locationDescription());
+            }
+        }
+    }
+
+
+    /**
+     * Reads a comma-separated list of java identifiers or of file names. If an
+     * empty list is allowed, the reading will end after a closing parenthesis
+     * or semi-colon.
+     */
+    private List parseCommaSeparatedList(String  expectedDescription,
+                                         boolean readFirstWord,
+                                         boolean allowEmptyList,
+                                         boolean expectClosingParenthesis,
+                                         boolean checkJavaIdentifiers,
+                                         boolean replaceSystemProperties,
+                                         boolean replaceExternalClassNames,
+                                         boolean replaceExternalTypes,
+                                         List    list)
+    throws ParseException, IOException
+    {
+        if (list == null)
+        {
+            list = new ArrayList();
+        }
+
+        if (readFirstWord)
+        {
+            if (expectClosingParenthesis || !allowEmptyList)
+            {
+                // Read the first list entry.
+                readNextWord(expectedDescription);
+            }
+            else
+            {
+                // Read the first list entry, if there is any.
+                readNextWord();
+
+                // Check if the list is empty.
+                if (configurationEnd() ||
+                    nextWord.equals(ConfigurationConstants.ANY_ATTRIBUTE_KEYWORD))
+                {
+                    return list;
+                }
+            }
+        }
+
+        while (true)
+        {
+            if (expectClosingParenthesis &&
+                list.size() == 0         &&
+                (ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD.equals(nextWord) ||
+                 ConfigurationConstants.SEPARATOR_KEYWORD.equals(nextWord)))
+            {
+                break;
+            }
+
+            if (checkJavaIdentifiers)
+            {
+                checkJavaIdentifier("java type");
+            }
+
+            if (replaceSystemProperties)
+            {
+                nextWord = replaceSystemProperties(nextWord);
+            }
+
+            if (replaceExternalClassNames)
+            {
+                nextWord = ClassUtil.internalClassName(nextWord);
+            }
+
+            if (replaceExternalTypes)
+            {
+                nextWord = ClassUtil.internalType(nextWord);
+            }
+
+            list.add(nextWord);
+
+            if (expectClosingParenthesis)
+            {
+                // Read a comma (or a closing parenthesis, or a different word).
+                readNextWord("separating '" + ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
+                             "' or closing '" + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD +
+                             "'");
+            }
+            else
+            {
+                // Read a comma (or a different word).
+                readNextWord();
+            }
+
+            if (!ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD.equals(nextWord))
+            {
+                break;
+            }
+
+            // Read the next list entry.
+            readNextWord(expectedDescription);
+        }
+
+        return list;
+    }
+
+
+    /**
+     * Throws a ParseException for an unexpected keyword.
+     */
+    private int unknownAccessFlag() throws ParseException
+    {
+        throw new ParseException("Unexpected keyword " + reader.locationDescription());
+    }
+
+
+    /**
+     * Creates a properly resolved File, based on the given word.
+     */
+    private File file(String word) throws ParseException
+    {
+        String fileName = replaceSystemProperties(word);
+        File   file     = new File(fileName);
+
+        // Try to get an absolute file.
+        if (!file.isAbsolute())
+        {
+            file = new File(reader.getBaseDir(), fileName);
+        }
+
+        // Try to get a canonical representation.
+        try
+        {
+            file = file.getCanonicalFile();
+        }
+        catch (IOException ex)
+        {
+            // Just keep the original representation.
+        }
+
+        return file;
+    }
+
+
+    /**
+     * Replaces any system properties in the given word by their values
+     * (e.g. the substring "<java.home>" is replaced by its value).
+     */
+    private String replaceSystemProperties(String word) throws ParseException
+    {
+        int fromIndex = 0;
+        while (true)
+        {
+            fromIndex = word.indexOf(ConfigurationConstants.OPEN_SYSTEM_PROPERTY, fromIndex);
+            if (fromIndex < 0)
+            {
+                break;
+            }
+
+            int toIndex = word.indexOf(ConfigurationConstants.CLOSE_SYSTEM_PROPERTY, fromIndex+1);
+            if (toIndex < 0)
+            {
+                throw new ParseException("Expecting closing '" + ConfigurationConstants.CLOSE_SYSTEM_PROPERTY +
+                                         "' after opening '" + ConfigurationConstants.OPEN_SYSTEM_PROPERTY +
+                                         "' in " + reader.locationDescription());
+            }
+
+            String propertyName  = word.substring(fromIndex+1, toIndex);
+            String propertyValue = System.getProperty(propertyName);
+            if (propertyValue == null)
+            {
+                throw new ParseException("Value of system property '" + propertyName +
+                                         "' is undefined in " + reader.locationDescription());
+            }
+
+            word = word.substring(0, fromIndex) +
+                       propertyValue +
+                       word.substring(toIndex+1);
+        }
+
+        return word;
+    }
+
+
+    /**
+     * Reads the next word of the configuration in the 'nextWord' field,
+     * throwing an exception if there is no next word.
+     */
+    private void readNextWord(String expectedDescription)
+    throws ParseException, IOException
+    {
+        readNextWord(expectedDescription, false);
+    }
+
+
+    /**
+     * Reads the next word of the configuration in the 'nextWord' field,
+     * throwing an exception if there is no next word.
+     */
+    private void readNextWord(String  expectedDescription,
+                              boolean expectingAtCharacter)
+    throws ParseException, IOException
+    {
+        readNextWord();
+        if (configurationEnd(expectingAtCharacter))
+        {
+            throw new ParseException("Expecting " + expectedDescription +
+                                     " before " + reader.locationDescription());
+        }
+    }
+
+
+    /**
+     * Reads the next word of the configuration in the 'nextWord' field.
+     */
+    private void readNextWord() throws IOException
+    {
+        nextWord = reader.nextWord();
+    }
+
+
+    /**
+     * Returns whether the end of the configuration has been reached.
+     */
+    private boolean configurationEnd()
+    {
+        return configurationEnd(false);
+    }
+
+
+    /**
+     * Returns whether the end of the configuration has been reached.
+     */
+    private boolean configurationEnd(boolean expectingAtCharacter)
+    {
+        return nextWord == null ||
+               nextWord.startsWith(ConfigurationConstants.OPTION_PREFIX) ||
+               (!expectingAtCharacter &&
+                nextWord.equals(ConfigurationConstants.AT_DIRECTIVE));
+    }
+
+
+    /**
+     * Checks whether the given word is a valid Java identifier and throws
+     * a ParseException if it isn't. Wildcard characters are accepted.
+     */
+    private void checkJavaIdentifier(String expectedDescription)
+    throws ParseException
+    {
+        if (!isJavaIdentifier(nextWord))
+        {
+            throw new ParseException("Expecting " + expectedDescription +
+                                     " before " + reader.locationDescription());
+        }
+    }
+
+
+    /**
+     * Returns whether the given word is a valid Java identifier.
+     * Wildcard characters are accepted.
+     */
+    private boolean isJavaIdentifier(String aWord)
+    {
+        for (int index = 0; index < aWord.length(); index++)
+        {
+            char c = aWord.charAt(index);
+            if (!(Character.isJavaIdentifierPart(c) ||
+                  c == '.' ||
+                  c == '[' ||
+                  c == ']' ||
+                  c == '<' ||
+                  c == '>' ||
+                  c == '-' ||
+                  c == '!' ||
+                  c == '*' ||
+                  c == '?' ||
+                  c == '%'))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Checks whether the given access flags are valid field access flags,
+     * throwing a ParseException if they aren't.
+     */
+    private void checkFieldAccessFlags(int requiredSetMemberAccessFlags,
+                                       int requiredUnsetMemberAccessFlags)
+    throws ParseException
+    {
+        if (((requiredSetMemberAccessFlags |
+              requiredUnsetMemberAccessFlags) &
+            ~ClassConstants.VALID_INTERNAL_ACC_FIELD) != 0)
+        {
+            throw new ParseException("Invalid method access modifier for field before " +
+                                     reader.locationDescription());
+        }
+    }
+
+
+    /**
+     * Checks whether the given access flags are valid method access flags,
+     * throwing a ParseException if they aren't.
+     */
+    private void checkMethodAccessFlags(int requiredSetMemberAccessFlags,
+                                        int requiredUnsetMemberAccessFlags)
+    throws ParseException
+    {
+        if (((requiredSetMemberAccessFlags |
+              requiredUnsetMemberAccessFlags) &
+            ~ClassConstants.VALID_INTERNAL_ACC_METHOD) != 0)
+        {
+            throw new ParseException("Invalid field access modifier for method before " +
+                                     reader.locationDescription());
+        }
+    }
+
+
+    /**
+     * A main method for testing configuration parsing.
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            ConfigurationParser parser = new ConfigurationParser(args);
+
+            try
+            {
+                parser.parse(new Configuration());
+            }
+            catch (ParseException ex)
+            {
+                ex.printStackTrace();
+            }
+            finally
+            {
+                parser.close();
+            }
+        }
+        catch (IOException ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/ConfigurationWriter.java b/src/proguard/ConfigurationWriter.java
new file mode 100644
index 0000000..5d112d6
--- /dev/null
+++ b/src/proguard/ConfigurationWriter.java
@@ -0,0 +1,643 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.ClassConstants;
+import proguard.classfile.util.ClassUtil;
+import proguard.util.ListUtil;
+
+import java.io.*;
+import java.util.List;
+
+
+/**
+ * This class writes ProGuard configurations to a file.
+ *
+ * @author Eric Lafortune
+ */
+public class ConfigurationWriter
+{
+    private static final String[] KEEP_OPTIONS = new String[]
+    {
+        ConfigurationConstants.KEEP_OPTION,
+        ConfigurationConstants.KEEP_CLASS_MEMBERS_OPTION,
+        ConfigurationConstants.KEEP_CLASSES_WITH_MEMBERS_OPTION
+    };
+
+
+    private final PrintWriter writer;
+    private File        baseDir;
+
+
+    /**
+     * Creates a new ConfigurationWriter for the given file name.
+     */
+    public ConfigurationWriter(File configurationFile) throws IOException
+    {
+        this(new PrintWriter(new FileWriter(configurationFile)));
+
+        baseDir = configurationFile.getParentFile();
+    }
+
+
+    /**
+     * Creates a new ConfigurationWriter for the given OutputStream.
+     */
+    public ConfigurationWriter(OutputStream outputStream) throws IOException
+    {
+        this(new PrintWriter(outputStream));
+    }
+
+
+    /**
+     * Creates a new ConfigurationWriter for the given PrintWriter.
+     */
+    public ConfigurationWriter(PrintWriter writer) throws IOException
+    {
+        this.writer = writer;
+    }
+
+
+    /**
+     * Closes this ConfigurationWriter.
+     */
+    public void close() throws IOException
+    {
+        writer.close();
+    }
+
+
+    /**
+     * Writes the given configuration.
+     * @param configuration the configuration that is to be written out.
+     * @throws IOException if an IO error occurs while writing the configuration.
+     */
+    public void write(Configuration configuration) throws IOException
+    {
+        // Write the program class path (input and output entries).
+        writeJarOptions(ConfigurationConstants.INJARS_OPTION,
+                        ConfigurationConstants.OUTJARS_OPTION,
+                        configuration.programJars);
+        writer.println();
+
+        // Write the library class path (output entries only).
+        writeJarOptions(ConfigurationConstants.LIBRARYJARS_OPTION,
+                        ConfigurationConstants.LIBRARYJARS_OPTION,
+                        configuration.libraryJars);
+        writer.println();
+
+        // Write the other options.
+        writeOption(ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASSES_OPTION,       !configuration.skipNonPublicLibraryClasses);
+        writeOption(ConfigurationConstants.DONT_SKIP_NON_PUBLIC_LIBRARY_CLASS_MEMBERS_OPTION, !configuration.skipNonPublicLibraryClassMembers);
+        writeOption(ConfigurationConstants.KEEP_DIRECTORIES_OPTION,                           configuration.keepDirectories);
+        writeOption(ConfigurationConstants.TARGET_OPTION,                                     ClassUtil.externalClassVersion(configuration.targetClassVersion));
+        writeOption(ConfigurationConstants.FORCE_PROCESSING_OPTION,                           configuration.lastModified == Long.MAX_VALUE);
+
+        writeOption(ConfigurationConstants.DONT_SHRINK_OPTION, !configuration.shrink);
+        writeOption(ConfigurationConstants.PRINT_USAGE_OPTION, configuration.printUsage);
+
+        writeOption(ConfigurationConstants.DONT_OPTIMIZE_OPTION,                 !configuration.optimize);
+        writeOption(ConfigurationConstants.OPTIMIZATIONS,                        configuration.optimize ? ListUtil.commaSeparatedString(configuration.optimizations) : null);
+        writeOption(ConfigurationConstants.OPTIMIZATION_PASSES,                  configuration.optimizationPasses);
+        writeOption(ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION,     configuration.allowAccessModification);
+        writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively);
+
+        writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION,                  !configuration.obfuscate);
+        writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION,                   configuration.printMapping);
+        writeOption(ConfigurationConstants.APPLY_MAPPING_OPTION,                   configuration.applyMapping);
+        writeOption(ConfigurationConstants.OBFUSCATION_DICTIONARY_OPTION,          configuration.obfuscationDictionary);
+        writeOption(ConfigurationConstants.CLASS_OBFUSCATION_DICTIONARY_OPTION,    configuration.classObfuscationDictionary);
+        writeOption(ConfigurationConstants.PACKAGE_OBFUSCATION_DICTIONARY_OPTION,  configuration.packageObfuscationDictionary);
+        writeOption(ConfigurationConstants.OVERLOAD_AGGRESSIVELY_OPTION,           configuration.overloadAggressively);
+        writeOption(ConfigurationConstants.USE_UNIQUE_CLASS_MEMBER_NAMES_OPTION,   configuration.useUniqueClassMemberNames);
+        writeOption(ConfigurationConstants.DONT_USE_MIXED_CASE_CLASS_NAMES_OPTION, !configuration.useMixedCaseClassNames);
+        writeOption(ConfigurationConstants.KEEP_PACKAGE_NAMES_OPTION,              configuration.keepPackageNames, true);
+        writeOption(ConfigurationConstants.FLATTEN_PACKAGE_HIERARCHY_OPTION,       configuration.flattenPackageHierarchy, true);
+        writeOption(ConfigurationConstants.REPACKAGE_CLASSES_OPTION,               configuration.repackageClasses, true);
+        writeOption(ConfigurationConstants.KEEP_ATTRIBUTES_OPTION,                 configuration.keepAttributes);
+        writeOption(ConfigurationConstants.RENAME_SOURCE_FILE_ATTRIBUTE_OPTION,    configuration.newSourceFileAttribute);
+        writeOption(ConfigurationConstants.ADAPT_CLASS_STRINGS_OPTION,             configuration.adaptClassStrings, true);
+        writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_NAMES_OPTION,       configuration.adaptResourceFileNames);
+        writeOption(ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION,    configuration.adaptResourceFileContents);
+
+        writeOption(ConfigurationConstants.DONT_PREVERIFY_OPTION, !configuration.preverify);
+        writeOption(ConfigurationConstants.MICRO_EDITION_OPTION,  configuration.microEdition);
+
+        writeOption(ConfigurationConstants.VERBOSE_OPTION,             configuration.verbose);
+        writeOption(ConfigurationConstants.DONT_NOTE_OPTION,           configuration.note, true);
+        writeOption(ConfigurationConstants.DONT_WARN_OPTION,           configuration.warn, true);
+        writeOption(ConfigurationConstants.IGNORE_WARNINGS_OPTION,     configuration.ignoreWarnings);
+        writeOption(ConfigurationConstants.PRINT_CONFIGURATION_OPTION, configuration.printConfiguration);
+        writeOption(ConfigurationConstants.DUMP_OPTION,                configuration.dump);
+
+        writeOption(ConfigurationConstants.PRINT_SEEDS_OPTION,     configuration.printSeeds);
+        writer.println();
+
+        // Write the "why are you keeping" options.
+        writeOptions(ConfigurationConstants.WHY_ARE_YOU_KEEPING_OPTION, configuration.whyAreYouKeeping);
+
+        // Write the keep options.
+        writeOptions(KEEP_OPTIONS, configuration.keep);
+
+        // Write the "no side effect methods" options.
+        writeOptions(ConfigurationConstants.ASSUME_NO_SIDE_EFFECTS_OPTION, configuration.assumeNoSideEffects);
+
+        if (writer.checkError())
+        {
+            throw new IOException("Can't write configuration");
+        }
+    }
+
+
+    private void writeJarOptions(String    inputEntryOptionName,
+                                 String    outputEntryOptionName,
+                                 ClassPath classPath)
+    {
+        if (classPath != null)
+        {
+            for (int index = 0; index < classPath.size(); index++)
+            {
+                ClassPathEntry entry = classPath.get(index);
+                String optionName = entry.isOutput() ?
+                     outputEntryOptionName :
+                     inputEntryOptionName;
+
+                writer.print(optionName);
+                writer.print(' ');
+                writer.print(relativeFileName(entry.getFile()));
+
+                // Append the filters, if any.
+                boolean filtered = false;
+
+                filtered = writeFilter(filtered, entry.getZipFilter());
+                filtered = writeFilter(filtered, entry.getEarFilter());
+                filtered = writeFilter(filtered, entry.getWarFilter());
+                filtered = writeFilter(filtered, entry.getJarFilter());
+                filtered = writeFilter(filtered, entry.getFilter());
+
+                if (filtered)
+                {
+                    writer.print(ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD);
+                }
+
+                writer.println();
+            }
+        }
+    }
+
+
+    private boolean writeFilter(boolean filtered, List filter)
+    {
+        if (filtered)
+        {
+            writer.print(ConfigurationConstants.SEPARATOR_KEYWORD);
+        }
+
+        if (filter != null)
+        {
+            if (!filtered)
+            {
+                writer.print(ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD);
+            }
+
+            for (int index = 0; index < filter.size(); index++)
+            {
+                if (index > 0)
+                {
+                    writer.print(ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD);
+                }
+
+                writer.print(quotedString((String)filter.get(index)));
+            }
+
+            filtered = true;
+        }
+
+        return filtered;
+    }
+
+
+    private void writeOption(String optionName, boolean flag)
+    {
+        if (flag)
+        {
+            writer.println(optionName);
+        }
+    }
+
+
+    private void writeOption(String optionName, int argument)
+    {
+        if (argument != 1)
+        {
+            writer.print(optionName);
+            writer.print(' ');
+            writer.println(argument);
+        }
+    }
+
+
+    private void writeOption(String optionName, List arguments)
+    {
+        writeOption(optionName, arguments, false);
+    }
+
+
+    private void writeOption(String  optionName,
+                             List    arguments,
+                             boolean replaceInternalClassNames)
+    {
+        if (arguments != null)
+        {
+            if (arguments.isEmpty())
+            {
+                writer.println(optionName);
+            }
+            else
+            {
+                String argumentString = ListUtil.commaSeparatedString(arguments);
+
+                if (replaceInternalClassNames)
+                {
+                    argumentString = ClassUtil.externalClassName(argumentString);
+                }
+
+                writer.print(optionName);
+                writer.print(' ');
+                writer.println(quotedString(argumentString));
+            }
+        }
+    }
+
+
+    private void writeOption(String optionName, String arguments)
+    {
+        writeOption(optionName, arguments, false);
+    }
+
+
+    private void writeOption(String  optionName,
+                             String  arguments,
+                             boolean replaceInternalClassNames)
+    {
+        if (arguments != null)
+        {
+            if (replaceInternalClassNames)
+            {
+                arguments = ClassUtil.externalClassName(arguments);
+            }
+
+            writer.print(optionName);
+            writer.print(' ');
+            writer.println(quotedString(arguments));
+        }
+    }
+
+
+    private void writeOption(String optionName, File file)
+    {
+        if (file != null)
+        {
+            if (file.getPath().length() > 0)
+            {
+                writer.print(optionName);
+                writer.print(' ');
+                writer.println(relativeFileName(file));
+            }
+            else
+            {
+                writer.println(optionName);
+            }
+        }
+    }
+
+
+    private void writeOptions(String[] optionNames,
+                              List     keepClassSpecifications)
+    {
+        if (keepClassSpecifications != null)
+        {
+            for (int index = 0; index < keepClassSpecifications.size(); index++)
+            {
+                writeOption(optionNames, (KeepClassSpecification)keepClassSpecifications.get(index));
+            }
+        }
+    }
+
+
+    private void writeOption(String[]               optionNames,
+                             KeepClassSpecification keepClassSpecification)
+    {
+        // Compose the option name.
+        String optionName = optionNames[keepClassSpecification.markConditionally ? 2 :
+                                        keepClassSpecification.markClasses       ? 0 :
+                                                                              1];
+
+        if (keepClassSpecification.allowShrinking)
+        {
+            optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
+                          ConfigurationConstants.ALLOW_SHRINKING_SUBOPTION;
+        }
+
+        if (keepClassSpecification.allowOptimization)
+        {
+            optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
+                          ConfigurationConstants.ALLOW_OPTIMIZATION_SUBOPTION;
+        }
+
+        if (keepClassSpecification.allowObfuscation)
+        {
+            optionName += ConfigurationConstants.ARGUMENT_SEPARATOR_KEYWORD +
+                          ConfigurationConstants.ALLOW_OBFUSCATION_SUBOPTION;
+        }
+
+        // Write out the option with the proper class specification.
+        writeOption(optionName, keepClassSpecification);
+    }
+
+
+    private void writeOptions(String optionName,
+                              List   classSpecifications)
+    {
+        if (classSpecifications != null)
+        {
+            for (int index = 0; index < classSpecifications.size(); index++)
+            {
+                writeOption(optionName, (ClassSpecification)classSpecifications.get(index));
+            }
+        }
+    }
+
+
+    private void writeOption(String             optionName,
+                             ClassSpecification classSpecification)
+    {
+        writer.println();
+
+        // Write out the comments for this option.
+        writeComments(classSpecification.comments);
+
+        writer.print(optionName);
+        writer.print(' ');
+
+        // Write out the required annotation, if any.
+        if (classSpecification.annotationType != null)
+        {
+            writer.print(ConfigurationConstants.ANNOTATION_KEYWORD);
+            writer.print(ClassUtil.externalType(classSpecification.annotationType));
+            writer.print(' ');
+        }
+
+        // Write out the class access flags.
+        writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredUnsetAccessFlags,
+                                                        ConfigurationConstants.NEGATOR_KEYWORD));
+
+        writer.print(ClassUtil.externalClassAccessFlags(classSpecification.requiredSetAccessFlags));
+
+        // Write out the class keyword, if we didn't write the interface
+        // keyword earlier.
+        if (((classSpecification.requiredSetAccessFlags |
+              classSpecification.requiredUnsetAccessFlags) &
+             (ClassConstants.INTERNAL_ACC_INTERFACE |
+              ClassConstants.INTERNAL_ACC_ENUM)) == 0)
+        {
+            writer.print(ConfigurationConstants.CLASS_KEYWORD);
+        }
+
+        writer.print(' ');
+
+        // Write out the class name.
+        writer.print(classSpecification.className != null ?
+            ClassUtil.externalClassName(classSpecification.className) :
+            ConfigurationConstants.ANY_CLASS_KEYWORD);
+
+        // Write out the extends template, if any.
+        if (classSpecification.extendsAnnotationType != null ||
+            classSpecification.extendsClassName      != null)
+        {
+            writer.print(' ');
+            writer.print(ConfigurationConstants.EXTENDS_KEYWORD);
+            writer.print(' ');
+
+            // Write out the required extends annotation, if any.
+            if (classSpecification.extendsAnnotationType != null)
+            {
+                writer.print(ConfigurationConstants.ANNOTATION_KEYWORD);
+                writer.print(ClassUtil.externalType(classSpecification.extendsAnnotationType));
+                writer.print(' ');
+            }
+
+            // Write out the extended class name.
+            writer.print(classSpecification.extendsClassName != null ?
+                ClassUtil.externalClassName(classSpecification.extendsClassName) :
+                ConfigurationConstants.ANY_CLASS_KEYWORD);
+        }
+
+        // Write out the keep field and keep method options, if any.
+        if (classSpecification.fieldSpecifications  != null ||
+            classSpecification.methodSpecifications != null)
+        {
+            writer.print(' ');
+            writer.println(ConfigurationConstants.OPEN_KEYWORD);
+
+            writeFieldSpecification( classSpecification.fieldSpecifications);
+            writeMethodSpecification(classSpecification.methodSpecifications);
+
+            writer.println(ConfigurationConstants.CLOSE_KEYWORD);
+        }
+        else
+        {
+            writer.println();
+        }
+    }
+
+
+
+    private void writeComments(String comments)
+    {
+        if (comments != null)
+        {
+            int index = 0;
+            while (index < comments.length())
+            {
+                int breakIndex = comments.indexOf('\n', index);
+                if (breakIndex < 0)
+                {
+                    breakIndex = comments.length();
+                }
+
+                writer.print('#');
+
+                if (comments.charAt(index) != ' ')
+                {
+                    writer.print(' ');
+                }
+
+                writer.println(comments.substring(index, breakIndex));
+
+                index = breakIndex + 1;
+            }
+        }
+    }
+
+
+    private void writeFieldSpecification(List memberSpecifications)
+    {
+        if (memberSpecifications != null)
+        {
+            for (int index = 0; index < memberSpecifications.size(); index++)
+            {
+                MemberSpecification memberSpecification =
+                    (MemberSpecification)memberSpecifications.get(index);
+
+                writer.print("    ");
+
+                // Write out the required annotation, if any.
+                if (memberSpecification.annotationType != null)
+                {
+                    writer.print(ConfigurationConstants.ANNOTATION_KEYWORD);
+                    writer.println(ClassUtil.externalType(memberSpecification.annotationType));
+                    writer.print("    ");
+                }
+
+                // Write out the field access flags.
+                writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredUnsetAccessFlags,
+                                                                ConfigurationConstants.NEGATOR_KEYWORD));
+
+                writer.print(ClassUtil.externalFieldAccessFlags(memberSpecification.requiredSetAccessFlags));
+
+                // Write out the field name and descriptor.
+                String name       = memberSpecification.name;
+                String descriptor = memberSpecification.descriptor;
+
+                writer.print(descriptor == null ? name == null ?
+                    ConfigurationConstants.ANY_FIELD_KEYWORD             :
+                    ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name :
+                    ClassUtil.externalFullFieldDescription(0,
+                                                           name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name,
+                                                           descriptor));
+
+                writer.println(ConfigurationConstants.SEPARATOR_KEYWORD);
+            }
+        }
+    }
+
+
+    private void writeMethodSpecification(List memberSpecifications)
+    {
+        if (memberSpecifications != null)
+        {
+            for (int index = 0; index < memberSpecifications.size(); index++)
+            {
+                MemberSpecification memberSpecification =
+                    (MemberSpecification)memberSpecifications.get(index);
+
+                writer.print("    ");
+
+                // Write out the required annotation, if any.
+                if (memberSpecification.annotationType != null)
+                {
+                    writer.print(ConfigurationConstants.ANNOTATION_KEYWORD);
+                    writer.println(ClassUtil.externalType(memberSpecification.annotationType));
+                    writer.print("    ");
+                }
+
+                // Write out the method access flags.
+                writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredUnsetAccessFlags,
+                                                                 ConfigurationConstants.NEGATOR_KEYWORD));
+
+                writer.print(ClassUtil.externalMethodAccessFlags(memberSpecification.requiredSetAccessFlags));
+
+                // Write out the method name and descriptor.
+                String name       = memberSpecification.name;
+                String descriptor = memberSpecification.descriptor;
+
+                writer.print(descriptor == null ? name == null ?
+                    ConfigurationConstants.ANY_METHOD_KEYWORD :
+                    ConfigurationConstants.ANY_TYPE_KEYWORD + ' ' + name + ConfigurationConstants.OPEN_ARGUMENTS_KEYWORD + ConfigurationConstants.ANY_ARGUMENTS_KEYWORD + ConfigurationConstants.CLOSE_ARGUMENTS_KEYWORD :
+                    ClassUtil.externalFullMethodDescription(ClassConstants.INTERNAL_METHOD_NAME_INIT,
+                                                            0,
+                                                            name == null ? ConfigurationConstants.ANY_CLASS_MEMBER_KEYWORD : name,
+                                                            descriptor));
+
+                writer.println(ConfigurationConstants.SEPARATOR_KEYWORD);
+            }
+        }
+    }
+
+
+    /**
+     * Returns a relative file name of the given file, if possible.
+     * The file name is also quoted, if necessary.
+     */
+    private String relativeFileName(File file)
+    {
+        String fileName = file.getAbsolutePath();
+
+        // See if we can convert the file name into a relative file name.
+        if (baseDir != null)
+        {
+            String baseDirName = baseDir.getAbsolutePath() + File.separator;
+            if (fileName.startsWith(baseDirName))
+            {
+                fileName = fileName.substring(baseDirName.length());
+            }
+        }
+
+        return quotedString(fileName);
+    }
+
+
+    /**
+     * Returns a quoted version of the given string, if necessary.
+     */
+    private String quotedString(String string)
+    {
+        return string.length()     == 0 ||
+               string.indexOf(' ') >= 0 ||
+               string.indexOf('@') >= 0 ||
+               string.indexOf('{') >= 0 ||
+               string.indexOf('}') >= 0 ||
+               string.indexOf('(') >= 0 ||
+               string.indexOf(')') >= 0 ||
+               string.indexOf(':') >= 0 ||
+               string.indexOf(';') >= 0 ||
+               string.indexOf(',') >= 0  ? ("'" + string + "'") :
+                                           (      string      );
+    }
+
+
+    /**
+     * A main method for testing configuration writing.
+     */
+    public static void main(String[] args) {
+        try
+        {
+            ConfigurationWriter writer = new ConfigurationWriter(new File(args[0]));
+
+            writer.write(new Configuration());
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/DataEntryReaderFactory.java b/src/proguard/DataEntryReaderFactory.java
new file mode 100644
index 0000000..a9724b5
--- /dev/null
+++ b/src/proguard/DataEntryReaderFactory.java
@@ -0,0 +1,155 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.io.*;
+import proguard.util.*;
+
+import java.util.List;
+
+
+/**
+ * This class can create DataEntryReader instances based on class path entries.
+ * The readers will unwrap the input data entries from any jars, wars, ears,
+ * and zips, before passing them to a given reader.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryReaderFactory
+{
+    /**
+     * Creates a DataEntryReader that can read the given class path entry.
+     *
+     * @param messagePrefix  a prefix for messages that are printed out.
+     * @param classPathEntry the input class path entry.
+     * @param reader         a data entry reader to which the reading of actual
+     *                       classes and resource files can be delegated.
+     * @return a DataEntryReader for reading the given class path entry.
+     */
+    public static DataEntryReader createDataEntryReader(String          messagePrefix,
+                                                        ClassPathEntry  classPathEntry,
+                                                        DataEntryReader reader)
+    {
+        String entryName = classPathEntry.getName();
+        boolean isJar = endsWithIgnoreCase(entryName, ".jar");
+        boolean isWar = endsWithIgnoreCase(entryName, ".war");
+        boolean isEar = endsWithIgnoreCase(entryName, ".ear");
+        boolean isZip = endsWithIgnoreCase(entryName, ".zip");
+
+        List filter    = classPathEntry.getFilter();
+        List jarFilter = classPathEntry.getJarFilter();
+        List warFilter = classPathEntry.getWarFilter();
+        List earFilter = classPathEntry.getEarFilter();
+        List zipFilter = classPathEntry.getZipFilter();
+
+        System.out.println(messagePrefix +
+                           (isJar ? "jar" :
+                            isWar ? "war" :
+                            isEar ? "ear" :
+                            isZip ? "zip" :
+                                    "directory") +
+                           " [" + classPathEntry.getName() + "]" +
+                           (filter    != null ||
+                            jarFilter != null ||
+                            warFilter != null ||
+                            earFilter != null ||
+                            zipFilter != null ? " (filtered)" : ""));
+
+        // Add a filter, if specified.
+        if (filter != null)
+        {
+            reader = new FilteredDataEntryReader(
+                     new DataEntryNameFilter(
+                     new ListParser(new FileNameParser()).parse(filter)),
+                         reader);
+        }
+
+        // Unzip any jars, if necessary.
+        reader = wrapInJarReader(reader, isJar, jarFilter, ".jar");
+        if (!isJar)
+        {
+            // Unzip any wars, if necessary.
+            reader = wrapInJarReader(reader, isWar, warFilter, ".war");
+            if (!isWar)
+            {
+                // Unzip any ears, if necessary.
+                reader = wrapInJarReader(reader, isEar, earFilter, ".ear");
+                if (!isEar)
+                {
+                    // Unzip any zips, if necessary.
+                    reader = wrapInJarReader(reader, isZip, zipFilter, ".zip");
+                }
+            }
+        }
+
+        return reader;
+    }
+
+
+    /**
+     *  Wraps the given DataEntryReader in a JarReader, filtering it if necessary.
+     */
+    private static DataEntryReader wrapInJarReader(DataEntryReader reader,
+                                                   boolean         isJar,
+                                                   List            jarFilter,
+                                                   String          jarExtension)
+    {
+        // Unzip any jars, if necessary.
+        DataEntryReader jarReader = new JarReader(reader);
+
+        if (isJar)
+        {
+            // Always unzip.
+            return jarReader;
+        }
+        else
+        {
+            // Add a filter, if specified.
+            if (jarFilter != null)
+            {
+                jarReader = new FilteredDataEntryReader(
+                            new DataEntryNameFilter(
+                            new ListParser(new FileNameParser()).parse(jarFilter)),
+                                jarReader);
+            }
+
+            // Only unzip the right type of jars.
+            return new FilteredDataEntryReader(
+                   new DataEntryNameFilter(
+                   new ExtensionMatcher(jarExtension)),
+                       jarReader,
+                       reader);
+        }
+    }
+
+
+    /**
+     * Returns whether the given string ends with the given suffix, ignoring its
+     * case.
+     */
+    private static boolean endsWithIgnoreCase(String string, String suffix)
+    {
+        int stringLength = string.length();
+        int suffixLength = suffix.length();
+
+        return string.regionMatches(true, stringLength - suffixLength, suffix, 0, suffixLength);
+    }
+}
diff --git a/src/proguard/DataEntryWriterFactory.java b/src/proguard/DataEntryWriterFactory.java
new file mode 100644
index 0000000..9fbc6d0
--- /dev/null
+++ b/src/proguard/DataEntryWriterFactory.java
@@ -0,0 +1,164 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.io.*;
+import proguard.util.*;
+
+import java.util.List;
+
+/**
+ * This class can create DataEntryWriter instances based on class paths. The
+ * writers will wrap the output in the proper jars, wars, ears, and zips.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryWriterFactory
+{
+    /**
+     * Creates a DataEntryWriter that can write to the given class path entries.
+     *
+     * @param classPath the output class path.
+     * @param fromIndex the start index in the class path.
+     * @param toIndex   the end index in the class path.
+     * @return a DataEntryWriter for writing to the given class path entries.
+     */
+    public static DataEntryWriter createDataEntryWriter(ClassPath classPath,
+                                                        int       fromIndex,
+                                                        int       toIndex)
+    {
+        DataEntryWriter writer = null;
+
+        // Create a chain of writers, one for each class path entry.
+        for (int index = toIndex - 1; index >= fromIndex; index--)
+        {
+            ClassPathEntry entry = classPath.get(index);
+            writer = createClassPathEntryWriter(entry, writer);
+        }
+
+        return writer;
+    }
+
+
+    /**
+     * Creates a DataEntryWriter that can write to the given class path entry,
+     * or delegate to another DataEntryWriter if its filters don't match.
+     */
+    private static DataEntryWriter createClassPathEntryWriter(ClassPathEntry  classPathEntry,
+                                                              DataEntryWriter alternativeWriter)
+    {
+        String entryName = classPathEntry.getName();
+        boolean isJar = endsWithIgnoreCase(entryName, ".jar");
+        boolean isWar = endsWithIgnoreCase(entryName, ".war");
+        boolean isEar = endsWithIgnoreCase(entryName, ".ear");
+        boolean isZip = endsWithIgnoreCase(entryName, ".zip");
+
+        List filter    = classPathEntry.getFilter();
+        List jarFilter = classPathEntry.getJarFilter();
+        List warFilter = classPathEntry.getWarFilter();
+        List earFilter = classPathEntry.getEarFilter();
+        List zipFilter = classPathEntry.getZipFilter();
+
+        System.out.println("Preparing output " +
+                           (isJar ? "jar" :
+                            isWar ? "war" :
+                            isEar ? "ear" :
+                            isZip ? "zip" :
+                                    "directory") +
+                           " [" + entryName + "]" +
+                           (filter    != null ||
+                            jarFilter != null ||
+                            warFilter != null ||
+                            earFilter != null ||
+                            zipFilter != null ? " (filtered)" : ""));
+
+        DataEntryWriter writer = new DirectoryWriter(classPathEntry.getFile(),
+                                                     isJar ||
+                                                     isWar ||
+                                                     isEar ||
+                                                     isZip);
+
+        // Set up the filtered jar writers.
+        writer = wrapInJarWriter(writer, isZip, zipFilter, ".zip", isJar || isWar || isEar);
+        writer = wrapInJarWriter(writer, isEar, earFilter, ".ear", isJar || isWar);
+        writer = wrapInJarWriter(writer, isWar, warFilter, ".war", isJar);
+        writer = wrapInJarWriter(writer, isJar, jarFilter, ".jar", false);
+
+        // Add a filter, if specified.
+        writer = filter != null?
+            new FilteredDataEntryWriter(
+            new DataEntryNameFilter(
+            new ListParser(new FileNameParser()).parse(filter)),
+                writer) :
+            writer;
+
+        // Let the writer cascade, if specified.
+        return alternativeWriter != null ?
+            new CascadingDataEntryWriter(writer, alternativeWriter) :
+            writer;
+    }
+
+
+    /**
+     * Wraps the given DataEntryWriter in a JarWriter, filtering if necessary.
+     */
+    private static DataEntryWriter wrapInJarWriter(DataEntryWriter writer,
+                                                   boolean         isJar,
+                                                   List            jarFilter,
+                                                   String          jarExtension,
+                                                   boolean         dontWrap)
+    {
+        // Zip up jars, if necessary.
+        DataEntryWriter jarWriter = dontWrap ?
+            (DataEntryWriter)new ParentDataEntryWriter(writer) :
+            (DataEntryWriter)new JarWriter(writer);
+
+        // Add a filter, if specified.
+        DataEntryWriter filteredJarWriter = jarFilter != null?
+            new FilteredDataEntryWriter(
+            new DataEntryParentFilter(
+            new DataEntryNameFilter(
+            new ListParser(new FileNameParser()).parse(jarFilter))),
+                 jarWriter) :
+            jarWriter;
+
+        // Only zip up jars, unless the output is a jar file itself.
+        return new FilteredDataEntryWriter(
+               new DataEntryParentFilter(
+               new DataEntryNameFilter(
+               new ExtensionMatcher(jarExtension))),
+                   filteredJarWriter,
+                   isJar ? jarWriter : writer);
+    }
+
+
+    /**
+     * Returns whether the given string ends with the given suffix, ignoring its
+     * case.
+     */
+    private static boolean endsWithIgnoreCase(String string, String suffix)
+    {
+        int stringLength = string.length();
+        int suffixLength = suffix.length();
+
+        return string.regionMatches(true, stringLength - suffixLength, suffix, 0, suffixLength);
+    }
+}
diff --git a/src/proguard/DescriptorKeepChecker.java b/src/proguard/DescriptorKeepChecker.java
new file mode 100644
index 0000000..1dfaf1a
--- /dev/null
+++ b/src/proguard/DescriptorKeepChecker.java
@@ -0,0 +1,163 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.optimize.KeepMarker;
+
+import java.util.List;
+
+
+/**
+ * This class checks whether classes referenced by class members that are
+ * marked to be kept are marked to be kept too.
+ *
+ * @author Eric Lafortune
+ */
+public class DescriptorKeepChecker
+extends      SimplifiedVisitor
+implements   MemberVisitor,
+             ClassVisitor
+{
+    private final ClassPool      programClassPool;
+    private final ClassPool      libraryClassPool;
+    private final WarningPrinter notePrinter;
+
+    // Some fields acting as parameters for the class visitor.
+    private Clazz   referencingClass;
+    private Member  referencingMember;
+    private boolean isField;
+
+
+    /**
+     * Creates a new DescriptorKeepChecker.
+     */
+    public DescriptorKeepChecker(ClassPool      programClassPool,
+                                 ClassPool      libraryClassPool,
+                                 WarningPrinter notePrinter)
+    {
+        this.programClassPool = programClassPool;
+        this.libraryClassPool = libraryClassPool;
+        this.notePrinter      = notePrinter;
+    }
+
+
+    /**
+     * Checks the classes mentioned in the given keep specifications, printing
+     * notes if necessary. Returns the number of notes printed.
+     */
+    public void checkClassSpecifications(List keepSpecifications)
+    {
+        // Clean up any old visitor info.
+        programClassPool.classesAccept(new ClassCleaner());
+        libraryClassPool.classesAccept(new ClassCleaner());
+
+        // Create a visitor for marking the seeds.
+        KeepMarker keepMarker = new KeepMarker();
+        ClassPoolVisitor classPoolvisitor =
+            ClassSpecificationVisitorFactory.createClassPoolVisitor(keepSpecifications,
+                                                                    keepMarker,
+                                                                    keepMarker,
+                                                                    false,
+                                                                    true,
+                                                                    true);
+        // Mark the seeds.
+        programClassPool.accept(classPoolvisitor);
+        libraryClassPool.accept(classPoolvisitor);
+
+        // Print out notes about argument types that are not being kept.
+        programClassPool.classesAccept(new AllMemberVisitor(this));
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (KeepMarker.isKept(programField))
+        {
+            referencingClass  = programClass;
+            referencingMember = programField;
+            isField           = true;
+
+            // Don't check the type, because it is not required for introspection.
+            //programField.referencedClassesAccept(this);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (KeepMarker.isKept(programMethod))
+        {
+            referencingClass  = programClass;
+            referencingMember = programMethod;
+            isField           = false;
+
+            // Don't check the return type, because it is not required for
+            // introspection (e.g. the return type of the special Enum methods).
+            //programMethod.referencedClassesAccept(this);
+
+            Clazz[] referencedClasses = programMethod.referencedClasses;
+            if (referencedClasses != null)
+            {
+                for (int index = 0; index < referencedClasses.length-1; index++)
+                {
+                    if (referencedClasses[index] != null)
+                    {
+                        referencedClasses[index].accept(this);
+                    }
+                }
+            }
+        }
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (!KeepMarker.isKept(programClass))
+        {
+            notePrinter.print(referencingClass.getName(),
+                              programClass.getName(),
+                              "Note: the configuration keeps the entry point '" +
+                              ClassUtil.externalClassName(referencingClass.getName()) +
+                              " { " +
+                              (isField ?
+                                   ClassUtil.externalFullFieldDescription(0,
+                                                                          referencingMember.getName(referencingClass),
+                                                                          referencingMember.getDescriptor(referencingClass)) :
+                                   ClassUtil.externalFullMethodDescription(referencingClass.getName(),
+                                                                           0,
+                                                                           referencingMember.getName(referencingClass),
+                                                                           referencingMember.getDescriptor(referencingClass))) +
+                              "; }', but not the descriptor class '" +
+                              ClassUtil.externalClassName(programClass.getName()) +
+                              "'");
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+}
diff --git a/src/proguard/DuplicateClassPrinter.java b/src/proguard/DuplicateClassPrinter.java
new file mode 100644
index 0000000..21b6aa0
--- /dev/null
+++ b/src/proguard/DuplicateClassPrinter.java
@@ -0,0 +1,63 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor writes out notes about the class files that it visits
+ * being duplicates.
+ *
+ * @author Eric Lafortune
+ */
+public class DuplicateClassPrinter implements ClassVisitor
+{
+    private final WarningPrinter notePrinter;
+
+
+    /**
+     * Creates a new DuplicateClassVisitor.
+     */
+    public DuplicateClassPrinter(WarningPrinter notePrinter)
+    {
+        this.notePrinter = notePrinter;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        notePrinter.print(programClass.getName(),
+                          "Note: duplicate definition of program class [" +
+                          ClassUtil.externalClassName(programClass.getName()) + "]");
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        notePrinter.print(libraryClass.getName(),
+                          "Note: duplicate definition of library class [" +
+                          ClassUtil.externalClassName(libraryClass.getName()) + "]");
+    }
+}
diff --git a/src/proguard/FileWordReader.java b/src/proguard/FileWordReader.java
new file mode 100644
index 0000000..fb9fa50
--- /dev/null
+++ b/src/proguard/FileWordReader.java
@@ -0,0 +1,89 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.io.*;
+import java.net.URL;
+
+
+/**
+ * A <code>WordReader</code> that returns words from a file or a URL.
+ *
+ * @author Eric Lafortune
+ */
+public class FileWordReader extends WordReader
+{
+    private final String           name;
+    private LineNumberReader reader;
+
+
+    /**
+     * Creates a new FileWordReader for the given file.
+     */
+    public FileWordReader(File file) throws IOException
+    {
+        super(file.getParentFile());
+
+        this.name   = file.getPath();
+        this.reader = new LineNumberReader(
+                      new BufferedReader(
+                      new FileReader(file)));
+    }
+
+
+    /**
+     * Creates a new FileWordReader for the given URL.
+     */
+    public FileWordReader(URL url) throws IOException
+    {
+        super(null);
+
+        this.name   = url.toString();
+        this.reader = new LineNumberReader(
+                       new BufferedReader(
+                       new InputStreamReader(url.openStream())));
+    }
+
+
+    // Implementations for WordReader.
+
+    protected String nextLine() throws IOException
+    {
+        return reader.readLine();
+    }
+
+
+    protected String lineLocationDescription()
+    {
+        return "line " + reader.getLineNumber() + " of file '" + name + "'";
+    }
+
+
+    public void close() throws IOException
+    {
+        super.close();
+
+        if (reader != null)
+        {
+            reader.close();
+        }
+    }
+}
diff --git a/src/proguard/FullyQualifiedClassNameChecker.java b/src/proguard/FullyQualifiedClassNameChecker.java
new file mode 100644
index 0000000..06949b5
--- /dev/null
+++ b/src/proguard/FullyQualifiedClassNameChecker.java
@@ -0,0 +1,190 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+import java.util.List;
+
+/**
+ * This class checks if the user has forgotten to fully qualify any classes
+ * in the configuration.
+ *
+ * @author Eric Lafortune
+ */
+public class FullyQualifiedClassNameChecker
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private final ClassPool      programClassPool;
+    private final ClassPool      libraryClassPool;
+    private final WarningPrinter notePrinter;
+
+
+    /**
+     * Creates a new DescriptorKeepChecker.
+     */
+    public FullyQualifiedClassNameChecker(ClassPool      programClassPool,
+                                          ClassPool      libraryClassPool,
+                                          WarningPrinter notePrinter)
+    {
+        this.programClassPool = programClassPool;
+        this.libraryClassPool = libraryClassPool;
+        this.notePrinter      = notePrinter;
+    }
+
+
+    /**
+     * Checks the classes mentioned in the given class specifications, printing
+     * notes if necessary. Returns the number of notes printed.
+     */
+    public void checkClassSpecifications(List classSpecifications)
+    {
+        if (classSpecifications != null)
+        {
+            for (int index = 0; index < classSpecifications.size(); index++)
+            {
+                ClassSpecification classSpecification =
+                    (ClassSpecification)classSpecifications.get(index);
+
+                checkType(classSpecification.annotationType);
+                checkClassName(classSpecification.className);
+                checkType(classSpecification.extendsAnnotationType);
+                checkClassName(classSpecification.extendsClassName);
+
+                checkMemberSpecifications(classSpecification.fieldSpecifications,  true);
+                checkMemberSpecifications(classSpecification.methodSpecifications, false);
+            }
+        }
+    }
+
+
+    /**
+     * Checks the classes mentioned in the given class member specifications,
+     * printing notes if necessary.
+     */
+    private void checkMemberSpecifications(List memberSpecifications, boolean isField)
+    {
+        if (memberSpecifications != null)
+        {
+            for (int index = 0; index < memberSpecifications.size(); index++)
+            {
+                MemberSpecification memberSpecification =
+                    (MemberSpecification)memberSpecifications.get(index);
+
+                checkType(memberSpecification.annotationType);
+
+                if (isField)
+                {
+                     checkType(memberSpecification.descriptor);
+                }
+                else
+                {
+                    checkDescriptor(memberSpecification.descriptor);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Checks the classes mentioned in the given class member descriptor,
+     * printing notes if necessary.
+     */
+    private void checkDescriptor(String descriptor)
+    {
+        if (descriptor != null)
+        {
+            InternalTypeEnumeration internalTypeEnumeration =
+                new InternalTypeEnumeration(descriptor);
+
+            checkType(internalTypeEnumeration.returnType());
+
+            while (internalTypeEnumeration.hasMoreTypes())
+            {
+                checkType(internalTypeEnumeration.nextType());
+            }
+        }
+    }
+
+
+    /**
+     * Checks the class mentioned in the given type (if any),
+     * printing notes if necessary.
+     */
+    private void checkType(String type)
+    {
+        if (type != null)
+        {
+            checkClassName(ClassUtil.internalClassNameFromType(type));
+        }
+    }
+
+
+    /**
+     * Checks the specified class (if any),
+     * printing notes if necessary.
+     */
+    private void checkClassName(String className)
+    {
+        if (className != null                            &&
+            !containsWildCards(className)                &&
+            programClassPool.getClass(className) == null &&
+            libraryClassPool.getClass(className) == null &&
+            notePrinter.accepts(className))
+        {
+            notePrinter.print(className,
+                              "Note: the configuration refers to the unknown class '" +
+                              ClassUtil.externalClassName(className) + "'");
+
+            String fullyQualifiedClassName =
+                "**" + ClassConstants.INTERNAL_PACKAGE_SEPARATOR +
+                className.substring(className.lastIndexOf(ClassConstants.INTERNAL_PACKAGE_SEPARATOR)+1);
+
+            ClassNameFilter classNameFilter =
+                new ClassNameFilter(fullyQualifiedClassName, this);
+
+            programClassPool.classesAccept(classNameFilter);
+            libraryClassPool.classesAccept(classNameFilter);
+        }
+    }
+
+
+    private static boolean containsWildCards(String string)
+    {
+        return string != null &&
+            (string.indexOf('*')   >= 0 ||
+             string.indexOf('?')   >= 0 ||
+             string.indexOf(',')   >= 0 ||
+             string.indexOf("///") >= 0);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitAnyClass(Clazz clazz)
+    {
+        System.out.println("      Maybe you meant the fully qualified name '" +
+                           ClassUtil.externalClassName(clazz.getName()) + "'?");
+    }
+}
diff --git a/src/proguard/GPL.java b/src/proguard/GPL.java
new file mode 100644
index 0000000..272a837
--- /dev/null
+++ b/src/proguard/GPL.java
@@ -0,0 +1,191 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * This class checks and prints out information about the GPL.
+ *
+ * @author Eric Lafortune
+ */
+public class GPL
+{
+    /**
+     * Prints out a note about the GPL if ProGuard is linked against unknown
+     * code.
+     */
+    public static void check()
+    {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        new Exception().printStackTrace(new PrintStream(out));
+        LineNumberReader reader = new LineNumberReader(
+                                  new InputStreamReader(
+                                  new ByteArrayInputStream(out.toByteArray())));
+
+        Set unknownPackageNames = unknownPackageNames(reader);
+
+        if (unknownPackageNames.size() > 0)
+        {
+            String uniquePackageNames = uniquePackageNames(unknownPackageNames);
+
+            System.out.println("ProGuard is released under the GNU General Public License. The authors of all");
+            System.out.println("programs or plugins that link to it ("+uniquePackageNames+"...) therefore");
+            System.out.println("must ensure that these programs carry the GNU General Public License as well.");
+        }
+    }
+
+
+    /**
+     * Returns a set of package names from the given stack trace.
+     */
+    private static Set unknownPackageNames(LineNumberReader reader)
+    {
+        Set packageNames = new HashSet();
+
+        try
+        {
+            while (true)
+            {
+                String line = reader.readLine();
+                if (line == null)
+                {
+                    break;
+                }
+
+                line = line.trim();
+                if (line.startsWith("at "))
+                {
+                    line = line.substring(2).trim();
+                    line = trimSuffix(line, '(');
+                    line = trimSuffix(line, '.');
+                    line = trimSuffix(line, '.');
+
+                    if (line.length() > 0 && !isKnown(line))
+                    {
+                        packageNames.add(line);
+                    }
+                }
+            }
+        }
+        catch (IOException ex)
+        {
+            // We'll just stop looking for more names.
+        }
+
+        return packageNames;
+    }
+
+
+    /**
+     * Returns a comma-separated list of package names from the set, excluding
+     * any subpackages of packages in the set.
+     */
+    private static String uniquePackageNames(Set packageNames)
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        Iterator iterator = packageNames.iterator();
+        while (iterator.hasNext())
+        {
+            String packageName = (String)iterator.next();
+            if (!containsPrefix(packageNames, packageName))
+            {
+                buffer.append(packageName).append(", ");
+            }
+        }
+
+        return buffer.toString();
+    }
+
+
+    /**
+     * Returns a given string without the suffix, as defined by the given
+     * separator.
+     */
+    private static String trimSuffix(String string, char separator)
+    {
+        int index = string.lastIndexOf(separator);
+        return index < 0 ? "" : string.substring(0, index);
+    }
+
+
+    /**
+     * Returns whether the given set contains a prefix of the given name.
+     */
+    private static boolean containsPrefix(Set set, String name)
+    {
+        int index = 0;
+
+        while (!set.contains(name.substring(0, index)))
+        {
+            index = name.indexOf('.', index + 1);
+            if (index < 0)
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Returns whether the given package name has been granted an exception
+     * against the GPL linking clause, by the copyright holder of ProGuard.
+     * This method is not legally binding, but of course the actual license is.
+     * Please contact the copyright holder if you would like an exception for
+     * your code as well.
+     */
+    private static boolean isKnown(String packageName)
+    {
+        return packageName.startsWith("java")                   ||
+               packageName.startsWith("sun.reflect")            ||
+               packageName.startsWith("proguard")               ||
+               packageName.startsWith("org.apache.tools.ant")   ||
+               packageName.startsWith("org.apache.tools.maven") ||
+               packageName.startsWith("org.eclipse")            ||
+               packageName.startsWith("org.netbeans")           ||
+               packageName.startsWith("com.sun.kvem")           ||
+               packageName.startsWith("net.certiv.proguarddt")  ||
+               packageName.startsWith("eclipseme")              ||
+               packageName.startsWith("jg.j2me")                ||
+               packageName.startsWith("jg.common")              ||
+               packageName.startsWith("jg.buildengine");
+    }
+
+
+    public static void main(String[] args)
+    {
+        LineNumberReader reader = new LineNumberReader(
+                                  new InputStreamReader(System.in));
+
+        Set unknownPackageNames = unknownPackageNames(reader);
+
+        if (unknownPackageNames.size() > 0)
+        {
+            String uniquePackageNames = uniquePackageNames(unknownPackageNames);
+
+            System.out.println(uniquePackageNames);
+        }
+    }
+}
diff --git a/src/proguard/Initializer.java b/src/proguard/Initializer.java
new file mode 100644
index 0000000..41cf971
--- /dev/null
+++ b/src/proguard/Initializer.java
@@ -0,0 +1,411 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.ClassPool;
+import proguard.classfile.attribute.visitor.AllAttributeVisitor;
+import proguard.classfile.constant.visitor.*;
+import proguard.classfile.instruction.visitor.AllInstructionVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.util.*;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * This class initializes class pools.
+ *
+ * @author Eric Lafortune
+ */
+public class Initializer
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new Initializer to initialize classes according to the given
+     * configuration.
+     */
+    public Initializer(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Initializes the classes in the given program class pool and library class
+     * pool, performs some basic checks, and shrinks the library class pool.
+     */
+    public void execute(ClassPool programClassPool,
+                        ClassPool libraryClassPool) throws IOException
+    {
+        int originalLibraryClassPoolSize = libraryClassPool.size();
+
+        // Construct a reduced library class pool with only those library
+        // classes whose hierarchies are referenced by the program classes.
+        // We can't do this if we later have to come up with the obfuscated
+        // class member names that are globally unique.
+        ClassPool reducedLibraryClassPool = configuration.useUniqueClassMemberNames ?
+            null : new ClassPool();
+
+        WarningPrinter classReferenceWarningPrinter = new WarningPrinter(System.err, configuration.warn);
+        WarningPrinter dependencyWarningPrinter     = new WarningPrinter(System.err, configuration.warn);
+
+        // Initialize the superclass hierarchies for program classes.
+        programClassPool.classesAccept(
+            new ClassSuperHierarchyInitializer(programClassPool,
+                                               libraryClassPool,
+                                               classReferenceWarningPrinter,
+                                               null));
+
+        // Initialize the superclass hierarchy of all library classes, without
+        // warnings.
+        libraryClassPool.classesAccept(
+            new ClassSuperHierarchyInitializer(programClassPool,
+                                               libraryClassPool,
+                                               null,
+                                               dependencyWarningPrinter));
+
+        // Initialize the class references of program class members and
+        // attributes. Note that all superclass hierarchies have to be
+        // initialized for this purpose.
+        WarningPrinter memberReferenceWarningPrinter = new WarningPrinter(System.err, configuration.warn);
+
+        programClassPool.classesAccept(
+            new ClassReferenceInitializer(programClassPool,
+                                          libraryClassPool,
+                                          classReferenceWarningPrinter,
+                                          memberReferenceWarningPrinter,
+                                          null));
+
+        if (reducedLibraryClassPool != null)
+        {
+            // Collect the library classes that are directly referenced by
+            // program classes, without introspection.
+            programClassPool.classesAccept(
+                new ReferencedClassVisitor(
+                new LibraryClassFilter(
+                new ClassPoolFiller(reducedLibraryClassPool))));
+
+            // Reinitialize the superclass hierarchies of referenced library
+            // classes, this time with warnings.
+            reducedLibraryClassPool.classesAccept(
+                new ClassSuperHierarchyInitializer(programClassPool,
+                                                   libraryClassPool,
+                                                   classReferenceWarningPrinter,
+                                                   null));
+        }
+
+        // Initialize the Class.forName references.
+        WarningPrinter dynamicClassReferenceNotePrinter = new WarningPrinter(System.out, configuration.note);
+        WarningPrinter classForNameNotePrinter          = new WarningPrinter(System.out, configuration.note);
+
+        programClassPool.classesAccept(
+            new AllMethodVisitor(
+            new AllAttributeVisitor(
+            new AllInstructionVisitor(
+            new DynamicClassReferenceInitializer(programClassPool,
+                                                 libraryClassPool,
+                                                 dynamicClassReferenceNotePrinter,
+                                                 null,
+                                                 classForNameNotePrinter,
+                                                 createClassNoteExceptionMatcher(configuration.keep))))));
+
+        // Initialize the Class.get[Declared]{Field,Method} references.
+        WarningPrinter getMemberNotePrinter = new WarningPrinter(System.out, configuration.note);
+
+        programClassPool.classesAccept(
+            new AllMethodVisitor(
+            new AllAttributeVisitor(
+            new AllInstructionVisitor(
+            new DynamicMemberReferenceInitializer(programClassPool,
+                                                  libraryClassPool,
+                                                  getMemberNotePrinter,
+                                                  createClassMemberNoteExceptionMatcher(configuration.keep, true),
+                                                  createClassMemberNoteExceptionMatcher(configuration.keep, false))))));
+
+        // Initialize other string constant references, if requested.
+        if (configuration.adaptClassStrings != null)
+        {
+            programClassPool.classesAccept(
+                new ClassNameFilter(configuration.adaptClassStrings,
+                new AllConstantVisitor(
+                new StringReferenceInitializer(programClassPool,
+                                               libraryClassPool))));
+        }
+
+        // Print various notes, if specified.
+        WarningPrinter fullyQualifiedClassNameNotePrinter = new WarningPrinter(System.out, configuration.note);
+        WarningPrinter descriptorKeepNotePrinter          = new WarningPrinter(System.out, configuration.note);
+
+        new FullyQualifiedClassNameChecker(programClassPool,
+                                           libraryClassPool,
+                                           fullyQualifiedClassNameNotePrinter).checkClassSpecifications(configuration.keep);
+
+        new DescriptorKeepChecker(programClassPool,
+                                  libraryClassPool,
+                                  descriptorKeepNotePrinter).checkClassSpecifications(configuration.keep);
+
+        // Initialize the class references of library class members.
+        if (reducedLibraryClassPool != null)
+        {
+            // Collect the library classes that are referenced by program
+            // classes, directly or indirectly, with or without introspection.
+            programClassPool.classesAccept(
+                new ReferencedClassVisitor(
+                new LibraryClassFilter(
+                new ClassHierarchyTraveler(true, true, true, false,
+                new LibraryClassFilter(
+                new ClassPoolFiller(reducedLibraryClassPool))))));
+
+            // Initialize the class references of referenced library
+            // classes, without warnings.
+            reducedLibraryClassPool.classesAccept(
+                new ClassReferenceInitializer(programClassPool,
+                                              libraryClassPool,
+                                              null,
+                                              null,
+                                              dependencyWarningPrinter));
+
+            // Reset the library class pool.
+            libraryClassPool.clear();
+
+            // Copy the library classes that are referenced directly by program
+            // classes and the library classes that are referenced by referenced
+            // library classes.
+            reducedLibraryClassPool.classesAccept(
+                new MultiClassVisitor(new ClassVisitor[]
+                {
+                    new ClassHierarchyTraveler(true, true, true, false,
+                    new LibraryClassFilter(
+                    new ClassPoolFiller(libraryClassPool))),
+
+                    new ReferencedClassVisitor(
+                    new LibraryClassFilter(
+                    new ClassHierarchyTraveler(true, true, true, false,
+                    new LibraryClassFilter(
+                    new ClassPoolFiller(libraryClassPool)))))
+                }));
+        }
+        else
+        {
+            // Initialize the class references of all library class members.
+            libraryClassPool.classesAccept(
+                new ClassReferenceInitializer(programClassPool,
+                                              libraryClassPool,
+                                              null,
+                                              null,
+                                              dependencyWarningPrinter));
+        }
+
+        // Initialize the subclass hierarchies.
+        programClassPool.classesAccept(new ClassSubHierarchyInitializer());
+        libraryClassPool.classesAccept(new ClassSubHierarchyInitializer());
+
+        // Share strings between the classes, to reduce heap memory usage.
+        programClassPool.classesAccept(new StringSharer());
+        libraryClassPool.classesAccept(new StringSharer());
+
+        // Print out a summary of the notes, if necessary.
+        int fullyQualifiedNoteCount = fullyQualifiedClassNameNotePrinter.getWarningCount();
+        if (fullyQualifiedNoteCount > 0)
+        {
+            System.out.println("Note: there were " + fullyQualifiedNoteCount +
+                               " references to unknown classes.");
+            System.out.println("      You should check your configuration for typos.");
+        }
+
+        int descriptorNoteCount = descriptorKeepNotePrinter.getWarningCount();
+        if (descriptorNoteCount > 0)
+        {
+            System.out.println("Note: there were " + descriptorNoteCount +
+                               " unkept descriptor classes in kept class members.");
+            System.out.println("      You should consider explicitly keeping the mentioned classes");
+            System.out.println("      (using '-keep').");
+        }
+
+        int dynamicClassReferenceNoteCount = dynamicClassReferenceNotePrinter.getWarningCount();
+        if (dynamicClassReferenceNoteCount > 0)
+        {
+            System.out.println("Note: there were " + dynamicClassReferenceNoteCount +
+                               " unresolved dynamic references to classes or interfaces.");
+            System.err.println("      You should check if you need to specify additional program jars.");
+        }
+
+        int classForNameNoteCount = classForNameNotePrinter.getWarningCount();
+        if (classForNameNoteCount > 0)
+        {
+            System.out.println("Note: there were " + classForNameNoteCount +
+                               " class casts of dynamically created class instances.");
+            System.out.println("      You might consider explicitly keeping the mentioned classes and/or");
+            System.out.println("      their implementations (using '-keep').");
+        }
+
+        int getmemberNoteCount = getMemberNotePrinter.getWarningCount();
+        if (getmemberNoteCount > 0)
+        {
+            System.out.println("Note: there were " + getmemberNoteCount +
+                               " accesses to class members by means of introspection.");
+            System.out.println("      You should consider explicitly keeping the mentioned class members");
+            System.out.println("      (using '-keep' or '-keepclassmembers').");
+        }
+
+        // Print out a summary of the warnings, if necessary.
+        int classReferenceWarningCount = classReferenceWarningPrinter.getWarningCount();
+        if (classReferenceWarningCount > 0)
+        {
+            System.err.println("Warning: there were " + classReferenceWarningCount +
+                               " unresolved references to classes or interfaces.");
+            System.err.println("         You may need to specify additional library jars (using '-libraryjars'),");
+            System.err.println("         or perhaps the '-dontskipnonpubliclibraryclasses' option.");
+        }
+
+        int dependencyWarningCount = dependencyWarningPrinter.getWarningCount();
+        if (dependencyWarningCount > 0)
+        {
+            System.err.println("Warning: there were " + dependencyWarningCount +
+                               " instances of library classes depending on program classes.");
+            System.err.println("         You must avoid such dependencies, since the program classes will");
+            System.err.println("         be processed, while the library classes will remain unchanged.");
+        }
+
+        int memberReferenceWarningCount = memberReferenceWarningPrinter.getWarningCount();
+        if (memberReferenceWarningCount > 0)
+        {
+            System.err.println("Warning: there were " + memberReferenceWarningCount +
+                               " unresolved references to program class members.");
+            System.err.println("         Your input classes appear to be inconsistent.");
+            System.err.println("         You may need to recompile them and try again.");
+            System.err.println("         Alternatively, you may have to specify the options ");
+            System.err.println("         '-dontskipnonpubliclibraryclasses' and/or");
+            System.err.println("         '-dontskipnonpubliclibraryclassmembers'.");
+        }
+
+        if ((classReferenceWarningCount   > 0 ||
+             dependencyWarningCount       > 0 ||
+             memberReferenceWarningCount  > 0) &&
+            !configuration.ignoreWarnings)
+        {
+            throw new IOException("Please correct the above warnings first.");
+        }
+
+        if ((configuration.note == null ||
+             !configuration.note.isEmpty()) &&
+            (configuration.warn != null &&
+             configuration.warn.isEmpty() ||
+             configuration.ignoreWarnings))
+        {
+            System.out.println("Note: You're ignoring all warnings!");
+        }
+
+        // Discard unused library classes.
+        if (configuration.verbose)
+        {
+            System.out.println("Ignoring unused library classes...");
+            System.out.println("  Original number of library classes: " + originalLibraryClassPoolSize);
+            System.out.println("  Final number of library classes:    " + libraryClassPool.size());
+        }
+    }
+
+
+    /**
+     * Extracts a list of exceptions of classes for which not to print notes,
+     * from the keep configuration.
+     */
+    private StringMatcher createClassNoteExceptionMatcher(List noteExceptions)
+    {
+        if (noteExceptions != null)
+        {
+            List noteExceptionNames = new ArrayList(noteExceptions.size());
+            for (int index = 0; index < noteExceptions.size(); index++)
+            {
+                KeepClassSpecification keepClassSpecification = (KeepClassSpecification)noteExceptions.get(index);
+                if (keepClassSpecification.markClasses)
+                {
+                    // If the class itself is being kept, it's ok.
+                    String className = keepClassSpecification.className;
+                    if (className != null)
+                    {
+                        noteExceptionNames.add(className);
+                    }
+
+                    // If all of its extensions are being kept, it's ok too.
+                    String extendsClassName = keepClassSpecification.extendsClassName;
+                    if (extendsClassName != null)
+                    {
+                        noteExceptionNames.add(extendsClassName);
+                    }
+                }
+            }
+
+            if (noteExceptionNames.size() > 0)
+            {
+                return new ListParser(new ClassNameParser()).parse(noteExceptionNames);
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Extracts a list of exceptions of field or method names for which not to
+     * print notes, from the keep configuration.
+     */
+    private StringMatcher createClassMemberNoteExceptionMatcher(List    noteExceptions,
+                                                                boolean isField)
+    {
+        if (noteExceptions != null)
+        {
+            List noteExceptionNames = new ArrayList();
+            for (int index = 0; index < noteExceptions.size(); index++)
+            {
+                KeepClassSpecification keepClassSpecification = (KeepClassSpecification)noteExceptions.get(index);
+                List memberSpecifications = isField ?
+                    keepClassSpecification.fieldSpecifications :
+                    keepClassSpecification.methodSpecifications;
+
+                if (memberSpecifications != null)
+                {
+                    for (int index2 = 0; index2 < memberSpecifications.size(); index2++)
+                    {
+                        MemberSpecification memberSpecification =
+                            (MemberSpecification)memberSpecifications.get(index2);
+
+                        String memberName = memberSpecification.name;
+                        if (memberName != null)
+                        {
+                            noteExceptionNames.add(memberName);
+                        }
+                    }
+                }
+            }
+
+            if (noteExceptionNames.size() > 0)
+            {
+                return new ListParser(new ClassNameParser()).parse(noteExceptionNames);
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/src/proguard/InputReader.java b/src/proguard/InputReader.java
new file mode 100644
index 0000000..c088324
--- /dev/null
+++ b/src/proguard/InputReader.java
@@ -0,0 +1,233 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.ClassPool;
+import proguard.classfile.util.WarningPrinter;
+import proguard.classfile.visitor.*;
+import proguard.io.*;
+
+import java.io.IOException;
+
+/**
+ * This class reads the input class files.
+ *
+ * @author Eric Lafortune
+ */
+public class InputReader
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new InputReader to read input class files as specified by the
+     * given configuration.
+     */
+    public InputReader(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Fills the given program class pool and library class pool by reading
+     * class files, based on the current configuration.
+     */
+    public void execute(ClassPool programClassPool,
+                        ClassPool libraryClassPool) throws IOException
+    {
+        // Check if we have at least some input classes.
+        if (configuration.programJars == null)
+        {
+            throw new IOException("The input is empty. You have to specify one or more '-injars' options");
+        }
+
+        // Perform some sanity checks on the class paths.
+        checkInputOutput(configuration.libraryJars,
+                         configuration.programJars);
+        checkInputOutput(configuration.programJars,
+                         configuration.programJars);
+
+        WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
+        WarningPrinter notePrinter    = new WarningPrinter(System.out, configuration.note);
+
+        DuplicateClassPrinter duplicateClassPrinter = new DuplicateClassPrinter(notePrinter);
+
+        // Read the program class files.
+        // Prepare a data entry reader to filter all classes,
+        // which are then decoded to classes by a class reader,
+        // which are then put in the class pool by a class pool filler.
+        readInput("Reading program ",
+                  configuration.programJars,
+                  new ClassFilter(
+                  new ClassReader(false,
+                                  configuration.skipNonPublicLibraryClasses,
+                                  configuration.skipNonPublicLibraryClassMembers,
+                                  warningPrinter,
+                  new ClassPresenceFilter(programClassPool, duplicateClassPrinter,
+                  new ClassPoolFiller(programClassPool)))));
+
+        // Check if we have at least some input classes.
+        if (programClassPool.size() == 0)
+        {
+            throw new IOException("The input doesn't contain any classes. Did you specify the proper '-injars' options?");
+        }
+
+        // Read the library class files, if any.
+        if (configuration.libraryJars != null)
+        {
+            // Prepare a data entry reader to filter all classes,
+            // which are then decoded to classes by a class reader,
+            // which are then put in the class pool by a class pool filler.
+            readInput("Reading library ",
+                      configuration.libraryJars,
+                      new ClassFilter(
+                      new ClassReader(true,
+                                      configuration.skipNonPublicLibraryClasses,
+                                      configuration.skipNonPublicLibraryClassMembers,
+                                      warningPrinter,
+                      new ClassPresenceFilter(programClassPool, duplicateClassPrinter,
+                      new ClassPresenceFilter(libraryClassPool, duplicateClassPrinter,
+                      new ClassPoolFiller(libraryClassPool))))));
+        }
+
+        // Print out a summary of the notes, if necessary.
+        int noteCount = notePrinter.getWarningCount();
+        if (noteCount > 0)
+        {
+            System.err.println("Note: there were " + noteCount +
+                               " duplicate class definitions.");
+        }
+
+        // Print out a summary of the warnings, if necessary.
+        int warningCount = warningPrinter.getWarningCount();
+        if (warningCount > 0)
+        {
+            System.err.println("Warning: there were " + warningCount +
+                               " classes in incorrectly named files.");
+            System.err.println("         You should make sure all file names correspond to their class names.");
+            System.err.println("         The directory hierarchies must correspond to the package hierarchies.");
+
+            if (!configuration.ignoreWarnings)
+            {
+                System.err.println("         If you don't mind the mentioned classes not being written out,");
+                System.err.println("         you could try your luck using the '-ignorewarnings' option.");
+                throw new IOException("Please correct the above warnings first.");
+            }
+        }
+    }
+
+
+    /**
+     * Performs some sanity checks on the class paths.
+     */
+    private void checkInputOutput(ClassPath inputClassPath,
+                                  ClassPath outputClassPath)
+    throws IOException
+    {
+        if (inputClassPath == null ||
+            outputClassPath == null)
+        {
+            return;
+        }
+
+        for (int index1 = 0; index1 < inputClassPath.size(); index1++)
+        {
+            ClassPathEntry entry1 = inputClassPath.get(index1);
+            if (!entry1.isOutput())
+            {
+                for (int index2 = 0; index2 < outputClassPath.size(); index2++)
+                {
+                    ClassPathEntry entry2 = outputClassPath.get(index2);
+                    if (entry2.isOutput() &&
+                        entry2.getName().equals(entry1.getName()))
+                    {
+                        throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"]");
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Reads all input entries from the given class path.
+     */
+    private void readInput(String          messagePrefix,
+                           ClassPath       classPath,
+                           DataEntryReader reader) throws IOException
+    {
+        readInput(messagePrefix,
+                  classPath,
+                  0,
+                  classPath.size(),
+                  reader);
+    }
+
+
+    /**
+     * Reads all input entries from the given section of the given class path.
+     */
+    public void readInput(String          messagePrefix,
+                          ClassPath       classPath,
+                          int             fromIndex,
+                          int             toIndex,
+                          DataEntryReader reader) throws IOException
+    {
+        for (int index = fromIndex; index < toIndex; index++)
+        {
+            ClassPathEntry entry = classPath.get(index);
+            if (!entry.isOutput())
+            {
+                readInput(messagePrefix, entry, reader);
+            }
+        }
+    }
+
+
+    /**
+     * Reads the given input class path entry.
+     */
+    private void readInput(String          messagePrefix,
+                           ClassPathEntry  classPathEntry,
+                           DataEntryReader dataEntryReader) throws IOException
+    {
+        try
+        {
+            // Create a reader that can unwrap jars, wars, ears, and zips.
+            DataEntryReader reader =
+                DataEntryReaderFactory.createDataEntryReader(messagePrefix,
+                                                             classPathEntry,
+                                                             dataEntryReader);
+
+            // Create the data entry pump.
+            DirectoryPump directoryPump =
+                new DirectoryPump(classPathEntry.getFile());
+
+            // Pump the data entries into the reader.
+            directoryPump.pumpDataEntries(reader);
+        }
+        catch (IOException ex)
+        {
+            throw new IOException("Can't read [" + classPathEntry + "] (" + ex.getMessage() + ")");
+        }
+    }
+}
diff --git a/src/proguard/KeepClassSpecification.java b/src/proguard/KeepClassSpecification.java
new file mode 100644
index 0000000..29c0d3c
--- /dev/null
+++ b/src/proguard/KeepClassSpecification.java
@@ -0,0 +1,137 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+/**
+ * This class represents a keep option with class specification.
+ *
+ * @author Eric Lafortune
+ */
+public class KeepClassSpecification extends ClassSpecification
+{
+    public final boolean markClasses;
+    public final boolean markConditionally;
+    public final boolean allowShrinking;
+    public final boolean allowOptimization;
+    public final boolean allowObfuscation;
+
+
+    /**
+     * Creates a new KeepClassSpecification for all possible classes.
+     * @param markClasses        specifies whether to mark the classes.
+     *                           If false, only class members are marked.
+     *                           If true, the classes are marked as well.
+     * @param markConditionally  specifies whether to mark the classes and
+     *                           class members conditionally. If true, classes
+     *                           and class members are marked, on the condition
+     *                           that all specified class members are present.
+     * @param allowShrinking     specifies whether shrinking is allowed.
+     * @param allowOptimization  specifies whether optimization is allowed.
+     * @param allowObfuscation   specifies whether obfuscation is allowed.
+     */
+    public KeepClassSpecification(boolean markClasses,
+                                  boolean markConditionally,
+                                  boolean allowShrinking,
+                                  boolean allowOptimization,
+                                  boolean allowObfuscation)
+    {
+        this.markClasses       = markClasses;
+        this.markConditionally = markConditionally;
+        this.allowShrinking    = allowShrinking;
+        this.allowOptimization = allowOptimization;
+        this.allowObfuscation  = allowObfuscation;
+    }
+
+
+    /**
+     * Creates a new KeepClassSpecification.
+     * @param markClasses        specifies whether to mark the classes.
+     *                           If false, only class members are marked.
+     *                           If true, the classes are marked as well.
+     * @param markConditionally  specifies whether to mark the classes and
+     *                           class members conditionally. If true, classes
+     *                           and class members are marked, on the condition
+     *                           that all specified class members are present.
+     * @param allowShrinking     specifies whether shrinking is allowed.
+     * @param allowOptimization  specifies whether optimization is allowed.
+     * @param allowObfuscation   specifies whether obfuscation is allowed.
+     * @param classSpecification the specification of classes and class members.
+     */
+    public KeepClassSpecification(boolean            markClasses,
+                                  boolean            markConditionally,
+                                  boolean            allowShrinking,
+                                  boolean            allowOptimization,
+                                  boolean            allowObfuscation,
+                                  ClassSpecification classSpecification)
+    {
+        super(classSpecification);
+
+        this.markClasses       = markClasses;
+        this.markConditionally = markConditionally;
+        this.allowShrinking    = allowShrinking;
+        this.allowOptimization = allowOptimization;
+        this.allowObfuscation  = allowObfuscation;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        KeepClassSpecification other = (KeepClassSpecification)object;
+        return
+            this.markClasses       == other.markClasses       &&
+            this.markConditionally == other.markConditionally &&
+            this.allowShrinking    == other.allowShrinking    &&
+            this.allowOptimization == other.allowOptimization &&
+            this.allowObfuscation  == other.allowObfuscation  &&
+            super.equals(other);
+    }
+
+    public int hashCode()
+    {
+        return
+            (markClasses       ? 0 :  1) ^
+            (markConditionally ? 0 :  2) ^
+            (allowShrinking    ? 0 :  4) ^
+            (allowOptimization ? 0 :  8) ^
+            (allowObfuscation  ? 0 : 16) ^
+            super.hashCode();
+    }
+
+    public Object clone()
+    {
+//        try
+//        {
+            return super.clone();
+//        }
+//        catch (CloneNotSupportedException e)
+//        {
+//            return null;
+//        }
+    }
+}
diff --git a/src/proguard/MANIFEST.MF b/src/proguard/MANIFEST.MF
new file mode 100644
index 0000000..3bad15e
--- /dev/null
+++ b/src/proguard/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: proguard.ProGuard
diff --git a/src/proguard/MemberSpecification.java b/src/proguard/MemberSpecification.java
new file mode 100644
index 0000000..1cfa962
--- /dev/null
+++ b/src/proguard/MemberSpecification.java
@@ -0,0 +1,114 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+
+/**
+ * This class stores a specification of class members. The specification is
+ * template-based: the class member names and descriptors can contain wildcards.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberSpecification
+{
+    public int    requiredSetAccessFlags;
+    public int    requiredUnsetAccessFlags;
+    public final String annotationType;
+    public final String name;
+    public final String descriptor;
+
+
+    /**
+     * Creates a new option to keep all possible class members.
+     */
+    public MemberSpecification()
+    {
+        this(0,
+             0,
+             null,
+             null,
+             null);
+    }
+
+
+    /**
+     * Creates a new option to keep the specified class member(s).
+     *
+     * @param requiredSetAccessFlags   the class access flags that must be set
+     *                                 in order for the class to apply.
+     * @param requiredUnsetAccessFlags the class access flags that must be unset
+     *                                 in order for the class to apply.
+     * @param annotationType           the name of the class that must be an
+     *                                 annotation in order for the class member
+     *                                 to apply. The name may be null to specify
+     *                                 that no annotation is required.
+     * @param name                     the class member name. The name may be
+     *                                 null to specify any class member or it
+     *                                 may contain "*" or "?" wildcards.
+     * @param descriptor               the class member descriptor. The
+     *                                 descriptor may be null to specify any
+     *                                 class member or it may contain
+     *                                 "**", "*", or "?" wildcards.
+     */
+    public MemberSpecification(int    requiredSetAccessFlags,
+                               int    requiredUnsetAccessFlags,
+                               String annotationType,
+                               String name,
+                               String descriptor)
+    {
+        this.requiredSetAccessFlags   = requiredSetAccessFlags;
+        this.requiredUnsetAccessFlags = requiredUnsetAccessFlags;
+        this.annotationType           = annotationType;
+        this.name                     = name;
+        this.descriptor               = descriptor;
+    }
+
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        MemberSpecification other = (MemberSpecification)object;
+        return
+            (this.requiredSetAccessFlags   == other.requiredSetAccessFlags                                                                      ) &&
+            (this.requiredUnsetAccessFlags == other.requiredUnsetAccessFlags                                                                    ) &&
+            (this.annotationType == null ? other.annotationType == null : this.annotationType.equals(other.annotationType)) &&
+            (this.name           == null ? other.name           == null : this.name.equals(other.name)                    ) &&
+            (this.descriptor     == null ? other.descriptor     == null : this.descriptor.equals(other.descriptor)        );
+    }
+
+    public int hashCode()
+    {
+        return
+            (requiredSetAccessFlags                                ) ^
+            (requiredUnsetAccessFlags                              ) ^
+            (annotationType == null ? 0 : annotationType.hashCode()) ^
+            (name           == null ? 0 : name.hashCode()          ) ^
+            (descriptor     == null ? 0 : descriptor.hashCode()    );
+    }
+}
diff --git a/src/proguard/OutputWriter.java b/src/proguard/OutputWriter.java
new file mode 100644
index 0000000..10c18fb
--- /dev/null
+++ b/src/proguard/OutputWriter.java
@@ -0,0 +1,256 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.ClassPool;
+import proguard.classfile.util.ClassUtil;
+import proguard.io.*;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * This class writes the output class files.
+ *
+ * @author Eric Lafortune
+ */
+public class OutputWriter
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new OutputWriter to write output class files as specified by
+     * the given configuration.
+     */
+    public OutputWriter(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Writes the given class pool to class files, based on the current
+     * configuration.
+     */
+    public void execute(ClassPool programClassPool) throws IOException
+    {
+        ClassPath programJars = configuration.programJars;
+
+        // Perform a check on the first jar.
+        ClassPathEntry firstEntry = programJars.get(0);
+        if (firstEntry.isOutput())
+        {
+            throw new IOException("The output jar [" + firstEntry.getName() +
+                                  "] must be specified after an input jar, or it will be empty.");
+        }
+
+        // Perform some checks on the output jars.
+        for (int index = 0; index < programJars.size() - 1; index++)
+        {
+            ClassPathEntry entry = programJars.get(index);
+            if (entry.isOutput())
+            {
+                // Check if all but the last output jars have filters.
+                if (entry.getFilter()    == null &&
+                    entry.getJarFilter() == null &&
+                    entry.getWarFilter() == null &&
+                    entry.getEarFilter() == null &&
+                    entry.getZipFilter() == null &&
+                    programJars.get(index + 1).isOutput())
+                {
+                    throw new IOException("The output jar [" + entry.getName() +
+                                          "] must have a filter, or all subsequent jars will be empty.");
+                }
+
+                // Check if the output jar name is different from the input jar names.
+                for (int inIndex = 0; inIndex < programJars.size(); inIndex++)
+                {
+                    ClassPathEntry otherEntry = programJars.get(inIndex);
+
+                    if (!otherEntry.isOutput() &&
+                        entry.getFile().equals(otherEntry.getFile()))
+                    {
+                        throw new IOException("The output jar [" + entry.getName() +
+                                              "] must be different from all input jars.");
+                    }
+                }
+            }
+        }
+
+        int firstInputIndex = 0;
+        int lastInputIndex  = 0;
+
+        // Go over all program class path entries.
+        for (int index = 0; index < programJars.size(); index++)
+        {
+            // Is it an input entry?
+            ClassPathEntry entry = programJars.get(index);
+            if (!entry.isOutput())
+            {
+                // Remember the index of the last input entry.
+                lastInputIndex = index;
+            }
+            else
+            {
+                // Check if this the last output entry in a series.
+                int nextIndex = index + 1;
+                if (nextIndex == programJars.size() ||
+                    !programJars.get(nextIndex).isOutput())
+                {
+                    // Write the processed input entries to the output entries.
+                    writeOutput(programClassPool,
+                                programJars,
+                                firstInputIndex,
+                                lastInputIndex + 1,
+                                nextIndex);
+
+                    // Start with the next series of input entries.
+                    firstInputIndex = nextIndex;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Transfers the specified input jars to the specified output jars.
+     */
+    private void writeOutput(ClassPool programClassPool,
+                             ClassPath classPath,
+                             int       fromInputIndex,
+                             int       fromOutputIndex,
+                             int       toOutputIndex)
+    throws IOException
+    {
+        try
+        {
+            // Construct the writer that can write jars, wars, ears, zips, and
+            // directories, cascading over the specified output entries.
+            DataEntryWriter writer =
+                DataEntryWriterFactory.createDataEntryWriter(classPath,
+                                                             fromOutputIndex,
+                                                             toOutputIndex);
+
+            // The writer will be used to write possibly obfuscated class files.
+            DataEntryReader classRewriter =
+                new ClassRewriter(programClassPool, writer);
+
+            // The writer will also be used to write resource files.
+            DataEntryReader resourceCopier =
+                new DataEntryCopier(writer);
+
+            DataEntryReader resourceRewriter = resourceCopier;
+
+            // Wrap the resource writer with a filter and a data entry rewriter,
+            // if required.
+            if (configuration.adaptResourceFileContents != null)
+            {
+                resourceRewriter =
+                    new NameFilter(configuration.adaptResourceFileContents,
+                    new NameFilter("META-INF/**",
+                        new ManifestRewriter(programClassPool, writer),
+                        new DataEntryRewriter(programClassPool, writer)),
+                    resourceRewriter);
+            }
+
+            // Wrap the resource writer with a filter and a data entry renamer,
+            // if required.
+            if (configuration.adaptResourceFileNames != null)
+            {
+                Map packagePrefixMap = createPackagePrefixMap(programClassPool);
+
+                resourceRewriter =
+                    new NameFilter(configuration.adaptResourceFileNames,
+                    new DataEntryObfuscator(programClassPool,
+                                            packagePrefixMap,
+                                            resourceRewriter),
+                    resourceRewriter);
+            }
+
+            DataEntryReader directoryRewriter = null;
+
+            // Wrap the directory writer with a filter and a data entry renamer,
+            // if required.
+            if (configuration.keepDirectories != null)
+            {
+                Map packagePrefixMap = createPackagePrefixMap(programClassPool);
+
+                directoryRewriter =
+                    new NameFilter(configuration.keepDirectories,
+                    new DataEntryRenamer(packagePrefixMap,
+                                         resourceCopier,
+                                         resourceCopier));
+            }
+
+            // Create the reader that can write class files and copy directories
+            // and resource files to the main writer.
+            DataEntryReader reader =
+                new ClassFilter(    classRewriter,
+                new DirectoryFilter(directoryRewriter,
+                                    resourceRewriter));
+
+            // Go over the specified input entries and write their processed
+            // versions.
+            new InputReader(configuration).readInput("  Copying resources from program ",
+                                                     classPath,
+                                                     fromInputIndex,
+                                                     fromOutputIndex,
+                                                     reader);
+
+            // Close all output entries.
+            writer.close();
+        }
+        catch (IOException ex)
+        {
+            throw new IOException("Can't write [" + classPath.get(fromOutputIndex).getName() + "] (" + ex.getMessage() + ")");
+        }
+    }
+
+
+    /**
+     * Creates a map of old package prefixes to new package prefixes, based on
+     * the given class pool.
+     */
+    private static Map createPackagePrefixMap(ClassPool classPool)
+    {
+        Map PackagePrefixMap = new HashMap();
+
+        Iterator iterator = classPool.classNames();
+        while (iterator.hasNext())
+        {
+            String className     = (String)iterator.next();
+            String PackagePrefix = ClassUtil.internalPackagePrefix(className);
+
+            String mappedNewPackagePrefix = (String)PackagePrefixMap.get(PackagePrefix);
+            if (mappedNewPackagePrefix == null ||
+                !mappedNewPackagePrefix.equals(PackagePrefix))
+            {
+                String newClassName     = classPool.getClass(className).getName();
+                String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
+
+                PackagePrefixMap.put(PackagePrefix, newPackagePrefix);
+            }
+        }
+
+        return PackagePrefixMap;
+    }
+}
diff --git a/src/proguard/ParseException.java b/src/proguard/ParseException.java
new file mode 100644
index 0000000..9294200
--- /dev/null
+++ b/src/proguard/ParseException.java
@@ -0,0 +1,51 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+
+/**
+ * This <code>Exception</code> signals that a parse exception of some
+ * sort has occurred.
+ *
+ * @author Eric Lafortune
+ */
+public class ParseException extends Exception {
+
+    /**
+     * Constructs a <code>ParseException</code> with <code>null</code>
+     * as its error detail message.
+     */
+    public ParseException() {
+        super();
+    }
+
+    /**
+     * Constructs a <code>ParseException</code> with the specified detail
+     * message. The error message string <code>s</code> can later be
+     * retrieved by the <code>{@link Throwable#getMessage}</code>
+     * method of class <code>Throwable</code>.
+     *
+     * @param s the detail message.
+     */
+    public ParseException(String s) {
+        super(s);
+    }
+}
diff --git a/src/proguard/ProGuard.java b/src/proguard/ProGuard.java
new file mode 100644
index 0000000..8c30e10
--- /dev/null
+++ b/src/proguard/ProGuard.java
@@ -0,0 +1,519 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.ClassPool;
+import proguard.classfile.editor.ClassElementSorter;
+import proguard.classfile.visitor.*;
+import proguard.obfuscate.Obfuscator;
+import proguard.optimize.Optimizer;
+import proguard.preverify.*;
+import proguard.shrink.Shrinker;
+
+import java.io.*;
+
+/**
+ * Tool for shrinking, optimizing, obfuscating, and preverifying Java classes.
+ *
+ * @author Eric Lafortune
+ */
+public class ProGuard
+{
+    public static final String VERSION = "ProGuard, version 4.4";
+
+    private final Configuration configuration;
+    private       ClassPool     programClassPool = new ClassPool();
+    private final ClassPool     libraryClassPool = new ClassPool();
+
+
+    /**
+     * Creates a new ProGuard object to process jars as specified by the given
+     * configuration.
+     */
+    public ProGuard(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Performs all subsequent ProGuard operations.
+     */
+    public void execute() throws IOException
+    {
+        System.out.println(VERSION);
+
+        GPL.check();
+
+        if (configuration.printConfiguration != null)
+        {
+            printConfiguration();
+        }
+
+        if (configuration.programJars != null     &&
+            configuration.programJars.hasOutput() &&
+            new UpToDateChecker(configuration).check())
+        {
+            return;
+        }
+
+        readInput();
+
+        if (configuration.shrink    ||
+            configuration.optimize  ||
+            configuration.obfuscate ||
+            configuration.preverify)
+        {
+            initialize();
+        }
+
+        if (configuration.targetClassVersion != 0)
+        {
+            target();
+        }
+
+        if (configuration.printSeeds != null)
+        {
+            printSeeds();
+        }
+
+        if (configuration.shrink)
+        {
+            shrink();
+        }
+
+        if (configuration.preverify)
+        {
+            inlineSubroutines();
+        }
+
+        if (configuration.optimize)
+        {
+            for (int optimizationPass = 0;
+                 optimizationPass < configuration.optimizationPasses;
+                 optimizationPass++)
+            {
+                if (!optimize())
+                {
+                    // Stop optimizing if the code doesn't improve any further.
+                    break;
+                }
+
+                // Shrink again, if we may.
+                if (configuration.shrink)
+                {
+                    // Don't print any usage this time around.
+                    configuration.printUsage       = null;
+                    configuration.whyAreYouKeeping = null;
+
+                    shrink();
+                }
+            }
+        }
+
+        if (configuration.obfuscate)
+        {
+            obfuscate();
+        }
+
+        if (configuration.preverify)
+        {
+            preverify();
+        }
+
+        if (configuration.shrink    ||
+            configuration.optimize  ||
+            configuration.obfuscate ||
+            configuration.preverify)
+        {
+            sortClassElements();
+        }
+
+        if (configuration.programJars.hasOutput())
+        {
+            writeOutput();
+        }
+
+        if (configuration.dump != null)
+        {
+            dump();
+        }
+    }
+
+
+    /**
+     * Prints out the configuration that ProGuard is using.
+     */
+    private void printConfiguration() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Printing configuration to [" + fileName(configuration.printConfiguration) + "]...");
+        }
+
+        PrintStream ps = createPrintStream(configuration.printConfiguration);
+        try
+        {
+            new ConfigurationWriter(ps).write(configuration);
+        }
+        finally
+        {
+            closePrintStream(ps);
+        }
+    }
+
+
+    /**
+     * Reads the input class files.
+     */
+    private void readInput() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Reading input...");
+        }
+
+        // Fill the program class pool and the library class pool.
+        new InputReader(configuration).execute(programClassPool, libraryClassPool);
+    }
+
+
+    /**
+     * Initializes the cross-references between all classes, performs some
+     * basic checks, and shrinks the library class pool.
+     */
+    private void initialize() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Initializing...");
+        }
+
+        new Initializer(configuration).execute(programClassPool, libraryClassPool);
+    }
+
+
+    /**
+     * Sets that target versions of the program classes.
+     */
+    private void target() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Setting target versions...");
+        }
+
+        new Targeter(configuration).execute(programClassPool);
+    }
+
+
+    /**
+     * Prints out classes and class members that are used as seeds in the
+     * shrinking and obfuscation steps.
+     */
+    private void printSeeds() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Printing kept classes, fields, and methods...");
+        }
+
+        // Check if we have at least some keep commands.
+        if (configuration.keep == null)
+        {
+            throw new IOException("You have to specify '-keep' options for the shrinking step.");
+        }
+
+        PrintStream ps = createPrintStream(configuration.printSeeds);
+        try
+        {
+            // Create a visitor for printing out the seeds. We're  printing out
+            // the program elements that are preserved against shrinking,
+            // optimization, or obfuscation.
+            SimpleClassPrinter printer = new SimpleClassPrinter(false, ps);
+            ClassPoolVisitor classPoolvisitor =
+                ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
+                                                                        new ProgramClassFilter(printer),
+                                                                        new ProgramMemberFilter(printer),
+                                                                        true,
+                                                                        true,
+                                                                        true);
+
+            // Print out the seeds.
+            programClassPool.accept(classPoolvisitor);
+            libraryClassPool.accept(classPoolvisitor);
+        }
+        finally
+        {
+            closePrintStream(ps);
+        }
+    }
+
+
+    /**
+     * Performs the shrinking step.
+     */
+    private void shrink() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Shrinking...");
+
+            // We'll print out some explanation, if requested.
+            if (configuration.whyAreYouKeeping != null)
+            {
+                System.out.println("Explaining why classes and class members are being kept...");
+            }
+
+            // We'll print out the usage, if requested.
+            if (configuration.printUsage != null)
+            {
+                System.out.println("Printing usage to [" + fileName(configuration.printUsage) + "]...");
+            }
+        }
+
+        // Perform the actual shrinking.
+        programClassPool =
+            new Shrinker(configuration).execute(programClassPool, libraryClassPool);
+    }
+
+
+    /**
+     * Performs the subroutine inlining step.
+     */
+    private void inlineSubroutines()
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Inlining subroutines...");
+        }
+
+        // Perform the actual inlining.
+        new SubroutineInliner(configuration).execute(programClassPool);
+    }
+
+
+    /**
+     * Performs the optimization step.
+     */
+    private boolean optimize() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Optimizing...");
+        }
+
+        // Perform the actual optimization.
+        return new Optimizer(configuration).execute(programClassPool, libraryClassPool);
+    }
+
+
+    /**
+     * Performs the obfuscation step.
+     */
+    private void obfuscate() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Obfuscating...");
+
+            // We'll apply a mapping, if requested.
+            if (configuration.applyMapping != null)
+            {
+                System.out.println("Applying mapping [" + fileName(configuration.applyMapping) + "]");
+            }
+
+            // We'll print out the mapping, if requested.
+            if (configuration.printMapping != null)
+            {
+                System.out.println("Printing mapping to [" + fileName(configuration.printMapping) + "]...");
+            }
+        }
+
+        // Perform the actual obfuscation.
+        new Obfuscator(configuration).execute(programClassPool, libraryClassPool);
+    }
+
+
+    /**
+     * Performs the preverification step.
+     */
+    private void preverify()
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Preverifying...");
+        }
+
+        // Perform the actual preverification.
+        new Preverifier(configuration).execute(programClassPool);
+    }
+
+
+    /**
+     * Sorts the elements of all program classes.
+     */
+    private void sortClassElements()
+    {
+        programClassPool.classesAccept(new ClassElementSorter());
+    }
+
+
+    /**
+     * Writes the output class files.
+     */
+    private void writeOutput() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Writing output...");
+        }
+
+        // Write out the program class pool.
+        new OutputWriter(configuration).execute(programClassPool);
+    }
+
+
+    /**
+     * Prints out the contents of the program classes.
+     */
+    private void dump() throws IOException
+    {
+        if (configuration.verbose)
+        {
+            System.out.println("Printing classes to [" + fileName(configuration.dump) + "]...");
+        }
+
+        PrintStream ps = createPrintStream(configuration.dump);
+        try
+        {
+            programClassPool.classesAccept(new ClassPrinter(ps));
+        }
+        finally
+        {
+            closePrintStream(ps);
+        }
+    }
+
+
+    /**
+     * Returns a print stream for the given file, or the standard output if
+     * the file name is empty.
+     */
+    private PrintStream createPrintStream(File file)
+    throws FileNotFoundException
+    {
+        return isFile(file) ?
+            new PrintStream(new BufferedOutputStream(new FileOutputStream(file))) :
+            System.out;
+    }
+
+
+    /**
+     * Closes the given print stream, or closes it if is the standard output.
+     * @param printStream
+     */
+    private void closePrintStream(PrintStream printStream)
+    {
+        if (printStream == System.out)
+        {
+            printStream.flush();
+        }
+        else
+        {
+            printStream.close();
+        }
+    }
+
+
+    /**
+     * Returns the absolute file name for the given file, or the standard output
+     * if the file name is empty.
+     */
+    private String fileName(File file)
+    {
+        return isFile(file) ?
+            file.getAbsolutePath() :
+            "standard output";
+    }
+
+
+    /**
+     * Returns whether the given file is actually a file, or just a placeholder
+     * for the standard output.
+     */
+    private boolean isFile(File file)
+    {
+        return file.getPath().length() > 0;
+    }
+
+
+    /**
+     * The main method for ProGuard.
+     */
+    public static void main(String[] args)
+    {
+        if (args.length == 0)
+        {
+            System.out.println(VERSION);
+            System.out.println("Usage: java proguard.ProGuard [options ...]");
+            System.exit(1);
+        }
+
+        // Create the default options.
+        Configuration configuration = new Configuration();
+
+        try
+        {
+            // Parse the options specified in the command line arguments.
+            ConfigurationParser parser = new ConfigurationParser(args);
+
+            try
+            {
+                parser.parse(configuration);
+            }
+            finally
+            {
+                parser.close();
+            }
+
+            // Execute ProGuard with these options.
+            new ProGuard(configuration).execute();
+        }
+        catch (Exception ex)
+        {
+            if (configuration.verbose)
+            {
+                // Print a verbose stack trace.
+                ex.printStackTrace();
+            }
+            else
+            {
+                // Print just the stack trace message.
+                System.err.println("Error: "+ex.getMessage());
+            }
+
+            System.exit(1);
+        }
+
+        System.exit(0);
+    }
+}
diff --git a/src/proguard/SubclassedClassFilter.java b/src/proguard/SubclassedClassFilter.java
new file mode 100644
index 0000000..27cac11
--- /dev/null
+++ b/src/proguard/SubclassedClassFilter.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor delegates all its method calls to another ClassVisitor,
+ * but only for Clazz objects that are being subclassed.
+ *
+ * @author Eric Lafortune
+ */
+final class SubclassedClassFilter
+implements ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    public SubclassedClassFilter(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (programClass.subClasses != null)
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (libraryClass.subClasses != null)
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
diff --git a/src/proguard/Targeter.java b/src/proguard/Targeter.java
new file mode 100644
index 0000000..5067205
--- /dev/null
+++ b/src/proguard/Targeter.java
@@ -0,0 +1,88 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import proguard.classfile.ClassPool;
+import proguard.classfile.util.ClassUtil;
+import proguard.classfile.visitor.ClassVersionSetter;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * This class sets the target version on program classes.
+ *
+ * @author Eric Lafortune
+ */
+public class Targeter
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new Targeter to set the target version on program classes
+     * according to the given configuration.
+     */
+    public Targeter(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Sets the target version on classes in the given program class pool.
+     */
+    public void execute(ClassPool programClassPool) throws IOException
+    {
+        Set newerClassVersions = configuration.warn != null ? null : new HashSet();
+
+        programClassPool.classesAccept(new ClassVersionSetter(configuration.targetClassVersion,
+                                                              newerClassVersions));
+
+        if (newerClassVersions != null &&
+            newerClassVersions.size() > 0)
+        {
+            System.err.print("Warning: some classes have more recent versions (");
+
+            Iterator iterator = newerClassVersions.iterator();
+            while (iterator.hasNext())
+            {
+                Integer classVersion = (Integer)iterator.next();
+                System.err.print(ClassUtil.externalClassVersion(classVersion.intValue()));
+
+                if (iterator.hasNext())
+                {
+                    System.err.print(",");
+                }
+            }
+
+            System.err.println(")");
+            System.err.println("         than the target version ("+ClassUtil.externalClassVersion(configuration.targetClassVersion)+").");
+
+            if (!configuration.ignoreWarnings)
+            {
+                System.err.println("         If you are sure this is not a problem,");
+                System.err.println("         you could try your luck using the '-ignorewarnings' option.");
+                throw new IOException("Please correct the above warnings first.");
+            }
+        }
+    }
+}
diff --git a/src/proguard/UpToDateChecker.java b/src/proguard/UpToDateChecker.java
new file mode 100644
index 0000000..9fa5d16
--- /dev/null
+++ b/src/proguard/UpToDateChecker.java
@@ -0,0 +1,153 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.io.File;
+
+/**
+ * This class checks whether the output is up to date.
+ *
+ * @author Eric Lafortune
+ */
+public class UpToDateChecker
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new UpToDateChecker with the given configuration.
+     */
+    public UpToDateChecker(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Returns whether the output is up to date, based on the modification times
+     * of the input jars, output jars, and library jars (or directories).
+     */
+    public boolean check()
+    {
+        long inputLastModified  = configuration.lastModified;
+        long outputLastModified = Long.MAX_VALUE;
+
+        ClassPath programJars = configuration.programJars;
+        ClassPath libraryJars = configuration.libraryJars;
+
+        // Check the dates of the program jars, if any.
+        if (programJars != null)
+        {
+            for (int index = 0; index < programJars.size(); index++)
+            {
+                // Break early, if possible.
+                if (inputLastModified >= outputLastModified)
+                {
+                    break;
+                }
+
+                // Update the input and output modification times.
+                ClassPathEntry classPathEntry = programJars.get(index);
+                if (classPathEntry.isOutput())
+                {
+                    long lastModified = lastModified(classPathEntry.getFile(), true);
+                    if (outputLastModified > lastModified)
+                    {
+                        outputLastModified = lastModified;
+                    }
+                }
+                else
+                {
+                    long lastModified = lastModified(classPathEntry.getFile(), false);
+                    if (inputLastModified < lastModified)
+                    {
+                        inputLastModified = lastModified;
+                    }
+                }
+            }
+        }
+
+        // Check the dates of the library jars, if any.
+        if (libraryJars != null)
+        {
+            for (int index = 0; index < libraryJars.size(); index++)
+            {
+                // Break early, if possible.
+                if (inputLastModified >= outputLastModified)
+                {
+                    break;
+                }
+
+                // Update the input modification time.
+                ClassPathEntry classPathEntry = libraryJars.get(index);
+                long lastModified = lastModified(classPathEntry.getFile(), false);
+                if (inputLastModified < lastModified)
+                {
+                    inputLastModified = lastModified;
+                }
+            }
+        }
+
+        boolean outputUpToDate = inputLastModified < outputLastModified;
+        if (outputUpToDate)
+        {
+            System.out.println("The output is up to date");
+        }
+
+        return outputUpToDate;
+    }
+
+
+    /**
+     * Returns the minimum or maximum modification time of the given file or
+     * of the files in the given directory (recursively).
+     */
+    private long lastModified(File file, boolean minimum)
+    {
+        // Is it a directory?
+        if (file.isDirectory())
+        {
+            // Ignore the directory's modification time; just recurse on its files.
+            File[] files = file.listFiles();
+
+            // Still, an empty output directory is probably a sign that it is
+            // not up to date.
+            long lastModified = files.length != 0 && minimum ?
+                Long.MAX_VALUE : 0L;
+
+            for (int index = 0; index < files.length; index++)
+            {
+                long fileLastModified = lastModified(files[index], minimum);
+                if ((lastModified < fileLastModified) ^ minimum)
+                {
+                    lastModified = fileLastModified;
+                }
+            }
+
+            return lastModified;
+        }
+        else
+        {
+            // Return the file's modification time.
+            return file.lastModified();
+        }
+    }
+}
diff --git a/src/proguard/WordReader.java b/src/proguard/WordReader.java
new file mode 100644
index 0000000..d73505a
--- /dev/null
+++ b/src/proguard/WordReader.java
@@ -0,0 +1,326 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard;
+
+import java.io.*;
+
+
+/**
+ * An abstract reader of words, with the possibility to include other readers.
+ * Words are separated by spaces or broken off at delimiters. Words containing
+ * spaces or delimiters can be quoted with single or double quotes.
+ * Comments (everything starting with '#' on a single line) are ignored.
+ *
+ * @author Eric Lafortune
+ * @noinspection TailRecursion
+ */
+public abstract class WordReader
+{
+    private static final char COMMENT_CHARACTER = '#';
+
+
+    private File       baseDir;
+    private WordReader includeWordReader;
+    private String     currentLine;
+    private int        currentLineLength;
+    private int        currentIndex;
+    private String     currentWord;
+    private String     currentComments;
+
+
+    /**
+     * Creates a new WordReader with the given base directory.
+     */
+    protected WordReader(File baseDir)
+    {
+        this.baseDir = baseDir;
+    }
+
+
+    /**
+     * Sets the base directory of this reader.
+     */
+    public void setBaseDir(File baseDir)
+    {
+        if (includeWordReader != null)
+        {
+            includeWordReader.setBaseDir(baseDir);
+        }
+        else
+        {
+            this.baseDir = baseDir;
+        }
+    }
+
+
+    /**
+     * Returns the base directory of this reader, if any.
+     */
+    public File getBaseDir()
+    {
+        return includeWordReader != null ?
+            includeWordReader.getBaseDir() :
+            baseDir;
+    }
+
+
+    /**
+     * Specifies to start reading words from the given WordReader. When it is
+     * exhausted, this WordReader will continue to provide its own words.
+     *
+     * @param newIncludeWordReader the WordReader that will start reading words.
+     */
+    public void includeWordReader(WordReader newIncludeWordReader)
+    {
+        if (includeWordReader == null)
+        {
+            includeWordReader = newIncludeWordReader;
+        }
+        else
+        {
+            includeWordReader.includeWordReader(newIncludeWordReader);
+        }
+    }
+
+
+    /**
+     * Reads a word from this WordReader, or from one of its active included
+     * WordReader objects.
+     *
+     * @return the read word.
+     */
+    public String nextWord() throws IOException
+    {
+        currentWord = null;
+
+        // See if we have an included reader to produce a word.
+        if (includeWordReader != null)
+        {
+            // Does the included word reader still produce a word?
+            currentWord = includeWordReader.nextWord();
+            if (currentWord != null)
+            {
+                // Return it if so.
+                return currentWord;
+            }
+
+            // Otherwise close and ditch the word reader.
+            includeWordReader.close();
+            includeWordReader = null;
+        }
+
+        // Get a word from this reader.
+
+        // Skip leading whitespace.
+        while (currentLine != null &&
+               currentIndex < currentLineLength &&
+               Character.isWhitespace(currentLine.charAt(currentIndex)))
+        {
+            currentIndex++;
+        }
+
+        // Make sure we have a non-blank line.
+        while (currentLine == null || currentIndex == currentLineLength)
+        {
+            currentLine = nextLine();
+            if (currentLine == null)
+            {
+                return null;
+            }
+
+            // Trim off any comments.
+            int comments_start = currentLine.indexOf(COMMENT_CHARACTER);
+            if (comments_start >= 0)
+            {
+                currentLineLength = comments_start;
+
+                // Remember the comments.
+                String comment = currentLine.substring(comments_start + 1);
+                currentComments = currentComments == null ?
+                    comment :
+                    currentComments + '\n' + comment;
+            }
+            else
+            {
+                currentLineLength = currentLine.length();
+            }
+
+            // Skip leading whitespace.
+            currentIndex = 0;
+            while (currentIndex < currentLineLength &&
+                   Character.isWhitespace(currentLine.charAt(currentIndex)))
+            {
+                currentIndex++;
+            }
+        }
+
+        // Find the word starting at the current index.
+        int startIndex = currentIndex;
+        int endIndex;
+
+        char startChar = currentLine.charAt(startIndex);
+
+        if (isDelimiter(startChar))
+        {
+            // The next word is a single delimiting character.
+            endIndex = ++currentIndex;
+        }
+        else if (isQuote(startChar))
+        {
+            // The next word is starting with a quote character.
+            // Skip the opening quote.
+            startIndex++;
+
+            // The next word is a quoted character string.
+            // Find the closing quote.
+            do
+            {
+                currentIndex++;
+
+                if (currentIndex == currentLineLength)
+                {
+                    currentWord = currentLine.substring(startIndex-1, currentIndex);
+                    throw new IOException("Missing closing quote for "+locationDescription());
+                }
+            }
+            while (currentLine.charAt(currentIndex) != startChar);
+
+            endIndex = currentIndex++;
+        }
+        else
+        {
+            // The next word is a simple character string.
+            // Find the end of the line, the first delimiter, or the first
+            // white space.
+            while (currentIndex < currentLineLength)
+            {
+                char currentCharacter = currentLine.charAt(currentIndex);
+                if (isDelimiter(currentCharacter) ||
+                    Character.isWhitespace(currentCharacter))
+                {
+                    break;
+                }
+
+                currentIndex++;
+            }
+
+            endIndex = currentIndex;
+        }
+
+        // Remember and return the parsed word.
+        currentWord = currentLine.substring(startIndex, endIndex);
+
+        return currentWord;
+    }
+
+
+    /**
+     * Returns the comments collected before returning the last word.
+     * Starts collecting new comments.
+     *
+     * @return the collected comments, or <code>null</code> if there weren't any.
+     */
+    public String lastComments() throws IOException
+    {
+        if (includeWordReader == null)
+        {
+            String comments = currentComments;
+            currentComments = null;
+            return comments;
+        }
+        else
+        {
+            return includeWordReader.lastComments();
+        }
+    }
+
+
+    /**
+     * Constructs a readable description of the current position in this
+     * WordReader and its included WordReader objects.
+     *
+     * @return the description.
+     */
+    public String locationDescription()
+    {
+        return
+            (includeWordReader == null ?
+                (currentWord == null ?
+                    "end of " :
+                    "'" + currentWord + "' in " ) :
+                (includeWordReader.locationDescription() + ",\n" +
+                 "  included from ")) +
+            lineLocationDescription();
+    }
+
+
+    /**
+     * Reads a line from this WordReader, or from one of its active included
+     * WordReader objects.
+     *
+     * @return the read line.
+     */
+    protected abstract String nextLine() throws IOException;
+
+
+    /**
+     * Returns a readable description of the current WordReader position.
+     *
+     * @return the description.
+     */
+    protected abstract String lineLocationDescription();
+
+
+    /**
+     * Closes the FileWordReader.
+     */
+    public void close() throws IOException
+    {
+        // Close and ditch the included word reader, if any.
+        if (includeWordReader != null)
+        {
+            includeWordReader.close();
+            includeWordReader = null;
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean isDelimiter(char character)
+    {
+        return character == '@' ||
+               character == '{' ||
+               character == '}' ||
+               character == '(' ||
+               character == ')' ||
+               character == ',' ||
+               character == ';' ||
+               character == File.pathSeparatorChar;
+    }
+
+
+    private boolean isQuote(char character)
+    {
+        return character == '\'' ||
+               character == '"';
+    }
+}
diff --git a/src/proguard/ant/ClassPathElement.java b/src/proguard/ant/ClassPathElement.java
new file mode 100644
index 0000000..b5c2df4
--- /dev/null
+++ b/src/proguard/ant/ClassPathElement.java
@@ -0,0 +1,191 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.ant;
+
+import org.apache.tools.ant.*;
+import org.apache.tools.ant.types.*;
+import proguard.*;
+import proguard.util.ListUtil;
+
+import java.io.File;
+
+/**
+ * This FileSet represents a class path entry (or a set of class path entries)
+ * in Ant.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassPathElement extends Path
+{
+    private String filter;
+    private String jarFilter;
+    private String warFilter;
+    private String earFilter;
+    private String zipFilter;
+
+
+    /**
+     * @see Path#Path(Project)
+     */
+    public ClassPathElement(Project project)
+    {
+        super(project);
+    }
+
+
+    /**
+     * Adds the contents of this class path element to the given class path.
+     * @param classPath the class path to be extended.
+     * @param output    specifies whether this is an output entry or not.
+     */
+    public void appendClassPathEntriesTo(ClassPath classPath, boolean output)
+    {
+        File     baseDir = getProject().getBaseDir();
+        String[] fileNames;
+
+        if (isReference())
+        {
+            // Get the referenced path or file set.
+            Object referencedObject = getCheckedRef(DataType.class,
+                                                    DataType.class.getName());
+
+            if (referencedObject instanceof Path)
+            {
+                Path path = (Path)referencedObject;
+
+                // Get the names of the files in the referenced path.
+                fileNames = path.list();
+            }
+            else if (referencedObject instanceof AbstractFileSet)
+            {
+                AbstractFileSet fileSet = (AbstractFileSet)referencedObject;
+
+                // Get the names of the existing input files in the referenced file set.
+                DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
+                baseDir   = scanner.getBasedir();
+                fileNames = scanner.getIncludedFiles();
+            }
+            else
+            {
+                throw new BuildException("The refid attribute doesn't point to a <path> element or a <fileset> element");
+            }
+        }
+        else
+        {
+            // Get the names of the files in this path.
+            fileNames = list();
+        }
+
+        if (output)
+        {
+            if (fileNames.length != 1)
+            {
+                throw new BuildException("The <outjar> element must specify exactly one file or directory ["+fileNames.length+"]");
+            }
+        }
+        //else
+        //{
+        //    if (fileNames.length < 1)
+        //    {
+        //        throw new BuildException("The <injar> element must specify at least one file or directory");
+        //    }
+        //}
+
+        for (int index = 0; index < fileNames.length; index++)
+        {
+            // Create a new class path entry, with the proper file name and
+            // any filters.
+            String fileName = fileNames[index];
+            File   file     = new File(fileName);
+
+            ClassPathEntry entry =
+                new ClassPathEntry(file.isAbsolute() ? file : new File(baseDir, fileName),
+                                   output);
+            entry.setFilter(ListUtil.commaSeparatedList(filter));
+            entry.setJarFilter(ListUtil.commaSeparatedList(jarFilter));
+            entry.setWarFilter(ListUtil.commaSeparatedList(warFilter));
+            entry.setEarFilter(ListUtil.commaSeparatedList(earFilter));
+            entry.setZipFilter(ListUtil.commaSeparatedList(zipFilter));
+
+            // Add it to the class path.
+            classPath.add(entry);
+        }
+    }
+
+
+    // Ant task attributes.
+
+    /**
+     * @deprecated Use {@link #setLocation(File)} instead.
+     */
+    public void setFile(File file)
+    {
+        setLocation(file);
+    }
+
+
+    /**
+     * @deprecated Use {@link #setLocation(File)} instead.
+     */
+    public void setDir(File file)
+    {
+        setLocation(file);
+    }
+
+
+    /**
+     * @deprecated Use {@link #setLocation(File)} instead.
+     */
+    public void setName(File file)
+    {
+        setLocation(file);
+    }
+
+
+    public void setFilter(String filter)
+    {
+        this.filter = filter;
+    }
+
+
+    public void setJarfilter(String jarFilter)
+    {
+        this.jarFilter = jarFilter;
+    }
+
+
+    public void setWarfilter(String warFilter)
+    {
+        this.warFilter = warFilter;
+    }
+
+
+    public void setEarfilter(String earFilter)
+    {
+        this.earFilter = earFilter;
+    }
+
+
+    public void setZipfilter(String zipFilter)
+    {
+        this.zipFilter = zipFilter;
+    }
+}
diff --git a/src/proguard/ant/ClassSpecificationElement.java b/src/proguard/ant/ClassSpecificationElement.java
new file mode 100644
index 0000000..f4ea2ff
--- /dev/null
+++ b/src/proguard/ant/ClassSpecificationElement.java
@@ -0,0 +1,257 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.ant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.DataType;
+import proguard.*;
+import proguard.classfile.ClassConstants;
+import proguard.classfile.util.ClassUtil;
+
+import java.util.*;
+
+/**
+ * This DataType represents a class specification in Ant.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassSpecificationElement extends DataType
+{
+    private static final String ANY_CLASS_KEYWORD  = "*";
+
+    private String access;
+    private String annotation;
+    private String type;
+    private String name;
+    private String extendsAnnotation;
+    private String extends_;
+    private List   fieldSpecifications  = new ArrayList();
+    private List   methodSpecifications = new ArrayList();
+
+
+    /**
+     * Adds the contents of this class specification element to the given list.
+     * @param classSpecifications the class specifications to be extended.
+     */
+    public void appendTo(List classSpecifications)
+    {
+        // Get the referenced file set, or else this one.
+        ClassSpecificationElement classSpecificationElement = isReference() ?
+            (ClassSpecificationElement)getCheckedRef(this.getClass(),
+                                                     this.getClass().getName()) :
+            this;
+
+        ClassSpecification classSpecification =
+            createClassSpecification(classSpecificationElement);
+
+        // Add it to the list.
+        classSpecifications.add(classSpecification);
+    }
+
+
+    /**
+     * Creates a new class specification corresponding to the contents of this
+     * class specification element.
+     */
+    protected ClassSpecification createClassSpecification(ClassSpecificationElement classSpecificationElement)
+    {
+        String access            = classSpecificationElement.access;
+        String annotation        = classSpecificationElement.annotation;
+        String type              = classSpecificationElement.type;
+        String name              = classSpecificationElement.name;
+        String extendsAnnotation = classSpecificationElement.extendsAnnotation;
+        String extends_          = classSpecificationElement.extends_;
+
+        // For backward compatibility, allow a single "*" wildcard to match
+        // any class.
+        if (name != null &&
+            name.equals(ANY_CLASS_KEYWORD))
+        {
+            name = null;
+        }
+
+        ClassSpecification classSpecification =
+            new ClassSpecification(null,
+                                   requiredAccessFlags(true,  access, type),
+                                   requiredAccessFlags(false, access, type),
+                                   annotation        != null ? ClassUtil.internalType(annotation)        : null,
+                                   name              != null ? ClassUtil.internalClassName(name)         : null,
+                                   extendsAnnotation != null ? ClassUtil.internalType(extendsAnnotation) : null,
+                                   extends_          != null ? ClassUtil.internalClassName(extends_)     : null);
+
+        for (int index = 0; index < fieldSpecifications.size(); index++)
+        {
+            classSpecification.addField((MemberSpecification)fieldSpecifications.get(index));
+        }
+
+        for (int index = 0; index < methodSpecifications.size(); index++)
+        {
+            classSpecification.addMethod((MemberSpecification)methodSpecifications.get(index));
+        }
+
+        return classSpecification;
+    }
+
+
+    // Ant task attributes.
+
+    public void setAccess(String access)
+    {
+        this.access = access;
+    }
+
+
+    public void setAnnotation(String annotation)
+    {
+        this.annotation = annotation;
+    }
+
+
+    public void setType(String type)
+    {
+        this.type = type;
+    }
+
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+
+    public void setExtendsannotation(String extendsAnnotation)
+    {
+        this.extendsAnnotation = extendsAnnotation;
+    }
+
+
+    public void setExtends(String extends_)
+    {
+        this.extends_ = extends_;
+    }
+
+
+    public void setImplements(String implements_)
+    {
+        this.extends_ = implements_;
+    }
+
+
+    // Ant task nested elements.
+
+    public void addConfiguredField(MemberSpecificationElement memberSpecificationElement)
+    {
+        if (fieldSpecifications == null)
+        {
+            fieldSpecifications = new ArrayList();
+        }
+
+        memberSpecificationElement.appendTo(fieldSpecifications,
+                                            false,
+                                            false);
+    }
+
+
+    public void addConfiguredMethod(MemberSpecificationElement memberSpecificationElement)
+    {
+        if (methodSpecifications == null)
+        {
+            methodSpecifications = new ArrayList();
+        }
+
+        memberSpecificationElement.appendTo(methodSpecifications,
+                                            true,
+                                            false);
+    }
+
+
+    public void addConfiguredConstructor(MemberSpecificationElement memberSpecificationElement)
+    {
+        if (methodSpecifications == null)
+        {
+            methodSpecifications = new ArrayList();
+        }
+
+        memberSpecificationElement.appendTo(methodSpecifications,
+                                            true,
+                                            true);
+    }
+
+
+    // Small utility methods.
+
+    private int requiredAccessFlags(boolean set,
+                                    String  access,
+                                    String  type)
+    throws BuildException
+    {
+        int accessFlags = 0;
+
+        if (access != null)
+        {
+            StringTokenizer tokenizer = new StringTokenizer(access, " ,");
+            while (tokenizer.hasMoreTokens())
+            {
+                String token = tokenizer.nextToken();
+
+                if (token.startsWith("!") ^ set)
+                {
+                    String strippedToken = token.startsWith("!") ?
+                        token.substring(1) :
+                        token;
+
+                    int accessFlag =
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_PUBLIC)     ? ClassConstants.INTERNAL_ACC_PUBLIC      :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_FINAL)      ? ClassConstants.INTERNAL_ACC_FINAL       :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT)   ? ClassConstants.INTERNAL_ACC_ABSTRACT    :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_ANNOTATION) ? ClassConstants.INTERNAL_ACC_ANNOTATTION :
+                        0;
+
+                    if (accessFlag == 0)
+                    {
+                        throw new BuildException("Incorrect class access modifier ["+strippedToken+"]");
+                    }
+
+                    accessFlags |= accessFlag;
+                }
+            }
+        }
+
+        if (type != null && (type.startsWith("!") ^ set))
+        {
+            int accessFlag =
+                type.equals("class")                                     ? 0                                     :
+                type.equals(      ClassConstants.EXTERNAL_ACC_INTERFACE) ||
+                type.equals("!" + ClassConstants.EXTERNAL_ACC_INTERFACE) ? ClassConstants.INTERNAL_ACC_INTERFACE :
+                type.equals(      ClassConstants.EXTERNAL_ACC_ENUM)      ||
+                type.equals("!" + ClassConstants.EXTERNAL_ACC_ENUM)      ? ClassConstants.INTERNAL_ACC_ENUM      :
+                                                                           -1;
+            if (accessFlag == -1)
+            {
+                throw new BuildException("Incorrect class type ["+type+"]");
+            }
+
+            accessFlags |= accessFlag;
+        }
+
+        return accessFlags;
+    }
+}
diff --git a/src/proguard/ant/ConfigurationElement.java b/src/proguard/ant/ConfigurationElement.java
new file mode 100644
index 0000000..76e9418
--- /dev/null
+++ b/src/proguard/ant/ConfigurationElement.java
@@ -0,0 +1,53 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.ant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.DataType;
+import proguard.Configuration;
+
+/**
+ * This DataType represents a reference to a ProGuard configuration in Ant.
+ *
+ * @author Eric Lafortune
+ */
+public class ConfigurationElement extends DataType
+{
+    /**
+     * Adds the contents of this configuration task to the given configuration.
+     * @param configuration the configuration to be extended.
+     */
+    public void appendTo(Configuration configuration)
+    {
+        // Get the referenced element.
+        if (!isReference())
+        {
+            throw new BuildException("Nested element <configuration> must have a refid attribute");
+        }
+
+        ConfigurationTask configurationTask =
+            (ConfigurationTask)getCheckedRef(ConfigurationTask.class,
+                                             ConfigurationTask.class.getName());
+
+        // Append the referenced configuration entries to the given configuration.
+        configurationTask.appendTo(configuration);
+    }
+}
diff --git a/src/proguard/ant/ConfigurationTask.java b/src/proguard/ant/ConfigurationTask.java
new file mode 100644
index 0000000..0d2f04f
--- /dev/null
+++ b/src/proguard/ant/ConfigurationTask.java
@@ -0,0 +1,440 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.ant;
+
+import org.apache.tools.ant.*;
+import proguard.*;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * This Task allows to define a ProGuard configuration from Ant.
+ *
+ * @author Eric Lafortune
+ */
+public class ConfigurationTask extends Task
+{
+    protected final Configuration configuration = new Configuration();
+
+
+    /**
+     * Adds the contents of this configuration task to the given configuration.
+     * @param configuration the configuration to be extended.
+     */
+    public void appendTo(Configuration configuration)
+    {
+        // Append all of these configuration entries to the given configuration.
+        configuration.programJars               = extendClassPath(configuration.programJars,
+                                                                  this.configuration.programJars);
+
+        configuration.libraryJars               = extendClassPath(configuration.libraryJars,
+                                                                  this.configuration.libraryJars);
+
+        configuration.keep                      = extendClassSpecifications(configuration.keep,
+                                                                            this.configuration.keep);
+
+        configuration.keepDirectories           = extendList(configuration.keepDirectories,
+                                                             this.configuration.keepDirectories);
+
+        configuration.whyAreYouKeeping          = extendClassSpecifications(configuration.whyAreYouKeeping,
+                                                                            this.configuration.whyAreYouKeeping);
+
+        configuration.optimizations             = extendClassSpecifications(configuration.optimizations,
+                                                                            this.configuration.optimizations);
+
+        configuration.assumeNoSideEffects       = extendClassSpecifications(configuration.assumeNoSideEffects,
+                                                                            this.configuration.assumeNoSideEffects);
+
+        configuration.keepPackageNames          = extendList(configuration.keepPackageNames,
+                                                             this.configuration.keepPackageNames);
+
+        configuration.keepAttributes            = extendList(configuration.keepAttributes,
+                                                             this.configuration.keepAttributes);
+
+        configuration.adaptClassStrings         = extendList(configuration.adaptClassStrings,
+                                                             this.configuration.adaptClassStrings);
+
+        configuration.adaptResourceFileNames    = extendList(configuration.adaptResourceFileNames,
+                                                             this.configuration.adaptResourceFileNames);
+
+        configuration.adaptResourceFileContents = extendList(configuration.adaptResourceFileContents,
+                                                             this.configuration.adaptResourceFileContents);
+
+        configuration.note                      = extendList(configuration.note,
+                                                             this.configuration.note);
+
+        configuration.warn                      = extendList(configuration.warn,
+                                                             this.configuration.warn);
+    }
+
+
+    // Ant task nested elements.
+
+    public void addConfiguredInjar(ClassPathElement classPathElement)
+    {
+        configuration.programJars = extendClassPath(configuration.programJars,
+                                                    classPathElement,
+                                                    false);
+    }
+
+
+    public void addConfiguredOutjar(ClassPathElement classPathElement)
+    {
+        configuration.programJars = extendClassPath(configuration.programJars,
+                                                    classPathElement,
+                                                    true);
+    }
+
+
+    public void addConfiguredLibraryjar(ClassPathElement classPathElement)
+    {
+        configuration.libraryJars = extendClassPath(configuration.libraryJars,
+                                                    classPathElement,
+                                                    false);
+    }
+
+
+    public void addConfiguredKeepdirectory(FilterElement filterElement)
+    {
+        configuration.keepDirectories = extendFilter(configuration.keepDirectories,
+                                                     filterElement);
+    }
+
+
+    public void addConfiguredKeepdirectories(FilterElement filterElement)
+    {
+        configuration.keepDirectories = extendFilter(configuration.keepDirectories,
+                                                     filterElement);
+    }
+
+
+    public void addConfiguredKeep(KeepSpecificationElement keepSpecificationElement)
+    {
+        configuration.keep = extendKeepSpecifications(configuration.keep,
+                                                      keepSpecificationElement,
+                                                      true,
+                                                      false);
+    }
+
+
+    public void addConfiguredKeepclassmembers(KeepSpecificationElement keepSpecificationElement)
+    {
+        configuration.keep = extendKeepSpecifications(configuration.keep,
+                                                      keepSpecificationElement,
+                                                      false,
+                                                      false);
+    }
+
+
+    public void addConfiguredKeepclasseswithmembers(KeepSpecificationElement keepSpecificationElement)
+    {
+        configuration.keep = extendKeepSpecifications(configuration.keep,
+                                                      keepSpecificationElement,
+                                                      true,
+                                                      true);
+    }
+
+
+    public void addConfiguredKeepnames(KeepSpecificationElement keepSpecificationElement)
+    {
+        // Set the shrinking flag, based on the name (backward compatibility).
+        keepSpecificationElement.setAllowshrinking(true);
+
+        configuration.keep = extendKeepSpecifications(configuration.keep,
+                                                      keepSpecificationElement,
+                                                      true,
+                                                      false);
+    }
+
+
+    public void addConfiguredKeepclassmembernames(KeepSpecificationElement keepSpecificationElement)
+    {
+        // Set the shrinking flag, based on the name (backward compatibility).
+        keepSpecificationElement.setAllowshrinking(true);
+
+        configuration.keep = extendKeepSpecifications(configuration.keep,
+                                                      keepSpecificationElement,
+                                                      false,
+                                                      false);
+    }
+
+
+    public void addConfiguredKeepclasseswithmembernames(KeepSpecificationElement keepSpecificationElement)
+    {
+        // Set the shrinking flag, based on the name (backward compatibility).
+        keepSpecificationElement.setAllowshrinking(true);
+
+        configuration.keep = extendKeepSpecifications(configuration.keep,
+                                                      keepSpecificationElement,
+                                                      true,
+                                                      true);
+    }
+
+
+    public void addConfiguredWhyareyoukeeping(ClassSpecificationElement classSpecificationElement)
+    {
+        configuration.whyAreYouKeeping = extendClassSpecifications(configuration.whyAreYouKeeping,
+                                                                   classSpecificationElement);
+    }
+
+
+    public void addConfiguredAssumenosideeffects(ClassSpecificationElement classSpecificationElement)
+    {
+        configuration.assumeNoSideEffects = extendClassSpecifications(configuration.assumeNoSideEffects,
+                                                                      classSpecificationElement);
+    }
+
+
+    public void addConfiguredOptimizations(FilterElement filterElement)
+    {
+        addConfiguredOptimization(filterElement);
+    }
+
+
+    public void addConfiguredOptimization(FilterElement filterElement)
+    {
+        configuration.optimizations = extendFilter(configuration.optimizations,
+                                                   filterElement);
+    }
+
+
+    public void addConfiguredKeeppackagename(FilterElement filterElement)
+    {
+        configuration.keepPackageNames = extendFilter(configuration.keepPackageNames,
+                                                      filterElement,
+                                                      true);
+    }
+
+
+    public void addConfiguredKeeppackagenames(FilterElement filterElement)
+    {
+        configuration.keepPackageNames = extendFilter(configuration.keepPackageNames,
+                                                      filterElement,
+                                                      true);
+    }
+
+
+    public void addConfiguredKeepattributes(FilterElement filterElement)
+    {
+        addConfiguredKeepattribute(filterElement);
+    }
+
+
+    public void addConfiguredKeepattribute(FilterElement filterElement)
+    {
+        configuration.keepAttributes = extendFilter(configuration.keepAttributes,
+                                                    filterElement);
+    }
+
+
+    public void addConfiguredAdaptclassstrings(FilterElement filterElement)
+    {
+        configuration.adaptClassStrings = extendFilter(configuration.adaptClassStrings,
+                                                       filterElement, true);
+    }
+
+
+    public void addConfiguredAdaptresourcefilenames(FilterElement filterElement)
+    {
+        configuration.adaptResourceFileNames = extendFilter(configuration.adaptResourceFileNames,
+                                                            filterElement);
+    }
+
+
+    public void addConfiguredAdaptresourcefilecontents(FilterElement filterElement)
+    {
+        configuration.adaptResourceFileContents = extendFilter(configuration.adaptResourceFileContents,
+                                                               filterElement);
+    }
+
+
+    public void addConfiguredDontnote(FilterElement filterElement)
+    {
+        configuration.note = extendFilter(configuration.note, filterElement, true);
+    }
+
+
+    public void addConfiguredDontwarn(FilterElement filterElement)
+    {
+        configuration.warn = extendFilter(configuration.warn, filterElement, true);
+    }
+
+
+    public void addConfiguredConfiguration(ConfigurationElement configurationElement)
+    {
+        configurationElement.appendTo(configuration);
+    }
+
+
+    // Implementations for Task.
+
+    public void addText(String text) throws BuildException
+    {
+        try
+        {
+            String arg = getProject().replaceProperties(text);
+
+            ConfigurationParser parser = new ConfigurationParser(new String[] { arg },
+                                                                 getProject().getBaseDir());
+
+            try
+            {
+                parser.parse(configuration);
+            }
+            catch (ParseException ex)
+            {
+                throw new BuildException(ex.getMessage());
+            }
+            finally
+            {
+                parser.close();
+            }
+        }
+        catch (IOException ex)
+        {
+            throw new BuildException(ex.getMessage());
+        }
+    }
+
+
+    // Small utility methods.
+
+    private ClassPath extendClassPath(ClassPath        classPath,
+                                      ClassPathElement classPathElement,
+                                      boolean          output)
+    {
+        if (classPath == null)
+        {
+            classPath = new ClassPath();
+        }
+
+        classPathElement.appendClassPathEntriesTo(classPath,
+                                                  output);
+
+        return classPath;
+    }
+
+
+    private ClassPath extendClassPath(ClassPath classPath,
+                                      ClassPath additionalClassPath)
+    {
+        if (additionalClassPath != null)
+        {
+            if (classPath == null)
+            {
+                classPath = new ClassPath();
+            }
+
+            classPath.addAll(additionalClassPath);
+        }
+
+        return classPath;
+    }
+
+
+    private List extendKeepSpecifications(List                     keepSpecifications,
+                                          KeepSpecificationElement keepSpecificationElement,
+                                          boolean                  markClasses,
+                                          boolean                  markClassesConditionally)
+    {
+        if (keepSpecifications == null)
+        {
+            keepSpecifications = new ArrayList();
+        }
+
+        keepSpecificationElement.appendTo(keepSpecifications,
+                                          markClasses,
+                                          markClassesConditionally);
+
+        return keepSpecifications;
+    }
+
+
+    private List extendClassSpecifications(List                      classSpecifications,
+                                           ClassSpecificationElement classSpecificationElement)
+    {
+        if (classSpecifications == null)
+        {
+            classSpecifications = new ArrayList();
+        }
+
+        classSpecificationElement.appendTo(classSpecifications);
+
+        return classSpecifications;
+    }
+
+
+    private List extendClassSpecifications(List classSpecifications,
+                                           List additionalClassSpecifications)
+    {
+        if (additionalClassSpecifications != null)
+        {
+            if (classSpecifications == null)
+            {
+                classSpecifications = new ArrayList();
+            }
+
+            classSpecifications.addAll(additionalClassSpecifications);
+        }
+
+        return classSpecifications;
+    }
+
+
+    private List extendFilter(List          filter,
+                              FilterElement filterElement)
+    {
+        return extendFilter(filter, filterElement, false);
+    }
+
+
+    private List extendFilter(List          filter,
+                              FilterElement filterElement,
+                              boolean       internal)
+    {
+        if (filter == null)
+        {
+            filter = new ArrayList();
+        }
+
+        filterElement.appendTo(filter, internal);
+
+        return filter;
+    }
+
+
+    private List extendList(List list,
+                            List additionalList)
+    {
+        if (additionalList != null)
+        {
+            if (list == null)
+            {
+                list = new ArrayList();
+            }
+
+            list.addAll(additionalList);
+        }
+
+        return list;
+    }
+}
diff --git a/src/proguard/ant/FilterElement.java b/src/proguard/ant/FilterElement.java
new file mode 100644
index 0000000..d792c5d
--- /dev/null
+++ b/src/proguard/ant/FilterElement.java
@@ -0,0 +1,85 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.ant;
+
+import org.apache.tools.ant.types.DataType;
+import proguard.classfile.util.ClassUtil;
+import proguard.util.ListUtil;
+
+import java.util.List;
+
+/**
+ * This DataType represents a name filter in Ant.
+ *
+ * @author Eric Lafortune
+ */
+public class FilterElement extends DataType
+{
+    private String filter;
+
+
+    /**
+     * Adds the contents of this element to the given name filter.
+     * @param filter   the list of attributes to be extended.
+     * @param internal specifies whether the filter string should be converted
+     *                 to internal types.
+     */
+    public void appendTo(List filter, boolean internal)
+    {
+        // Get the referenced element, or else this one.
+        FilterElement filterElement = isReference() ?
+            (FilterElement)getCheckedRef(this.getClass(),
+                                         this.getClass().getName()) :
+            this;
+
+        String filterString = filterElement.filter;
+
+        if (filterString == null)
+        {
+            // Clear the filter to keep all names.
+            filter.clear();
+        }
+        else
+        {
+            if (internal)
+            {
+                filterString = ClassUtil.internalClassName(filterString);
+            }
+
+            // Append the filter.
+            filter.addAll(ListUtil.commaSeparatedList(filterString));
+        }
+    }
+
+
+    // Ant task attributes.
+
+    public void setName(String name)
+    {
+        this.filter = name;
+    }
+
+
+    public void setFilter(String filter)
+    {
+        this.filter = filter;
+    }
+}
diff --git a/src/proguard/ant/KeepSpecificationElement.java b/src/proguard/ant/KeepSpecificationElement.java
new file mode 100644
index 0000000..e36b744
--- /dev/null
+++ b/src/proguard/ant/KeepSpecificationElement.java
@@ -0,0 +1,87 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.ant;
+
+import proguard.KeepClassSpecification;
+
+import java.util.List;
+
+/**
+ * This DataType represents a class specification in Ant.
+ *
+ * @author Eric Lafortune
+ */
+public class KeepSpecificationElement extends ClassSpecificationElement
+{
+    private boolean allowShrinking;
+    private boolean allowOptimization;
+    private boolean allowObfuscation;
+
+
+    /**
+     * Adds the contents of this class specification element to the given list.
+     * @param keepSpecifications the class specifications to be extended.
+     * @param markClasses         specifies whether to mark the classes.
+     * @param markConditionally   specifies whether to mark the classes
+     *                            and class members conditionally.
+     */
+    public void appendTo(List    keepSpecifications,
+                         boolean markClasses,
+                         boolean markConditionally)
+    {
+        // Get the referenced file set, or else this one.
+        KeepSpecificationElement keepSpecificationElement = isReference() ?
+            (KeepSpecificationElement)getCheckedRef(this.getClass(),
+                                                     this.getClass().getName()) :
+            this;
+
+        KeepClassSpecification keepClassSpecification =
+            new KeepClassSpecification(markClasses,
+                                  markConditionally,
+                                  allowShrinking,
+                                  allowOptimization,
+                                  allowObfuscation,
+                                  createClassSpecification(keepSpecificationElement));
+
+        // Add it to the list.
+        keepSpecifications.add(keepClassSpecification);
+    }
+
+
+    // Ant task attributes.
+
+    public void setAllowshrinking(boolean allowShrinking)
+    {
+        this.allowShrinking = allowShrinking;
+    }
+
+
+    public void setAllowoptimization(boolean allowOptimization)
+    {
+        this.allowOptimization = allowOptimization;
+    }
+
+
+    public void setAllowobfuscation(boolean allowObfuscation)
+    {
+        this.allowObfuscation = allowObfuscation;
+    }
+}
diff --git a/src/proguard/ant/MemberSpecificationElement.java b/src/proguard/ant/MemberSpecificationElement.java
new file mode 100644
index 0000000..d4bb4a9
--- /dev/null
+++ b/src/proguard/ant/MemberSpecificationElement.java
@@ -0,0 +1,215 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.ant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.DataType;
+import proguard.MemberSpecification;
+import proguard.classfile.ClassConstants;
+import proguard.classfile.util.ClassUtil;
+import proguard.util.ListUtil;
+
+import java.util.*;
+
+/**
+ * This DataType represents a class member specification in Ant.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberSpecificationElement extends DataType
+{
+    private String access;
+    private String annotation;
+    private String type;
+    private String name;
+    private String parameters;
+
+
+    /**
+     * Adds the contents of this class member specification element to the given
+     * list.
+     * @param memberSpecifications the class member specifications to be
+     *                                  extended.
+     * @param isMethod                  specifies whether this specification
+     *                                  refers to a method.
+     * @param isConstructor             specifies whether this specification
+     *                                  refers to a constructor.
+     */
+    public void appendTo(List    memberSpecifications,
+                         boolean isMethod,
+                         boolean isConstructor)
+    {
+        // Get the referenced file set, or else this one.
+        MemberSpecificationElement memberSpecificationElement = isReference() ?
+            (MemberSpecificationElement)getCheckedRef(this.getClass(),
+                                                      this.getClass().getName()) :
+            this;
+
+        // Create a new class specification.
+        String access     = memberSpecificationElement.access;
+        String type       = memberSpecificationElement.type;
+        String annotation = memberSpecificationElement.annotation;
+        String name       = memberSpecificationElement.name;
+        String parameters = memberSpecificationElement.parameters;
+
+        // Perform some basic conversions and checks on the attributes.
+        if (annotation != null)
+        {
+            annotation = ClassUtil.internalType(annotation);
+        }
+
+        if (isMethod)
+        {
+            if (isConstructor)
+            {
+                if (type != null)
+                {
+                    throw new BuildException("Type attribute not allowed in constructor specification ["+type+"]");
+                }
+
+                if (parameters != null)
+                {
+                    type = ClassConstants.EXTERNAL_TYPE_VOID;
+                }
+
+                name = ClassConstants.INTERNAL_METHOD_NAME_INIT;
+            }
+            else if ((type != null) ^ (parameters != null))
+            {
+                throw new BuildException("Type and parameters attributes must always be present in combination in method specification");
+            }
+        }
+        else
+        {
+            if (parameters != null)
+            {
+                throw new BuildException("Parameters attribute not allowed in field specification ["+parameters+"]");
+            }
+        }
+
+        List parameterList = ListUtil.commaSeparatedList(parameters);
+
+        String descriptor =
+            parameters != null ? ClassUtil.internalMethodDescriptor(type, parameterList) :
+            type       != null ? ClassUtil.internalType(type)                            :
+                                 null;
+
+        MemberSpecification memberSpecification =
+            new MemberSpecification(requiredAccessFlags(true,  access),
+                                    requiredAccessFlags(false, access),
+                                    annotation,
+                                    name,
+                                    descriptor);
+
+        // Add it to the list.
+        memberSpecifications.add(memberSpecification);
+    }
+
+
+    // Ant task attributes.
+
+    public void setAccess(String access)
+    {
+        this.access = access;
+    }
+
+
+    public void setAnnotation(String annotation)
+    {
+        this.annotation = annotation;
+    }
+
+
+    public void setType(String type)
+    {
+        this.type = type;
+    }
+
+
+    public void setName(String name)
+    {
+        this.name = name;
+    }
+
+
+    public void setParameters(String parameters)
+    {
+        this.parameters = parameters;
+    }
+
+
+    /**
+     * @deprecated Use {@link #setParameters(String)} instead.
+     */
+    public void setParam(String parameters)
+    {
+        this.parameters = parameters;
+    }
+
+
+    // Small utility methods.
+
+    private int requiredAccessFlags(boolean set,
+                                    String  access)
+    throws BuildException
+    {
+        int accessFlags = 0;
+
+        if (access != null)
+        {
+            StringTokenizer tokenizer = new StringTokenizer(access, " ,");
+            while (tokenizer.hasMoreTokens())
+            {
+                String token = tokenizer.nextToken();
+
+                if (token.startsWith("!") ^ set)
+                {
+                    String strippedToken = token.startsWith("!") ?
+                        token.substring(1) :
+                        token;
+
+                    int accessFlag =
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_PUBLIC)       ? ClassConstants.INTERNAL_ACC_PUBLIC       :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_PRIVATE)      ? ClassConstants.INTERNAL_ACC_PRIVATE      :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_PROTECTED)    ? ClassConstants.INTERNAL_ACC_PROTECTED    :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_STATIC)       ? ClassConstants.INTERNAL_ACC_STATIC       :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_FINAL)        ? ClassConstants.INTERNAL_ACC_FINAL        :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED) ? ClassConstants.INTERNAL_ACC_SYNCHRONIZED :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_VOLATILE)     ? ClassConstants.INTERNAL_ACC_VOLATILE     :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_TRANSIENT)    ? ClassConstants.INTERNAL_ACC_TRANSIENT    :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_NATIVE)       ? ClassConstants.INTERNAL_ACC_NATIVE       :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT)     ? ClassConstants.INTERNAL_ACC_ABSTRACT     :
+                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_STRICT)       ? ClassConstants.INTERNAL_ACC_STRICT       :
+                        0;
+
+                    if (accessFlag == 0)
+                    {
+                        throw new BuildException("Incorrect class member access modifier ["+strippedToken+"]");
+                    }
+
+                    accessFlags |= accessFlag;
+                }
+            }
+        }
+
+        return accessFlags;
+    }
+}
diff --git a/src/proguard/ant/ProGuardTask.java b/src/proguard/ant/ProGuardTask.java
new file mode 100644
index 0000000..b7fc361
--- /dev/null
+++ b/src/proguard/ant/ProGuardTask.java
@@ -0,0 +1,320 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.ant;
+
+import org.apache.tools.ant.BuildException;
+import proguard.*;
+import proguard.classfile.util.ClassUtil;
+
+import java.io.*;
+import java.util.ArrayList;
+
+/**
+ * This Task allows to configure and run ProGuard from Ant.
+ *
+ * @author Eric Lafortune
+ */
+public class ProGuardTask extends ConfigurationTask
+{
+    // Ant task attributes.
+
+    public void setConfiguration(File configurationFile) throws BuildException
+    {
+        try
+        {
+            ConfigurationParser parser = new ConfigurationParser(configurationFile);
+
+            try
+            {
+                parser.parse(configuration);
+            }
+            catch (ParseException ex)
+            {
+                throw new BuildException(ex.getMessage());
+            }
+            finally
+            {
+                parser.close();
+            }
+        }
+        catch (IOException ex)
+        {
+            throw new BuildException(ex.getMessage());
+        }
+    }
+
+
+    /**
+     * @deprecated Use the nested outjar element instead.
+     */
+    public void setOutjar(String parameters)
+    {
+        throw new BuildException("Use the <outjar> nested element instead of the 'outjar' attribute");
+    }
+
+
+    public void setSkipnonpubliclibraryclasses(boolean skipNonPublicLibraryClasses)
+    {
+        configuration.skipNonPublicLibraryClasses = skipNonPublicLibraryClasses;
+    }
+
+
+    public void setSkipnonpubliclibraryclassmembers(boolean skipNonPublicLibraryClassMembers)
+    {
+        configuration.skipNonPublicLibraryClassMembers = skipNonPublicLibraryClassMembers;
+    }
+
+
+    public void setTarget(String target)
+    {
+        configuration.targetClassVersion = ClassUtil.internalClassVersion(target);
+        if (configuration.targetClassVersion == 0)
+        {
+            throw new BuildException("Unsupported target '"+target+"'");
+        }
+    }
+
+
+    public void setForceprocessing(boolean forceProcessing)
+    {
+        configuration.lastModified = forceProcessing ? Long.MAX_VALUE : 0;
+    }
+
+
+    public void setPrintseeds(File printSeeds)
+    {
+        configuration.printSeeds = optionalFile(printSeeds);
+    }
+
+
+    public void setShrink(boolean shrink)
+    {
+        configuration.shrink = shrink;
+    }
+
+
+    public void setPrintusage(File printUsage)
+    {
+        configuration.printUsage = optionalFile(printUsage);
+    }
+
+
+    public void setOptimize(boolean optimize)
+    {
+        configuration.optimize = optimize;
+    }
+
+
+    public void setOptimizationpasses(int optimizationPasses)
+    {
+        configuration.optimizationPasses = optimizationPasses;
+    }
+
+
+    public void setAllowaccessmodification(boolean allowAccessModification)
+    {
+        configuration.allowAccessModification = allowAccessModification;
+    }
+
+
+    public void setMergeinterfacesaggressively(boolean mergeinterfacesaggressively)
+    {
+        configuration.mergeInterfacesAggressively = mergeinterfacesaggressively;
+    }
+
+
+    public void setObfuscate(boolean obfuscate)
+    {
+        configuration.obfuscate = obfuscate;
+    }
+
+
+    public void setPrintmapping(File printMapping)
+    {
+        configuration.printMapping = optionalFile(printMapping);
+    }
+
+
+    public void setApplymapping(File applyMapping)
+    {
+        configuration.applyMapping = resolvedFile(applyMapping);
+    }
+
+
+    public void setObfuscationdictionary(File obfuscationDictionary)
+    {
+        configuration.obfuscationDictionary = resolvedFile(obfuscationDictionary);
+    }
+
+
+    public void setClassobfuscationdictionary(File classObfuscationDictionary)
+    {
+        configuration.classObfuscationDictionary = resolvedFile(classObfuscationDictionary);
+    }
+
+
+    public void setPackageobfuscationdictionary(File packageObfuscationDictionary)
+    {
+        configuration.packageObfuscationDictionary = resolvedFile(packageObfuscationDictionary);
+    }
+
+
+    public void setOverloadaggressively(boolean overloadAggressively)
+    {
+        configuration.overloadAggressively = overloadAggressively;
+    }
+
+
+    public void setUseuniqueclassmembernames(boolean useUniqueClassMemberNames)
+    {
+        configuration.useUniqueClassMemberNames = useUniqueClassMemberNames;
+    }
+
+
+    public void setUsemixedcaseclassnames(boolean useMixedCaseClassNames)
+    {
+        configuration.useMixedCaseClassNames = useMixedCaseClassNames;
+    }
+
+
+    public void setFlattenpackagehierarchy(String flattenPackageHierarchy)
+    {
+        configuration.flattenPackageHierarchy = ClassUtil.internalClassName(flattenPackageHierarchy);
+    }
+
+
+    public void setRepackageclasses(String repackageClasses)
+    {
+        configuration.repackageClasses = ClassUtil.internalClassName(repackageClasses);
+    }
+
+    /**
+     * @deprecated Use the repackageclasses attribute instead.
+     */
+    public void setDefaultpackage(String defaultPackage)
+    {
+        configuration.repackageClasses = ClassUtil.internalClassName(defaultPackage);
+    }
+
+
+    public void setRenamesourcefileattribute(String newSourceFileAttribute)
+    {
+        configuration.newSourceFileAttribute = newSourceFileAttribute;
+    }
+
+
+    public void setPreverify(boolean preverify)
+    {
+        configuration.preverify = preverify;
+    }
+
+
+    public void setMicroedition(boolean microEdition)
+    {
+        configuration.microEdition = microEdition;
+    }
+
+
+    public void setVerbose(boolean verbose)
+    {
+        configuration.verbose = verbose;
+    }
+
+
+    public void setNote(boolean note)
+    {
+        configuration.note = note ? null : new ArrayList();
+    }
+
+
+    public void setWarn(boolean warn)
+    {
+        configuration.warn = warn ? null : new ArrayList();
+    }
+
+
+    public void setIgnorewarnings(boolean ignoreWarnings)
+    {
+        configuration.ignoreWarnings = ignoreWarnings;
+    }
+
+
+    public void setPrintconfiguration(File printConfiguration)
+    {
+        configuration.printConfiguration = optionalFile(printConfiguration);
+    }
+
+
+    public void setDump(File dump)
+    {
+        configuration.dump = optionalFile(dump);
+    }
+
+
+    // Implementations for Task.
+
+    public void execute() throws BuildException
+    {
+        try
+        {
+            ProGuard proGuard = new ProGuard(configuration);
+            proGuard.execute();
+        }
+        catch (IOException ex)
+        {
+            throw new BuildException(ex.getMessage());
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns a file that is properly resolved with respect to the project
+     * directory, or <code>null</code> or empty if its name is actually a
+     * boolean flag.
+     */
+    private File optionalFile(File file)
+    {
+        String fileName = file.getName();
+
+        return
+            fileName.equalsIgnoreCase("false") ||
+            fileName.equalsIgnoreCase("no")    ||
+            fileName.equalsIgnoreCase("off")    ? null :
+            fileName.equalsIgnoreCase("true")  ||
+            fileName.equalsIgnoreCase("yes")   ||
+            fileName.equalsIgnoreCase("on")     ? new File("")   :
+                                                  resolvedFile(file);
+    }
+
+
+    /**
+     * Returns a file that is properly resolved with respect to the project
+     * directory.
+     */
+    private File resolvedFile(File file)
+    {
+        return file.isAbsolute() ? file :
+                                   new File(getProject().getBaseDir(),
+                                            file.getName());
+    }
+}
diff --git a/src/proguard/ant/package.html b/src/proguard/ant/package.html
new file mode 100644
index 0000000..75e0466
--- /dev/null
+++ b/src/proguard/ant/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains the Ant task for ProGuard.
+</body>
diff --git a/src/proguard/ant/task.properties b/src/proguard/ant/task.properties
new file mode 100644
index 0000000..b676db7
--- /dev/null
+++ b/src/proguard/ant/task.properties
@@ -0,0 +1,2 @@
+proguard              = proguard.ant.ProGuardTask

+proguardconfiguration = proguard.ant.ConfigurationTask

diff --git a/src/proguard/classfile/ClassConstants.java b/src/proguard/classfile/ClassConstants.java
new file mode 100644
index 0000000..3b243e0
--- /dev/null
+++ b/src/proguard/classfile/ClassConstants.java
@@ -0,0 +1,255 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+/**
+ * Constants used in representing a Java class (*.class).
+ *
+ * @author Eric Lafortune
+ */
+public interface ClassConstants
+{
+    public static final String CLASS_FILE_EXTENSION = ".class";
+
+    public static final int MAGIC = 0xCAFEBABE;
+
+    public static final int INTERNAL_CLASS_VERSION_1_0_MAJOR = 45;
+    public static final int INTERNAL_CLASS_VERSION_1_0_MINOR = 3;
+    public static final int INTERNAL_CLASS_VERSION_1_2_MAJOR = 46;
+    public static final int INTERNAL_CLASS_VERSION_1_2_MINOR = 0;
+    public static final int INTERNAL_CLASS_VERSION_1_3_MAJOR = 47;
+    public static final int INTERNAL_CLASS_VERSION_1_3_MINOR = 0;
+    public static final int INTERNAL_CLASS_VERSION_1_4_MAJOR = 48;
+    public static final int INTERNAL_CLASS_VERSION_1_4_MINOR = 0;
+    public static final int INTERNAL_CLASS_VERSION_1_5_MAJOR = 49;
+    public static final int INTERNAL_CLASS_VERSION_1_5_MINOR = 0;
+    public static final int INTERNAL_CLASS_VERSION_1_6_MAJOR = 50;
+    public static final int INTERNAL_CLASS_VERSION_1_6_MINOR = 0;
+
+    public static final int INTERNAL_CLASS_VERSION_1_0 = (INTERNAL_CLASS_VERSION_1_0_MAJOR << 16) | INTERNAL_CLASS_VERSION_1_0_MINOR;
+    public static final int INTERNAL_CLASS_VERSION_1_2 = (INTERNAL_CLASS_VERSION_1_2_MAJOR << 16) | INTERNAL_CLASS_VERSION_1_2_MINOR;
+    public static final int INTERNAL_CLASS_VERSION_1_3 = (INTERNAL_CLASS_VERSION_1_3_MAJOR << 16) | INTERNAL_CLASS_VERSION_1_3_MINOR;
+    public static final int INTERNAL_CLASS_VERSION_1_4 = (INTERNAL_CLASS_VERSION_1_4_MAJOR << 16) | INTERNAL_CLASS_VERSION_1_4_MINOR;
+    public static final int INTERNAL_CLASS_VERSION_1_5 = (INTERNAL_CLASS_VERSION_1_5_MAJOR << 16) | INTERNAL_CLASS_VERSION_1_5_MINOR;
+    public static final int INTERNAL_CLASS_VERSION_1_6 = (INTERNAL_CLASS_VERSION_1_6_MAJOR << 16) | INTERNAL_CLASS_VERSION_1_6_MINOR;
+
+    public static final String EXTERNAL_CLASS_VERSION_1_0       = "1.0";
+    public static final String EXTERNAL_CLASS_VERSION_1_1       = "1.1";
+    public static final String EXTERNAL_CLASS_VERSION_1_2       = "1.2";
+    public static final String EXTERNAL_CLASS_VERSION_1_3       = "1.3";
+    public static final String EXTERNAL_CLASS_VERSION_1_4       = "1.4";
+    public static final String EXTERNAL_CLASS_VERSION_1_5       = "1.5";
+    public static final String EXTERNAL_CLASS_VERSION_1_6       = "1.6";
+    public static final String EXTERNAL_CLASS_VERSION_1_5_ALIAS = "5";
+    public static final String EXTERNAL_CLASS_VERSION_1_6_ALIAS = "6";
+
+    public static final int INTERNAL_ACC_PUBLIC       = 0x0001;
+    public static final int INTERNAL_ACC_PRIVATE      = 0x0002;
+    public static final int INTERNAL_ACC_PROTECTED    = 0x0004;
+    public static final int INTERNAL_ACC_STATIC       = 0x0008;
+    public static final int INTERNAL_ACC_FINAL        = 0x0010;
+    public static final int INTERNAL_ACC_SUPER        = 0x0020;
+    public static final int INTERNAL_ACC_SYNCHRONIZED = 0x0020;
+    public static final int INTERNAL_ACC_VOLATILE     = 0x0040;
+    public static final int INTERNAL_ACC_TRANSIENT    = 0x0080;
+    public static final int INTERNAL_ACC_BRIDGE       = 0x0040;
+    public static final int INTERNAL_ACC_VARARGS      = 0x0080;
+    public static final int INTERNAL_ACC_NATIVE       = 0x0100;
+    public static final int INTERNAL_ACC_INTERFACE    = 0x0200;
+    public static final int INTERNAL_ACC_ABSTRACT     = 0x0400;
+    public static final int INTERNAL_ACC_STRICT       = 0x0800;
+    public static final int INTERNAL_ACC_SYNTHETIC    = 0x1000;
+    public static final int INTERNAL_ACC_ANNOTATTION  = 0x2000;
+    public static final int INTERNAL_ACC_ENUM         = 0x4000;
+
+    public static final int VALID_INTERNAL_ACC_CLASS  = INTERNAL_ACC_PUBLIC       |
+                                                        INTERNAL_ACC_FINAL        |
+                                                        INTERNAL_ACC_SUPER        |
+                                                        INTERNAL_ACC_INTERFACE    |
+                                                        INTERNAL_ACC_ABSTRACT     |
+                                                        INTERNAL_ACC_SYNTHETIC    |
+                                                        INTERNAL_ACC_ANNOTATTION  |
+                                                        INTERNAL_ACC_ENUM;
+    public static final int VALID_INTERNAL_ACC_FIELD  = INTERNAL_ACC_PUBLIC       |
+                                                        INTERNAL_ACC_PRIVATE      |
+                                                        INTERNAL_ACC_PROTECTED    |
+                                                        INTERNAL_ACC_STATIC       |
+                                                        INTERNAL_ACC_FINAL        |
+                                                        INTERNAL_ACC_VOLATILE     |
+                                                        INTERNAL_ACC_TRANSIENT    |
+                                                        INTERNAL_ACC_SYNTHETIC    |
+                                                        INTERNAL_ACC_ENUM;
+    public static final int VALID_INTERNAL_ACC_METHOD = INTERNAL_ACC_PUBLIC       |
+                                                        INTERNAL_ACC_PRIVATE      |
+                                                        INTERNAL_ACC_PROTECTED    |
+                                                        INTERNAL_ACC_STATIC       |
+                                                        INTERNAL_ACC_FINAL        |
+                                                        INTERNAL_ACC_SYNCHRONIZED |
+                                                        INTERNAL_ACC_BRIDGE       |
+                                                        INTERNAL_ACC_VARARGS      |
+                                                        INTERNAL_ACC_NATIVE       |
+                                                        INTERNAL_ACC_ABSTRACT     |
+                                                        INTERNAL_ACC_STRICT       |
+                                                        INTERNAL_ACC_SYNTHETIC;
+
+    public static final String EXTERNAL_ACC_PUBLIC       = "public";
+    public static final String EXTERNAL_ACC_PRIVATE      = "private";
+    public static final String EXTERNAL_ACC_PROTECTED    = "protected";
+    public static final String EXTERNAL_ACC_STATIC       = "static";
+    public static final String EXTERNAL_ACC_FINAL        = "final";
+    public static final String EXTERNAL_ACC_SUPER        = "super";
+    public static final String EXTERNAL_ACC_SYNCHRONIZED = "synchronized";
+    public static final String EXTERNAL_ACC_VOLATILE     = "volatile";
+    public static final String EXTERNAL_ACC_TRANSIENT    = "transient";
+    public static final String EXTERNAL_ACC_NATIVE       = "native";
+    public static final String EXTERNAL_ACC_INTERFACE    = "interface";
+    public static final String EXTERNAL_ACC_ABSTRACT     = "abstract";
+    public static final String EXTERNAL_ACC_STRICT       = "strictfp";
+    public static final String EXTERNAL_ACC_ANNOTATION   = "@";
+    public static final String EXTERNAL_ACC_ENUM         = "enum";
+
+    public static final int CONSTANT_Utf8               = 1;
+    public static final int CONSTANT_Integer            = 3;
+    public static final int CONSTANT_Float              = 4;
+    public static final int CONSTANT_Long               = 5;
+    public static final int CONSTANT_Double             = 6;
+    public static final int CONSTANT_Class              = 7;
+    public static final int CONSTANT_String             = 8;
+    public static final int CONSTANT_Fieldref           = 9;
+    public static final int CONSTANT_Methodref          = 10;
+    public static final int CONSTANT_InterfaceMethodref = 11;
+    public static final int CONSTANT_NameAndType        = 12;
+
+    public static final String ATTR_SourceFile                           = "SourceFile";
+    public static final String ATTR_SourceDir                            = "SourceDir";
+    public static final String ATTR_InnerClasses                         = "InnerClasses";
+    public static final String ATTR_EnclosingMethod                      = "EnclosingMethod";
+    public static final String ATTR_Deprecated                           = "Deprecated";
+    public static final String ATTR_Synthetic                            = "Synthetic";
+    public static final String ATTR_Signature                            = "Signature";
+    public static final String ATTR_ConstantValue                        = "ConstantValue";
+    public static final String ATTR_Exceptions                           = "Exceptions";
+    public static final String ATTR_Code                                 = "Code";
+    public static final String ATTR_StackMap                             = "StackMap";
+    public static final String ATTR_StackMapTable                        = "StackMapTable";
+    public static final String ATTR_LineNumberTable                      = "LineNumberTable";
+    public static final String ATTR_LocalVariableTable                   = "LocalVariableTable";
+    public static final String ATTR_LocalVariableTypeTable               = "LocalVariableTypeTable";
+    public static final String ATTR_RuntimeVisibleAnnotations            = "RuntimeVisibleAnnotations";
+    public static final String ATTR_RuntimeInvisibleAnnotations          = "RuntimeInvisibleAnnotations";
+    public static final String ATTR_RuntimeVisibleParameterAnnotations   = "RuntimeVisibleParameterAnnotations";
+    public static final String ATTR_RuntimeInvisibleParameterAnnotations = "RuntimeInvisibleParameterAnnotations";
+    public static final String ATTR_AnnotationDefault                    = "AnnotationDefault";
+
+    public static final int ELEMENT_VALUE_STRING_CONSTANT = 's';
+    public static final int ELEMENT_VALUE_ENUM_CONSTANT   = 'e';
+    public static final int ELEMENT_VALUE_CLASS           = 'c';
+    public static final int ELEMENT_VALUE_ANNOTATION      = '@';
+    public static final int ELEMENT_VALUE_ARRAY           = '[';
+
+    public static final char EXTERNAL_PACKAGE_SEPARATOR     = '.';
+    public static final char EXTERNAL_INNER_CLASS_SEPARATOR = '.';
+    public static final char INTERNAL_PACKAGE_SEPARATOR     = '/';
+    public static final char INTERNAL_INNER_CLASS_SEPARATOR = '$';
+    public static final char SPECIAL_CLASS_CHARACTER        = '-';
+    public static final char SPECIAL_MEMBER_SEPARATOR       = '$';
+
+    public static final char EXTERNAL_METHOD_ARGUMENTS_OPEN      = '(';
+    public static final char EXTERNAL_METHOD_ARGUMENTS_CLOSE     = ')';
+    public static final char EXTERNAL_METHOD_ARGUMENTS_SEPARATOR = ',';
+
+    public static final char INTERNAL_METHOD_ARGUMENTS_OPEN  = '(';
+    public static final char INTERNAL_METHOD_ARGUMENTS_CLOSE = ')';
+
+    public static final String INTERNAL_PACKAGE_JAVA_LANG         = "java/lang/";
+    public static final String INTERNAL_NAME_JAVA_LANG_OBJECT     = "java/lang/Object";
+    public static final String INTERNAL_TYPE_JAVA_LANG_OBJECT     = "Ljava/lang/Object;";
+    public static final String INTERNAL_NAME_JAVA_LANG_CLONEABLE  = "java/lang/Cloneable";
+    public static final String INTERNAL_NAME_JAVA_LANG_THROWABLE  = "java/lang/Throwable";
+    public static final String INTERNAL_NAME_JAVA_LANG_CLASS      = "java/lang/Class";
+    public static final String INTERNAL_NAME_JAVA_LANG_STRING     = "java/lang/String";
+    public static final String INTERNAL_NAME_JAVA_IO_SERIALIZABLE = "java/io/Serializable";
+
+    public static final String INTERNAL_METHOD_NAME_INIT   = "<init>";
+    public static final String INTERNAL_METHOD_TYPE_INIT   = "()V";
+    public static final String INTERNAL_METHOD_NAME_CLINIT = "<clinit>";
+    public static final String INTERNAL_METHOD_TYPE_CLINIT = "()V";
+
+    public static final String INTERNAL_METHOD_NAME_CLASS_FOR_NAME            = "forName";
+    public static final String INTERNAL_METHOD_TYPE_CLASS_FOR_NAME            = "(Ljava/lang/String;)Ljava/lang/Class;";
+    public static final String INTERNAL_METHOD_NAME_CLASS_GET_COMPONENT_TYPE  = "getComponentType";
+    public static final String INTERNAL_METHOD_TYPE_CLASS_GET_COMPONENT_TYPE  = "()Ljava/lang/Class;";
+    public static final String INTERNAL_METHOD_NAME_CLASS_GET_FIELD           = "getField";
+    public static final String INTERNAL_METHOD_TYPE_CLASS_GET_FIELD           = "(Ljava/lang/String;)Ljava/lang/reflect/Field;";
+    public static final String INTERNAL_METHOD_NAME_CLASS_GET_DECLARED_FIELD  = "getDeclaredField";
+    public static final String INTERNAL_METHOD_TYPE_CLASS_GET_DECLARED_FIELD  = "(Ljava/lang/String;)Ljava/lang/reflect/Field;";
+    public static final String INTERNAL_METHOD_NAME_CLASS_GET_METHOD          = "getMethod";
+    public static final String INTERNAL_METHOD_TYPE_CLASS_GET_METHOD          = "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;";
+    public static final String INTERNAL_METHOD_NAME_CLASS_GET_DECLARED_METHOD = "getDeclaredMethod";
+    public static final String INTERNAL_METHOD_TYPE_CLASS_GET_DECLARED_METHOD = "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;";
+
+    public static final String INTERNAL_METHOD_NAME_DOT_CLASS_JAVAC = "class$";
+    public static final String INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC = "(Ljava/lang/String;)Ljava/lang/Class;";
+    public static final String INTERNAL_METHOD_NAME_DOT_CLASS_JIKES = "class";
+    public static final String INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES = "(Ljava/lang/String;Z)Ljava/lang/Class;";
+
+    public static final String INTERNAL_METHOD_NAME_NEW_INSTANCE = "newInstance";
+    public static final String INTERNAL_METHOD_TYPE_NEW_INSTANCE = "()Ljava/lang/Object;";
+
+    public static final char INTERNAL_TYPE_VOID                   = 'V';
+    public static final char INTERNAL_TYPE_BOOLEAN                = 'Z';
+    public static final char INTERNAL_TYPE_BYTE                   = 'B';
+    public static final char INTERNAL_TYPE_CHAR                   = 'C';
+    public static final char INTERNAL_TYPE_SHORT                  = 'S';
+    public static final char INTERNAL_TYPE_INT                    = 'I';
+    public static final char INTERNAL_TYPE_LONG                   = 'J';
+    public static final char INTERNAL_TYPE_FLOAT                  = 'F';
+    public static final char INTERNAL_TYPE_DOUBLE                 = 'D';
+    public static final char INTERNAL_TYPE_CLASS_START            = 'L';
+    public static final char INTERNAL_TYPE_CLASS_END              = ';';
+    public static final char INTERNAL_TYPE_ARRAY                  = '[';
+    public static final char INTERNAL_TYPE_GENERIC_VARIABLE_START = 'T';
+    public static final char INTERNAL_TYPE_GENERIC_START          = '<';
+    public static final char INTERNAL_TYPE_GENERIC_BOUND          = ':';
+    public static final char INTERNAL_TYPE_GENERIC_END            = '>';
+
+    public static final String EXTERNAL_TYPE_JAVA_LANG_OBJECT = "java.lang.Object";
+    public static final String EXTERNAL_PACKAGE_JAVA_LANG     = "java.lang.";
+
+    public static final String EXTERNAL_TYPE_VOID    = "void";
+    public static final String EXTERNAL_TYPE_BOOLEAN = "boolean";
+    public static final String EXTERNAL_TYPE_BYTE    = "byte";
+    public static final String EXTERNAL_TYPE_CHAR    = "char";
+    public static final String EXTERNAL_TYPE_SHORT   = "short";
+    public static final String EXTERNAL_TYPE_INT     = "int";
+    public static final String EXTERNAL_TYPE_FLOAT   = "float";
+    public static final String EXTERNAL_TYPE_LONG    = "long";
+    public static final String EXTERNAL_TYPE_DOUBLE  = "double";
+    public static final String EXTERNAL_TYPE_ARRAY   = "[]";
+
+    public static final int TYPICAL_CONSTANT_POOL_SIZE     = 256;
+    public static final int TYPICAL_FIELD_COUNT            = 64;
+    public static final int TYPICAL_METHOD_COUNT           = 64;
+    public static final int TYPICAL_CODE_LENGTH            = 1024;
+    public static final int TYPICAL_EXCEPTION_TABLE_LENGTH = 16;
+    public static final int TYPICAL_VARIABLES_SIZE         = 64;
+    public static final int TYPICAL_STACK_SIZE             = 16;
+}
diff --git a/src/proguard/classfile/ClassPool.java b/src/proguard/classfile/ClassPool.java
new file mode 100644
index 0000000..57728a5
--- /dev/null
+++ b/src/proguard/classfile/ClassPool.java
@@ -0,0 +1,147 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.util.ClassUtil;
+import proguard.classfile.visitor.*;
+
+import java.util.*;
+
+/**
+ * This is a set of representations of classes. They      can be enumerated or
+ * retrieved by name. They can also be accessed by means of class visitors.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassPool
+{
+    private final Map classes = new HashMap();
+
+
+    /**
+     * Clears the class pool.
+     */
+    public void clear()
+    {
+        classes.clear();
+    }
+
+
+    /**
+     * Adds the given Clazz to the class pool.
+     */
+    public void addClass(Clazz clazz)
+    {
+        classes.put(clazz.getName(), clazz);
+    }
+
+
+    /**
+     * Removes the given Clazz from the class pool.
+     */
+    public void removeClass(Clazz clazz)
+    {
+        classes.remove(clazz.getName());
+    }
+
+
+    /**
+     * Returns a Clazz from the class pool based on its name. Returns
+     * <code>null</code> if the class with the given name is not in the class
+     * pool. Returns the base class if the class name is an array type.
+     */
+    public Clazz getClass(String className)
+    {
+        return (Clazz)classes.get(ClassUtil.internalClassNameFromClassType(className));
+    }
+
+
+    /**
+     * Returns an Iterator of all class names in the class pool.
+     */
+    public Iterator classNames()
+    {
+        return classes.keySet().iterator();
+    }
+
+
+    /**
+     * Returns the number of classes in the class pool.
+     */
+    public int size()
+    {
+        return classes.size();
+    }
+
+
+    /**
+     * Applies the given ClassPoolVisitor to the class pool.
+     */
+    public void accept(ClassPoolVisitor classPoolVisitor)
+    {
+        classPoolVisitor.visitClassPool(this);
+    }
+
+
+    /**
+     * Applies the given ClassVisitor to all classes in the class pool,
+     * in random order.
+     */
+    public void classesAccept(ClassVisitor classVisitor)
+    {
+        Iterator iterator = classes.values().iterator();
+        while (iterator.hasNext())
+        {
+            Clazz clazz = (Clazz)iterator.next();
+            clazz.accept(classVisitor);
+        }
+    }
+
+
+    /**
+     * Applies the given ClassVisitor to all classes in the class pool,
+     * in sorted order.
+     */
+    public void classesAcceptAlphabetically(ClassVisitor classVisitor)
+    {
+        TreeMap sortedClasses = new TreeMap(classes);
+        Iterator iterator = sortedClasses.values().iterator();
+        while (iterator.hasNext())
+        {
+            Clazz clazz = (Clazz)iterator.next();
+            clazz.accept(classVisitor);
+        }
+    }
+
+
+    /**
+     * Applies the given ClassVisitor to the class with the given name,
+     * if it is present in the class pool.
+     */
+    public void classAccept(String className, ClassVisitor classVisitor)
+    {
+        Clazz clazz = getClass(className);
+        if (clazz != null)
+        {
+            clazz.accept(classVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/Clazz.java b/src/proguard/classfile/Clazz.java
new file mode 100644
index 0000000..da37d9a
--- /dev/null
+++ b/src/proguard/classfile/Clazz.java
@@ -0,0 +1,232 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.visitor.*;
+
+
+/**
+ * This interface provides access to the representation of a Java class.
+ *
+ * @author Eric Lafortune
+ */
+public interface Clazz extends VisitorAccepter
+{
+    /**
+     * Returns the access flags of this class.
+     * @see ClassConstants
+     */
+    public int getAccessFlags();
+
+    /**
+     * Returns the full internal name of this class.
+     */
+    public String getName();
+
+    /**
+     * Returns the full internal name of the super class of this class, or
+     * null if this class represents java.lang.Object.
+     */
+    public String getSuperName();
+
+    /**
+     * Returns the number of interfaces that this class implements.
+     */
+    public int getInterfaceCount();
+
+    /**
+     * Returns the full internal name of the interface at the given index of
+     * this class.
+     */
+    public String getInterfaceName(int index);
+
+    /**
+     * Returns the tag value of the Constant at the specified index.
+     */
+    public int getTag(int constantIndex);
+
+    /**
+     * Returns the String value of the Utf8Constant at the specified index.
+     */
+    public String getString(int constantIndex);
+
+    /**
+     * Returns the String value of the StringConstant at the specified index.
+     */
+    public String getStringString(int constantIndex);
+
+    /**
+     * Returns the class name of ClassConstant at the specified index.
+     */
+    public String getClassName(int constantIndex);
+
+    /**
+     * Returns the name of the NameAndTypeConstant at the specified index.
+     */
+    public String getName(int constantIndex);
+
+    /**
+     * Returns the type of the NameAndTypeConstant at the specified index.
+     */
+    public String getType(int constantIndex);
+
+
+    // Methods pertaining to related classes.
+
+    /**
+     * Notifies this Clazz that it is being subclassed by another class.
+     */
+    public void addSubClass(Clazz clazz);
+
+    /**
+     * Returns the super class of this class.
+     */
+    public Clazz getSuperClass();
+
+    /**
+     * Returns the interface at the given index.
+     */
+    public Clazz getInterface(int index);
+
+    /**
+     * Returns whether this class extends the given class.
+     * A class is always considered to extend itself.
+     * Interfaces are considered to only extend the root Object class.
+     */
+    public boolean extends_(Clazz clazz);
+
+    /**
+     * Returns whether this class implements the given class.
+     * A class is always considered to implement itself.
+     * Interfaces are considered to implement all their superinterfaces.
+     */
+    public boolean extendsOrImplements(Clazz clazz);
+
+
+    // Methods for getting specific class members.
+
+    /**
+     * Returns the field with the given name and descriptor.
+     */
+    Field findField(String name, String descriptor);
+
+    /**
+     * Returns the method with the given name and descriptor.
+     */
+    Method findMethod(String name, String descriptor);
+
+
+    // Methods for accepting various types of visitors.
+
+    /**
+     * Accepts the given class visitor.
+     */
+    public void accept(ClassVisitor classVisitor);
+
+    /**
+     * Accepts the given class visitor in the class hierarchy.
+     * @param visitThisClass   specifies whether to visit this class.
+     * @param visitSuperClass  specifies whether to visit the super classes.
+     * @param visitInterfaces  specifies whether to visit the interfaces.
+     * @param visitSubclasses  specifies whether to visit the subclasses.
+     * @param classVisitor     the <code>ClassVisitor</code> that will
+     *                         visit the class hierarchy.
+     */
+    public void hierarchyAccept(boolean      visitThisClass,
+                                boolean      visitSuperClass,
+                                boolean      visitInterfaces,
+                                boolean      visitSubclasses,
+                                ClassVisitor classVisitor);
+
+    /**
+     * Lets the given class visitor visit all known subclasses.
+     * @param classVisitor the <code>ClassVisitor</code> that will visit the
+     *                     subclasses.
+     */
+    public void subclassesAccept(ClassVisitor classVisitor);
+
+    /**
+     * Lets the given constant pool entry visitor visit all constant pool entries
+     * of this class.
+     */
+    public void constantPoolEntriesAccept(ConstantVisitor constantVisitor);
+
+    /**
+     * Lets the given constant pool entry visitor visit the constant pool entry
+     * at the specified index.
+     */
+    public void constantPoolEntryAccept(int index, ConstantVisitor constantVisitor);
+
+    /**
+     * Lets the given constant pool entry visitor visit the class constant pool
+     * entry of this class.
+     */
+    public void thisClassConstantAccept(ConstantVisitor constantVisitor);
+
+    /**
+     * Lets the given constant pool entry visitor visit the class constant pool
+     * entry of the super class of this class, if there is one.
+     */
+    public void superClassConstantAccept(ConstantVisitor constantVisitor);
+
+    /**
+     * Lets the given constant pool entry visitor visit the class constant pool
+     * entries for all interfaces of this class.
+     */
+    public void interfaceConstantsAccept(ConstantVisitor constantVisitor);
+
+    /**
+     * Lets the given member info visitor visit all fields of this class.
+     */
+    public void fieldsAccept(MemberVisitor memberVisitor);
+
+    /**
+     * Lets the given member info visitor visit the specified field.
+     */
+    public void fieldAccept(String name, String descriptor, MemberVisitor memberVisitor);
+
+    /**
+     * Lets the given member info visitor visit all methods of this class.
+     */
+    public void methodsAccept(MemberVisitor memberVisitor);
+
+    /**
+     * Lets the given member info visitor visit the specified method.
+     */
+    public void methodAccept(String name, String descriptor, MemberVisitor memberVisitor);
+
+    /**
+     * Returns whether the given method may possibly have implementing or
+     * overriding methods down the class hierarchy. This can only be true
+     * if the class is not final, and the method is not private, static, or
+     * final, or a constructor.
+     * @param method the method that may have implementations.
+     * @return whether it may have implementations.
+     */
+    public boolean mayHaveImplementations(Method method);
+
+    /**
+     * Lets the given attribute info visitor visit all attributes of this class.
+     */
+    public void attributesAccept(AttributeVisitor attributeVisitor);
+}
diff --git a/src/proguard/classfile/Field.java b/src/proguard/classfile/Field.java
new file mode 100644
index 0000000..ba1315a
--- /dev/null
+++ b/src/proguard/classfile/Field.java
@@ -0,0 +1,32 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+
+
+/**
+ * Representation of a field from a class.
+ *
+ * @author Eric Lafortune
+ */
+public interface Field extends Member
+{
+}
diff --git a/src/proguard/classfile/LibraryClass.java b/src/proguard/classfile/LibraryClass.java
new file mode 100644
index 0000000..0a27593
--- /dev/null
+++ b/src/proguard/classfile/LibraryClass.java
@@ -0,0 +1,489 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This Clazz is a compact representation of the essential data in a Java class.
+ *
+ * @author Eric Lafortune
+ */
+public class LibraryClass implements Clazz
+{
+    public int             u2accessFlags;
+    public String          thisClassName;
+    public String          superClassName;
+    public String[]        interfaceNames;
+    public LibraryField[]  fields;
+    public LibraryMethod[] methods;
+
+    /**
+     * An extra field pointing to the superclass of this class.
+     * This field is filled out by the {@link ClassSuperHierarchyInitializer}.
+     */
+    public Clazz   superClass;
+
+    /**
+     * An extra field pointing to the interfaces of this class.
+     * This field is filled out by the {@link ClassSuperHierarchyInitializer}.
+     */
+    public Clazz[] interfaceClasses;
+
+    /**
+     * An extra field pointing to the subclasses of this class.
+     * This field is filled out by the {@link ClassSubHierarchyInitializer}.
+     */
+    public Clazz[] subClasses;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Creates an empty LibraryClass.
+     */
+    public LibraryClass() {}
+
+
+    /**
+     * Returns whether this library class is visible to the outside world.
+     */
+    boolean isVisible()
+    {
+        return (u2accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC) != 0;
+    }
+
+
+    // Implementations for Clazz.
+
+    public int getAccessFlags()
+    {
+        return u2accessFlags;
+    }
+
+    public String getName()
+    {
+        return thisClassName;
+    }
+
+    public String getSuperName()
+    {
+        // This may be java/lang/Object, in which case there is no super.
+        return superClassName;
+    }
+
+    public int getInterfaceCount()
+    {
+        return interfaceClasses.length;
+    }
+
+    public String getInterfaceName(int index)
+    {
+        return interfaceNames[index];
+    }
+
+    public int getTag(int constantIndex)
+    {
+        throw new UnsupportedOperationException("Library class ["+thisClassName+"] doesn't store constant pool");
+    }
+
+    public String getString(int constantIndex)
+    {
+        throw new UnsupportedOperationException("Library class ["+thisClassName+"] doesn't store constant pool");
+    }
+
+    public String getStringString(int constantIndex)
+    {
+        throw new UnsupportedOperationException("Library class ["+thisClassName+"] doesn't store constant pool");
+    }
+
+    public String getClassName(int constantIndex)
+    {
+        throw new UnsupportedOperationException("Library class ["+thisClassName+"] doesn't store constant pool");
+    }
+
+    public String getName(int constantIndex)
+    {
+        throw new UnsupportedOperationException("Library class ["+thisClassName+"] doesn't store constant pool");
+    }
+
+    public String getType(int constantIndex)
+    {
+        throw new UnsupportedOperationException("Library class ["+thisClassName+"] doesn't store constant pool");
+    }
+
+
+    public void addSubClass(Clazz clazz)
+    {
+        if (subClasses == null)
+        {
+            subClasses = new Clazz[1];
+        }
+        else
+        {
+            // Copy the old elements into new larger array.
+            Clazz[] temp     = new Clazz[subClasses.length+1];
+            System.arraycopy(subClasses, 0, temp, 0, subClasses.length);
+            subClasses = temp;
+        }
+
+        subClasses[subClasses.length-1] = clazz;
+    }
+
+
+    public Clazz getSuperClass()
+    {
+        return superClass;
+    }
+
+
+    public Clazz getInterface(int index)
+    {
+        return interfaceClasses[index];
+    }
+
+
+    public boolean extends_(Clazz clazz)
+    {
+        if (this.equals(clazz))
+        {
+            return true;
+        }
+
+        return superClass != null &&
+               superClass.extends_(clazz);
+    }
+
+
+    public boolean extendsOrImplements(Clazz clazz)
+    {
+        if (this.equals(clazz))
+        {
+            return true;
+        }
+
+        if (superClass != null &&
+            superClass.extendsOrImplements(clazz))
+        {
+            return true;
+        }
+
+        if (interfaceClasses != null)
+        {
+            for (int index = 0; index < interfaceClasses.length; index++)
+            {
+                Clazz interfaceClass = interfaceClasses[index];
+                if (interfaceClass != null &&
+                    interfaceClass.extendsOrImplements(clazz))
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+    public Field findField(String name, String descriptor)
+    {
+        for (int index = 0; index < fields.length; index++)
+        {
+            Field field = fields[index];
+            if (field != null &&
+                (name       == null || field.getName(this).equals(name)) &&
+                (descriptor == null || field.getDescriptor(this).equals(descriptor)))
+            {
+                return field;
+            }
+        }
+
+        return null;
+    }
+
+
+    public Method findMethod(String name, String descriptor)
+    {
+        for (int index = 0; index < methods.length; index++)
+        {
+            Method method = methods[index];
+            if (method != null &&
+                (name       == null || method.getName(this).equals(name)) &&
+                (descriptor == null || method.getDescriptor(this).equals(descriptor)))
+            {
+                return method;
+            }
+        }
+
+        return null;
+    }
+
+
+    public void accept(ClassVisitor classVisitor)
+    {
+        classVisitor.visitLibraryClass(this);
+    }
+
+
+    public void hierarchyAccept(boolean      visitThisClass,
+                                boolean      visitSuperClass,
+                                boolean      visitInterfaces,
+                                boolean      visitSubclasses,
+                                ClassVisitor classVisitor)
+    {
+        // First visit the current classfile.
+        if (visitThisClass)
+        {
+            accept(classVisitor);
+        }
+
+        // Then visit its superclass, recursively.
+        if (visitSuperClass)
+        {
+            if (superClass != null)
+            {
+                superClass.hierarchyAccept(true,
+                                           true,
+                                           visitInterfaces,
+                                           false,
+                                           classVisitor);
+            }
+        }
+
+        // Then visit its interfaces, recursively.
+        if (visitInterfaces)
+        {
+            // Visit the interfaces of the superclasses, if we haven't done so yet.
+            if (!visitSuperClass)
+            {
+                if (superClass != null)
+                {
+                    superClass.hierarchyAccept(false,
+                                               false,
+                                               true,
+                                               false,
+                                               classVisitor);
+                }
+            }
+
+            // Visit the interfaces.
+            if (interfaceClasses != null)
+            {
+                for (int index = 0; index < interfaceClasses.length; index++)
+                {
+                    Clazz interfaceClass = interfaceClasses[index];
+                    if (interfaceClass != null)
+                    {
+                        interfaceClass.hierarchyAccept(true,
+                                                       false,
+                                                       true,
+                                                       false,
+                                                       classVisitor);
+                    }
+                }
+            }
+        }
+
+        // Then visit its subclasses, recursively.
+        if (visitSubclasses)
+        {
+            if (subClasses != null)
+            {
+                for (int index = 0; index < subClasses.length; index++)
+                {
+                    subClasses[index].hierarchyAccept(true,
+                                                      false,
+                                                      false,
+                                                      true,
+                                                      classVisitor);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Lets the given class visitor visit the superclass, if it is known.
+     * @param classVisitor the <code>ClassVisitor</code> that will visit the
+     *                     superclass.
+     */
+    public void superClassAccept(ClassVisitor classVisitor)
+    {
+        if (superClass != null)
+        {
+            superClass.accept(classVisitor);
+        }
+    }
+
+
+    /**
+     * Lets the given class visitor visit all known direct interfaces.
+     * @param classVisitor the <code>ClassVisitor</code> that will visit the
+     *                     interfaces.
+     */
+    public void interfacesAccept(ClassVisitor classVisitor)
+    {
+        if (interfaceClasses != null)
+        {
+            for (int index = 0; index < interfaceClasses.length; index++)
+            {
+                Clazz interfaceClass = interfaceClasses[index];
+                if (interfaceClass != null)
+                {
+                    interfaceClass.accept(classVisitor);
+                }
+            }
+        }
+    }
+
+
+    public void subclassesAccept(ClassVisitor classVisitor)
+    {
+        if (subClasses != null)
+        {
+            for (int index = 0; index < subClasses.length; index++)
+            {
+                subClasses[index].accept(classVisitor);
+            }
+        }
+    }
+
+
+    public void constantPoolEntriesAccept(ConstantVisitor constantVisitor)
+    {
+        // This class doesn't keep references to its constant pool entries.
+    }
+
+
+    public void constantPoolEntryAccept(int index, ConstantVisitor constantVisitor)
+    {
+        // This class doesn't keep references to its constant pool entries.
+    }
+
+
+    public void thisClassConstantAccept(ConstantVisitor constantVisitor)
+    {
+        // This class doesn't keep references to its constant pool entries.
+    }
+
+
+    public void superClassConstantAccept(ConstantVisitor constantVisitor)
+    {
+        // This class doesn't keep references to its constant pool entries.
+    }
+
+
+    public void interfaceConstantsAccept(ConstantVisitor constantVisitor)
+    {
+        // This class doesn't keep references to its constant pool entries.
+    }
+
+
+    public void fieldsAccept(MemberVisitor memberVisitor)
+    {
+        for (int index = 0; index < fields.length; index++)
+        {
+            Field field = fields[index];
+            if (field != null)
+            {
+                field.accept(this, memberVisitor);
+            }
+        }
+    }
+
+
+    public void fieldAccept(String name, String descriptor, MemberVisitor memberVisitor)
+    {
+        Field field = findField(name, descriptor);
+        if (field != null)
+        {
+            field.accept(this, memberVisitor);
+        }
+    }
+
+
+    public void methodsAccept(MemberVisitor memberVisitor)
+    {
+        for (int index = 0; index < methods.length; index++)
+        {
+            Method method = methods[index];
+            if (method != null)
+            {
+                method.accept(this, memberVisitor);
+            }
+        }
+    }
+
+
+    public void methodAccept(String name, String descriptor, MemberVisitor memberVisitor)
+    {
+        Method method = findMethod(name, descriptor);
+        if (method != null)
+        {
+            method.accept(this, memberVisitor);
+        }
+    }
+
+
+    public boolean mayHaveImplementations(Method method)
+    {
+        return
+           (u2accessFlags & ClassConstants.INTERNAL_ACC_FINAL) == 0 &&
+           (method == null ||
+            ((method.getAccessFlags() & (ClassConstants.INTERNAL_ACC_PRIVATE |
+                                         ClassConstants.INTERNAL_ACC_STATIC  |
+                                         ClassConstants.INTERNAL_ACC_FINAL)) == 0 &&
+                                                                                  !method.getName(this).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)));
+    }
+
+
+    public void attributesAccept(AttributeVisitor attributeVisitor)
+    {
+        throw new UnsupportedOperationException("Library class ["+thisClassName+"] doesn't store attributes");
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "LibraryClass("+getName()+")";
+    }
+}
diff --git a/src/proguard/classfile/LibraryField.java b/src/proguard/classfile/LibraryField.java
new file mode 100644
index 0000000..2908c37
--- /dev/null
+++ b/src/proguard/classfile/LibraryField.java
@@ -0,0 +1,77 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.visitor.*;
+
+/**
+ * Representation of a field from a class-file.
+ *
+ * @author Eric Lafortune
+ */
+public class LibraryField extends LibraryMember implements Field
+{
+    /**
+     * An extra field pointing to the Clazz object referenced in the
+     * descriptor string. This field is filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz referencedClass;
+
+
+    /**
+     * Creates an uninitialized LibraryField.
+     */
+    public LibraryField()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LibraryField.
+     */
+    public LibraryField(int    u2accessFlags,
+                        String name,
+                        String descriptor)
+    {
+        super(u2accessFlags, name, descriptor);
+    }
+
+
+    // Implementations for LibraryMember.
+
+    public void accept(LibraryClass libraryClass, MemberVisitor memberVisitor)
+    {
+        memberVisitor.visitLibraryField(libraryClass, this);
+    }
+
+
+    // Implementations for Member.
+
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClass != null)
+        {
+            referencedClass.accept(classVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/LibraryMember.java b/src/proguard/classfile/LibraryMember.java
new file mode 100644
index 0000000..41ccb60
--- /dev/null
+++ b/src/proguard/classfile/LibraryMember.java
@@ -0,0 +1,108 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * Representation of a field or method from a library class.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class LibraryMember implements Member
+{
+    private static final int ACC_VISIBLE = ClassConstants.INTERNAL_ACC_PUBLIC |
+                                           ClassConstants.INTERNAL_ACC_PROTECTED;
+
+
+    public int    u2accessFlags;
+    public String name;
+    public String descriptor;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Creates an uninitialized LibraryMember.
+     */
+    protected LibraryMember()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LibraryMember.
+     */
+    protected LibraryMember(int    u2accessFlags,
+                            String name,
+                            String descriptor)
+    {
+        this.u2accessFlags = u2accessFlags;
+        this.name          = name;
+        this.descriptor    = descriptor;
+    }
+
+
+    /**
+     * Accepts the given member info visitor.
+     */
+    public abstract void accept(LibraryClass  libraryClass,
+                                MemberVisitor memberVisitor);
+
+
+    // Implementations for Member.
+
+    public int getAccessFlags()
+    {
+        return u2accessFlags;
+    }
+
+    public String getName(Clazz clazz)
+    {
+        return name;
+    }
+
+    public String getDescriptor(Clazz clazz)
+    {
+        return descriptor;
+    }
+
+    public void accept(Clazz clazz, MemberVisitor memberVisitor)
+    {
+        accept((LibraryClass)clazz, memberVisitor);
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+}
diff --git a/src/proguard/classfile/LibraryMethod.java b/src/proguard/classfile/LibraryMethod.java
new file mode 100644
index 0000000..a49a5f1
--- /dev/null
+++ b/src/proguard/classfile/LibraryMethod.java
@@ -0,0 +1,83 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.visitor.*;
+
+/**
+ * Representation of a method from a class-file.
+ *
+ * @author Eric Lafortune
+ */
+public class LibraryMethod extends LibraryMember implements Method
+{
+    /**
+     * An extra field pointing to the Clazz objects referenced in the
+     * descriptor string. This field is filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz[] referencedClasses;
+
+
+    /**
+     * Creates an uninitialized LibraryMethod.
+     */
+    public LibraryMethod()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LibraryMethod.
+     */
+    public LibraryMethod(int    u2accessFlags,
+                         String name,
+                         String descriptor)
+    {
+        super(u2accessFlags, name, descriptor);
+    }
+
+
+    // Implementations for LibraryMember.
+
+    public void accept(LibraryClass libraryClass, MemberVisitor memberVisitor)
+    {
+        memberVisitor.visitLibraryMethod(libraryClass, this);
+    }
+
+
+    // Implementations for Member.
+
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClasses != null)
+        {
+            for (int index = 0; index < referencedClasses.length; index++)
+            {
+                if (referencedClasses[index] != null)
+                {
+                    referencedClasses[index].accept(classVisitor);
+                }
+            }
+        }
+    }
+}
diff --git a/src/proguard/classfile/Member.java b/src/proguard/classfile/Member.java
new file mode 100644
index 0000000..1400b9c
--- /dev/null
+++ b/src/proguard/classfile/Member.java
@@ -0,0 +1,57 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.visitor.*;
+
+/**
+ * Representation of a field or method from a class.
+ *
+ * @author Eric Lafortune
+ */
+public interface Member extends VisitorAccepter
+{
+    /**
+     * Returns the access flags.
+     */
+    public int getAccessFlags();
+
+    /**
+     * Returns the class member name.
+     */
+    public String getName(Clazz clazz);
+
+    /**
+     * Returns the class member's descriptor.
+     */
+    public String getDescriptor(Clazz clazz);
+
+    /**
+     * Accepts the given class visitor.
+     */
+    public void accept(Clazz clazz, MemberVisitor memberVisitor);
+
+    /**
+     * Lets the Clazz objects referenced in the descriptor string
+     * accept the given visitor.
+     */
+    public void referencedClassesAccept(ClassVisitor classVisitor);
+}
diff --git a/src/proguard/classfile/Method.java b/src/proguard/classfile/Method.java
new file mode 100644
index 0000000..ebcae2b
--- /dev/null
+++ b/src/proguard/classfile/Method.java
@@ -0,0 +1,32 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+
+
+/**
+ * Representation of a method from a class.
+ *
+ * @author Eric Lafortune
+ */
+public interface Method extends Member
+{
+}
diff --git a/src/proguard/classfile/ProgramClass.java b/src/proguard/classfile/ProgramClass.java
new file mode 100644
index 0000000..9d0fc0c
--- /dev/null
+++ b/src/proguard/classfile/ProgramClass.java
@@ -0,0 +1,494 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.ClassSubHierarchyInitializer;
+import proguard.classfile.visitor.*;
+
+/**
+ * This Clazz is a complete representation of the data in a Java class.
+ *
+ * @author Eric Lafortune
+ */
+public class ProgramClass implements Clazz
+{
+    public int             u4magic;
+    public int             u4version;
+    public int             u2constantPoolCount;
+    public Constant[]      constantPool;
+    public int             u2accessFlags;
+    public int             u2thisClass;
+    public int             u2superClass;
+    public int             u2interfacesCount;
+    public int[]           u2interfaces;
+    public int             u2fieldsCount;
+    public ProgramField[]  fields;
+    public int             u2methodsCount;
+    public ProgramMethod[] methods;
+    public int             u2attributesCount;
+    public Attribute[]     attributes;
+
+    /**
+     * An extra field pointing to the subclasses of this class.
+     * This field is filled out by the {@link ClassSubHierarchyInitializer}.
+     */
+    public Clazz[] subClasses;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Creates an uninitialized ProgramClass.
+     */
+    public ProgramClass() {}
+
+
+    /**
+     * Returns the Constant at the given index in the constant pool.
+     */
+    public Constant getConstant(int constantIndex)
+    {
+        return constantPool[constantIndex];
+    }
+
+
+    // Implementations for Clazz.
+
+    public int getAccessFlags()
+    {
+        return u2accessFlags;
+    }
+
+    public String getName()
+    {
+        return getClassName(u2thisClass);
+    }
+
+    public String getSuperName()
+    {
+        return u2superClass == 0 ? null : getClassName(u2superClass);
+    }
+
+    public int getInterfaceCount()
+    {
+        return u2interfacesCount;
+    }
+
+    public String getInterfaceName(int index)
+    {
+        return getClassName(u2interfaces[index]);
+    }
+
+    public int getTag(int constantIndex)
+    {
+        return constantPool[constantIndex].getTag();
+    }
+
+    public String getString(int constantIndex)
+    {
+        try
+        {
+            return ((Utf8Constant)constantPool[constantIndex]).getString();
+        }
+        catch (ClassCastException ex)
+        {
+            new ClassPrinter().visitProgramClass(this);
+            throw new ClassCastException("Expected Utf8Constant at index ["+constantIndex+"] in class ["+getName()+"], found ["+ex.getMessage()+"]");
+        }
+    }
+
+    public String getStringString(int constantIndex)
+    {
+        try
+        {
+            return ((StringConstant)constantPool[constantIndex]).getString(this);
+        }
+        catch (ClassCastException ex)
+        {
+            throw new ClassCastException("Expected StringConstant at index ["+constantIndex+"] in class ["+getName()+"], found ["+ex.getMessage()+"]");
+        }
+    }
+
+    public String getClassName(int constantIndex)
+    {
+        try
+        {
+            return ((ClassConstant)constantPool[constantIndex]).getName(this);
+        }
+        catch (ClassCastException ex)
+        {
+            throw new ClassCastException("Expected ClassConstant at index ["+constantIndex+"] in class ["+getName()+"], found ["+ex.getMessage()+"]");
+        }
+    }
+
+    public String getName(int constantIndex)
+    {
+        try
+        {
+            return ((NameAndTypeConstant)constantPool[constantIndex]).getName(this);
+        }
+        catch (ClassCastException ex)
+        {
+            throw new ClassCastException("Expected NameAndTypeConstant at index ["+constantIndex+"] in class ["+getName()+"], found ["+ex.getMessage()+"]");
+        }
+    }
+
+    public String getType(int constantIndex)
+    {
+        try
+        {
+            return ((NameAndTypeConstant)constantPool[constantIndex]).getType(this);
+        }
+        catch (ClassCastException ex)
+        {
+            throw new ClassCastException("Expected NameAndTypeConstant at index ["+constantIndex+"] in class ["+getName()+"], found ["+ex.getMessage()+"]");
+        }
+    }
+
+
+    public void addSubClass(Clazz clazz)
+    {
+        if (subClasses == null)
+        {
+            subClasses = new Clazz[1];
+        }
+        else
+        {
+            // Copy the old elements into new larger array.
+            Clazz[] temp = new Clazz[subClasses.length+1];
+            System.arraycopy(subClasses, 0, temp, 0, subClasses.length);
+            subClasses = temp;
+        }
+
+        subClasses[subClasses.length-1] = clazz;
+    }
+
+
+    public Clazz getSuperClass()
+    {
+        return u2superClass != 0 ?
+            ((ClassConstant)constantPool[u2superClass]).referencedClass :
+            null;
+    }
+
+
+    public Clazz getInterface(int index)
+    {
+        return ((ClassConstant)constantPool[u2interfaces[index]]).referencedClass;
+    }
+
+
+    public boolean extends_(Clazz clazz)
+    {
+        if (this.equals(clazz))
+        {
+            return true;
+        }
+
+        Clazz superClass = getSuperClass();
+        return superClass != null &&
+               superClass.extends_(clazz);
+    }
+
+
+    public boolean extendsOrImplements(Clazz clazz)
+    {
+        if (this.equals(clazz))
+        {
+            return true;
+        }
+
+        Clazz superClass = getSuperClass();
+        if (superClass != null &&
+            superClass.extendsOrImplements(clazz))
+        {
+            return true;
+        }
+
+        for (int index = 0; index < u2interfacesCount; index++)
+        {
+            Clazz interfaceClass = getInterface(index);
+            if (interfaceClass != null &&
+                interfaceClass.extendsOrImplements(clazz))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    public Field findField(String name, String descriptor)
+    {
+        for (int index = 0; index < u2fieldsCount; index++)
+        {
+            Field field = fields[index];
+            if ((name       == null || field.getName(this).equals(name)) &&
+                (descriptor == null || field.getDescriptor(this).equals(descriptor)))
+            {
+                return field;
+            }
+        }
+
+        return null;
+    }
+
+
+    public Method findMethod(String name, String descriptor)
+    {
+        for (int index = 0; index < u2methodsCount; index++)
+        {
+            Method method = methods[index];
+            if ((name       == null || method.getName(this).equals(name)) &&
+                (descriptor == null || method.getDescriptor(this).equals(descriptor)))
+            {
+                return method;
+            }
+        }
+
+        return null;
+    }
+
+
+    public void accept(ClassVisitor classVisitor)
+    {
+        classVisitor.visitProgramClass(this);
+    }
+
+
+    public void hierarchyAccept(boolean      visitThisClass,
+                                boolean      visitSuperClass,
+                                boolean      visitInterfaces,
+                                boolean      visitSubclasses,
+                                ClassVisitor classVisitor)
+    {
+        // First visit the current classfile.
+        if (visitThisClass)
+        {
+            accept(classVisitor);
+        }
+
+        // Then visit its superclass, recursively.
+        if (visitSuperClass)
+        {
+            Clazz superClass = getSuperClass();
+            if (superClass != null)
+            {
+                superClass.hierarchyAccept(true,
+                                           true,
+                                           visitInterfaces,
+                                           false,
+                                           classVisitor);
+            }
+        }
+
+        // Then visit its interfaces, recursively.
+        if (visitInterfaces)
+        {
+            // Visit the interfaces of the superclasses, if we haven't done so yet.
+            if (!visitSuperClass)
+            {
+                Clazz superClass = getSuperClass();
+                if (superClass != null)
+                {
+                    superClass.hierarchyAccept(false,
+                                               false,
+                                               true,
+                                               false,
+                                               classVisitor);
+                }
+            }
+
+            // Visit the interfaces.
+            for (int index = 0; index < u2interfacesCount; index++)
+            {
+                Clazz interfaceClass = getInterface(index);
+                if (interfaceClass != null)
+                {
+                    interfaceClass.hierarchyAccept(true,
+                                                   false,
+                                                   true,
+                                                   false,
+                                                   classVisitor);
+                }
+            }
+        }
+
+        // Then visit its subclasses, recursively.
+        if (visitSubclasses)
+        {
+            if (subClasses != null)
+            {
+                for (int index = 0; index < subClasses.length; index++)
+                {
+                    Clazz subClass = subClasses[index];
+                    subClass.hierarchyAccept(true,
+                                             false,
+                                             false,
+                                             true,
+                                             classVisitor);
+                }
+            }
+        }
+    }
+
+
+    public void subclassesAccept(ClassVisitor classVisitor)
+    {
+        if (subClasses != null)
+        {
+            for (int index = 0; index < subClasses.length; index++)
+            {
+                subClasses[index].accept(classVisitor);
+            }
+        }
+    }
+
+
+    public void constantPoolEntriesAccept(ConstantVisitor constantVisitor)
+    {
+        for (int index = 1; index < u2constantPoolCount; index++)
+        {
+            if (constantPool[index] != null)
+            {
+                constantPool[index].accept(this, constantVisitor);
+            }
+        }
+    }
+
+
+    public void constantPoolEntryAccept(int index, ConstantVisitor constantVisitor)
+    {
+        constantPool[index].accept(this, constantVisitor);
+    }
+
+
+    public void thisClassConstantAccept(ConstantVisitor constantVisitor)
+    {
+        constantPool[u2thisClass].accept(this, constantVisitor);
+    }
+
+
+    public void superClassConstantAccept(ConstantVisitor constantVisitor)
+    {
+        if (u2superClass != 0)
+        {
+            constantPool[u2superClass].accept(this, constantVisitor);
+        }
+    }
+
+
+    public void interfaceConstantsAccept(ConstantVisitor constantVisitor)
+    {
+        for (int index = 0; index < u2interfacesCount; index++)
+        {
+            constantPool[u2interfaces[index]].accept(this, constantVisitor);
+        }
+    }
+
+
+    public void fieldsAccept(MemberVisitor memberVisitor)
+    {
+        for (int index = 0; index < u2fieldsCount; index++)
+        {
+            fields[index].accept(this, memberVisitor);
+        }
+    }
+
+
+    public void fieldAccept(String name, String descriptor, MemberVisitor memberVisitor)
+    {
+        Field field = findField(name, descriptor);
+        if (field != null)
+        {
+            field.accept(this, memberVisitor);
+        }
+    }
+
+
+    public void methodsAccept(MemberVisitor memberVisitor)
+    {
+        for (int index = 0; index < u2methodsCount; index++)
+        {
+            methods[index].accept(this, memberVisitor);
+        }
+    }
+
+
+    public void methodAccept(String name, String descriptor, MemberVisitor memberVisitor)
+    {
+        Method method = findMethod(name, descriptor);
+        if (method != null)
+        {
+            method.accept(this, memberVisitor);
+        }
+    }
+
+
+    public boolean mayHaveImplementations(Method method)
+    {
+        return
+            (u2accessFlags & ClassConstants.INTERNAL_ACC_FINAL) == 0 &&
+            (method == null ||
+             ((method.getAccessFlags() & (ClassConstants.INTERNAL_ACC_PRIVATE |
+                                          ClassConstants.INTERNAL_ACC_STATIC  |
+                                          ClassConstants.INTERNAL_ACC_FINAL)) == 0 &&
+              !method.getName(this).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)));
+    }
+
+
+    public void attributesAccept(AttributeVisitor attributeVisitor)
+    {
+        for (int index = 0; index < u2attributesCount; index++)
+        {
+            attributes[index].accept(this, attributeVisitor);
+        }
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "ProgramClass("+getName()+")";
+    }
+}
diff --git a/src/proguard/classfile/ProgramField.java b/src/proguard/classfile/ProgramField.java
new file mode 100644
index 0000000..5991b00
--- /dev/null
+++ b/src/proguard/classfile/ProgramField.java
@@ -0,0 +1,93 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.visitor.*;
+
+/**
+ * Representation of a field from a program class.
+ *
+ * @author Eric Lafortune
+ */
+public class ProgramField extends ProgramMember implements Field
+{
+    /**
+     * An extra field pointing to the Clazz object referenced in the
+     * descriptor string. This field is filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz referencedClass;
+
+
+    /**
+     * Creates an uninitialized ProgramField.
+     */
+    public ProgramField()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ProgramField.
+     */
+    public ProgramField(int         u2accessFlags,
+                        int         u2nameIndex,
+                        int         u2descriptorIndex,
+                        int         u2attributesCount,
+                        Attribute[] attributes,
+                        Clazz       referencedClass)
+    {
+        super(u2accessFlags, u2nameIndex, u2descriptorIndex, u2attributesCount, attributes);
+
+        this.referencedClass = referencedClass;
+    }
+
+
+    // Implementations for ProgramMember.
+
+    public void accept(ProgramClass programClass, MemberVisitor memberVisitor)
+    {
+        memberVisitor.visitProgramField(programClass, this);
+    }
+
+
+    public void attributesAccept(ProgramClass programClass, AttributeVisitor attributeVisitor)
+    {
+        for (int index = 0; index < u2attributesCount; index++)
+        {
+            attributes[index].accept(programClass, this, attributeVisitor);
+        }
+    }
+
+
+    // Implementations for Member.
+
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClass != null)
+        {
+            referencedClass.accept(classVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/ProgramMember.java b/src/proguard/classfile/ProgramMember.java
new file mode 100644
index 0000000..ea6f46d
--- /dev/null
+++ b/src/proguard/classfile/ProgramMember.java
@@ -0,0 +1,168 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * Representation of a field or method from a program class.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class ProgramMember implements Member
+{
+    public int         u2accessFlags;
+    public int         u2nameIndex;
+    public int         u2descriptorIndex;
+    public int         u2attributesCount;
+    public Attribute[] attributes;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Creates an uninitialized ProgramMember.
+     */
+    protected ProgramMember()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ProgramMember. 
+     */
+    protected ProgramMember(int         u2accessFlags,
+                            int         u2nameIndex,
+                            int         u2descriptorIndex,
+                            int         u2attributesCount,
+                            Attribute[] attributes)
+    {
+        this.u2accessFlags     = u2accessFlags;
+        this.u2nameIndex       = u2nameIndex;
+        this.u2descriptorIndex = u2descriptorIndex;
+        this.u2attributesCount = u2attributesCount;
+        this.attributes        = attributes;
+    }
+
+
+    /**
+     * Returns the line number range of the given class member as "m:n",
+     * if it can find it, or <code>null</code> otherwise.
+     */
+    public String getLineNumberRange(Clazz clazz)
+    {
+        CodeAttribute codeAttribute =
+            (CodeAttribute)getAttribute(clazz, ClassConstants.ATTR_Code);
+        if (codeAttribute  == null)
+        {
+            return null;
+        }
+
+        LineNumberTableAttribute lineNumberTableAttribute =
+            (LineNumberTableAttribute)codeAttribute.getAttribute(clazz,
+                                                                 ClassConstants.ATTR_LineNumberTable);
+        if (lineNumberTableAttribute  == null)
+        {
+            return null;
+        }
+
+        return "" +
+               lineNumberTableAttribute.getLineNumber(0) +
+               ":" +
+               lineNumberTableAttribute.getLineNumber(Integer.MAX_VALUE);
+    }
+
+
+    /**
+     * Returns the (first) attribute with the given name.
+     */
+    private Attribute getAttribute(Clazz clazz, String name)
+    {
+        for (int index = 0; index < u2attributesCount; index++)
+        {
+            Attribute attribute = attributes[index];
+            if (attribute.getAttributeName(clazz).equals(name))
+            {
+                return attribute;
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Accepts the given member info visitor.
+     */
+    public abstract void accept(ProgramClass  programClass,
+                                MemberVisitor memberVisitor);
+
+
+
+    /**
+     * Lets the given attribute info visitor visit all the attributes of
+     * this member info.
+     */
+    public abstract void attributesAccept(ProgramClass     programClass,
+                                          AttributeVisitor attributeVisitor);
+
+
+    // Implementations for Member.
+
+    public int getAccessFlags()
+    {
+        return u2accessFlags;
+    }
+
+    public String getName(Clazz clazz)
+    {
+        return clazz.getString(u2nameIndex);
+    }
+
+    public String getDescriptor(Clazz clazz)
+    {
+        return clazz.getString(u2descriptorIndex);
+    }
+
+    public void accept(Clazz clazz, MemberVisitor memberVisitor)
+    {
+        accept((ProgramClass)clazz, memberVisitor);
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+}
diff --git a/src/proguard/classfile/ProgramMethod.java b/src/proguard/classfile/ProgramMethod.java
new file mode 100644
index 0000000..943c3d6
--- /dev/null
+++ b/src/proguard/classfile/ProgramMethod.java
@@ -0,0 +1,99 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.visitor.*;
+
+/**
+ * Representation of a method from a program class.
+ *
+ * @author Eric Lafortune
+ */
+public class ProgramMethod extends ProgramMember implements Method
+{
+    /**
+     * An extra field pointing to the Clazz objects referenced in the
+     * descriptor string. This field is filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz[] referencedClasses;
+
+
+    /**
+     * Creates an uninitialized ProgramMethod.
+     */
+    public ProgramMethod()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ProgramMethod.
+     */
+    public ProgramMethod(int         u2accessFlags,
+                         int         u2nameIndex,
+                         int         u2descriptorIndex,
+                         int         u2attributesCount,
+                         Attribute[] attributes,
+                         Clazz[]     referencedClasses)
+    {
+        super(u2accessFlags, u2nameIndex, u2descriptorIndex, u2attributesCount, attributes);
+
+        this.referencedClasses = referencedClasses;
+    }
+
+
+    // Implementations for ProgramMember.
+
+    public void accept(ProgramClass programClass, MemberVisitor memberVisitor)
+    {
+        memberVisitor.visitProgramMethod(programClass, this);
+    }
+
+
+    public void attributesAccept(ProgramClass programClass, AttributeVisitor attributeVisitor)
+    {
+        for (int index = 0; index < u2attributesCount; index++)
+        {
+            attributes[index].accept(programClass, this, attributeVisitor);
+        }
+    }
+
+
+    // Implementations for Member.
+
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClasses != null)
+        {
+            for (int index = 0; index < referencedClasses.length; index++)
+            {
+                if (referencedClasses[index] != null)
+                {
+                    referencedClasses[index].accept(classVisitor);
+                }
+            }
+        }
+    }
+}
diff --git a/src/proguard/classfile/VisitorAccepter.java b/src/proguard/classfile/VisitorAccepter.java
new file mode 100644
index 0000000..e38f888
--- /dev/null
+++ b/src/proguard/classfile/VisitorAccepter.java
@@ -0,0 +1,47 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile;
+
+
+
+
+/**
+ * This interface is a base interface for visitor accepters. It allows
+ * visitors to set and get any temporary information they desire on the
+ * objects they are visiting. Note that every visitor accepter has only one
+ * such property, so visitors will have to take care not to overwrite each
+ * other's information, if it is still required.
+ *
+ * @author Eric Lafortune
+ */
+public interface VisitorAccepter
+{
+    /**
+     * Gets the visitor information of the visitor accepter.
+     */
+    public Object getVisitorInfo();
+
+
+    /**
+     * Sets the visitor information of the visitor accepter.
+     */
+    public void setVisitorInfo(Object visitorInfo);
+}
diff --git a/src/proguard/classfile/attribute/Attribute.java b/src/proguard/classfile/attribute/Attribute.java
new file mode 100644
index 0000000..2e16e22
--- /dev/null
+++ b/src/proguard/classfile/attribute/Attribute.java
@@ -0,0 +1,142 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This abstract class represents an attribute that is attached to a class,
+ * a class member, or a code attribute. Specific types of attributes are
+ * subclassed from it.
+ *
+ * @author Eric Lafortune
+ * @noinspection AbstractClassWithoutAbstractMethods
+ */
+public abstract class Attribute implements VisitorAccepter
+{
+    public int u2attributeNameIndex;
+    //public int  u4attributeLength;
+    //public byte info[];
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Create an uninitialized Attribute.
+     */
+    protected Attribute()
+    {
+    }
+
+
+    /**
+     * Create an initialized Attribute.
+     */
+    protected Attribute(int u2attributeNameIndex)
+    {
+        this.u2attributeNameIndex = u2attributeNameIndex;
+    }
+
+
+    /**
+     * Returns the String name of the attribute.
+     */
+    public String getAttributeName(Clazz clazz)
+    {
+        return clazz.getString(u2attributeNameIndex);
+    }
+
+
+    // Methods to be implemented by extensions, if applicable.
+
+    /**
+     * Accepts the given visitor.
+     */
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+    /**
+     * Accepts the given visitor in the context of the given field.
+     */
+    public void accept(Clazz clazz, Field field, AttributeVisitor attributeVisitor)
+    {
+        // Delegate the default invocation if the field is null anyway.
+        if (field == null)
+        {
+            accept(clazz, attributeVisitor);
+        }
+        else
+        {
+            throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+        }
+    }
+
+    /**
+     * Accepts the given visitor in the context of the given method.
+     */
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        // Delegate the default invocation if the method is null anyway.
+        if (method == null)
+        {
+            accept(clazz, (Field)null, attributeVisitor);
+        }
+        else
+        {
+            throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+        }
+    }
+
+    /**
+     * Accepts the given visitor in the context of the given code attribute.
+     */
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, AttributeVisitor attributeVisitor)
+    {
+        // Delegate the default invocation if the code attribute is null anyway.
+        if (codeAttribute == null)
+        {
+            accept(clazz, method, attributeVisitor);
+        }
+        else
+        {
+            throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+        }
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+}
diff --git a/src/proguard/classfile/attribute/CodeAttribute.java b/src/proguard/classfile/attribute/CodeAttribute.java
new file mode 100644
index 0000000..92ff9ea
--- /dev/null
+++ b/src/proguard/classfile/attribute/CodeAttribute.java
@@ -0,0 +1,202 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+
+/**
+ * This Attribute represents a code attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class CodeAttribute extends Attribute
+{
+    public int             u2maxStack;
+    public int             u2maxLocals;
+    public int             u4codeLength;
+    public byte[]          code;
+    public int             u2exceptionTableLength;
+    public ExceptionInfo[] exceptionTable;
+    public int             u2attributesCount;
+    public Attribute[]     attributes;
+
+
+    /**
+     * Creates an uninitialized CodeAttribute.
+     */
+    public CodeAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized CodeAttribute.
+     */
+    public CodeAttribute(int             u2attributeNameIndex,
+                         int             u2maxStack,
+                         int             u2maxLocals,
+                         int             u4codeLength,
+                         byte[]          code,
+                         int             u2exceptionTableLength,
+                         ExceptionInfo[] exceptionTable,
+                         int             u2attributesCount,
+                         Attribute[]     attributes)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2maxStack             = u2maxStack;
+        this.u2maxLocals            = u2maxLocals;
+        this.u4codeLength           = u4codeLength;
+        this.code                   = code;
+        this.u2exceptionTableLength = u2exceptionTableLength;
+        this.exceptionTable         = exceptionTable;
+        this.u2attributesCount      = u2attributesCount;
+        this.attributes             = attributes;
+    }
+
+
+    /**
+     * Returns the (first) attribute with the given name.
+     */
+    public Attribute getAttribute(Clazz clazz, String name)
+    {
+        for (int index = 0; index < u2attributesCount; index++)
+        {
+            Attribute attribute = attributes[index];
+            if (attribute.getAttributeName(clazz).equals(name))
+            {
+                return attribute;
+            }
+        }
+
+        return null;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitCodeAttribute(clazz, method, this);
+    }
+
+
+    /**
+     * Applies the given instruction visitor to all instructions.
+     */
+    public void instructionsAccept(Clazz clazz, Method method, InstructionVisitor instructionVisitor)
+    {
+        instructionsAccept(clazz, method, 0, u4codeLength, instructionVisitor);
+    }
+
+
+    /**
+     * Applies the given instruction visitor to the instruction at the specified
+     * offset.
+     */
+    public void instructionAccept(Clazz clazz, Method method, int offset, InstructionVisitor instructionVisitor)
+    {
+        Instruction instruction = InstructionFactory.create(code, offset);
+        instruction.accept(clazz, method, this, offset, instructionVisitor);
+    }
+
+
+    /**
+     * Applies the given instruction visitor to all instructions in the
+     * specified range of offsets.
+     */
+    public void instructionsAccept(Clazz clazz, Method method, int startOffset, int endOffset, InstructionVisitor instructionVisitor)
+    {
+        int offset = startOffset;
+
+        while (offset < endOffset)
+        {
+            // Note that the instruction is only volatile.
+            Instruction instruction = InstructionFactory.create(code, offset);
+            int instructionLength = instruction.length(offset);
+            instruction.accept(clazz, method, this, offset, instructionVisitor);
+            offset += instructionLength;
+        }
+    }
+
+
+    /**
+     * Applies the given exception visitor to all exceptions.
+     */
+    public void exceptionsAccept(Clazz clazz, Method method, ExceptionInfoVisitor exceptionInfoVisitor)
+    {
+        for (int index = 0; index < u2exceptionTableLength; index++)
+        {
+            // We don't need double dispatching here, since there is only one
+            // type of ExceptionInfo.
+            exceptionInfoVisitor.visitExceptionInfo(clazz, method, this, exceptionTable[index]);
+        }
+    }
+
+
+    /**
+     * Applies the given exception visitor to all exceptions that are applicable
+     * to the instruction at the specified offset.
+     */
+    public void exceptionsAccept(Clazz clazz, Method method, int offset, ExceptionInfoVisitor exceptionInfoVisitor)
+    {
+        for (int index = 0; index < u2exceptionTableLength; index++)
+        {
+            ExceptionInfo exceptionInfo = exceptionTable[index];
+            if (exceptionInfo.isApplicable(offset))
+            {
+                exceptionInfoVisitor.visitExceptionInfo(clazz, method, this, exceptionInfo);
+            }
+        }
+    }
+
+
+    /**
+     * Applies the given exception visitor to all exceptions that are applicable
+     * to any of the instructions in the specified range of offsets.
+     */
+    public void exceptionsAccept(Clazz clazz, Method method, int startOffset, int endOffset, ExceptionInfoVisitor exceptionInfoVisitor)
+    {
+        for (int index = 0; index < u2exceptionTableLength; index++)
+        {
+            ExceptionInfo exceptionInfo = exceptionTable[index];
+            if (exceptionInfo.isApplicable(startOffset, endOffset))
+            {
+                exceptionInfoVisitor.visitExceptionInfo(clazz, method, this, exceptionInfo);
+            }
+        }
+    }
+
+
+    /**
+     * Applies the given attribute visitor to all attributes.
+     */
+    public void attributesAccept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        for (int index = 0; index < u2attributesCount; index++)
+        {
+            attributes[index].accept(clazz, method, this, attributeVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/ConstantValueAttribute.java b/src/proguard/classfile/attribute/ConstantValueAttribute.java
new file mode 100644
index 0000000..3ae991e
--- /dev/null
+++ b/src/proguard/classfile/attribute/ConstantValueAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a constant value attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantValueAttribute extends Attribute
+{
+    public int u2constantValueIndex;
+
+
+    /**
+     * Creates an uninitialized ConstantValueAttribute.
+     */
+    public ConstantValueAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ConstantValueAttribute.
+     */
+    public ConstantValueAttribute(int u2attributeNameIndex,
+                                  int u2constantValueIndex)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2constantValueIndex = u2constantValueIndex;
+    }
+
+    
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Field field, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitConstantValueAttribute(clazz, field, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/DeprecatedAttribute.java b/src/proguard/classfile/attribute/DeprecatedAttribute.java
new file mode 100644
index 0000000..4180950
--- /dev/null
+++ b/src/proguard/classfile/attribute/DeprecatedAttribute.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a deprecated attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class DeprecatedAttribute extends Attribute
+{
+    /**
+     * Creates an uninitialized DeprecatedAttribute.
+     */
+    public DeprecatedAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized DeprecatedAttribute.
+     */
+    public DeprecatedAttribute(int u2attributeNameIndex)
+    {
+        super(u2attributeNameIndex);
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitDeprecatedAttribute(clazz, this);
+    }
+
+    public void accept(Clazz clazz, Field field, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitDeprecatedAttribute(clazz, field, this);
+    }
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitDeprecatedAttribute(clazz, method, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/EnclosingMethodAttribute.java b/src/proguard/classfile/attribute/EnclosingMethodAttribute.java
new file mode 100644
index 0000000..9275b3a
--- /dev/null
+++ b/src/proguard/classfile/attribute/EnclosingMethodAttribute.java
@@ -0,0 +1,132 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This Attribute represents an enclosing method attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class EnclosingMethodAttribute extends Attribute
+{
+    public int u2classIndex;
+    public int u2nameAndTypeIndex;
+
+    /**
+     * An extra field pointing to the referenced Clazz object.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>.
+     */
+    public Clazz referencedClass;
+
+    /**
+     * An extra field optionally pointing to the referenced Method object.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>.
+     */
+    public Method referencedMethod;
+
+
+    /**
+     * Creates an uninitialized EnclosingMethodAttribute.
+     */
+    public EnclosingMethodAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized EnclosingMethodAttribute.
+     */
+    public EnclosingMethodAttribute(int u2attributeNameIndex,
+                                    int u2classIndex,
+                                    int u2nameAndTypeIndex)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2classIndex       = u2classIndex;
+        this.u2nameAndTypeIndex = u2nameAndTypeIndex;
+    }
+
+
+    /**
+     * Returns the class name.
+     */
+    public String getClassName(Clazz clazz)
+    {
+        return clazz.getClassName(u2classIndex);
+    }
+
+    /**
+     * Returns the method/field name.
+     */
+    public String getName(Clazz clazz)
+    {
+        return clazz.getName(u2nameAndTypeIndex);
+    }
+
+    /**
+     * Returns the type.
+     */
+    public String getType(Clazz clazz)
+    {
+        return clazz.getType(u2nameAndTypeIndex);
+    }
+
+
+    /**
+     * Lets the referenced class accept the given visitor.
+     */
+    public void referencedClassAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClass != null)
+        {
+            referencedClass.accept(classVisitor);
+        }
+    }
+
+
+    /**
+     * Lets the referenced class member accept the given visitor.
+     */
+    public void referencedMethodAccept(MemberVisitor memberVisitor)
+    {
+        if (referencedMethod != null)
+        {
+            referencedMethod.accept(referencedClass,
+                                    memberVisitor);
+        }
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitEnclosingMethodAttribute(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/ExceptionInfo.java b/src/proguard/classfile/attribute/ExceptionInfo.java
new file mode 100644
index 0000000..082efab
--- /dev/null
+++ b/src/proguard/classfile/attribute/ExceptionInfo.java
@@ -0,0 +1,100 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.VisitorAccepter;
+
+/**
+ * Representation of an Exception table entry.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionInfo implements VisitorAccepter
+{
+    public int u2startPC;
+    public int u2endPC;
+    public int u2handlerPC;
+    public int u2catchType;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Creates an uninitialized ExceptionInfo.
+     */
+    public ExceptionInfo()
+    {
+        this(0, 0, 0, 0);
+    }
+
+
+    /**
+     * Creates an ExceptionInfo with the given properties.
+     */
+    public ExceptionInfo(int u2startPC,
+                         int u2endPC,
+                         int u2handlerPC,
+                         int u2catchType)
+    {
+        this.u2startPC   = u2startPC;
+        this.u2endPC     = u2endPC;
+        this.u2handlerPC = u2handlerPC;
+        this.u2catchType = u2catchType;
+    }
+
+
+    /**
+     * Returns whether the exception's try block contains the instruction at the
+     * given offset.
+     */
+    public boolean isApplicable(int instructionOffset)
+    {
+        return instructionOffset >= u2startPC &&
+               instructionOffset <  u2endPC;
+    }
+
+
+    /**
+     * Returns whether the exception's try block overlaps with the specified
+     * block of instructions.
+     */
+    public boolean isApplicable(int startOffset, int endOffset)
+    {
+        return u2startPC < endOffset &&
+               u2endPC   > startOffset;
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+}
diff --git a/src/proguard/classfile/attribute/ExceptionsAttribute.java b/src/proguard/classfile/attribute/ExceptionsAttribute.java
new file mode 100644
index 0000000..d22c4a6
--- /dev/null
+++ b/src/proguard/classfile/attribute/ExceptionsAttribute.java
@@ -0,0 +1,80 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Attribute represents an exceptions attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionsAttribute extends Attribute
+{
+    public int   u2exceptionIndexTableLength;
+    public int[] u2exceptionIndexTable;
+
+
+    /**
+     * Creates an uninitialized ExceptionsAttribute.
+     */
+    public ExceptionsAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ExceptionsAttribute.
+     */
+    public ExceptionsAttribute(int   u2attributeNameIndex,
+                               int   u2exceptionIndexTableLength,
+                               int[] u2exceptionIndexTable)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2exceptionIndexTableLength = u2exceptionIndexTableLength;
+        this.u2exceptionIndexTable       = u2exceptionIndexTable;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitExceptionsAttribute(clazz, method, this);
+    }
+
+
+    /**
+     * Applies the given constant pool visitor to all exception class pool info
+     * entries.
+     */
+    public void exceptionEntriesAccept(ProgramClass programClass, ConstantVisitor constantVisitor)
+    {
+        for (int index = 0; index < u2exceptionIndexTableLength; index++)
+        {
+            programClass.constantPoolEntryAccept(u2exceptionIndexTable[index],
+                                                 constantVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/InnerClassesAttribute.java b/src/proguard/classfile/attribute/InnerClassesAttribute.java
new file mode 100644
index 0000000..2f7e310
--- /dev/null
+++ b/src/proguard/classfile/attribute/InnerClassesAttribute.java
@@ -0,0 +1,80 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.visitor.*;
+
+/**
+ * This Attribute represents an inner classes attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class InnerClassesAttribute extends Attribute
+{
+    public int                u2classesCount;
+    public InnerClassesInfo[] classes;
+
+
+    /**
+     * Creates an uninitialized InnerClassesAttribute.
+     */
+    public InnerClassesAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized InnerClassesAttribute.
+     */
+    public InnerClassesAttribute(int                u2attributeNameIndex,
+                                 int                u2classesCount,
+                                 InnerClassesInfo[] classes)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2classesCount = u2classesCount;
+        this.classes        = classes;
+    }
+
+    //
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitInnerClassesAttribute(clazz, this);
+    }
+
+
+    /**
+     * Applies the given visitor to all inner classes.
+     */
+    public void innerClassEntriesAccept(Clazz clazz, InnerClassesInfoVisitor innerClassesInfoVisitor)
+    {
+        for (int index = 0; index < u2classesCount; index++)
+        {
+            // We don't need double dispatching here, since there is only one
+            // type of InnerClassesInfo.
+            innerClassesInfoVisitor.visitInnerClassesInfo(clazz, classes[index]);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/InnerClassesInfo.java b/src/proguard/classfile/attribute/InnerClassesInfo.java
new file mode 100644
index 0000000..1bdd6c3
--- /dev/null
+++ b/src/proguard/classfile/attribute/InnerClassesInfo.java
@@ -0,0 +1,122 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * Representation of an Inner Classes table entry.
+ *
+ * @author Eric Lafortune
+ */
+public class InnerClassesInfo implements VisitorAccepter
+{
+    public int u2innerClassIndex;
+    public int u2outerClassIndex;
+    public int u2innerNameIndex;
+    public int u2innerClassAccessFlags;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Returns the inner class index.
+     */
+    protected int getInnerClassIndex()
+    {
+        return u2innerClassIndex;
+    }
+
+    /**
+     * Returns the name index.
+     */
+    protected int getInnerNameIndex()
+    {
+        return u2innerNameIndex;
+    }
+
+    /**
+     * Sets the name index.
+     */
+    protected void setInnerNameIndex(int index)
+    {
+        u2innerNameIndex = index;
+    }
+
+
+    /**
+     * Applies the given constant pool visitor to the class constant of the
+     * inner class, if any.
+     */
+    public void innerClassConstantAccept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        if (u2innerClassIndex != 0)
+        {
+            clazz.constantPoolEntryAccept(u2innerClassIndex,
+                                                 constantVisitor);
+        }
+    }
+
+
+    /**
+     * Applies the given constant pool visitor to the class constant of the
+     * outer class, if any.
+     */
+    public void outerClassConstantAccept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        if (u2outerClassIndex != 0)
+        {
+            clazz.constantPoolEntryAccept(u2outerClassIndex,
+                                                 constantVisitor);
+        }
+    }
+
+
+    /**
+     * Applies the given constant pool visitor to the Utf8 constant of the
+     * inner name, if any.
+     */
+    public void innerNameConstantAccept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        if (u2innerNameIndex != 0)
+        {
+            clazz.constantPoolEntryAccept(u2innerNameIndex,
+                                                 constantVisitor);
+        }
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+}
diff --git a/src/proguard/classfile/attribute/LineNumberInfo.java b/src/proguard/classfile/attribute/LineNumberInfo.java
new file mode 100644
index 0000000..f58083a
--- /dev/null
+++ b/src/proguard/classfile/attribute/LineNumberInfo.java
@@ -0,0 +1,50 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+/**
+ * Representation of an Line Number table entry.
+ *
+ * @author Eric Lafortune
+ */
+public class LineNumberInfo
+{
+    public int u2startPC;
+    public int u2lineNumber;
+
+
+    /**
+     * Creates an uninitialized LineNumberInfo.
+     */
+    public LineNumberInfo()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LineNumberInfo.
+     */
+    public LineNumberInfo(int u2startPC, int u2lineNumber)
+    {
+        this.u2startPC    = u2startPC;
+        this.u2lineNumber = u2lineNumber;
+    }
+}
diff --git a/src/proguard/classfile/attribute/LineNumberTableAttribute.java b/src/proguard/classfile/attribute/LineNumberTableAttribute.java
new file mode 100644
index 0000000..4d507d9
--- /dev/null
+++ b/src/proguard/classfile/attribute/LineNumberTableAttribute.java
@@ -0,0 +1,100 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.*;
+
+/**
+ * This Attribute represents a line number table attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class LineNumberTableAttribute extends Attribute
+{
+    public int              u2lineNumberTableLength;
+    public LineNumberInfo[] lineNumberTable;
+
+
+    /**
+     * Creates an uninitialized LineNumberTableAttribute.
+     */
+    public LineNumberTableAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LineNumberTableAttribute.
+     */
+    public LineNumberTableAttribute(int              u2attributeNameIndex,
+                                    int              u2lineNumberTableLength,
+                                    LineNumberInfo[] lineNumberTable)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2lineNumberTableLength = u2lineNumberTableLength;
+        this.lineNumberTable         = lineNumberTable;
+    }
+
+
+    /**
+     * Returns the line number corresponding to the given byte code program
+     * counter.
+     */
+    public int getLineNumber(int pc)
+    {
+        for (int index = u2lineNumberTableLength-1 ; index >= 0 ; index--)
+        {
+            LineNumberInfo info = lineNumberTable[index];
+            if (pc >= info.u2startPC)
+            {
+                return info.u2lineNumber;
+            }
+        }
+
+        return u2lineNumberTableLength > 0 ?
+            lineNumberTable[0].u2lineNumber :
+            0;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitLineNumberTableAttribute(clazz, method, codeAttribute, this);
+    }
+
+
+    /**
+     * Applies the given visitor to all line numbers.
+     */
+    public void lineNumbersAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfoVisitor lineNumberInfoVisitor)
+    {
+        for (int index = 0; index < u2lineNumberTableLength; index++)
+        {
+            // We don't need double dispatching here, since there is only one
+            // type of LineNumberInfo.
+            lineNumberInfoVisitor.visitLineNumberInfo(clazz, method, codeAttribute, lineNumberTable[index]);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/LocalVariableInfo.java b/src/proguard/classfile/attribute/LocalVariableInfo.java
new file mode 100644
index 0000000..4e54c22
--- /dev/null
+++ b/src/proguard/classfile/attribute/LocalVariableInfo.java
@@ -0,0 +1,83 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * Representation of an Local Variable table entry.
+ *
+ * @author Eric Lafortune
+ */
+public class LocalVariableInfo
+{
+    public int u2startPC;
+    public int u2length;
+    public int u2nameIndex;
+    public int u2descriptorIndex;
+    public int u2index;
+
+    /**
+     * An extra field pointing to the referenced Clazz object.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>.
+     */
+    public Clazz referencedClass;
+
+
+    /**
+     * Creates an uninitialized LocalVariableInfo.
+     */
+    public LocalVariableInfo()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LocalVariableInfo.
+     */
+    public LocalVariableInfo(int   u2startPC,
+                             int   u2length,
+                             int   u2nameIndex,
+                             int   u2descriptorIndex,
+                             int   u2index)
+    {
+        this.u2startPC         = u2startPC;
+        this.u2length          = u2length;
+        this.u2nameIndex       = u2nameIndex;
+        this.u2descriptorIndex = u2descriptorIndex;
+        this.u2index           = u2index;
+    }
+
+
+    /**
+     * Lets the referenced class accept the given visitor.
+     */
+    public void referencedClassAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClass != null)
+        {
+            referencedClass.accept(classVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/LocalVariableTableAttribute.java b/src/proguard/classfile/attribute/LocalVariableTableAttribute.java
new file mode 100644
index 0000000..9c3f115
--- /dev/null
+++ b/src/proguard/classfile/attribute/LocalVariableTableAttribute.java
@@ -0,0 +1,79 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.*;
+
+/**
+ * This Attribute represents a local variable table attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class LocalVariableTableAttribute extends Attribute
+{
+    public int                 u2localVariableTableLength;
+    public LocalVariableInfo[] localVariableTable;
+
+
+    /**
+     * Creates an uninitialized LocalVariableTableAttribute.
+     */
+    public LocalVariableTableAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LocalVariableTableAttribute.
+     */
+    public LocalVariableTableAttribute(int                 u2attributeNameIndex,
+                                       int                 u2localVariableTableLength,
+                                       LocalVariableInfo[] localVariableTable)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2localVariableTableLength = u2localVariableTableLength;
+        this.localVariableTable         = localVariableTable;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitLocalVariableTableAttribute(clazz, method, codeAttribute, this);
+    }
+
+
+    /**
+     * Applies the given visitor to all local variables.
+     */
+    public void localVariablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfoVisitor localVariableInfoVisitor)
+    {
+        for (int index = 0; index < u2localVariableTableLength; index++)
+        {
+            // We don't need double dispatching here, since there is only one
+            // type of LocalVariableInfo.
+            localVariableInfoVisitor.visitLocalVariableInfo(clazz, method, codeAttribute, localVariableTable[index]);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/LocalVariableTypeInfo.java b/src/proguard/classfile/attribute/LocalVariableTypeInfo.java
new file mode 100644
index 0000000..1b71f35
--- /dev/null
+++ b/src/proguard/classfile/attribute/LocalVariableTypeInfo.java
@@ -0,0 +1,91 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * Representation of an Local Variable table entry.
+ *
+ * @author Eric Lafortune
+ */
+public class LocalVariableTypeInfo
+{
+    public int u2startPC;
+    public int u2length;
+    public int u2nameIndex;
+    public int u2signatureIndex;
+    public int u2index;
+
+    /**
+     * An extra field pointing to the Clazz objects referenced in the
+     * type string. This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz[] referencedClasses;
+
+
+    /**
+     * Creates an uninitialized LocalVariableTypeInfo.
+     */
+    public LocalVariableTypeInfo()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LocalVariableTypeInfo.
+     */
+    public LocalVariableTypeInfo(int   u2startPC,
+                                 int   u2length,
+                                 int   u2nameIndex,
+                                 int   u2signatureIndex,
+                                 int   u2index)
+    {
+        this.u2startPC        = u2startPC;
+        this.u2length         = u2length;
+        this.u2nameIndex      = u2nameIndex;
+        this.u2signatureIndex = u2signatureIndex;
+        this.u2index          = u2index;
+    }
+
+
+    /**
+     * Applies the given visitor to all referenced classes.
+     */
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClasses != null)
+        {
+            for (int index = 0; index < referencedClasses.length; index++)
+            {
+                Clazz referencedClass = referencedClasses[index];
+                if (referencedClass != null)
+                {
+                    referencedClass.accept(classVisitor);
+                }
+            }
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/LocalVariableTypeTableAttribute.java b/src/proguard/classfile/attribute/LocalVariableTypeTableAttribute.java
new file mode 100644
index 0000000..fd856fe
--- /dev/null
+++ b/src/proguard/classfile/attribute/LocalVariableTypeTableAttribute.java
@@ -0,0 +1,79 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.*;
+
+/**
+ * This Attribute represents a local variable table type attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class LocalVariableTypeTableAttribute extends Attribute
+{
+    public int                     u2localVariableTypeTableLength;
+    public LocalVariableTypeInfo[] localVariableTypeTable;
+
+
+    /**
+     * Creates an uninitialized LocalVariableTypeTableAttribute.
+     */
+    public LocalVariableTypeTableAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized LocalVariableTypeTableAttribute.
+     */
+    public LocalVariableTypeTableAttribute(int                     u2attributeNameIndex,
+                                           int                     u2localVariableTypeTableLength,
+                                           LocalVariableTypeInfo[] localVariableTypeTable)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2localVariableTypeTableLength = u2localVariableTypeTableLength;
+        this.localVariableTypeTable         = localVariableTypeTable;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitLocalVariableTypeTableAttribute(clazz, method, codeAttribute, this);
+    }
+
+
+    /**
+     * Applies the given visitor to all local variable types.
+     */
+    public void localVariablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfoVisitor localVariableTypeInfoVisitor)
+    {
+        for (int index = 0; index < u2localVariableTypeTableLength; index++)
+        {
+            // We don't need double dispatching here, since there is only one
+            // type of LocalVariableTypeInfo.
+            localVariableTypeInfoVisitor.visitLocalVariableTypeInfo(clazz, method, codeAttribute, localVariableTypeTable[index]);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/SignatureAttribute.java b/src/proguard/classfile/attribute/SignatureAttribute.java
new file mode 100644
index 0000000..c7585fe
--- /dev/null
+++ b/src/proguard/classfile/attribute/SignatureAttribute.java
@@ -0,0 +1,100 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This Attribute represents a signature attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class SignatureAttribute extends Attribute
+{
+    public int u2signatureIndex;
+
+    /**
+     * An extra field pointing to the Clazz objects referenced in the
+     * signature string. This field is filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz[] referencedClasses;
+
+
+    /**
+     * Creates an uninitialized SignatureAttribute.
+     */
+    public SignatureAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized SignatureAttribute.
+     */
+    public SignatureAttribute(int u2attributeNameIndex,
+                              int u2signatureIndex)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2signatureIndex = u2signatureIndex;
+    }
+
+
+    /**
+     * Lets the Clazz objects referenced in the signature string accept the
+     * given visitor.
+     */
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClasses != null)
+        {
+            for (int index = 0; index < referencedClasses.length; index++)
+            {
+                if (referencedClasses[index] != null)
+                {
+                    referencedClasses[index].accept(classVisitor);
+                }
+            }
+        }
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitSignatureAttribute(clazz, this);
+    }
+
+    public void accept(Clazz clazz, Field field, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitSignatureAttribute(clazz, field, this);
+    }
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitSignatureAttribute(clazz, method, this);
+    }
+ }
diff --git a/src/proguard/classfile/attribute/SourceDirAttribute.java b/src/proguard/classfile/attribute/SourceDirAttribute.java
new file mode 100644
index 0000000..a26e8b1
--- /dev/null
+++ b/src/proguard/classfile/attribute/SourceDirAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a source directory attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class SourceDirAttribute extends Attribute
+{
+    public int u2sourceDirIndex;
+
+
+    /**
+     * Creates an uninitialized SourceDirAttribute.
+     */
+    public SourceDirAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized SourceDirAttribute.
+     */
+    public SourceDirAttribute(int u2attributeNameIndex,
+                              int u2sourceDirIndex)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2sourceDirIndex = u2sourceDirIndex;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitSourceDirAttribute(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/SourceFileAttribute.java b/src/proguard/classfile/attribute/SourceFileAttribute.java
new file mode 100644
index 0000000..24269b7
--- /dev/null
+++ b/src/proguard/classfile/attribute/SourceFileAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a source file attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class SourceFileAttribute extends Attribute
+{
+    public int u2sourceFileIndex;
+
+
+    /**
+     * Creates an uninitialized SourceFileAttribute.
+     */
+    public SourceFileAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized SourceFileAttribute.
+     */
+    public SourceFileAttribute(int u2attributeNameIndex,
+                               int u2sourceFileIndex)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2sourceFileIndex = u2sourceFileIndex;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitSourceFileAttribute(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/SyntheticAttribute.java b/src/proguard/classfile/attribute/SyntheticAttribute.java
new file mode 100644
index 0000000..6ccb1b5
--- /dev/null
+++ b/src/proguard/classfile/attribute/SyntheticAttribute.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a synthetic attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class SyntheticAttribute extends Attribute
+{
+    /**
+     * Creates an uninitialized SyntheticAttribute.
+     */
+    public SyntheticAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized SyntheticAttribute.
+     */
+    public SyntheticAttribute(int u2attributeNameIndex)
+    {
+        super(u2attributeNameIndex);
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitSyntheticAttribute(clazz, this);
+    }
+
+    public void accept(Clazz clazz, Field field, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitSyntheticAttribute(clazz, field, this);
+    }
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitSyntheticAttribute(clazz, method, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/UnknownAttribute.java b/src/proguard/classfile/attribute/UnknownAttribute.java
new file mode 100644
index 0000000..2f138bd
--- /dev/null
+++ b/src/proguard/classfile/attribute/UnknownAttribute.java
@@ -0,0 +1,82 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute;
+
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents an unknown attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class UnknownAttribute extends Attribute
+{
+    public final int    u4attributeLength;
+    public byte[] info;
+
+
+    /**
+     * Creates an uninitialized UnknownAttribute with the given length.
+     */
+    public UnknownAttribute(int attributeLength)
+    {
+        u4attributeLength = attributeLength;
+    }
+
+
+    /**
+     * Creates an initialized UnknownAttribute.
+     */
+    public UnknownAttribute(int    u2attributeNameIndex,
+                            int    u4attributeLength,
+                            byte[] info)
+    {
+        super(u2attributeNameIndex);
+
+        this.u4attributeLength = u4attributeLength;
+        this.info              = info;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitUnknownAttribute(clazz, this);
+    }
+
+    public void accept(Clazz clazz, Field field, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitUnknownAttribute(clazz, this);
+    }
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitUnknownAttribute(clazz, this);
+    }
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitUnknownAttribute(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/Annotation.java b/src/proguard/classfile/attribute/annotation/Annotation.java
new file mode 100644
index 0000000..41bb8e3
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/Annotation.java
@@ -0,0 +1,143 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * Representation of an annotation.
+ *
+ * @author Eric Lafortune
+ */
+public class Annotation implements VisitorAccepter
+{
+    public int            u2typeIndex;
+    public int            u2elementValuesCount;
+    public ElementValue[] elementValues;
+
+    /**
+     * An extra field pointing to the Clazz objects referenced in the
+     * type string. This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz[] referencedClasses;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Creates an uninitialized Annotation.
+     */
+    public Annotation()
+    {
+    }
+
+
+    /**
+     * Creates an initialized Annotation.
+     */
+    public Annotation(int            u2typeIndex,
+                      int            u2elementValuesCount,
+                      ElementValue[] elementValues)
+    {
+        this.u2typeIndex          = u2typeIndex;
+        this.u2elementValuesCount = u2elementValuesCount;
+        this.elementValues        = elementValues;
+    }
+
+
+    /**
+     * Returns the type.
+     */
+    public String getType(Clazz clazz)
+    {
+        return clazz.getString(u2typeIndex);
+    }
+
+
+
+    /**
+     * Applies the given visitor to the first referenced class. This is the
+     * main annotation class.
+     */
+    public void referencedClassAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClasses != null)
+        {
+            Clazz referencedClass = referencedClasses[0];
+            if (referencedClass != null)
+            {
+                referencedClass.accept(classVisitor);
+            }
+        }
+    }
+
+
+    /**
+     * Applies the given visitor to all referenced classes.
+     */
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClasses != null)
+        {
+            for (int index = 0; index < referencedClasses.length; index++)
+            {
+                Clazz referencedClass = referencedClasses[index];
+                if (referencedClass != null)
+                {
+                    referencedClass.accept(classVisitor);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Applies the given visitor to all element value pairs.
+     */
+    public void elementValuesAccept(Clazz clazz, ElementValueVisitor elementValueVisitor)
+    {
+        for (int index = 0; index < u2elementValuesCount; index++)
+        {
+            elementValues[index].accept(clazz, this, elementValueVisitor);
+        }
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/AnnotationDefaultAttribute.java b/src/proguard/classfile/attribute/annotation/AnnotationDefaultAttribute.java
new file mode 100644
index 0000000..b378cd2
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/AnnotationDefaultAttribute.java
@@ -0,0 +1,73 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents an annotation default attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class AnnotationDefaultAttribute extends Attribute
+{
+    public ElementValue defaultValue;
+
+
+    /**
+     * Creates an uninitialized AnnotationDefaultAttribute.
+     */
+    public AnnotationDefaultAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized AnnotationDefaultAttribute.
+     */
+    public AnnotationDefaultAttribute(int          u2attributeNameIndex,
+                                      ElementValue defaultValue)
+    {
+        super(u2attributeNameIndex);
+
+        this.defaultValue = defaultValue;
+    }
+
+
+    /**
+     * Applies the given visitor to the default element value.
+     */
+    public void defaultValueAccept(Clazz clazz, ElementValueVisitor elementValueVisitor)
+    {
+        defaultValue.accept(clazz, null, elementValueVisitor);
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitAnnotationDefaultAttribute(clazz, method, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/AnnotationElementValue.java b/src/proguard/classfile/attribute/annotation/AnnotationElementValue.java
new file mode 100644
index 0000000..29129d0
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/AnnotationElementValue.java
@@ -0,0 +1,76 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+
+/**
+ * This ElementValue represents an annotation element value.
+ *
+ * @author Eric Lafortune
+ */
+public class AnnotationElementValue extends ElementValue
+{
+    public Annotation annotationValue;
+
+
+    /**
+     * Creates an uninitialized AnnotationElementValue.
+     */
+    public AnnotationElementValue()
+    {
+    }
+
+
+    /**
+     * Creates an initialized AnnotationElementValue.
+     */
+    public AnnotationElementValue(int        u2elementNameIndex,
+                                  Annotation annotationValue)
+    {
+        super(u2elementNameIndex);
+
+        this.annotationValue = annotationValue;
+    }
+
+
+    /**
+     * Applies the given visitor to the annotation.
+     */
+    public void annotationAccept(Clazz clazz, AnnotationVisitor annotationVisitor)
+    {
+        annotationVisitor.visitAnnotation(clazz, annotationValue);
+    }
+
+
+    // Implementations for ElementValue.
+
+    public int getTag()
+    {
+        return ClassConstants.ELEMENT_VALUE_ANNOTATION;
+    }
+
+    public void accept(Clazz clazz, Annotation annotation, ElementValueVisitor elementValueVisitor)
+    {
+        elementValueVisitor.visitAnnotationElementValue(clazz, annotation, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/AnnotationsAttribute.java b/src/proguard/classfile/attribute/annotation/AnnotationsAttribute.java
new file mode 100644
index 0000000..8117077
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/AnnotationsAttribute.java
@@ -0,0 +1,100 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.attribute.annotation.visitor.AnnotationVisitor;
+
+/**
+ * This Attribute represents an annotations attribute.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class AnnotationsAttribute extends Attribute
+{
+    public int          u2annotationsCount;
+    public Annotation[] annotations;
+
+
+    /**
+     * Creates an uninitialized AnnotationsAttribute.
+     */
+    protected AnnotationsAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized AnnotationsAttribute. 
+     */
+    protected AnnotationsAttribute(int          u2attributeNameIndex,
+                                   int          u2annotationsCount,
+                                   Annotation[] annotations)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2annotationsCount = u2annotationsCount;
+        this.annotations        = annotations;
+    }
+
+
+    /**
+     * Applies the given visitor to all class annotations.
+     */
+    public void annotationsAccept(Clazz clazz, AnnotationVisitor annotationVisitor)
+    {
+        for (int index = 0; index < u2annotationsCount; index++)
+        {
+            // We don't need double dispatching here, since there is only one
+            // type of Annotation.
+            annotationVisitor.visitAnnotation(clazz, annotations[index]);
+        }
+    }
+
+
+    /**
+     * Applies the given visitor to all field annotations.
+     */
+    public void annotationsAccept(Clazz clazz, Field field, AnnotationVisitor annotationVisitor)
+    {
+        for (int index = 0; index < u2annotationsCount; index++)
+        {
+            // We don't need double dispatching here, since there is only one
+            // type of Annotation.
+            annotationVisitor.visitAnnotation(clazz, field, annotations[index]);
+        }
+    }
+
+
+    /**
+     * Applies the given visitor to all method annotations.
+     */
+    public void annotationsAccept(Clazz clazz, Method method, AnnotationVisitor annotationVisitor)
+    {
+        for (int index = 0; index < u2annotationsCount; index++)
+        {
+            // We don't need double dispatching here, since there is only one
+            // type of Annotation.
+            annotationVisitor.visitAnnotation(clazz, method, annotations[index]);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/ArrayElementValue.java b/src/proguard/classfile/attribute/annotation/ArrayElementValue.java
new file mode 100644
index 0000000..25b8b9f
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/ArrayElementValue.java
@@ -0,0 +1,82 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+
+/**
+ * This ElementValue represents an array element value.
+ *
+ * @author Eric Lafortune
+ */
+public class ArrayElementValue extends ElementValue
+{
+    public int            u2elementValuesCount;
+    public ElementValue[] elementValues;
+
+
+    /**
+     * Creates an uninitialized ArrayElementValue.
+     */
+    public ArrayElementValue()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ArrayElementValue.
+     */
+    public ArrayElementValue(int            u2elementNameIndex,
+                             int            u2elementValuesCount,
+                             ElementValue[] elementValues)
+    {
+        super(u2elementNameIndex);
+
+        this.u2elementValuesCount = u2elementValuesCount;
+        this.elementValues        = elementValues;
+    }
+
+
+    // Implementations for ElementValue.
+
+    public int getTag()
+    {
+        return ClassConstants.ELEMENT_VALUE_ARRAY;
+    }
+
+    public void accept(Clazz clazz, Annotation annotation, ElementValueVisitor elementValueVisitor)
+    {
+        elementValueVisitor.visitArrayElementValue(clazz, annotation, this);
+    }
+
+
+    /**
+     * Applies the given visitor to all nested element values.
+     */
+    public void elementValuesAccept(Clazz clazz, Annotation annotation, ElementValueVisitor elementValueVisitor)
+    {
+        for (int index = 0; index < u2elementValuesCount; index++)
+        {
+            elementValues[index].accept(clazz, annotation, elementValueVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/ClassElementValue.java b/src/proguard/classfile/attribute/annotation/ClassElementValue.java
new file mode 100644
index 0000000..ba51641
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/ClassElementValue.java
@@ -0,0 +1,95 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ElementValue represents a class element value.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassElementValue extends ElementValue
+{
+    public int u2classInfoIndex;
+
+    /**
+     * An extra field pointing to the Clazz objects referenced in the
+     * type name string. This field is filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz[] referencedClasses;
+
+
+    /**
+     * Creates an uninitialized ClassElementValue.
+     */
+    public ClassElementValue()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ClassElementValue.
+     */
+    public ClassElementValue(int u2elementNameIndex,
+                             int u2classInfoIndex)
+    {
+        super(u2elementNameIndex);
+
+        this.u2classInfoIndex = u2classInfoIndex;
+    }
+
+
+    /**
+     * Applies the given visitor to all referenced classes.
+     */
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClasses != null)
+        {
+            for (int index = 0; index < referencedClasses.length; index++)
+            {
+                Clazz referencedClass = referencedClasses[index];
+                if (referencedClass != null)
+                {
+                    referencedClass.accept(classVisitor);
+                }
+            }
+        }
+    }
+
+
+    // Implementations for ElementValue.
+
+    public int getTag()
+    {
+        return ClassConstants.ELEMENT_VALUE_CLASS;
+    }
+
+    public void accept(Clazz clazz, Annotation annotation, ElementValueVisitor elementValueVisitor)
+    {
+        elementValueVisitor.visitClassElementValue(clazz, annotation, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/ConstantElementValue.java b/src/proguard/classfile/attribute/annotation/ConstantElementValue.java
new file mode 100644
index 0000000..3ebe5e5
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/ConstantElementValue.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+
+/**
+ * This ElementValue represents a constant element value.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantElementValue extends ElementValue
+{
+    public final int u1tag;
+    public       int u2constantValueIndex;
+
+
+    /**
+     * Creates an uninitialized ConstantElementValue.
+     */
+    public ConstantElementValue(int u1tag)
+    {
+        this.u1tag = u1tag;
+    }
+
+
+    /**
+     * Creates an initialized ConstantElementValue.
+     */
+    public ConstantElementValue(int u1tag,
+                                int u2elementNameIndex,
+                                int u2constantValueIndex)
+    {
+        super(u2elementNameIndex);
+
+        this.u1tag                = u1tag;
+        this.u2constantValueIndex = u2constantValueIndex;
+    }
+
+
+    // Implementations for ElementValue.
+
+    public int getTag()
+    {
+        return u1tag;
+    }
+
+    public void accept(Clazz clazz, Annotation annotation, ElementValueVisitor elementValueVisitor)
+    {
+        elementValueVisitor.visitConstantElementValue(clazz, annotation, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/ElementValue.java b/src/proguard/classfile/attribute/annotation/ElementValue.java
new file mode 100644
index 0000000..39f8953
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/ElementValue.java
@@ -0,0 +1,126 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This abstract class represents an element value that is attached to an
+ * annotation or an annotation default. Specific types of element values are
+ * subclassed from it.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class ElementValue implements VisitorAccepter
+{
+    /**
+     * An extra field for the optional element name. It is used in element value
+     * pairs of annotations. Otherwise, it is 0.
+     */
+    public int u2elementNameIndex;
+
+    /**
+     * An extra field pointing to the referenced <code>Clazz</code>
+     * object, if applicable. This field is typically filled out by the
+     * <code>{@link proguard.classfile.util.ClassReferenceInitializer}</code>.
+     */
+    public Clazz referencedClass;
+
+    /**
+     * An extra field pointing to the referenced <code>Method</code>
+     * object, if applicable. This field is typically filled out by the
+     * <code>{@link proguard.classfile.util.ClassReferenceInitializer}</code>.
+     */
+    public Method referencedMethod;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Creates an uninitialized ElementValue.
+     */
+    protected ElementValue()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ElementValue.
+     */
+    protected ElementValue(int u2elementNameIndex)
+    {
+        this.u2elementNameIndex = u2elementNameIndex;
+    }
+
+
+    /**
+     * Returns the element name.
+     */
+    public String getMethodName(Clazz clazz)
+    {
+        return clazz.getString(u2elementNameIndex);
+    }
+
+
+    // Abstract methods to be implemented by extensions.
+
+    /**
+     * Returns the tag of this element value.
+     */
+    public abstract int getTag();
+
+
+    /**
+     * Accepts the given visitor.
+     */
+    public abstract void accept(Clazz clazz, Annotation annotation, ElementValueVisitor elementValueVisitor);
+
+
+
+    /**
+     * Applies the given visitor to the referenced method.
+     */
+    public void referencedMethodAccept(MemberVisitor memberVisitor)
+    {
+        if (referencedMethod != null)
+        {
+            referencedMethod.accept(referencedClass, memberVisitor);
+        }
+    }
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/EnumConstantElementValue.java b/src/proguard/classfile/attribute/annotation/EnumConstantElementValue.java
new file mode 100644
index 0000000..d46bb7f
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/EnumConstantElementValue.java
@@ -0,0 +1,99 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ElementValue represents an enumeration constant element value.
+ *
+ * @author Eric Lafortune
+ */
+public class EnumConstantElementValue extends ElementValue
+{
+    public int u2typeNameIndex;
+    public int u2constantNameIndex;
+
+    /**
+     * An extra field pointing to the Clazz objects referenced in the
+     * type name string. This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>.
+     * References to primitive types are ignored.
+     */
+    public Clazz[] referencedClasses;
+
+
+    /**
+     * Creates an uninitialized EnumConstantElementValue.
+     */
+    public EnumConstantElementValue()
+    {
+    }
+
+
+    /**
+     * Creates an initialized EnumConstantElementValue.
+     */
+    public EnumConstantElementValue(int u2elementNameIndex,
+                                    int u2typeNameIndex,
+                                    int u2constantNameIndex)
+    {
+        super(u2elementNameIndex);
+
+        this.u2typeNameIndex     = u2typeNameIndex;
+        this.u2constantNameIndex = u2constantNameIndex;
+    }
+
+
+    /**
+     * Applies the given visitor to all referenced classes.
+     */
+    public void referencedClassesAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClasses != null)
+        {
+            for (int index = 0; index < referencedClasses.length; index++)
+            {
+                Clazz referencedClass = referencedClasses[index];
+                if (referencedClass != null)
+                {
+                    referencedClass.accept(classVisitor);
+                }
+            }
+        }
+    }
+
+
+    // Implementations for ElementValue.
+
+    public int getTag()
+    {
+        return ClassConstants.ELEMENT_VALUE_ENUM_CONSTANT;
+    }
+
+    public void accept(Clazz clazz, Annotation annotation, ElementValueVisitor elementValueVisitor)
+    {
+        elementValueVisitor.visitEnumConstantElementValue(clazz, annotation, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/ParameterAnnotationsAttribute.java b/src/proguard/classfile/attribute/annotation/ParameterAnnotationsAttribute.java
new file mode 100644
index 0000000..3c700c8
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/ParameterAnnotationsAttribute.java
@@ -0,0 +1,83 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.attribute.annotation.visitor.AnnotationVisitor;
+
+/**
+ * This Attribute represents a runtime parameter annotations attribute.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class ParameterAnnotationsAttribute extends Attribute
+{
+    public int            u2parametersCount;
+    public int[]          u2parameterAnnotationsCount;
+    public Annotation[][] parameterAnnotations;
+
+
+    /**
+     * Creates an uninitialized ParameterAnnotationsAttribute.
+     */
+    protected ParameterAnnotationsAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized ParameterAnnotationsAttribute.
+     */
+    protected ParameterAnnotationsAttribute(int            u2attributeNameIndex,
+                                            int            u2parametersCount,
+                                            int[]          u2parameterAnnotationsCount,
+                                            Annotation[][] parameterAnnotations)
+    {
+        super(u2attributeNameIndex);
+
+        this.u2parametersCount           = u2parametersCount;
+        this.u2parameterAnnotationsCount = u2parameterAnnotationsCount;
+        this.parameterAnnotations        = parameterAnnotations;
+    }
+
+
+    /**
+     * Applies the given visitor to all annotations.
+     */
+    public void annotationsAccept(Clazz clazz, Method method, AnnotationVisitor annotationVisitor)
+    {
+        // Loop over all parameters.
+        for (int parameterIndex = 0; parameterIndex < u2parametersCount; parameterIndex++)
+        {
+            int          annotationsCount = u2parameterAnnotationsCount[parameterIndex];
+            Annotation[] annotations      = parameterAnnotations[parameterIndex];
+
+            // Loop over all parameter annotations.
+            for (int index = 0; index < annotationsCount; index++)
+            {
+                // We don't need double dispatching here, since there is only one
+                // type of Annotation.
+                annotationVisitor.visitAnnotation(clazz, method, parameterIndex, annotations[index]);
+            }
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/RuntimeInvisibleAnnotationsAttribute.java b/src/proguard/classfile/attribute/annotation/RuntimeInvisibleAnnotationsAttribute.java
new file mode 100644
index 0000000..9c8180c
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/RuntimeInvisibleAnnotationsAttribute.java
@@ -0,0 +1,70 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a runtime invisible annotations attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class RuntimeInvisibleAnnotationsAttribute extends AnnotationsAttribute
+{
+    /**
+     * Creates an uninitialized RuntimeInvisibleAnnotationsAttribute.
+     */
+    public RuntimeInvisibleAnnotationsAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized RuntimeInvisibleAnnotationsAttribute.
+     */
+    public RuntimeInvisibleAnnotationsAttribute(int          u2attributeNameIndex,
+                                                int          u2annotationsCount,
+                                                Annotation[] annotations)
+    {
+        super(u2attributeNameIndex, u2annotationsCount, annotations);
+    }
+
+
+// Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, this);
+    }
+
+
+    public void accept(Clazz clazz, Field field, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, field, this);
+    }
+
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, method, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/RuntimeInvisibleParameterAnnotationsAttribute.java b/src/proguard/classfile/attribute/annotation/RuntimeInvisibleParameterAnnotationsAttribute.java
new file mode 100644
index 0000000..7e41656
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/RuntimeInvisibleParameterAnnotationsAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a runtime invisible parameter annotations attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class RuntimeInvisibleParameterAnnotationsAttribute extends ParameterAnnotationsAttribute
+{
+    /**
+     * Creates an uninitialized RuntimeInvisibleParameterAnnotationsAttribute.
+     */
+    public RuntimeInvisibleParameterAnnotationsAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized RuntimeInvisibleParameterAnnotationsAttribute.
+     */
+    public RuntimeInvisibleParameterAnnotationsAttribute(int            u2attributeNameIndex,
+                                                         int            u2parametersCount,
+                                                         int[]          u2parameterAnnotationsCount,
+                                                         Annotation[][] parameterAnnotations)
+    {
+        super(u2attributeNameIndex,
+              u2parametersCount,
+              u2parameterAnnotationsCount,
+              parameterAnnotations);
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitRuntimeInvisibleParameterAnnotationsAttribute(clazz, method, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/RuntimeVisibleAnnotationsAttribute.java b/src/proguard/classfile/attribute/annotation/RuntimeVisibleAnnotationsAttribute.java
new file mode 100644
index 0000000..380c52e
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/RuntimeVisibleAnnotationsAttribute.java
@@ -0,0 +1,70 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a runtime visible annotations attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class RuntimeVisibleAnnotationsAttribute extends AnnotationsAttribute
+{
+    /**
+     * Creates an uninitialized RuntimeVisibleAnnotationsAttribute.
+     */
+    public RuntimeVisibleAnnotationsAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized RuntimeVisibleAnnotationsAttribute.
+     */
+    public RuntimeVisibleAnnotationsAttribute(int          u2attributeNameIndex,
+                                              int          u2annotationsCount,
+                                              Annotation[] annotations)
+    {
+        super(u2attributeNameIndex, u2annotationsCount, annotations);
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, this);
+    }
+
+
+    public void accept(Clazz clazz, Field field, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, field, this);
+    }
+
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, method, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/RuntimeVisibleParameterAnnotationsAttribute.java b/src/proguard/classfile/attribute/annotation/RuntimeVisibleParameterAnnotationsAttribute.java
new file mode 100644
index 0000000..626fbda
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/RuntimeVisibleParameterAnnotationsAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a runtime visible parameter annotations attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class RuntimeVisibleParameterAnnotationsAttribute extends ParameterAnnotationsAttribute
+{
+    /**
+     * Creates an uninitialized RuntimeVisibleParameterAnnotationsAttribute.
+     */
+    public RuntimeVisibleParameterAnnotationsAttribute()
+    {
+    }
+
+
+    /**
+     * Creates an initialized RuntimeVisibleParameterAnnotationsAttribute.
+     */
+    public RuntimeVisibleParameterAnnotationsAttribute(int            u2attributeNameIndex,
+                                                       int            u2parametersCount,
+                                                       int[]          u2parameterAnnotationsCount,
+                                                       Annotation[][] parameterAnnotations)
+    {
+        super(u2attributeNameIndex,
+              u2parametersCount,
+              u2parameterAnnotationsCount,
+              parameterAnnotations);
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitRuntimeVisibleParameterAnnotationsAttribute(clazz, method, this);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/package.html b/src/proguard/classfile/attribute/annotation/package.html
new file mode 100644
index 0000000..6aacff3
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/package.html
@@ -0,0 +1,4 @@
+<body>
+This package contains classes to represent the annotation attributes inside
+class files.
+</body>
diff --git a/src/proguard/classfile/attribute/annotation/visitor/AllAnnotationVisitor.java b/src/proguard/classfile/attribute/annotation/visitor/AllAnnotationVisitor.java
new file mode 100644
index 0000000..bce7170
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/visitor/AllAnnotationVisitor.java
@@ -0,0 +1,100 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor lets a given AnnotationVisitor visit all Annotation
+ * objects of the attributes it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllAnnotationVisitor
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private final AnnotationVisitor annotationVisitor;
+
+
+    public AllAnnotationVisitor(AnnotationVisitor annotationVisitor)
+    {
+        this.annotationVisitor = annotationVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        // Visit the annotations.
+        runtimeVisibleAnnotationsAttribute.annotationsAccept(clazz, annotationVisitor);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        // Visit the annotations.
+        runtimeVisibleAnnotationsAttribute.annotationsAccept(clazz, field, annotationVisitor);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        // Visit the annotations.
+        runtimeVisibleAnnotationsAttribute.annotationsAccept(clazz, method, annotationVisitor);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        // Visit the annotations.
+        runtimeInvisibleAnnotationsAttribute.annotationsAccept(clazz, annotationVisitor);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        // Visit the annotations.
+        runtimeInvisibleAnnotationsAttribute.annotationsAccept(clazz, field, annotationVisitor);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        // Visit the annotations.
+        runtimeInvisibleAnnotationsAttribute.annotationsAccept(clazz, method, annotationVisitor);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Visit the annotations.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, annotationVisitor);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/visitor/AnnotatedClassVisitor.java b/src/proguard/classfile/attribute/annotation/visitor/AnnotatedClassVisitor.java
new file mode 100644
index 0000000..7a1d7c6
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/visitor/AnnotatedClassVisitor.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation.visitor;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.annotation.Annotation;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+
+/**
+ * This AnnotationVisitor delegates all visits to a given ClassVisitor.
+ * The latter visits the class of each visited annotation, although
+ * never twice in a row.
+ *
+ * @author Eric Lafortune
+ */
+public class AnnotatedClassVisitor
+extends      SimplifiedVisitor
+implements   AnnotationVisitor
+{
+    private final ClassVisitor classVisitor;
+
+    private Clazz lastVisitedClass;
+
+
+    public AnnotatedClassVisitor(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        if (!clazz.equals(lastVisitedClass))
+        {
+            clazz.accept(classVisitor);
+
+            lastVisitedClass = clazz;
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/visitor/AnnotationToMemberVisitor.java b/src/proguard/classfile/attribute/annotation/visitor/AnnotationToMemberVisitor.java
new file mode 100644
index 0000000..c206c16
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/visitor/AnnotationToMemberVisitor.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.Annotation;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+
+/**
+ * This AnnotationVisitor delegates all visits to a given MemberVisitor.
+ * The latter visits the class member of each visited class member annotation
+ * or method parameter annotation, although never twice in a row.
+ *
+ * @author Eric Lafortune
+ */
+public class AnnotationToMemberVisitor
+extends      SimplifiedVisitor
+implements   AnnotationVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+    private Member lastVisitedMember;
+
+
+    public AnnotationToMemberVisitor(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Member member, Annotation annotation)
+    {
+        if (!member.equals(lastVisitedMember))
+        {
+            member.accept(clazz, memberVisitor);
+
+            lastVisitedMember = member;
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/visitor/AnnotationTypeFilter.java b/src/proguard/classfile/attribute/annotation/visitor/AnnotationTypeFilter.java
new file mode 100644
index 0000000..d869fd2
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/visitor/AnnotationTypeFilter.java
@@ -0,0 +1,102 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.Annotation;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.util.*;
+
+/**
+ * This <code>AnnotationVisitor</code> delegates its visits to another given
+ * <code>AnnotationVisitor</code>, but only when the visited annotation has
+ * a type that matches a given regular expression.
+ *
+ * @author Eric Lafortune
+ */
+public class AnnotationTypeFilter
+extends      SimplifiedVisitor
+implements   AnnotationVisitor
+{
+    private final StringMatcher     regularExpressionMatcher;
+    private final AnnotationVisitor annotationVisitor;
+
+
+    /**
+     * Creates a new ClassNameFilter.
+     * @param regularExpression the regular expression against which annotation
+     *                          type names will be matched.
+     * @param annotationVisitor the <code>annotationVisitor</code> to which
+     *                          visits will be delegated.
+     */
+    public AnnotationTypeFilter(String            regularExpression,
+                                AnnotationVisitor annotationVisitor)
+    {
+        this.regularExpressionMatcher = new ListParser(new ClassNameParser()).parse(regularExpression);
+        this.annotationVisitor        = annotationVisitor;
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        if (accepted(annotation.getType(clazz)))
+        {
+            annotationVisitor.visitAnnotation(clazz, annotation);
+        }
+    }
+
+
+    public void visitAnnotation(Clazz clazz, Field field, Annotation annotation)
+    {
+        if (accepted(annotation.getType(clazz)))
+        {
+            annotationVisitor.visitAnnotation(clazz, field, annotation);
+        }
+    }
+
+
+    public void visitAnnotation(Clazz clazz, Method method, Annotation annotation)
+    {
+        if (accepted(annotation.getType(clazz)))
+        {
+            annotationVisitor.visitAnnotation(clazz, method, annotation);
+        }
+    }
+
+
+    public void visitAnnotation(Clazz clazz, Method method, int parameterIndex, Annotation annotation)
+    {
+        if (accepted(annotation.getType(clazz)))
+        {
+            annotationVisitor.visitAnnotation(clazz, method, parameterIndex, annotation);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean accepted(String name)
+    {
+        return regularExpressionMatcher.matches(name);
+    }
+}
diff --git a/src/proguard/classfile/attribute/annotation/visitor/AnnotationVisitor.java b/src/proguard/classfile/attribute/annotation/visitor/AnnotationVisitor.java
new file mode 100644
index 0000000..16b2a56
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/visitor/AnnotationVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.Annotation;
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>Annotation</code> objects. Note that there is only a single
+ * implementation of <code>Annotation</code>, such that this interface
+ * is not strictly necessary as a visitor.
+ *
+ * @author Eric Lafortune
+ */
+public interface AnnotationVisitor
+{
+    public void visitAnnotation(Clazz clazz,                                    Annotation annotation);
+    public void visitAnnotation(Clazz clazz, Field  field,                      Annotation annotation);
+    public void visitAnnotation(Clazz clazz, Method method,                     Annotation annotation);
+    public void visitAnnotation(Clazz clazz, Method method, int parameterIndex, Annotation annotation);
+}
diff --git a/src/proguard/classfile/attribute/annotation/visitor/ElementValueVisitor.java b/src/proguard/classfile/attribute/annotation/visitor/ElementValueVisitor.java
new file mode 100644
index 0000000..112084a
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/visitor/ElementValueVisitor.java
@@ -0,0 +1,51 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.annotation.visitor;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.annotation.*;
+
+/**
+ * This interface specifies the methods for a visitor of <code>ElementValue</code>
+ * objects.
+ *
+ * @author Eric Lafortune
+ */
+public interface ElementValueVisitor
+{
+    public void visitConstantElementValue(    Clazz clazz,                Annotation annotation, ConstantElementValue     constantElementValue);
+    public void visitEnumConstantElementValue(Clazz clazz,                Annotation annotation, EnumConstantElementValue enumConstantElementValue);
+    public void visitClassElementValue(       Clazz clazz,                Annotation annotation, ClassElementValue        classElementValue);
+    public void visitAnnotationElementValue(  Clazz clazz,                Annotation annotation, AnnotationElementValue   annotationElementValue);
+    public void visitArrayElementValue(       Clazz clazz,                Annotation annotation, ArrayElementValue        arrayElementValue);
+
+//    public void visitConstantElementValue(    Clazz clazz, Field  field,  Annotation annotation, ConstantElementValue     constantElementValue);
+//    public void visitEnumConstantElementValue(Clazz clazz, Field  field,  Annotation annotation, EnumConstantElementValue enumConstantElementValue);
+//    public void visitClassElementValue(       Clazz clazz, Field  field,  Annotation annotation, ClassElementValue        classElementValue);
+//    public void visitAnnotationElementValue(  Clazz clazz, Field  field,  Annotation annotation, AnnotationElementValue   annotationElementValue);
+//    public void visitArrayElementValue(       Clazz clazz, Field  field,  Annotation annotation, ArrayElementValue        arrayElementValue);
+//
+//    public void visitConstantElementValue(    Clazz clazz, Method method, Annotation annotation, ConstantElementValue     constantElementValue);
+//    public void visitEnumConstantElementValue(Clazz clazz, Method method, Annotation annotation, EnumConstantElementValue enumConstantElementValue);
+//    public void visitClassElementValue(       Clazz clazz, Method method, Annotation annotation, ClassElementValue        classElementValue);
+//    public void visitAnnotationElementValue(  Clazz clazz, Method method, Annotation annotation, AnnotationElementValue   annotationElementValue);
+//    public void visitArrayElementValue(       Clazz clazz, Method method, Annotation annotation, ArrayElementValue        arrayElementValue);
+}
diff --git a/src/proguard/classfile/attribute/annotation/visitor/package.html b/src/proguard/classfile/attribute/annotation/visitor/package.html
new file mode 100644
index 0000000..10d0648
--- /dev/null
+++ b/src/proguard/classfile/attribute/annotation/visitor/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains visitors for annotation attributes and their components.
+</body>
diff --git a/src/proguard/classfile/attribute/package.html b/src/proguard/classfile/attribute/package.html
new file mode 100644
index 0000000..d17caaa
--- /dev/null
+++ b/src/proguard/classfile/attribute/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains classes to represent the attributes inside class files.
+</body>
diff --git a/src/proguard/classfile/attribute/preverification/DoubleType.java b/src/proguard/classfile/attribute/preverification/DoubleType.java
new file mode 100644
index 0000000..d574dcb
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/DoubleType.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents a Double type.
+ *
+ * @author Eric Lafortune
+ */
+public class DoubleType extends VerificationType
+{
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return DOUBLE_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitDoubleType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackDoubleType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesDoubleType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "d";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/FloatType.java b/src/proguard/classfile/attribute/preverification/FloatType.java
new file mode 100644
index 0000000..2f24720
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/FloatType.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents a Float type.
+ *
+ * @author Eric Lafortune
+ */
+public class FloatType extends VerificationType
+{
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return FLOAT_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitFloatType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackFloatType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesFloatType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "f";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/FullFrame.java b/src/proguard/classfile/attribute/preverification/FullFrame.java
new file mode 100644
index 0000000..adf5684
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/FullFrame.java
@@ -0,0 +1,202 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.*;
+
+/**
+ * This StackMapFrame represents a "full frame".
+ *
+ * @author Eric Lafortune
+ */
+public class FullFrame extends StackMapFrame
+{
+    public int                variablesCount;
+    public VerificationType[] variables;
+    public int                stackCount;
+    public VerificationType[] stack;
+
+
+    /**
+     * Creates an uninitialized FullFrame.
+     */
+    public FullFrame()
+    {
+    }
+
+
+    /**
+     * Creates a FullFrame with the given variables and stack.
+     */
+    public FullFrame(int                offsetDelta,
+                     VerificationType[] variables,
+                     VerificationType[] stack)
+    {
+        this(offsetDelta,
+             variables.length,
+             variables,
+             stack.length,
+             stack);
+    }
+
+
+    /**
+     * Creates a FullFrame with the given variables and stack.
+     */
+    public FullFrame(int                offsetDelta,
+                     int                variablesCount,
+                     VerificationType[] variables,
+                     int                stackCount,
+                     VerificationType[] stack)
+    {
+        this.u2offsetDelta  = offsetDelta;
+        this.variablesCount = variablesCount;
+        this.variables      = variables;
+        this.stackCount     = stackCount;
+        this.stack          = stack;
+    }
+
+
+    /**
+     * Applies the given verification type visitor to all variables.
+     */
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        for (int index = 0; index < variablesCount; index++)
+        {
+            variables[index].variablesAccept(clazz, method, codeAttribute, offset, index, verificationTypeVisitor);
+        }
+    }
+
+
+    /**
+     * Applies the given verification type visitor to all stack.
+     */
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        for (int index = 0; index < stackCount; index++)
+        {
+            stack[index].stackAccept(clazz, method, codeAttribute, offset, index, verificationTypeVisitor);
+        }
+    }
+
+
+    // Implementations for StackMapFrame.
+
+    public int getTag()
+    {
+        return FULL_FRAME;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrameVisitor stackMapFrameVisitor)
+    {
+        stackMapFrameVisitor.visitFullFrame(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (!super.equals(object))
+        {
+            return false;
+        }
+
+        FullFrame other = (FullFrame)object;
+
+        if (this.u2offsetDelta  != other.u2offsetDelta  ||
+            this.variablesCount != other.variablesCount ||
+            this.stackCount     != other.stackCount)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < variablesCount; index++)
+        {
+            VerificationType thisType  = this.variables[index];
+            VerificationType otherType = other.variables[index];
+
+            if (!thisType.equals(otherType))
+            {
+                return false;
+            }
+        }
+
+        for (int index = 0; index < stackCount; index++)
+        {
+            VerificationType thisType  = this.stack[index];
+            VerificationType otherType = other.stack[index];
+
+            if (!thisType.equals(otherType))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    public int hashCode()
+    {
+        int hashCode = super.hashCode();
+
+        for (int index = 0; index < variablesCount; index++)
+        {
+            hashCode ^= variables[index].hashCode();
+        }
+
+        for (int index = 0; index < stackCount; index++)
+        {
+            hashCode ^= stack[index].hashCode();
+        }
+
+        return hashCode;
+    }
+
+
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer(super.toString()).append("Var: ");
+
+        for (int index = 0; index < variablesCount; index++)
+        {
+            buffer = buffer.append('[')
+                           .append(variables[index].toString())
+                           .append(']');
+        }
+
+        buffer.append(", Stack: ");
+
+        for (int index = 0; index < stackCount; index++)
+        {
+            buffer = buffer.append('[')
+                           .append(stack[index].toString())
+                           .append(']');
+        }
+
+        return buffer.toString();
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/IntegerType.java b/src/proguard/classfile/attribute/preverification/IntegerType.java
new file mode 100644
index 0000000..55e3abe
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/IntegerType.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents a Integer type.
+ *
+ * @author Eric Lafortune
+ */
+public class IntegerType extends VerificationType
+{
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return INTEGER_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitIntegerType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackIntegerType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesIntegerType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "i";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/LessZeroFrame.java b/src/proguard/classfile/attribute/preverification/LessZeroFrame.java
new file mode 100644
index 0000000..fcc8e0a
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/LessZeroFrame.java
@@ -0,0 +1,103 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.StackMapFrameVisitor;
+
+/**
+ * This StackMapFrame represents an "chop frame".
+ *
+ * @author Eric Lafortune
+ */
+public class LessZeroFrame extends StackMapFrame
+{
+    public int choppedVariablesCount;
+
+
+    /**
+     * Creates an uninitialized LessZeroFrame.
+     */
+    public LessZeroFrame()
+    {
+    }
+
+
+    /**
+     * Creates a LessZeroFrame with the given tag.
+     */
+    public LessZeroFrame(int tag)
+    {
+        choppedVariablesCount = LESS_ZERO_FRAME + 3 - tag;
+    }
+
+
+    /**
+     * Creates a LessZeroFrame with the given number of chopped variables.
+     */
+    public LessZeroFrame(byte choppedVariablesCount)
+    {
+        this.choppedVariablesCount = (int)choppedVariablesCount;
+    }
+
+
+    // Implementations for StackMapFrame.
+
+    public int getTag()
+    {
+        return LESS_ZERO_FRAME + 3 - choppedVariablesCount;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrameVisitor stackMapFrameVisitor)
+    {
+        stackMapFrameVisitor.visitLessZeroFrame(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (!super.equals(object))
+        {
+            return false;
+        }
+
+        LessZeroFrame other = (LessZeroFrame)object;
+
+        return this.u2offsetDelta == other.u2offsetDelta &&
+               this.choppedVariablesCount != other.choppedVariablesCount;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^ choppedVariablesCount;
+    }
+
+
+    public String toString()
+    {
+        return super.toString()+"Var: (chopped "+choppedVariablesCount+"), Stack: (empty)";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/LongType.java b/src/proguard/classfile/attribute/preverification/LongType.java
new file mode 100644
index 0000000..9b14dd6
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/LongType.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents a Long type.
+ *
+ * @author Eric Lafortune
+ */
+public class LongType extends VerificationType
+{
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return LONG_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitLongType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackLongType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesLongType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "l";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/MoreZeroFrame.java b/src/proguard/classfile/attribute/preverification/MoreZeroFrame.java
new file mode 100644
index 0000000..881f188
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/MoreZeroFrame.java
@@ -0,0 +1,161 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.*;
+
+/**
+ * This StackMapFrame represents an "append frame".
+ *
+ * @author Eric Lafortune
+ */
+public class MoreZeroFrame extends StackMapFrame
+{
+    public int                additionalVariablesCount;
+    public VerificationType[] additionalVariables;
+
+
+    /**
+     * Creates an uninitialized MoreZeroFrame.
+     */
+    public MoreZeroFrame()
+    {
+    }
+
+
+    /**
+     * Creates a MoreZeroFrame with the given tag.
+     */
+    public MoreZeroFrame(int tag)
+    {
+        additionalVariablesCount = tag + 1 - MORE_ZERO_FRAME;
+    }
+
+
+    /**
+     * Creates a MoreZeroFrame with the given additional variables.
+     */
+    public MoreZeroFrame(VerificationType[] additionalVariables)
+    {
+        this(additionalVariables.length, additionalVariables);
+    }
+
+
+    /**
+     * Creates a MoreZeroFrame with the given additional variables.
+     */
+    public MoreZeroFrame(int                additionalVariablesCount,
+                         VerificationType[] additionalVariables)
+    {
+        this.additionalVariablesCount = additionalVariablesCount;
+        this.additionalVariables      = additionalVariables;
+    }
+
+
+    /**
+     * Applies the given verification type visitor to all variables.
+     */
+    public void additionalVariablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        for (int index = 0; index < additionalVariablesCount; index++)
+        {
+            additionalVariables[index].accept(clazz, method, codeAttribute, offset, verificationTypeVisitor);
+        }
+    }
+
+
+    // Implementations for StackMapFrame.
+
+    public int getTag()
+    {
+        return MORE_ZERO_FRAME + additionalVariablesCount - 1;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrameVisitor stackMapFrameVisitor)
+    {
+        stackMapFrameVisitor.visitMoreZeroFrame(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (!super.equals(object))
+        {
+            return false;
+        }
+
+        MoreZeroFrame other = (MoreZeroFrame)object;
+
+        if (this.u2offsetDelta            != other.u2offsetDelta ||
+            this.additionalVariablesCount != other.additionalVariablesCount)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < additionalVariablesCount; index++)
+        {
+            VerificationType thisType  = this.additionalVariables[index];
+            VerificationType otherType = other.additionalVariables[index];
+
+            if (!thisType.equals(otherType))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    public int hashCode()
+    {
+        int hashCode = super.hashCode();
+
+        for (int index = 0; index < additionalVariablesCount; index++)
+        {
+            hashCode ^= additionalVariables[index].hashCode();
+        }
+
+        return hashCode;
+    }
+
+
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer(super.toString()).append("Var: ...");
+
+        for (int index = 0; index < additionalVariablesCount; index++)
+        {
+            buffer = buffer.append('[')
+                           .append(additionalVariables[index].toString())
+                           .append(']');
+        }
+
+        buffer.append(", Stack: (empty)");
+
+        return buffer.toString();
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/NullType.java b/src/proguard/classfile/attribute/preverification/NullType.java
new file mode 100644
index 0000000..f35cefd
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/NullType.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents a Null type.
+ *
+ * @author Eric Lafortune
+ */
+public class NullType extends VerificationType
+{
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return NULL_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitNullType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackNullType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesNullType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "n";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/ObjectType.java b/src/proguard/classfile/attribute/preverification/ObjectType.java
new file mode 100644
index 0000000..fbdeec7
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/ObjectType.java
@@ -0,0 +1,107 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents an Object type.
+ *
+ * @author Eric Lafortune
+ */
+public class ObjectType extends VerificationType
+{
+    public int u2classIndex;
+
+
+
+    /**
+     * Creates an uninitialized ObjectType.
+     */
+    public ObjectType()
+    {
+    }
+
+
+    /**
+     * Creates an ObjectType that points to the given class constant.
+     */
+    public ObjectType(int u2classIndex)
+    {
+        this.u2classIndex = u2classIndex;
+    }
+
+
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return OBJECT_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitObjectType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackObjectType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesObjectType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (!super.equals(object))
+        {
+            return false;
+        }
+
+        ObjectType other = (ObjectType)object;
+
+        return this.u2classIndex == other.u2classIndex;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               u2classIndex;
+    }
+
+
+    public String toString()
+    {
+        return "a:" + u2classIndex;
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/SameOneFrame.java b/src/proguard/classfile/attribute/preverification/SameOneFrame.java
new file mode 100644
index 0000000..db6747b
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/SameOneFrame.java
@@ -0,0 +1,115 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.*;
+
+/**
+ * This StackMapFrame represents a "same locals 1 stack item frame" or a
+ * "same locals 1 stack item frame extended".
+ *
+ * @author Eric Lafortune
+ */
+public class SameOneFrame extends StackMapFrame
+{
+    public VerificationType stackItem;
+
+
+    /**
+     * Creates an uninitialized SameOneFrame.
+     */
+    public SameOneFrame()
+    {
+    }
+
+
+    /**
+     * Creates a SameOneFrame with the given tag.
+     */
+    public SameOneFrame(int tag)
+    {
+        u2offsetDelta = tag - SAME_ONE_FRAME;
+    }
+
+
+    /**
+     * Creates a SameOneFrame with the given stack verification type.
+     */
+    public SameOneFrame(VerificationType stackItem)
+    {
+        this.stackItem = stackItem;
+    }
+
+
+    /**
+     * Applies the given verification type visitor to the stack item.
+     */
+    public void stackItemAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        stackItem.accept(clazz, method, codeAttribute, offset, verificationTypeVisitor);
+    }
+
+
+    // Implementations for StackMapFrame.
+
+    public int getTag()
+    {
+        return u2offsetDelta < 64 ?
+            SAME_ONE_FRAME + u2offsetDelta :
+            SAME_ONE_FRAME_EXTENDED;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrameVisitor stackMapFrameVisitor)
+    {
+        stackMapFrameVisitor.visitSameOneFrame(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (!super.equals(object))
+        {
+            return false;
+        }
+
+        SameOneFrame other = (SameOneFrame)object;
+
+        return this.u2offsetDelta == other.u2offsetDelta &&
+               this.stackItem.equals(other.stackItem);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^ stackItem.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return super.toString()+"Var: ..., Stack: ["+stackItem.toString()+"]";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/SameZeroFrame.java b/src/proguard/classfile/attribute/preverification/SameZeroFrame.java
new file mode 100644
index 0000000..64b17f5
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/SameZeroFrame.java
@@ -0,0 +1,74 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.StackMapFrameVisitor;
+
+/**
+ * This StackMapFrame represents a "same frame" or a "same frame extended".
+ *
+ * @author Eric Lafortune
+ * @noinspection PointlessArithmeticExpression
+ */
+public class SameZeroFrame extends StackMapFrame
+{
+    /**
+     * Creates an uninitialized SameZeroFrame.
+     */
+    public SameZeroFrame()
+    {
+    }
+
+
+    /**
+     * Creates a SameZeroFrame with the given tag.
+     */
+    public SameZeroFrame(int tag)
+    {
+        u2offsetDelta = tag - SAME_ZERO_FRAME;
+    }
+
+
+    // Implementations for StackMapFrame.
+
+    public int getTag()
+    {
+        return u2offsetDelta < 64 ?
+            SAME_ZERO_FRAME + u2offsetDelta :
+            SAME_ZERO_FRAME_EXTENDED;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrameVisitor stackMapFrameVisitor)
+    {
+        stackMapFrameVisitor.visitSameZeroFrame(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return super.toString()+"Var: ..., Stack: (empty)";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/StackMapAttribute.java b/src/proguard/classfile/attribute/preverification/StackMapAttribute.java
new file mode 100644
index 0000000..db53ff1
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/StackMapAttribute.java
@@ -0,0 +1,91 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.preverification.visitor.StackMapFrameVisitor;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents an exceptions attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class StackMapAttribute extends Attribute
+{
+    public int         u2stackMapFramesCount;
+    public FullFrame[] stackMapFrames;
+
+
+    /**
+     * Creates an uninitialized ExceptionsAttribute.
+     */
+    public StackMapAttribute()
+    {
+    }
+
+
+    /**
+     * Creates a StackMapTableAttribute with the given stack map frames.
+     */
+    public StackMapAttribute(FullFrame[] stackMapFrames)
+    {
+        this(stackMapFrames.length, stackMapFrames);
+    }
+
+
+    /**
+     * Creates a StackMapTableAttribute with the given stack map frames.
+     */
+    public StackMapAttribute(int         stackMapFramesCount,
+                             FullFrame[] stackMapFrames)
+    {
+        this.u2stackMapFramesCount = stackMapFramesCount;
+        this.stackMapFrames        = stackMapFrames;
+    }
+
+
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitStackMapAttribute(clazz, method, codeAttribute, this);
+    }
+
+
+    /**
+     * Applies the given stack map frame visitor to all stack map frames.
+     */
+    public void stackMapFramesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapFrameVisitor stackMapFrameVisitor)
+    {
+        for (int index = 0; index < u2stackMapFramesCount; index++)
+        {
+            FullFrame stackMapFrame = stackMapFrames[index];
+
+            // We don't need double dispatching here, since there is only one
+            // type of StackMapFrame.
+            stackMapFrameVisitor.visitFullFrame(clazz, method, codeAttribute, stackMapFrame.getOffsetDelta(), stackMapFrame);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/StackMapFrame.java b/src/proguard/classfile/attribute/preverification/StackMapFrame.java
new file mode 100644
index 0000000..aa3e1f2
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/StackMapFrame.java
@@ -0,0 +1,117 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.StackMapFrameVisitor;
+
+/**
+ * This abstract class represents a stack map frame. Specific types
+ * of entries are subclassed from it.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class StackMapFrame implements VisitorAccepter
+{
+    public static final int SAME_ZERO_FRAME          =   0;
+    public static final int SAME_ONE_FRAME           =  64;
+    public static final int SAME_ONE_FRAME_EXTENDED  = 247;
+    public static final int LESS_ZERO_FRAME          = 248;
+    public static final int SAME_ZERO_FRAME_EXTENDED = 251;
+    public static final int MORE_ZERO_FRAME          = 252;
+    public static final int FULL_FRAME               = 255;
+
+
+    public int u2offsetDelta;
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+
+    /**
+     * Returns the bytecode offset delta relative to the previous stack map
+     * frame.
+     */
+    public int getOffsetDelta()
+    {
+        return u2offsetDelta;
+    }
+
+
+    // Abstract methods to be implemented by extensions.
+
+    /**
+     * Returns the stack map frame tag that specifies the entry type.
+     */
+    public abstract int getTag();
+
+
+    /**
+     * Accepts the given visitor.
+     */
+    public abstract void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrameVisitor stackMapFrameVisitor);
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        StackMapFrame other = (StackMapFrame)object;
+
+        return this.u2offsetDelta == other.u2offsetDelta;
+    }
+
+
+    public int hashCode()
+    {
+        return getClass().hashCode() ^
+               u2offsetDelta;
+    }
+
+
+    public String toString()
+    {
+        return "[" + u2offsetDelta + "] ";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/StackMapTableAttribute.java b/src/proguard/classfile/attribute/preverification/StackMapTableAttribute.java
new file mode 100644
index 0000000..0cddf70
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/StackMapTableAttribute.java
@@ -0,0 +1,93 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.preverification.visitor.StackMapFrameVisitor;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+
+/**
+ * This Attribute represents a stack map table attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class StackMapTableAttribute extends Attribute
+{
+    public int             u2stackMapFramesCount;
+    public StackMapFrame[] stackMapFrames;
+
+
+    /**
+     * Creates an uninitialized StackMapTableAttribute.
+     */
+    public StackMapTableAttribute()
+    {
+    }
+
+
+    /**
+     * Creates a StackMapTableAttribute with the given stack map frames.
+     */
+    public StackMapTableAttribute(StackMapFrame[] stackMapFrames)
+    {
+        this(stackMapFrames.length, stackMapFrames);
+    }
+
+
+    /**
+     * Creates a StackMapTableAttribute with the given stack map frames.
+     */
+    public StackMapTableAttribute(int             stackMapFramesCount,
+                                  StackMapFrame[] stackMapFrames)
+    {
+        this.u2stackMapFramesCount = stackMapFramesCount;
+        this.stackMapFrames        = stackMapFrames;
+    }
+
+
+    // Implementations for Attribute.
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, AttributeVisitor attributeVisitor)
+    {
+        attributeVisitor.visitStackMapTableAttribute(clazz, method, codeAttribute, this);
+    }
+
+
+    /**
+     * Applies the given stack map frame visitor to all stack map frames.
+     */
+    public void stackMapFramesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapFrameVisitor stackMapFrameVisitor)
+    {
+        int offset = 0;
+
+        for (int index = 0; index < u2stackMapFramesCount; index++)
+        {
+            StackMapFrame stackMapFrame = stackMapFrames[index];
+
+            // Note that the byte code offset is computed differently for the
+            // first stack map frame.
+            offset += stackMapFrame.getOffsetDelta() + (index == 0 ? 0 : 1);
+
+            stackMapFrame.accept(clazz, method, codeAttribute, offset, stackMapFrameVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/TopType.java b/src/proguard/classfile/attribute/preverification/TopType.java
new file mode 100644
index 0000000..bde8dda
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/TopType.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents a Top type.
+ *
+ * @author Eric Lafortune
+ */
+public class TopType extends VerificationType
+{
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return TOP_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitTopType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackTopType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesTopType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "T";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/UninitializedThisType.java b/src/proguard/classfile/attribute/preverification/UninitializedThisType.java
new file mode 100644
index 0000000..dc4654f
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/UninitializedThisType.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents a UninitializedThis type.
+ *
+ * @author Eric Lafortune
+ */
+public class UninitializedThisType extends VerificationType
+{
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return UNINITIALIZED_THIS_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitUninitializedThisType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackUninitializedThisType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesUninitializedThisType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "u:this";
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/UninitializedType.java b/src/proguard/classfile/attribute/preverification/UninitializedType.java
new file mode 100644
index 0000000..a495f1f
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/UninitializedType.java
@@ -0,0 +1,106 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This VerificationType represents a Uninitialized type.
+ *
+ * @author Eric Lafortune
+ */
+public class UninitializedType extends VerificationType
+{
+    public int u2newInstructionOffset;
+
+
+    /**
+     * Creates an uninitialized UninitializedType.
+     */
+    public UninitializedType()
+    {
+    }
+
+
+    /**
+     * Creates an UninitializedType pointing to the given 'new' instruction.
+     */
+    public UninitializedType(int u2newInstructionOffset)
+    {
+        this.u2newInstructionOffset = u2newInstructionOffset;
+    }
+
+
+    // Implementations for VerificationType.
+
+    public int getTag()
+    {
+        return UNINITIALIZED_TYPE;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitUninitializedType(clazz, method, codeAttribute, instructionOffset, this);
+    }
+
+
+    public void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitStackUninitializedType(clazz, method, codeAttribute, instructionOffset, stackIndex, this);
+    }
+
+
+    public void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor)
+    {
+        verificationTypeVisitor.visitVariablesUninitializedType(clazz, method, codeAttribute, instructionOffset, variableIndex, this);
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (!super.equals(object))
+        {
+            return false;
+        }
+
+        UninitializedType other = (UninitializedType)object;
+
+        return this.u2newInstructionOffset == other.u2newInstructionOffset;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               u2newInstructionOffset;
+    }
+
+
+    public String toString()
+    {
+        return "u:" + u2newInstructionOffset;
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/VerificationType.java b/src/proguard/classfile/attribute/preverification/VerificationType.java
new file mode 100644
index 0000000..f33d511
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/VerificationType.java
@@ -0,0 +1,103 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.visitor.VerificationTypeVisitor;
+
+/**
+ * This abstract class represents a verification type of a local variable or
+ * a stack element. Specific verification types are subclassed from it.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class VerificationType implements VisitorAccepter
+{
+    public static final int TOP_TYPE                = 0;
+    public static final int INTEGER_TYPE            = 1;
+    public static final int FLOAT_TYPE              = 2;
+    public static final int DOUBLE_TYPE             = 3;
+    public static final int LONG_TYPE               = 4;
+    public static final int NULL_TYPE               = 5;
+    public static final int UNINITIALIZED_THIS_TYPE = 6;
+    public static final int OBJECT_TYPE             = 7;
+    public static final int UNINITIALIZED_TYPE      = 8;
+
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    /**
+     * Returns the tag of the verification type.
+     */
+    public abstract int getTag();
+
+
+    /**
+     * Accepts the given visitor in the context of a method's code, either on
+     * a stack or as a variable.
+     */
+    public abstract void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, VerificationTypeVisitor verificationTypeVisitor);
+
+
+    /**
+     * Accepts the given visitor in the context of a stack in a method's code .
+     */
+    public abstract void stackAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int stackIndex, VerificationTypeVisitor verificationTypeVisitor);
+
+
+    /**
+     * Accepts the given visitor in the context of a variable in a method's code.
+     */
+    public abstract void variablesAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset, int variableIndex, VerificationTypeVisitor verificationTypeVisitor);
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/VerificationTypeFactory.java b/src/proguard/classfile/attribute/preverification/VerificationTypeFactory.java
new file mode 100644
index 0000000..f8ef7e0
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/VerificationTypeFactory.java
@@ -0,0 +1,112 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification;
+
+/**
+ * This class provides methods to create and reuse IntegerType objects.
+ *
+ * @author Eric Lafortune
+ */
+public class VerificationTypeFactory
+{
+    // Shared copies of Type objects, to avoid creating a lot of objects.
+    static final IntegerType           INTEGER_TYPE            = new IntegerType();
+    static final LongType              LONG_TYPE               = new LongType();
+    static final FloatType             FLOAT_TYPE              = new FloatType();
+    static final DoubleType            DOUBLE_TYPE             = new DoubleType();
+    static final TopType               TOP_TYPE                = new TopType();
+    static final NullType              NULL_TYPE               = new NullType();
+    static final UninitializedThisType UNINITIALIZED_THIS_TYPE = new UninitializedThisType();
+
+
+    /**
+     * Creates a new IntegerType.
+     */
+    public static IntegerType createIntegerType()
+    {
+        return INTEGER_TYPE;
+    }
+
+    /**
+     * Creates a new LongType.
+     */
+    public static LongType createLongType()
+    {
+        return LONG_TYPE;
+    }
+
+    /**
+     * Creates a new FloatType.
+     */
+    public static FloatType createFloatType()
+    {
+        return FLOAT_TYPE;
+    }
+
+    /**
+     * Creates a new DoubleType.
+     */
+    public static DoubleType createDoubleType()
+    {
+        return DOUBLE_TYPE;
+    }
+
+    /**
+     * Creates a new TopType.
+     */
+    public static TopType createTopType()
+    {
+        return TOP_TYPE;
+    }
+
+    /**
+     * Creates a new NullType.
+     */
+    public static NullType createNullType()
+    {
+        return NULL_TYPE;
+    }
+
+    /**
+     * Creates a new UninitializedThisType.
+     */
+    public static UninitializedThisType createUninitializedThisType()
+    {
+        return UNINITIALIZED_THIS_TYPE;
+    }
+
+    /**
+     * Creates a new UninitializedType for an instance that was created at
+     * the given offset.
+     */
+    public static UninitializedType createUninitializedType(int newInstructionOffset)
+    {
+        return new UninitializedType(newInstructionOffset);
+    }
+
+    /**
+     * Creates a new ObjectType of the given type.
+     */
+    public static ObjectType createObjectType(int classIndex)
+    {
+        return new ObjectType(classIndex);
+    }
+}
diff --git a/src/proguard/classfile/attribute/preverification/visitor/StackMapFrameVisitor.java b/src/proguard/classfile/attribute/preverification/visitor/StackMapFrameVisitor.java
new file mode 100644
index 0000000..7db246c
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/visitor/StackMapFrameVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.*;
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>StackMapFrame</code> objects.
+ *
+ * @author Eric Lafortune
+ */
+public interface StackMapFrameVisitor
+{
+    public void visitSameZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameZeroFrame sameZeroFrame);
+    public void visitSameOneFrame( Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame  sameOneFrame);
+    public void visitLessZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LessZeroFrame lessZeroFrame);
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame);
+    public void visitFullFrame(    Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame     fullFrame);
+}
diff --git a/src/proguard/classfile/attribute/preverification/visitor/VerificationTypeVisitor.java b/src/proguard/classfile/attribute/preverification/visitor/VerificationTypeVisitor.java
new file mode 100644
index 0000000..e9931f8
--- /dev/null
+++ b/src/proguard/classfile/attribute/preverification/visitor/VerificationTypeVisitor.java
@@ -0,0 +1,65 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.preverification.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.preverification.*;
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>VerificationType</code> objects. There a methods for stack entries
+ * and methods for variable entries.
+ *
+ * @author Eric Lafortune
+ */
+public interface VerificationTypeVisitor
+{
+    public void visitIntegerType(          Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, IntegerType           integerType);
+    public void visitFloatType(            Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FloatType             floatType);
+    public void visitLongType(             Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LongType              longType);
+    public void visitDoubleType(           Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, DoubleType            doubleType);
+    public void visitTopType(              Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TopType               topType);
+    public void visitObjectType(           Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType            objectType);
+    public void visitNullType(             Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, NullType              nullType);
+    public void visitUninitializedType(    Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType     uninitializedType);
+    public void visitUninitializedThisType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedThisType uninitializedThisType);
+
+    public void visitStackIntegerType(          Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, IntegerType           integerType);
+    public void visitStackFloatType(            Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, FloatType             floatType);
+    public void visitStackLongType(             Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, LongType              longType);
+    public void visitStackDoubleType(           Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, DoubleType            doubleType);
+    public void visitStackTopType(              Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, TopType               topType);
+    public void visitStackObjectType(           Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, ObjectType            objectType);
+    public void visitStackNullType(             Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, NullType              nullType);
+    public void visitStackUninitializedType(    Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, UninitializedType     uninitializedType);
+    public void visitStackUninitializedThisType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, UninitializedThisType uninitializedThisType);
+
+    public void visitVariablesIntegerType(          Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, IntegerType           integerType);
+    public void visitVariablesFloatType(            Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, FloatType             floatType);
+    public void visitVariablesLongType(             Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, LongType              longType);
+    public void visitVariablesDoubleType(           Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, DoubleType            doubleType);
+    public void visitVariablesTopType(              Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, TopType               topType);
+    public void visitVariablesObjectType(           Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, ObjectType            objectType);
+    public void visitVariablesNullType(             Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, NullType              nullType);
+    public void visitVariablesUninitializedType(    Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, UninitializedType     uninitializedType);
+    public void visitVariablesUninitializedThisType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, UninitializedThisType uninitializedThisType);
+}
diff --git a/src/proguard/classfile/attribute/visitor/AllAttributeVisitor.java b/src/proguard/classfile/attribute/visitor/AllAttributeVisitor.java
new file mode 100644
index 0000000..61b0f1a
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/AllAttributeVisitor.java
@@ -0,0 +1,117 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor, MemberVisitor, and AttributeVisitor lets a given
+ * AttributeVisitor visit all Attribute objects of the program classes,
+ * program class members, or code attributes, respectively, that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllAttributeVisitor
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             AttributeVisitor
+{
+    private final boolean          deep;
+    private final AttributeVisitor attributeVisitor;
+
+
+    /**
+     * Creates a new shallow AllAttributeVisitor.
+     * @param attributeVisitor the AttributeVisitor to which visits will be
+     *                         delegated.
+     */
+    public AllAttributeVisitor(AttributeVisitor attributeVisitor)
+    {
+        this(false, attributeVisitor);
+    }
+
+
+    /**
+     * Creates a new optionally deep AllAttributeVisitor.
+     * @param deep             specifies whether the attributes contained
+     *                         further down the class structure should be
+     *                         visited too.
+     * @param attributeVisitor the AttributeVisitor to which visits will be
+     *                         delegated.
+     */
+    public AllAttributeVisitor(boolean          deep,
+                               AttributeVisitor attributeVisitor)
+    {
+        this.deep             = deep;
+        this.attributeVisitor = attributeVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.attributesAccept(attributeVisitor);
+
+        // Visit the attributes further down the class structure, if required.
+        if (deep)
+        {
+            programClass.fieldsAccept(this);
+            programClass.methodsAccept(this);
+            programClass.attributesAccept(this);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        programMember.attributesAccept(programClass, attributeVisitor);
+
+        // Visit the attributes further down the member structure, if required.
+        if (deep)
+        {
+            programMember.attributesAccept(programClass, this);
+        }
+    }
+
+
+    public void visitLibraryMember(LibraryClass programClass, LibraryMember programMember) {}
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        codeAttribute.attributesAccept(clazz, method, attributeVisitor);
+    }
+}
diff --git a/src/proguard/classfile/attribute/visitor/AllExceptionInfoVisitor.java b/src/proguard/classfile/attribute/visitor/AllExceptionInfoVisitor.java
new file mode 100644
index 0000000..839e104
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/AllExceptionInfoVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor lets a given ExceptionInfoVisitor visit all exceptions
+ * objects of the CodeAttribute objects it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllExceptionInfoVisitor
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private final ExceptionInfoVisitor exceptionInfoVisitor;
+
+
+    public AllExceptionInfoVisitor(ExceptionInfoVisitor exceptionInfoVisitor)
+    {
+        this.exceptionInfoVisitor = exceptionInfoVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        codeAttribute.exceptionsAccept(clazz, method, exceptionInfoVisitor);
+    }
+}
diff --git a/src/proguard/classfile/attribute/visitor/AttributeNameFilter.java b/src/proguard/classfile/attribute/visitor/AttributeNameFilter.java
new file mode 100644
index 0000000..aa81ce0
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/AttributeNameFilter.java
@@ -0,0 +1,345 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.util.StringMatcher;
+
+/**
+ * This AttributeVisitor delegates its visits another AttributeVisitor, but
+ * only when the visited attribute has a name that passes a given string
+ * matcher.
+ *
+ * @author Eric Lafortune
+ */
+public class AttributeNameFilter
+implements   AttributeVisitor
+{
+    private final StringMatcher    stringMatcher;
+    private final AttributeVisitor attributeVisitor;
+
+
+    /**
+     * Creates a new AttributeNameFilter.
+     * @param stringMatcher    the string matcher that will check the attribute
+     *                         names.
+     * @param attributeVisitor the <code>AttributeVisitor</code> to which
+     *                         visits will be delegated.
+     */
+    public AttributeNameFilter(StringMatcher    stringMatcher,
+                               AttributeVisitor attributeVisitor)
+    {
+        this.stringMatcher    = stringMatcher;
+        this.attributeVisitor = attributeVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        if (accepted(clazz, unknownAttribute))
+        {
+            unknownAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        if (accepted(clazz, sourceFileAttribute))
+        {
+            sourceFileAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        if (accepted(clazz, sourceDirAttribute))
+        {
+            sourceDirAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        if (accepted(clazz, innerClassesAttribute))
+        {
+            innerClassesAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        if (accepted(clazz, enclosingMethodAttribute))
+        {
+            enclosingMethodAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        if (accepted(clazz, deprecatedAttribute))
+        {
+            deprecatedAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Field field, DeprecatedAttribute deprecatedAttribute)
+    {
+        if (accepted(clazz, deprecatedAttribute))
+        {
+            deprecatedAttribute.accept(clazz, field, attributeVisitor);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Method method, DeprecatedAttribute deprecatedAttribute)
+    {
+        if (accepted(clazz, deprecatedAttribute))
+        {
+            deprecatedAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        if (accepted(clazz, syntheticAttribute))
+        {
+            syntheticAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Field field, SyntheticAttribute syntheticAttribute)
+    {
+        if (accepted(clazz, syntheticAttribute))
+        {
+            syntheticAttribute.accept(clazz, field, attributeVisitor);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Method method, SyntheticAttribute syntheticAttribute)
+    {
+        if (accepted(clazz, syntheticAttribute))
+        {
+            syntheticAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        if (accepted(clazz, signatureAttribute))
+        {
+            signatureAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute signatureAttribute)
+    {
+        if (accepted(clazz, signatureAttribute))
+        {
+            signatureAttribute.accept(clazz, field, attributeVisitor);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute signatureAttribute)
+    {
+        if (accepted(clazz, signatureAttribute))
+        {
+            signatureAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        if (accepted(clazz, constantValueAttribute))
+        {
+            constantValueAttribute.accept(clazz, field, attributeVisitor);
+        }
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        if (accepted(clazz, exceptionsAttribute))
+        {
+            exceptionsAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (accepted(clazz, codeAttribute))
+        {
+            codeAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        if (accepted(clazz, stackMapAttribute))
+        {
+            stackMapAttribute.accept(clazz, method, codeAttribute, attributeVisitor);
+        }
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        if (accepted(clazz, stackMapTableAttribute))
+        {
+            stackMapTableAttribute.accept(clazz, method, codeAttribute, attributeVisitor);
+        }
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        if (accepted(clazz, lineNumberTableAttribute))
+        {
+            lineNumberTableAttribute.accept(clazz, method, codeAttribute, attributeVisitor);
+        }
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        if (accepted(clazz, localVariableTableAttribute))
+        {
+            localVariableTableAttribute.accept(clazz, method, codeAttribute, attributeVisitor);
+        }
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        if (accepted(clazz, localVariableTypeTableAttribute))
+        {
+            localVariableTypeTableAttribute.accept(clazz, method, codeAttribute, attributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        if (accepted(clazz, runtimeVisibleAnnotationsAttribute))
+        {
+            runtimeVisibleAnnotationsAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        if (accepted(clazz, runtimeVisibleAnnotationsAttribute))
+        {
+            runtimeVisibleAnnotationsAttribute.accept(clazz, field, attributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        if (accepted(clazz, runtimeVisibleAnnotationsAttribute))
+        {
+            runtimeVisibleAnnotationsAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        if (accepted(clazz, runtimeInvisibleAnnotationsAttribute))
+        {
+            runtimeInvisibleAnnotationsAttribute.accept(clazz, attributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        if (accepted(clazz, runtimeInvisibleAnnotationsAttribute))
+        {
+            runtimeInvisibleAnnotationsAttribute.accept(clazz, field, attributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        if (accepted(clazz, runtimeInvisibleAnnotationsAttribute))
+        {
+            runtimeInvisibleAnnotationsAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute)
+    {
+        if (accepted(clazz, runtimeVisibleParameterAnnotationsAttribute))
+        {
+            runtimeVisibleParameterAnnotationsAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute)
+    {
+        if (accepted(clazz, runtimeInvisibleParameterAnnotationsAttribute))
+        {
+            runtimeInvisibleParameterAnnotationsAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        if (accepted(clazz, annotationDefaultAttribute))
+        {
+            annotationDefaultAttribute.accept(clazz, method, attributeVisitor);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean accepted(Clazz clazz, Attribute attribute)
+    {
+        return stringMatcher.matches(attribute.getAttributeName(clazz));
+    }
+}
diff --git a/src/proguard/classfile/attribute/visitor/AttributeVisitor.java b/src/proguard/classfile/attribute/visitor/AttributeVisitor.java
new file mode 100644
index 0000000..e8f226b
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/AttributeVisitor.java
@@ -0,0 +1,89 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+
+/**
+ * This interface specifies the methods for a visitor of <code>Attribute</code>
+ * objects.
+ *
+ * @author Eric Lafortune
+ */
+public interface AttributeVisitor
+{
+    // Attributes that are attached to classes.
+
+    public void visitUnknownAttribute(               Clazz clazz,                UnknownAttribute         unknownAttribute);
+    public void visitSourceFileAttribute(            Clazz clazz,                SourceFileAttribute      sourceFileAttribute);
+    public void visitSourceDirAttribute(             Clazz clazz,                SourceDirAttribute       sourceDirAttribute);
+    public void visitInnerClassesAttribute(          Clazz clazz,                InnerClassesAttribute    innerClassesAttribute);
+    public void visitEnclosingMethodAttribute(       Clazz clazz,                EnclosingMethodAttribute enclosingMethodAttribute);
+
+    // Attributes that are attached to classes, fields, and methods.
+
+    public void visitDeprecatedAttribute(            Clazz clazz,                DeprecatedAttribute deprecatedAttribute);
+    public void visitDeprecatedAttribute(            Clazz clazz, Field  field,  DeprecatedAttribute deprecatedAttribute);
+    public void visitDeprecatedAttribute(            Clazz clazz, Method method, DeprecatedAttribute deprecatedAttribute);
+
+    public void visitSyntheticAttribute(             Clazz clazz,                SyntheticAttribute  syntheticAttribute);
+    public void visitSyntheticAttribute(             Clazz clazz, Field  field,  SyntheticAttribute  syntheticAttribute);
+    public void visitSyntheticAttribute(             Clazz clazz, Method method, SyntheticAttribute  syntheticAttribute);
+
+    public void visitSignatureAttribute(             Clazz clazz,                SignatureAttribute  signatureAttribute);
+    public void visitSignatureAttribute(             Clazz clazz, Field  field,  SignatureAttribute  signatureAttribute);
+    public void visitSignatureAttribute(             Clazz clazz, Method method, SignatureAttribute  signatureAttribute);
+
+    // Attributes that are attached to fields.
+
+    public void visitConstantValueAttribute(         Clazz clazz, Field  field,  ConstantValueAttribute constantValueAttribute);
+
+    // Attributes that are attached to methods.
+
+    public void visitExceptionsAttribute(            Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute);
+    public void visitCodeAttribute(                  Clazz clazz, Method method, CodeAttribute       codeAttribute);
+
+    // Attributes that are attached to code attributes.
+
+    public void visitStackMapAttribute(              Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute               stackMapAttribute);
+    public void visitStackMapTableAttribute(         Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute          stackMapTableAttribute);
+    public void visitLineNumberTableAttribute(       Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute        lineNumberTableAttribute);
+    public void visitLocalVariableTableAttribute(    Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute     localVariableTableAttribute);
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute);
+
+    // Annotation attributes.
+
+    public void visitRuntimeVisibleAnnotationsAttribute(           Clazz clazz,                RuntimeVisibleAnnotationsAttribute   runtimeVisibleAnnotationsAttribute);
+    public void visitRuntimeVisibleAnnotationsAttribute(           Clazz clazz, Field  field,  RuntimeVisibleAnnotationsAttribute   runtimeVisibleAnnotationsAttribute);
+    public void visitRuntimeVisibleAnnotationsAttribute(           Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute   runtimeVisibleAnnotationsAttribute);
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(         Clazz clazz,                RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute);
+    public void visitRuntimeInvisibleAnnotationsAttribute(         Clazz clazz, Field  field,  RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute);
+    public void visitRuntimeInvisibleAnnotationsAttribute(         Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute);
+
+    public void visitRuntimeVisibleParameterAnnotationsAttribute(  Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute   runtimeVisibleParameterAnnotationsAttribute);
+    public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute);
+
+    public void visitAnnotationDefaultAttribute(                   Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute);
+}
diff --git a/src/proguard/classfile/attribute/visitor/ExceptionInfoVisitor.java b/src/proguard/classfile/attribute/visitor/ExceptionInfoVisitor.java
new file mode 100644
index 0000000..7c85e53
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/ExceptionInfoVisitor.java
@@ -0,0 +1,37 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>ExceptionInfo</code> objects. Note that there is only a single
+ * implementation of <code>ExceptionInfo</code>, such that this interface
+ * is not strictly necessary as a visitor.
+ *
+ * @author Eric Lafortune
+ */
+public interface ExceptionInfoVisitor
+{
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo);
+}
diff --git a/src/proguard/classfile/attribute/visitor/InnerClassesInfoVisitor.java b/src/proguard/classfile/attribute/visitor/InnerClassesInfoVisitor.java
new file mode 100644
index 0000000..91267b0
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/InnerClassesInfoVisitor.java
@@ -0,0 +1,38 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.InnerClassesInfo;
+
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>InnerClassesInfo</code> objects. Note that there is only a single
+ * implementation of <code>InnerClassesInfo</code>, such that this interface
+ * is not strictly necessary as a visitor.
+ *
+ * @author Eric Lafortune
+ */
+public interface InnerClassesInfoVisitor
+{
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo);
+}
diff --git a/src/proguard/classfile/attribute/visitor/LineNumberInfoVisitor.java b/src/proguard/classfile/attribute/visitor/LineNumberInfoVisitor.java
new file mode 100644
index 0000000..e59ed7b
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/LineNumberInfoVisitor.java
@@ -0,0 +1,38 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>LineNumberInfo</code> objects. Note that there is only a single
+ * implementation of <code>LineNumberInfo</code>, such that this interface
+ * is not strictly necessary as a visitor.
+ *
+ * @author Eric Lafortune
+ */
+public interface LineNumberInfoVisitor
+{
+    public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo);
+}
diff --git a/src/proguard/classfile/attribute/visitor/LocalVariableInfoVisitor.java b/src/proguard/classfile/attribute/visitor/LocalVariableInfoVisitor.java
new file mode 100644
index 0000000..8647cb3
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/LocalVariableInfoVisitor.java
@@ -0,0 +1,38 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>LocalVariableInfo</code> objects. Note that there is only a single
+ * implementation of <code>LocalVariableInfo</code>, such that this interface
+ * is not strictly necessary as a visitor.
+ *
+ * @author Eric Lafortune
+ */
+public interface LocalVariableInfoVisitor
+{
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo);
+}
diff --git a/src/proguard/classfile/attribute/visitor/LocalVariableTypeInfoVisitor.java b/src/proguard/classfile/attribute/visitor/LocalVariableTypeInfoVisitor.java
new file mode 100644
index 0000000..9ad38e0
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/LocalVariableTypeInfoVisitor.java
@@ -0,0 +1,38 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>LocalVariableTypeInfo</code> objects. Note that there is only a single
+ * implementation of <code>LocalVariableTypeInfo</code>, such that this interface
+ * is not strictly necessary as a visitor.
+ *
+ * @author Eric Lafortune
+ */
+public interface LocalVariableTypeInfoVisitor
+{
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo);
+}
diff --git a/src/proguard/classfile/attribute/visitor/MultiAttributeVisitor.java b/src/proguard/classfile/attribute/visitor/MultiAttributeVisitor.java
new file mode 100644
index 0000000..870ba94
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/MultiAttributeVisitor.java
@@ -0,0 +1,356 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+
+/**
+ * This AttributeVisitor delegates all visits to each AttributeVisitor
+ * in a given list.
+ *
+ * @author Eric Lafortune
+ */
+public class MultiAttributeVisitor implements AttributeVisitor
+{
+    private AttributeVisitor[] attributeVisitors;
+
+
+    public MultiAttributeVisitor()
+    {
+    }
+
+
+    public MultiAttributeVisitor(AttributeVisitor[] attributeVisitors)
+    {
+        this.attributeVisitors = attributeVisitors;
+    }
+
+
+    public void addAttributeVisitor(AttributeVisitor attributeVisitor)
+    {
+        incrementArraySize();
+
+        attributeVisitors[attributeVisitors.length - 1] = attributeVisitor;
+    }
+
+
+    private void incrementArraySize()
+    {
+        if (attributeVisitors == null)
+        {
+            attributeVisitors = new AttributeVisitor[1];
+        }
+        else
+        {
+            AttributeVisitor[] newAttributeVisitors =
+                new AttributeVisitor[attributeVisitors.length + 1];
+            System.arraycopy(attributeVisitors, 0,
+                             newAttributeVisitors, 0,
+                             attributeVisitors.length);
+            attributeVisitors = newAttributeVisitors;
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitUnknownAttribute(clazz, unknownAttribute);
+        }
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitSourceFileAttribute(clazz, sourceFileAttribute);
+        }
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitSourceDirAttribute(clazz, sourceDirAttribute);
+        }
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitInnerClassesAttribute(clazz, innerClassesAttribute);
+        }
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitEnclosingMethodAttribute(clazz, enclosingMethodAttribute);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitDeprecatedAttribute(clazz, deprecatedAttribute);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitSyntheticAttribute(clazz, syntheticAttribute);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute syntheticAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitSignatureAttribute(clazz, syntheticAttribute);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Field field, DeprecatedAttribute deprecatedAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitDeprecatedAttribute(clazz, field, deprecatedAttribute);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Field field, SyntheticAttribute syntheticAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitSyntheticAttribute(clazz, field, syntheticAttribute);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute syntheticAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitSignatureAttribute(clazz, field, syntheticAttribute);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Method method, DeprecatedAttribute deprecatedAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitDeprecatedAttribute(clazz, method, deprecatedAttribute);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Method method, SyntheticAttribute syntheticAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitSyntheticAttribute(clazz, method, syntheticAttribute);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute syntheticAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitSignatureAttribute(clazz, method, syntheticAttribute);
+        }
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitConstantValueAttribute(clazz, field, constantValueAttribute);
+        }
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitExceptionsAttribute(clazz, method, exceptionsAttribute);
+        }
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitCodeAttribute(clazz, method, codeAttribute);
+        }
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitStackMapAttribute(clazz, method, codeAttribute, stackMapAttribute);
+        }
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitStackMapTableAttribute(clazz, method, codeAttribute, stackMapTableAttribute);
+        }
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitLineNumberTableAttribute(clazz, method, codeAttribute, lineNumberTableAttribute);
+        }
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitLocalVariableTableAttribute(clazz, method, codeAttribute, localVariableTableAttribute);
+        }
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitLocalVariableTypeTableAttribute(clazz, method, codeAttribute, localVariableTypeTableAttribute);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitRuntimeVisibleAnnotationsAttribute(clazz, runtimeVisibleAnnotationsAttribute);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitRuntimeInvisibleAnnotationsAttribute(clazz, runtimeInvisibleAnnotationsAttribute);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitRuntimeVisibleAnnotationsAttribute(clazz, field, runtimeVisibleAnnotationsAttribute);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitRuntimeInvisibleAnnotationsAttribute(clazz, field, runtimeInvisibleAnnotationsAttribute);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitRuntimeVisibleAnnotationsAttribute(clazz, method, runtimeVisibleAnnotationsAttribute);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitRuntimeInvisibleAnnotationsAttribute(clazz, method, runtimeInvisibleAnnotationsAttribute);
+        }
+    }
+
+
+    public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitRuntimeVisibleParameterAnnotationsAttribute(clazz, method, runtimeVisibleParameterAnnotationsAttribute);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitRuntimeInvisibleParameterAnnotationsAttribute(clazz, method, runtimeInvisibleParameterAnnotationsAttribute);
+        }
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        for (int index = 0; index < attributeVisitors.length; index++)
+        {
+            attributeVisitors[index].visitAnnotationDefaultAttribute(clazz, method, annotationDefaultAttribute);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/visitor/RequiredAttributeFilter.java b/src/proguard/classfile/attribute/visitor/RequiredAttributeFilter.java
new file mode 100644
index 0000000..92099f9
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/RequiredAttributeFilter.java
@@ -0,0 +1,351 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.obfuscate.AttributeShrinker;
+
+/**
+ * This AttributeVisitor delegates its visits to one of two other
+ * AttributeVisitor instances, depending on whether the visited attribute
+ * is strictly required or not.
+ *
+ * @see AttributeShrinker
+ *
+ * @author Eric Lafortune
+ */
+public class RequiredAttributeFilter
+implements   AttributeVisitor
+{
+    private final AttributeVisitor requiredAttributeVisitor;
+    private final AttributeVisitor optionalAttributeVisitor;
+
+
+    /**
+     * Creates a new RequiredAttributeFilter for visiting required attributes.
+     * @param requiredAttributeVisitor   the visitor that will visit required
+     *                                   attributes.
+     */
+    public RequiredAttributeFilter(AttributeVisitor requiredAttributeVisitor)
+    {
+        this(requiredAttributeVisitor, null);
+    }
+
+
+    /**
+     * Creates a new RequiredAttributeFilter for visiting required and
+     * optional attributes.
+     * @param requiredAttributeVisitor the visitor that will visit required
+     *                                 attributes.
+     * @param optionalAttributeVisitor the visitor that will visit optional
+     *                                 attributes.
+     */
+    public RequiredAttributeFilter(AttributeVisitor requiredAttributeVisitor,
+                                   AttributeVisitor optionalAttributeVisitor)
+    {
+        this.requiredAttributeVisitor = requiredAttributeVisitor;
+        this.optionalAttributeVisitor = optionalAttributeVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            unknownAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            sourceFileAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            sourceDirAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            innerClassesAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            enclosingMethodAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            deprecatedAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Field field, DeprecatedAttribute deprecatedAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            deprecatedAttribute.accept(clazz, field, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Method method, DeprecatedAttribute deprecatedAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            deprecatedAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            syntheticAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Field field, SyntheticAttribute syntheticAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            syntheticAttribute.accept(clazz, field, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Method method, SyntheticAttribute syntheticAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            syntheticAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            signatureAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute signatureAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            signatureAttribute.accept(clazz, field, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute signatureAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            signatureAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        if (requiredAttributeVisitor != null)
+        {
+            constantValueAttribute.accept(clazz, field, requiredAttributeVisitor);
+        }
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            exceptionsAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (requiredAttributeVisitor != null)
+        {
+            codeAttribute.accept(clazz, method, requiredAttributeVisitor);
+        }
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            stackMapAttribute.accept(clazz, method, codeAttribute, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        if (requiredAttributeVisitor != null)
+        {
+            stackMapTableAttribute.accept(clazz, method, codeAttribute, requiredAttributeVisitor);
+        }
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            lineNumberTableAttribute.accept(clazz, method, codeAttribute, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            localVariableTableAttribute.accept(clazz, method, codeAttribute, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            localVariableTypeTableAttribute.accept(clazz, method, codeAttribute, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            runtimeVisibleAnnotationsAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            runtimeVisibleAnnotationsAttribute.accept(clazz, field, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            runtimeVisibleAnnotationsAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            runtimeInvisibleAnnotationsAttribute.accept(clazz, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            runtimeInvisibleAnnotationsAttribute.accept(clazz, field, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            runtimeInvisibleAnnotationsAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            runtimeVisibleParameterAnnotationsAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            runtimeInvisibleParameterAnnotationsAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        if (optionalAttributeVisitor != null)
+        {
+            annotationDefaultAttribute.accept(clazz, method, optionalAttributeVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/attribute/visitor/StackSizeComputer.java b/src/proguard/classfile/attribute/visitor/StackSizeComputer.java
new file mode 100644
index 0000000..401f188
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/StackSizeComputer.java
@@ -0,0 +1,379 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.attribute.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassPrinter;
+import proguard.classfile.attribute.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor computes the stack sizes at all instruction offsets
+ * of the code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class StackSizeComputer
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ExceptionInfoVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    private boolean[] evaluated  = new boolean[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int[]     stackSizes = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+
+    private boolean exitInstructionBlock;
+
+    private int stackSize;
+    private int maxStackSize;
+
+
+    /**
+     * Returns whether the instruction at the given offset is reachable in the
+     * most recently visited code attribute.
+     */
+    public boolean isReachable(int instructionOffset)
+    {
+        return evaluated[instructionOffset];
+    }
+
+
+    /**
+     * Returns the stack size at the given instruction offset of the most
+     * recently visited code attribute.
+     */
+    public int getStackSize(int instructionOffset)
+    {
+        if (!evaluated[instructionOffset])
+        {
+            throw new IllegalArgumentException("Unknown stack size at unreachable instruction offset ["+instructionOffset+"]");
+        }
+
+        return stackSizes[instructionOffset];
+    }
+
+
+    /**
+     * Returns the maximum stack size of the most recently visited code attribute.
+     */
+    public int getMaxStackSize()
+    {
+        return maxStackSize;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // TODO: Remove this when the code has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            // Process the code.
+            visitCodeAttribute0(clazz, method, codeAttribute);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while computing stack sizes:");
+            System.err.println("  Class       = ["+clazz.getName()+"]");
+            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+
+            if (DEBUG)
+            {
+                method.accept(clazz, new ClassPrinter());
+            }
+
+            throw ex;
+        }
+    }
+
+
+    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (DEBUG)
+        {
+            System.out.println("StackSizeComputer: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
+        }
+
+        // Try to reuse the previous array.
+        int codeLength = codeAttribute.u4codeLength;
+        if (evaluated.length < codeLength)
+        {
+            evaluated  = new boolean[codeLength];
+            stackSizes = new int[codeLength];
+        }
+        else
+        {
+            for (int index = 0; index < codeLength; index++)
+            {
+                evaluated[index] = false;
+            }
+        }
+
+        // The initial stack is always empty.
+        stackSize    = 0;
+        maxStackSize = 0;
+
+        // Evaluate the instruction block starting at the entry point of the method.
+        evaluateInstructionBlock(clazz, method, codeAttribute, 0);
+
+        // Evaluate the exception handlers.
+        codeAttribute.exceptionsAccept(clazz, method, this);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        byte opcode = simpleInstruction.opcode;
+
+        // Some simple instructions exit from the current instruction block.
+        exitInstructionBlock =
+            opcode == InstructionConstants.OP_IRETURN ||
+            opcode == InstructionConstants.OP_LRETURN ||
+            opcode == InstructionConstants.OP_FRETURN ||
+            opcode == InstructionConstants.OP_DRETURN ||
+            opcode == InstructionConstants.OP_ARETURN ||
+            opcode == InstructionConstants.OP_RETURN  ||
+            opcode == InstructionConstants.OP_ATHROW;
+    }
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        // Constant pool instructions never end the current instruction block.
+        exitInstructionBlock = false;
+    }
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        byte opcode = variableInstruction.opcode;
+
+        // The ret instruction end the current instruction block.
+        exitInstructionBlock =
+            opcode == InstructionConstants.OP_RET;
+    }
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        byte opcode = branchInstruction.opcode;
+
+        // Evaluate the target instruction blocks.
+        evaluateInstructionBlock(clazz,
+                                 method,
+                                 codeAttribute,
+                                 offset +
+                                 branchInstruction.branchOffset);
+
+        // Evaluate the instructions after a subroutine branch.
+        if (opcode == InstructionConstants.OP_JSR ||
+            opcode == InstructionConstants.OP_JSR_W)
+        {
+            // We assume subroutine calls (jsr and jsr_w instructions) don't
+            // change the stack, other than popping the return value.
+            stackSize -= 1;
+
+            evaluateInstructionBlock(clazz,
+                                     method,
+                                     codeAttribute,
+                                     offset + branchInstruction.length(offset));
+        }
+
+        // Some branch instructions always end the current instruction block.
+        exitInstructionBlock =
+            opcode == InstructionConstants.OP_GOTO   ||
+            opcode == InstructionConstants.OP_GOTO_W ||
+            opcode == InstructionConstants.OP_JSR    ||
+            opcode == InstructionConstants.OP_JSR_W;
+    }
+
+
+    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
+    {
+        // Evaluate the target instruction blocks.
+
+        // Loop over all jump offsets.
+        int[] jumpOffsets = switchInstruction.jumpOffsets;
+
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            // Evaluate the jump instruction block.
+            evaluateInstructionBlock(clazz,
+                                     method,
+                                     codeAttribute,
+                                     offset + jumpOffsets[index]);
+        }
+
+        // Also evaluate the default instruction block.
+        evaluateInstructionBlock(clazz,
+                                 method,
+                                 codeAttribute,
+                                 offset + switchInstruction.defaultOffset);
+
+        // The switch instruction always ends the current instruction block.
+        exitInstructionBlock = true;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        if (DEBUG)
+        {
+            System.out.println("Exception:");
+        }
+
+        // The stack size when entering the exception handler is always 1.
+        stackSize = 1;
+
+        // Evaluate the instruction block starting at the entry point of the
+        // exception handler.
+        evaluateInstructionBlock(clazz,
+                                 method,
+                                 codeAttribute,
+                                 exceptionInfo.u2handlerPC);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Evaluates a block of instructions that hasn't been handled before,
+     * starting at the given offset and ending at a branch instruction, a return
+     * instruction, or a throw instruction. Branch instructions are handled
+     * recursively.
+     */
+    private void evaluateInstructionBlock(Clazz         clazz,
+                                          Method        method,
+                                          CodeAttribute codeAttribute,
+                                          int           instructionOffset)
+    {
+        if (DEBUG)
+        {
+            if (evaluated[instructionOffset])
+            {
+                System.out.println("-- (instruction block at "+instructionOffset+" already evaluated)");
+            }
+            else
+            {
+                System.out.println("-- instruction block:");
+            }
+        }
+
+        // Remember the initial stack size.
+        int initialStackSize = stackSize;
+
+        // Remember the maximum stack size.
+        if (maxStackSize < stackSize)
+        {
+            maxStackSize = stackSize;
+        }
+
+        // Evaluate any instructions that haven't been evaluated before.
+        while (!evaluated[instructionOffset])
+        {
+            // Mark the instruction as evaluated.
+            evaluated[instructionOffset] = true;
+
+            Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                instructionOffset);
+
+            if (DEBUG)
+            {
+                int stackPushCount = instruction.stackPushCount(clazz);
+                int stackPopCount  = instruction.stackPopCount(clazz);
+                System.out.println("["+instructionOffset+"]: "+
+                                   stackSize+" - "+
+                                   stackPopCount+" + "+
+                                   stackPushCount+" = "+
+                                   (stackSize+stackPushCount-stackPopCount)+": "+
+                                   instruction.toString(instructionOffset));
+            }
+
+            // Compute the instruction's effect on the stack size.
+            stackSize -= instruction.stackPopCount(clazz);
+
+            if (stackSize < 0)
+            {
+                throw new IllegalArgumentException("Stack size becomes negative after instruction "+
+                                                   instruction.toString(instructionOffset)+" in ["+
+                                                   clazz.getName()+"."+
+                                                   method.getName(clazz)+
+                                                   method.getDescriptor(clazz)+"]");
+            }
+
+            stackSizes[instructionOffset] =
+            stackSize += instruction.stackPushCount(clazz);
+
+            // Remember the maximum stack size.
+            if (maxStackSize < stackSize)
+            {
+                maxStackSize = stackSize;
+            }
+
+            // Remember the next instruction offset.
+            int nextInstructionOffset = instructionOffset +
+                                        instruction.length(instructionOffset);
+
+            // Visit the instruction, in order to handle branches.
+            instruction.accept(clazz, method, codeAttribute, instructionOffset, this);
+
+            // Stop evaluating after a branch.
+            if (exitInstructionBlock)
+            {
+                break;
+            }
+
+            // Continue with the next instruction.
+            instructionOffset = nextInstructionOffset;
+
+            if (DEBUG)
+            {
+                if (evaluated[instructionOffset])
+                {
+                    System.out.println("-- (instruction at "+instructionOffset+" already evaluated)");
+                }
+            }
+        }
+
+        // Restore the stack size for possible subsequent instruction blocks.
+        this.stackSize = initialStackSize;
+    }
+}
diff --git a/src/proguard/classfile/attribute/visitor/package.html b/src/proguard/classfile/attribute/visitor/package.html
new file mode 100644
index 0000000..056244a
--- /dev/null
+++ b/src/proguard/classfile/attribute/visitor/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains visitors for attributes and their components.
+</body>
diff --git a/src/proguard/classfile/constant/ClassConstant.java b/src/proguard/classfile/constant/ClassConstant.java
new file mode 100644
index 0000000..d217bf6
--- /dev/null
+++ b/src/proguard/classfile/constant/ClassConstant.java
@@ -0,0 +1,105 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This Constant represents a class constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassConstant extends Constant
+{
+    public int u2nameIndex;
+
+    /**
+     * An extra field pointing to the referenced Clazz object.
+     * This field is filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer ClassReferenceInitializer}</code>.
+     */
+    public Clazz referencedClass;
+
+    /**
+     * An extra field pointing to the java.lang.Class Clazz object.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>..
+     */
+    public Clazz javaLangClassClass;
+
+
+    /**
+     * Creates an uninitialized ClassConstant.
+     */
+    public ClassConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new ClassConstant with the given name index.
+     * @param u2nameIndex     the index of the name in the constant pool.
+     * @param referencedClass the referenced class.
+     */
+    public ClassConstant(int   u2nameIndex,
+                         Clazz referencedClass)
+    {
+        this.u2nameIndex     = u2nameIndex;
+        this.referencedClass = referencedClass;
+    }
+
+
+    /**
+     * Returns the name.
+     */
+    public String getName(Clazz clazz)
+    {
+        return clazz.getString(u2nameIndex);
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_Class;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitClassConstant(clazz, this);
+    }
+
+
+    /**
+     * Lets the referenced class accept the given visitor.
+     */
+    public void referencedClassAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClass != null)
+        {
+            referencedClass.accept(classVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/constant/Constant.java b/src/proguard/classfile/constant/Constant.java
new file mode 100644
index 0000000..30ce5df
--- /dev/null
+++ b/src/proguard/classfile/constant/Constant.java
@@ -0,0 +1,68 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This abstract class represents an entry in the ConstantPool. Specific types
+ * of entries are subclassed from it.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class Constant implements VisitorAccepter
+{
+    //public int  u1tag;
+    //public byte info[];
+
+    /**
+     * An extra field in which visitors can store information.
+     */
+    public Object visitorInfo;
+
+
+    // Abstract methods to be implemented by extensions.
+
+    /**
+     * Returns the constant pool info tag that specifies the entry type.
+     */
+    public abstract int getTag();
+
+
+    /**
+     * Accepts the given visitor.
+     */
+    public abstract void accept(Clazz clazz, ConstantVisitor constantVisitor);
+
+
+    // Implementations for VisitorAccepter.
+
+    public Object getVisitorInfo()
+    {
+        return visitorInfo;
+    }
+
+    public void setVisitorInfo(Object visitorInfo)
+    {
+        this.visitorInfo = visitorInfo;
+    }
+}
diff --git a/src/proguard/classfile/constant/DoubleConstant.java b/src/proguard/classfile/constant/DoubleConstant.java
new file mode 100644
index 0000000..61779b5
--- /dev/null
+++ b/src/proguard/classfile/constant/DoubleConstant.java
@@ -0,0 +1,82 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Constant represents a double constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class DoubleConstant extends Constant
+{
+    public double f8value;
+
+
+    /**
+     * Creates an uninitialized DoubleConstant.
+     */
+    public DoubleConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new DoubleConstant with the given double value.
+     */
+    public DoubleConstant(double value)
+    {
+        f8value = value;
+    }
+
+
+    /**
+     * Returns the double value of this DoubleConstant.
+     */
+    public double getValue()
+    {
+        return f8value;
+    }
+
+
+    /**
+     * Sets the double value of this DoubleConstant.
+     */
+    public void setValue(double value)
+    {
+        f8value = value;
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_Double;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitDoubleConstant(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/constant/FieldrefConstant.java b/src/proguard/classfile/constant/FieldrefConstant.java
new file mode 100644
index 0000000..d4afce5
--- /dev/null
+++ b/src/proguard/classfile/constant/FieldrefConstant.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Constant represents a field reference constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class FieldrefConstant extends RefConstant
+{
+    /**
+     * Creates an uninitialized FieldrefConstant.
+     */
+    public FieldrefConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new FieldrefConstant with the given name and type indices.
+     * @param u2classIndex           the index of the class in the constant pool.
+     * @param u2nameAndTypeIndex     the index of the name and type entry in the constant pool.
+     * @param referencedClass        the referenced class.
+     * @param referencedMember       the referenced member info.
+     */
+    public FieldrefConstant(int    u2classIndex,
+                            int    u2nameAndTypeIndex,
+                            Clazz  referencedClass,
+                            Member referencedMember)
+    {
+        this.u2classIndex       = u2classIndex;
+        this.u2nameAndTypeIndex = u2nameAndTypeIndex;
+        this.referencedClass    = referencedClass;
+        this.referencedMember   = referencedMember;
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_Fieldref;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitFieldrefConstant(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/constant/FloatConstant.java b/src/proguard/classfile/constant/FloatConstant.java
new file mode 100644
index 0000000..578f567
--- /dev/null
+++ b/src/proguard/classfile/constant/FloatConstant.java
@@ -0,0 +1,82 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Constant represents a float constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class FloatConstant extends Constant
+{
+    public float f4value;
+
+
+    /**
+     * Creates an uninitialized FloatConstant.
+     */
+    public FloatConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new FloatConstant with the given float value.
+     */
+    public FloatConstant(float value)
+    {
+        f4value = value;
+    }
+
+
+    /**
+     * Returns the float value of this FloatConstant.
+     */
+    public float getValue()
+    {
+        return f4value;
+    }
+
+
+    /**
+     * Sets the float value of this FloatConstant.
+     */
+    public void setValue(float value)
+    {
+        f4value = value;
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_Float;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitFloatConstant(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/constant/IntegerConstant.java b/src/proguard/classfile/constant/IntegerConstant.java
new file mode 100644
index 0000000..8a476c6
--- /dev/null
+++ b/src/proguard/classfile/constant/IntegerConstant.java
@@ -0,0 +1,82 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Constant represents a integer constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class IntegerConstant extends Constant
+{
+    public int u4value;
+
+
+    /**
+     * Creates an uninitialized IntegerConstant.
+     */
+    public IntegerConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new IntegerConstant with the given integer value.
+     */
+    public IntegerConstant(int value)
+    {
+        u4value = value;
+    }
+
+
+    /**
+     * Returns the integer value of this IntegerConstant.
+     */
+    public int getValue()
+    {
+        return u4value;
+    }
+
+
+    /**
+     * Sets the integer value of this IntegerConstant.
+     */
+    public void setValue(int value)
+    {
+        u4value = value;
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_Integer;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitIntegerConstant(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/constant/InterfaceMethodrefConstant.java b/src/proguard/classfile/constant/InterfaceMethodrefConstant.java
new file mode 100644
index 0000000..ddee42f
--- /dev/null
+++ b/src/proguard/classfile/constant/InterfaceMethodrefConstant.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Constant represents a interface method reference constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class InterfaceMethodrefConstant extends RefConstant
+{
+    /**
+     * Creates an uninitialized InterfaceMethodrefConstant.
+     */
+    public InterfaceMethodrefConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new InterfaceMethodrefConstant with the given name and type indices.
+     * @param u2classIndex         the index   of the class in the constant pool.
+     * @param u2nameAndTypeIndex   the index   of the name and type entry in the constant pool.
+     * @param referencedClass      the referenced class.
+     * @param referencedMember     the referenced member info.
+     */
+    public InterfaceMethodrefConstant(int    u2classIndex,
+                                      int    u2nameAndTypeIndex,
+                                      Clazz  referencedClass,
+                                      Member referencedMember)
+    {
+        this.u2classIndex       = u2classIndex;
+        this.u2nameAndTypeIndex = u2nameAndTypeIndex;
+        this.referencedClass    = referencedClass;
+        this.referencedMember   = referencedMember;
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_InterfaceMethodref;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitInterfaceMethodrefConstant(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/constant/LongConstant.java b/src/proguard/classfile/constant/LongConstant.java
new file mode 100644
index 0000000..ea66e07
--- /dev/null
+++ b/src/proguard/classfile/constant/LongConstant.java
@@ -0,0 +1,82 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Constant represents a long constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class LongConstant extends Constant
+{
+    public long u8value;
+
+
+    /**
+     * Creates an uninitialized LongConstant.
+     */
+    public LongConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new LongConstant with the given long value.
+     */
+    public LongConstant(long value)
+    {
+        u8value = value;
+    }
+
+
+    /**
+     * Returns the long value of this LongConstant.
+     */
+    public long getValue()
+    {
+        return u8value;
+    }
+
+
+    /**
+     * Sets the long value of this LongConstant.
+     */
+    public void setValue(long value)
+    {
+        u8value = value;
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_Long;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitLongConstant(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/constant/MethodrefConstant.java b/src/proguard/classfile/constant/MethodrefConstant.java
new file mode 100644
index 0000000..858eec9
--- /dev/null
+++ b/src/proguard/classfile/constant/MethodrefConstant.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Constant represents a method reference constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class MethodrefConstant extends RefConstant
+{
+    /**
+     * Creates an uninitialized MethodrefConstant.
+     */
+    public MethodrefConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new MethodrefConstant with the given name and type indices.
+     * @param u2classIndex         the index of the class in the constant pool.
+     * @param u2nameAndTypeIndex   the index of the name and type entry in the constant pool.
+     * @param referencedClass      the referenced class.
+     * @param referencedMember     the referenced member info.
+     */
+    public MethodrefConstant(int    u2classIndex,
+                             int    u2nameAndTypeIndex,
+                             Clazz  referencedClass,
+                             Member referencedMember)
+    {
+        this.u2classIndex       = u2classIndex;
+        this.u2nameAndTypeIndex = u2nameAndTypeIndex;
+        this.referencedClass    = referencedClass;
+        this.referencedMember   = referencedMember;
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_Methodref;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitMethodrefConstant(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/constant/NameAndTypeConstant.java b/src/proguard/classfile/constant/NameAndTypeConstant.java
new file mode 100644
index 0000000..e83d2f1
--- /dev/null
+++ b/src/proguard/classfile/constant/NameAndTypeConstant.java
@@ -0,0 +1,119 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This Constant represents a name and type constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class NameAndTypeConstant extends Constant
+{
+    public int u2nameIndex;
+    public int u2descriptorIndex;
+
+
+    /**
+     * Creates an uninitialized NameAndTypeConstant.
+     */
+    public NameAndTypeConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new NameAndTypeConstant with the given name and type indices.
+     * @param u2nameIndex       the index of the name in the constant pool.
+     * @param u2descriptorIndex the index of the descriptor in the constant
+     *                          pool.
+     */
+    public NameAndTypeConstant(int u2nameIndex,
+                               int u2descriptorIndex)
+    {
+        this.u2nameIndex       = u2nameIndex;
+        this.u2descriptorIndex = u2descriptorIndex;
+    }
+
+
+    /**
+     * Returns the name index.
+     */
+    protected int getNameIndex()
+    {
+        return u2nameIndex;
+    }
+
+    /**
+     * Sets the name index.
+     */
+    protected void setNameIndex(int index)
+    {
+        u2nameIndex = index;
+    }
+
+    /**
+     * Returns the descriptor index.
+     */
+    protected int getDescriptorIndex()
+    {
+        return u2descriptorIndex;
+    }
+
+    /**
+     * Sets the descriptor index.
+     */
+    protected void setDescriptorIndex(int index)
+    {
+        u2descriptorIndex = index;
+    }
+
+    /**
+     * Returns the name.
+     */
+    public String getName(Clazz clazz)
+    {
+        return clazz.getString(u2nameIndex);
+    }
+
+    /**
+     * Returns the type.
+     */
+    public String getType(Clazz clazz)
+    {
+        return clazz.getString(u2descriptorIndex);
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_NameAndType;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitNameAndTypeConstant(clazz, this);
+    }
+}
diff --git a/src/proguard/classfile/constant/RefConstant.java b/src/proguard/classfile/constant/RefConstant.java
new file mode 100644
index 0000000..4e4d019
--- /dev/null
+++ b/src/proguard/classfile/constant/RefConstant.java
@@ -0,0 +1,130 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This Constant represents a ref constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class RefConstant extends Constant
+{
+    public int u2classIndex;
+    public int u2nameAndTypeIndex;
+
+    /**
+     * An extra field pointing to the referenced Clazz object.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>.
+     */
+    public Clazz referencedClass;
+
+    /**
+     * An extra field optionally pointing to the referenced Member object.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>.
+     */
+    public Member referencedMember;
+
+
+    protected RefConstant()
+    {
+    }
+
+
+    /**
+     * Returns the class index.
+     */
+    public int getClassIndex()
+    {
+        return u2classIndex;
+    }
+
+    /**
+     * Returns the name-and-type index.
+     */
+    public int getNameAndTypeIndex()
+    {
+        return u2nameAndTypeIndex;
+    }
+
+    /**
+     * Sets the name-and-type index.
+     */
+    public void setNameAndTypeIndex(int index)
+    {
+        u2nameAndTypeIndex = index;
+    }
+
+    /**
+     * Returns the class name.
+     */
+    public String getClassName(Clazz clazz)
+    {
+        return clazz.getClassName(u2classIndex);
+    }
+
+    /**
+     * Returns the method/field name.
+     */
+    public String getName(Clazz clazz)
+    {
+        return clazz.getName(u2nameAndTypeIndex);
+    }
+
+    /**
+     * Returns the type.
+     */
+    public String getType(Clazz clazz)
+    {
+        return clazz.getType(u2nameAndTypeIndex);
+    }
+
+
+    /**
+     * Lets the referenced class accept the given visitor.
+     */
+    public void referencedClassAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClass != null)
+        {
+            referencedClass.accept(classVisitor);
+        }
+    }
+
+
+    /**
+     * Lets the referenced class member accept the given visitor.
+     */
+    public void referencedMemberAccept(MemberVisitor memberVisitor)
+    {
+        if (referencedMember != null)
+        {
+            referencedMember.accept(referencedClass,
+                                    memberVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/constant/StringConstant.java b/src/proguard/classfile/constant/StringConstant.java
new file mode 100644
index 0000000..9a8d453
--- /dev/null
+++ b/src/proguard/classfile/constant/StringConstant.java
@@ -0,0 +1,135 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This Constant represents a string constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class StringConstant extends Constant
+{
+    public int u2stringIndex;
+
+    /**
+     * An extra field pointing to the referenced Clazz object, if this
+     * string is being used in Class.forName(), .class, or
+     * Class.getDeclaredField/Method constructs.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.DynamicClassReferenceInitializer
+     * DynamicClassReferenceInitializer}</code> or by the <code>{@link
+     * proguard.classfile.util.DynamicMemberReferenceInitializer
+     * DynamicMemberReferenceInitializer}</code>.
+     */
+    public Clazz referencedClass;
+
+    /**
+     * An extra field pointing to the referenced Member object, if this
+     * string is being used in Class.getDeclaredField/Method constructs.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.DynamicMemberReferenceInitializer
+     * DynamicMemberReferenceInitializer}</code>.
+     */
+    public Member referencedMember;
+
+    /**
+     * An extra field pointing to the java.lang.String Clazz object.
+     * This field is typically filled out by the <code>{@link
+     * proguard.classfile.util.ClassReferenceInitializer
+     * ClassReferenceInitializer}</code>..
+     */
+    public Clazz javaLangStringClass;
+
+
+    /**
+     * Creates an uninitialized StringConstant.
+     */
+    public StringConstant()
+    {
+    }
+
+
+    /**
+     * Creates a new StringConstant with the given string index.
+     * @param u2stringIndex   the index of the string in the constant pool.
+     * @param referencedClass the referenced class, if any.
+     * @param referenceMember the referenced class member, if any.
+     */
+    public StringConstant(int    u2stringIndex,
+                          Clazz  referencedClass,
+                          Member referenceMember)
+    {
+        this.u2stringIndex    = u2stringIndex;
+        this.referencedClass  = referencedClass;
+        this.referencedMember = referenceMember;
+    }
+
+
+    /**
+     * Returns the string value.
+     */
+    public String getString(Clazz clazz)
+    {
+        return clazz.getString(u2stringIndex);
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_String;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitStringConstant(clazz, this);
+    }
+
+
+    /**
+     * Lets the referenced class accept the given visitor.
+     */
+    public void referencedClassAccept(ClassVisitor classVisitor)
+    {
+        if (referencedClass  != null &&
+            referencedMember == null)
+        {
+            referencedClass.accept(classVisitor);
+        }
+    }
+
+
+    /**
+     * Lets the referenced member accept the given visitor.
+     */
+    public void referencedMemberAccept(MemberVisitor memberVisitor)
+    {
+        if (referencedMember != null)
+        {
+            referencedMember.accept(referencedClass, memberVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/constant/Utf8Constant.java b/src/proguard/classfile/constant/Utf8Constant.java
new file mode 100644
index 0000000..ae419c9
--- /dev/null
+++ b/src/proguard/classfile/constant/Utf8Constant.java
@@ -0,0 +1,285 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * This Constant represents a UTF-8 constant in the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class Utf8Constant extends Constant
+{
+    private static final char TWO_BYTE_LIMIT     = 0x80;
+    private static final int  TWO_BYTE_CONSTANT1 = 0xc0;
+    private static final int  TWO_BYTE_CONSTANT2 = 0x80;
+    private static final int  TWO_BYTE_SHIFT1    = 6;
+    private static final int  TWO_BYTE_MASK1     = 0x1f;
+    private static final int  TWO_BYTE_MASK2     = 0x3f;
+
+    private static final char THREE_BYTE_LIMIT     = 0x800;
+    private static final int  THREE_BYTE_CONSTANT1 = 0xe0;
+    private static final int  THREE_BYTE_CONSTANT2 = 0x80;
+    private static final int  THREE_BYTE_CONSTANT3 = 0x80;
+    private static final int  THREE_BYTE_SHIFT1    = 12;
+    private static final int  THREE_BYTE_SHIFT2    = 6;
+    private static final int  THREE_BYTE_MASK1     = 0x0f;
+    private static final int  THREE_BYTE_MASK2     = 0x3f;
+    private static final int  THREE_BYTE_MASK3     = 0x3f;
+
+
+    // There are a lot of Utf8Constant objects, so we're optimising their storage.
+    // Initially, we're storing the UTF-8 bytes in a byte array.
+    // When the corresponding String is requested, we ditch the array and just
+    // store the String.
+
+    //private int u2length;
+    private byte[] bytes;
+
+    private String string;
+
+
+    /**
+     * Creates an uninitialized Utf8Constant.
+     *
+     */
+    public Utf8Constant()
+    {
+    }
+
+
+    /**
+     * Creates a Utf8Constant containing the given string.
+     */
+    public Utf8Constant(String string)
+    {
+        this.bytes  = null;
+        this.string = string;
+    }
+
+
+    /**
+     * Initializes the UTF-8 data with an array of bytes.
+     */
+    public void setBytes(byte[] bytes)
+    {
+        this.bytes  = bytes;
+        this.string = null;
+    }
+
+
+    /**
+     * Returns the UTF-8 data as an array of bytes.
+     */
+    public byte[] getBytes()
+    {
+        try
+        {
+            switchToByteArrayRepresentation();
+        }
+        catch (UnsupportedEncodingException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+
+        return bytes;
+    }
+
+
+    /**
+     * Initializes the UTF-8 data with a String.
+     */
+    public void setString(String utf8String)
+    {
+        this.bytes  = null;
+        this.string = utf8String;
+    }
+
+
+    /**
+     * Returns the UTF-8 data as a String.
+     */
+    public String getString()
+    {
+        try
+        {
+            switchToStringRepresentation();
+        }
+        catch (UnsupportedEncodingException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+
+        return string;
+    }
+
+
+    // Implementations for Constant.
+
+    public int getTag()
+    {
+        return ClassConstants.CONSTANT_Utf8;
+    }
+
+    public void accept(Clazz clazz, ConstantVisitor constantVisitor)
+    {
+        constantVisitor.visitUtf8Constant(clazz, this);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Switches to a byte array representation of the UTF-8 data.
+     */
+    private void switchToByteArrayRepresentation() throws UnsupportedEncodingException
+    {
+        if (bytes == null)
+        {
+            bytes  = getByteArrayRepresentation(string);
+            string = null;
+        }
+    }
+
+
+    /**
+     * Switches to a String representation of the UTF-8 data.
+     */
+    private void switchToStringRepresentation() throws UnsupportedEncodingException
+    {
+        if (string == null)
+        {
+            string = getStringRepresentation(bytes);
+            bytes  = null;
+        }
+    }
+
+
+    /**
+     * Returns the modified UTF-8 byte array representation of the given string.
+     */
+    private byte[] getByteArrayRepresentation(String string) throws UnsupportedEncodingException
+    {
+        // We're computing the byte array ourselves, because the implementation
+        // of String.getBytes("UTF-8") has a bug, at least up to JRE 1.4.2.
+        // Also note the special treatment of the 0 character.
+
+        // Compute the byte array length.
+        int byteLength   = 0;
+        int stringLength = string.length();
+        for (int stringIndex = 0; stringIndex < stringLength; stringIndex++)
+        {
+            char c = string.charAt(stringIndex);
+
+            // The character is represented by one, two, or three bytes.
+            byteLength += c == 0                ? 2 :
+                          c <  TWO_BYTE_LIMIT   ? 1 :
+                          c <  THREE_BYTE_LIMIT ? 2 :
+                                                  3;
+        }
+
+        // Allocate the byte array with the computed length.
+        byte[] bytes  = new byte[byteLength];
+
+        // Fill out the array.
+        int byteIndex = 0;
+        for (int stringIndex = 0; stringIndex < stringLength; stringIndex++)
+        {
+            char c = string.charAt(stringIndex);
+            if (c == 0)
+            {
+                // The 0 character gets a two-byte representation in classes.
+                bytes[byteIndex++] = (byte)TWO_BYTE_CONSTANT1;
+                bytes[byteIndex++] = (byte)TWO_BYTE_CONSTANT2;
+            }
+            else if (c < TWO_BYTE_LIMIT)
+            {
+                // The character is represented by a single byte.
+                bytes[byteIndex++] = (byte)c;
+            }
+            else if (c < THREE_BYTE_LIMIT)
+            {
+                // The character is represented by two bytes.
+                bytes[byteIndex++] = (byte)(TWO_BYTE_CONSTANT1 | ((c >>> TWO_BYTE_SHIFT1) & TWO_BYTE_MASK1));
+                bytes[byteIndex++] = (byte)(TWO_BYTE_CONSTANT2 | ( c                      & TWO_BYTE_MASK2));
+            }
+            else
+            {
+                // The character is represented by three bytes.
+                bytes[byteIndex++] = (byte)(THREE_BYTE_CONSTANT1 | ((c >>> THREE_BYTE_SHIFT1) & THREE_BYTE_MASK1));
+                bytes[byteIndex++] = (byte)(THREE_BYTE_CONSTANT2 | ((c >>> THREE_BYTE_SHIFT2) & THREE_BYTE_MASK2));
+                bytes[byteIndex++] = (byte)(THREE_BYTE_CONSTANT3 | ( c                        & THREE_BYTE_MASK3));
+            }
+        }
+
+        return bytes;
+    }
+
+
+    /**
+     * Returns the String representation of the given modified UTF-8 byte array.
+     */
+    private String getStringRepresentation(byte[] bytes) throws UnsupportedEncodingException
+    {
+        // We're computing the string ourselves, because the implementation
+        // of "new String(bytes)" doesn't honor the special treatment of
+        // the 0 character in JRE 1.6_u11.
+
+        // Allocate the byte array with the computed length.
+        char[] chars  = new char[bytes.length];
+
+        // Fill out the array.
+        int charIndex = 0;
+        int byteIndex = 0;
+        while (byteIndex < bytes.length)
+        {
+
+            int b = bytes[byteIndex++] & 0xff;
+
+            // Depending on the flag bits in the first byte, the character
+            // is represented by a single byte, by two bytes, or by three
+            // bytes. We're not checking the redundant flag bits in the
+            // second byte and the third byte.
+            try
+            {
+                chars[charIndex++] =
+                    (char)(b < TWO_BYTE_CONSTANT1   ? b                                                          :
+
+                           b < THREE_BYTE_CONSTANT1 ? ((b                  & TWO_BYTE_MASK1) << TWO_BYTE_SHIFT1) |
+                                                      ((bytes[byteIndex++] & TWO_BYTE_MASK2)                   ) :
+
+                                                      ((b                  & THREE_BYTE_MASK1) << THREE_BYTE_SHIFT1) |
+                                                      ((bytes[byteIndex++] & THREE_BYTE_MASK2) << THREE_BYTE_SHIFT2) |
+                                                      ((bytes[byteIndex++] & THREE_BYTE_MASK3)                     ));
+            }
+            catch (ArrayIndexOutOfBoundsException e)
+            {
+                throw new UnsupportedEncodingException("Missing UTF-8 bytes after initial byte [0x"+Integer.toHexString(b)+"] in string ["+new String(chars, 0, charIndex)+"]");
+            }
+        }
+
+        return new String(chars, 0, charIndex);
+    }
+}
diff --git a/src/proguard/classfile/constant/visitor/AllConstantVisitor.java b/src/proguard/classfile/constant/visitor/AllConstantVisitor.java
new file mode 100644
index 0000000..d2d3c2c
--- /dev/null
+++ b/src/proguard/classfile/constant/visitor/AllConstantVisitor.java
@@ -0,0 +1,53 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+
+/**
+ * This ClassVisitor lets a given ConstantVisitor visit all constant pool
+ * entries of the program classes it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllConstantVisitor implements ClassVisitor
+{
+    private final ConstantVisitor constantVisitor;
+
+
+    public AllConstantVisitor(ConstantVisitor constantVisitor)
+    {
+        this.constantVisitor = constantVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.constantPoolEntriesAccept(constantVisitor);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+}
diff --git a/src/proguard/classfile/constant/visitor/ConstantVisitor.java b/src/proguard/classfile/constant/visitor/ConstantVisitor.java
new file mode 100644
index 0000000..6cae352
--- /dev/null
+++ b/src/proguard/classfile/constant/visitor/ConstantVisitor.java
@@ -0,0 +1,46 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant.visitor;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.constant.*;
+
+
+/**
+ * This interface specifies the methods for a visitor of <code>Constant</code>
+ * objects.
+ *
+ * @author Eric Lafortune
+ */
+public interface ConstantVisitor
+{
+    public void visitIntegerConstant(           Clazz clazz, IntegerConstant            integerConstant);
+    public void visitLongConstant(              Clazz clazz, LongConstant               longConstant);
+    public void visitFloatConstant(             Clazz clazz, FloatConstant              floatConstant);
+    public void visitDoubleConstant(            Clazz clazz, DoubleConstant             doubleConstant);
+    public void visitStringConstant(            Clazz clazz, StringConstant             stringConstant);
+    public void visitUtf8Constant(              Clazz clazz, Utf8Constant               utf8Constant);
+    public void visitFieldrefConstant(          Clazz clazz, FieldrefConstant           fieldrefConstant);
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant);
+    public void visitMethodrefConstant(         Clazz clazz, MethodrefConstant          methodrefConstant);
+    public void visitClassConstant(             Clazz clazz, ClassConstant              classConstant);
+    public void visitNameAndTypeConstant(       Clazz clazz, NameAndTypeConstant        nameAndTypeConstant);
+}
diff --git a/src/proguard/classfile/constant/visitor/ExceptClassConstantFilter.java b/src/proguard/classfile/constant/visitor/ExceptClassConstantFilter.java
new file mode 100644
index 0000000..fbb3e52
--- /dev/null
+++ b/src/proguard/classfile/constant/visitor/ExceptClassConstantFilter.java
@@ -0,0 +1,69 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.constant.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.editor.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This <code>ConstantVisitor</code> delegates its visits to class constants
+ * to another given <code>ConstantVisitor</code>, except for one given class.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptClassConstantFilter
+extends      SimplifiedVisitor
+implements   ConstantVisitor
+{
+    private final String           exceptClassName;
+    private final ConstantVisitor constantVisitor;
+
+
+    /**
+     * Creates a new ClassNameFilter.
+     * @param exceptClassName the name of the class that will not be visited.
+     * @param constantVisitor the <code>ConstantVisitor</code> to which visits
+     *                        will be delegated.
+     */
+        public ExceptClassConstantFilter(String          exceptClassName,
+                                         ConstantVisitor constantVisitor)
+    {
+            this.exceptClassName = exceptClassName;
+            this.constantVisitor = constantVisitor;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        if (!classConstant.getName(clazz).equals(exceptClassName))
+        {
+            constantVisitor.visitClassConstant(clazz, classConstant);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/constant/visitor/package.html b/src/proguard/classfile/constant/visitor/package.html
new file mode 100644
index 0000000..e20f48e
--- /dev/null
+++ b/src/proguard/classfile/constant/visitor/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains visitors for class constants.
+</body>
diff --git a/src/proguard/classfile/editor/AccessFixer.java b/src/proguard/classfile/editor/AccessFixer.java
new file mode 100644
index 0000000..7d6274e
--- /dev/null
+++ b/src/proguard/classfile/editor/AccessFixer.java
@@ -0,0 +1,164 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ConstantVisitor fixes the access modifiers of all classes and class
+ * members that are referenced by the constants that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AccessFixer
+extends      SimplifiedVisitor
+implements   ConstantVisitor,
+             ClassVisitor,
+             MemberVisitor
+{
+    private MyReferencedClassFinder referencedClassFinder = new MyReferencedClassFinder();
+
+    private Clazz referencingClass;
+    private Clazz referencedClass;
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        referencingClass = clazz;
+        referencedClass  = stringConstant.referencedClass;
+
+        // Make sure the access flags of the referenced class or class member,
+        // if any, are acceptable.
+        stringConstant.referencedClassAccept(this);
+        stringConstant.referencedMemberAccept(this);
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        referencingClass = clazz;
+
+        // Remember the specified class, since it might be different from
+        // the referenced class that acutally contains the class member.
+        clazz.constantPoolEntryAccept(refConstant.u2classIndex, referencedClassFinder);
+
+        // Make sure the access flags of the referenced class member are
+        // acceptable.
+        refConstant.referencedMemberAccept(this);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        referencingClass = clazz;
+
+        // Make sure the access flags of the referenced class are acceptable.
+        classConstant.referencedClassAccept(this);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        int currentAccessFlags  = programClass.getAccessFlags();
+        int currentAccessLevel  = AccessUtil.accessLevel(currentAccessFlags);
+
+        // Compute the required access level.
+        Clazz referencingClass = this.referencingClass;
+        int requiredAccessLevel =
+            inSamePackage(programClass, referencingClass) ? AccessUtil.PACKAGE_VISIBLE :
+                                                            AccessUtil.PUBLIC;
+
+        // Fix the class access flags if necessary.
+        if (currentAccessLevel < requiredAccessLevel)
+        {
+            programClass.u2accessFlags =
+                AccessUtil.replaceAccessFlags(currentAccessFlags,
+                                              AccessUtil.accessFlags(requiredAccessLevel));
+        }
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember) {}
+
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        int currentAccessFlags  = programMember.getAccessFlags();
+        int currentAccessLevel  = AccessUtil.accessLevel(currentAccessFlags);
+
+        // Compute the required access level.
+        int requiredAccessLevel =
+            programClass.equals(referencingClass)         ? AccessUtil.PRIVATE         :
+            inSamePackage(programClass, referencingClass) ? AccessUtil.PACKAGE_VISIBLE :
+            referencedClass.extends_(referencingClass) &&
+            referencingClass.extends_(programClass)       ? AccessUtil.PROTECTED       :
+                                                            AccessUtil.PUBLIC;
+
+        // Fix the class member access flags if necessary.
+        if (currentAccessLevel < requiredAccessLevel)
+        {
+            programMember.u2accessFlags =
+                AccessUtil.replaceAccessFlags(currentAccessFlags,
+                                              AccessUtil.accessFlags(requiredAccessLevel));
+        }
+    }
+
+
+    /**
+     * This ConstantVisitor returns the referenced class of the class constant
+     * that it visits.
+     */
+    private class MyReferencedClassFinder
+    extends       SimplifiedVisitor
+    implements    ConstantVisitor
+    {
+        // Implementations for ConstantVisitor.
+        public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+        {
+            referencedClass = classConstant.referencedClass;
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean inSamePackage(ProgramClass class1, Clazz class2)
+    {
+        return ClassUtil.internalPackageName(class1.getName()).equals(
+               ClassUtil.internalPackageName(class2.getName()));
+    }
+}
diff --git a/src/proguard/classfile/editor/AnnotationAdder.java b/src/proguard/classfile/editor/AnnotationAdder.java
new file mode 100644
index 0000000..359164a
--- /dev/null
+++ b/src/proguard/classfile/editor/AnnotationAdder.java
@@ -0,0 +1,153 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.AnnotationVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AnnotationVisitor adds all annotations that it visits to the given
+ * target annotation element value, target annotation attribute, or target
+ * parameter annotation attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class AnnotationAdder
+extends      SimplifiedVisitor
+implements   AnnotationVisitor
+{
+    private static final ElementValue[] EMPTY_ELEMENT_VALUES = new ElementValue[0];
+
+
+    private final ProgramClass                        targetClass;
+    private final AnnotationElementValue              targetAnnotationElementValue;
+    private final AnnotationsAttributeEditor          annotationsAttributeEditor;
+    private final ParameterAnnotationsAttributeEditor parameterAnnotationsAttributeEditor;
+
+    private final ConstantAdder constantAdder;
+
+
+    /**
+     * Creates a new AnnotationAdder that will copy annotations into the given
+     * target annotation element value.
+     */
+    public AnnotationAdder(ProgramClass           targetClass,
+                           AnnotationElementValue targetAnnotationElementValue)
+    {
+        this.targetClass                         = targetClass;
+        this.targetAnnotationElementValue        = targetAnnotationElementValue;
+        this.annotationsAttributeEditor          = null;
+        this.parameterAnnotationsAttributeEditor = null;
+
+        constantAdder = new ConstantAdder(targetClass);
+    }
+
+
+    /**
+     * Creates a new AnnotationAdder that will copy annotations into the given
+     * target annotations attribute.
+     */
+    public AnnotationAdder(ProgramClass         targetClass,
+                           AnnotationsAttribute targetAnnotationsAttribute)
+    {
+        this.targetClass                         = targetClass;
+        this.targetAnnotationElementValue        = null;
+        this.annotationsAttributeEditor          = new AnnotationsAttributeEditor(targetAnnotationsAttribute);
+        this.parameterAnnotationsAttributeEditor = null;
+
+        constantAdder = new ConstantAdder(targetClass);
+    }
+
+
+    /**
+     * Creates a new AnnotationAdder that will copy annotations into the given
+     * target parameter annotations attribute.
+     */
+    public AnnotationAdder(ProgramClass                  targetClass,
+                           ParameterAnnotationsAttribute targetParameterAnnotationsAttribute)
+    {
+        this.targetClass                         = targetClass;
+        this.targetAnnotationElementValue        = null;
+        this.annotationsAttributeEditor          = null;
+        this.parameterAnnotationsAttributeEditor = new ParameterAnnotationsAttributeEditor(targetParameterAnnotationsAttribute);
+
+        constantAdder = new ConstantAdder(targetClass);
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        Annotation newAnnotation =
+            new Annotation(constantAdder.addConstant(clazz, annotation.u2typeIndex),
+                           0,
+                           annotation.u2elementValuesCount > 0 ?
+                               new ElementValue[annotation.u2elementValuesCount] :
+                               EMPTY_ELEMENT_VALUES);
+
+        // TODO: Clone array.
+        newAnnotation.referencedClasses = annotation.referencedClasses;
+
+        // Add the element values.
+        annotation.elementValuesAccept(clazz,
+                                       new ElementValueAdder(targetClass,
+                                                             newAnnotation,
+                                                             false));
+
+        // What's the target?
+        if (targetAnnotationElementValue != null)
+        {
+            // Simply set the completed annotation.
+            targetAnnotationElementValue.annotationValue = newAnnotation;
+        }
+        else
+        {
+            // Add the completed annotation.
+            annotationsAttributeEditor.addAnnotation(newAnnotation);
+        }
+    }
+
+
+    public void visitAnnotation(Clazz clazz, Method method, int parameterIndex, Annotation annotation)
+    {
+        Annotation newAnnotation =
+            new Annotation(constantAdder.addConstant(clazz, annotation.u2typeIndex),
+                           0,
+                           annotation.u2elementValuesCount > 0 ?
+                               new ElementValue[annotation.u2elementValuesCount] :
+                               EMPTY_ELEMENT_VALUES);
+
+        // TODO: Clone array.
+        newAnnotation.referencedClasses = annotation.referencedClasses;
+
+        // Add the element values.
+        annotation.elementValuesAccept(clazz,
+                                       new ElementValueAdder(targetClass,
+                                                             newAnnotation,
+                                                             false));
+
+        // Add the completed annotation.
+        parameterAnnotationsAttributeEditor.addAnnotation(parameterIndex, newAnnotation);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/AnnotationsAttributeEditor.java b/src/proguard/classfile/editor/AnnotationsAttributeEditor.java
new file mode 100644
index 0000000..bf8852c
--- /dev/null
+++ b/src/proguard/classfile/editor/AnnotationsAttributeEditor.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.annotation.*;
+
+/**
+ * This class can add annotations to a given annotations attribute.
+ * Annotations to be added must have been filled out beforehand.
+ *
+ * @author Eric Lafortune
+ */
+public class AnnotationsAttributeEditor
+{
+    private AnnotationsAttribute targetAnnotationsAttribute;
+
+
+    /**
+     * Creates a new AnnotationsAttributeEditor that will edit annotations in
+     * the given annotations attribute.
+     */
+    public AnnotationsAttributeEditor(AnnotationsAttribute targetAnnotationsAttribute)
+    {
+        this.targetAnnotationsAttribute = targetAnnotationsAttribute;
+    }
+
+
+    /**
+     * Adds a given annotation to the annotations attribute.
+     */
+    public void addAnnotation(Annotation annotation)
+    {
+        int          annotationsCount = targetAnnotationsAttribute.u2annotationsCount;
+        Annotation[] annotations      = targetAnnotationsAttribute.annotations;
+
+        // Make sure there is enough space for the new annotation.
+        if (annotations.length <= annotationsCount)
+        {
+            targetAnnotationsAttribute.annotations = new Annotation[annotationsCount+1];
+            System.arraycopy(annotations, 0,
+                             targetAnnotationsAttribute.annotations, 0,
+                             annotationsCount);
+            annotations = targetAnnotationsAttribute.annotations;
+        }
+
+        // Add the annotation.
+        annotations[targetAnnotationsAttribute.u2annotationsCount++] = annotation;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/AttributeAdder.java b/src/proguard/classfile/editor/AttributeAdder.java
new file mode 100644
index 0000000..2b610b7
--- /dev/null
+++ b/src/proguard/classfile/editor/AttributeAdder.java
@@ -0,0 +1,457 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor adds all attributes that it visits to the given
+ * target class, class member, or attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class AttributeAdder
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private static final byte[]          EMPTY_BYTES       = new byte[0];
+    private static final int[]           EMPTY_INTS        = new int[0];
+    private static final Attribute[]     EMPTY_ATTRIBUTES  = new Attribute[0];
+    private static final ExceptionInfo[] EMPTY_EXCEPTIONS  = new ExceptionInfo[0];
+
+
+    private final ProgramClass  targetClass;
+    private final ProgramMember targetMember;
+    private final CodeAttribute targetCodeAttribute;
+    private final boolean       replaceAttributes;
+
+    private final ConstantAdder    constantAdder;
+    private final AttributesEditor attributesEditor;
+
+
+    /**
+     * Creates a new AttributeAdder that will copy attributes into the given
+     * target class.
+     */
+    public AttributeAdder(ProgramClass targetClass,
+                          boolean      replaceAttributes)
+    {
+        this(targetClass, null, null, replaceAttributes);
+    }
+
+
+    /**
+     * Creates a new AttributeAdder that will copy attributes into the given
+     * target class member.
+     */
+    public AttributeAdder(ProgramClass  targetClass,
+                          ProgramMember targetMember,
+                          boolean       replaceAttributes)
+    {
+        this(targetClass, targetMember, null, replaceAttributes);
+    }
+
+
+    /**
+     * Creates a new AttributeAdder that will copy attributes into the given
+     * target attribute.
+     */
+    public AttributeAdder(ProgramClass  targetClass,
+                          ProgramMember targetMember,
+                          CodeAttribute targetCodeAttribute,
+                          boolean       replaceAttributes)
+    {
+        this.targetClass         = targetClass;
+        this.targetMember        = targetMember;
+        this.targetCodeAttribute = targetCodeAttribute;
+        this.replaceAttributes   = replaceAttributes;
+
+        constantAdder    = new ConstantAdder(targetClass);
+        attributesEditor = new AttributesEditor(targetClass,
+                                                targetMember,
+                                                targetCodeAttribute,
+                                                replaceAttributes);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        // Create a copy of the attribute.
+        UnknownAttribute newUnknownAttribute =
+            new UnknownAttribute(constantAdder.addConstant(clazz, unknownAttribute.u2attributeNameIndex),
+                                 unknownAttribute.u4attributeLength,
+                                 unknownAttribute.info);
+
+        // Add it to the target class.
+        attributesEditor.addAttribute(newUnknownAttribute);
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        // Create a copy of the attribute.
+        SourceFileAttribute newSourceFileAttribute =
+            new SourceFileAttribute(constantAdder.addConstant(clazz, sourceFileAttribute.u2attributeNameIndex),
+                                    constantAdder.addConstant(clazz, sourceFileAttribute.u2sourceFileIndex));
+
+        // Add it to the target class.
+        attributesEditor.addAttribute(newSourceFileAttribute);
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        // Create a copy of the attribute.
+        SourceDirAttribute newSourceDirAttribute =
+            new SourceDirAttribute(constantAdder.addConstant(clazz, sourceDirAttribute.u2attributeNameIndex),
+                                   constantAdder.addConstant(clazz, sourceDirAttribute.u2sourceDirIndex));
+
+        // Add it to the target class.
+        attributesEditor.addAttribute(newSourceDirAttribute);
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // TODO: Implement method.
+        // Note that the attribute may already be present.
+//        // Create a copy of the attribute.
+//        InnerClassesAttribute newInnerClassesAttribute =
+//            new InnerClassesAttribute(constantAdder.addConstant(clazz, innerClassesAttribute.u2attributeNameIndex),
+//                                      0,
+//                                      null);
+//
+//        // Add it to the target class.
+//        attributesEditor.addClassAttribute(newInnerClassesAttribute);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        // Create a copy of the attribute.
+        EnclosingMethodAttribute newEnclosingMethodAttribute =
+            new EnclosingMethodAttribute(constantAdder.addConstant(clazz, enclosingMethodAttribute.u2attributeNameIndex),
+                                         constantAdder.addConstant(clazz, enclosingMethodAttribute.u2classIndex),
+                                         enclosingMethodAttribute.u2nameAndTypeIndex == 0 ? 0 :
+                                         constantAdder.addConstant(clazz, enclosingMethodAttribute.u2nameAndTypeIndex));
+
+        newEnclosingMethodAttribute.referencedClass  = enclosingMethodAttribute.referencedClass;
+        newEnclosingMethodAttribute.referencedMethod = enclosingMethodAttribute.referencedMethod;
+
+        // Add it to the target class.
+        attributesEditor.addAttribute(newEnclosingMethodAttribute);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        // Create a copy of the attribute.
+        DeprecatedAttribute newDeprecatedAttribute =
+            new DeprecatedAttribute(constantAdder.addConstant(clazz, deprecatedAttribute.u2attributeNameIndex));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newDeprecatedAttribute);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        // Create a copy of the attribute.
+        SyntheticAttribute newSyntheticAttribute =
+            new SyntheticAttribute(constantAdder.addConstant(clazz, syntheticAttribute.u2attributeNameIndex));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newSyntheticAttribute);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        // Create a copy of the attribute.
+        SignatureAttribute newSignatureAttribute =
+            new SignatureAttribute(constantAdder.addConstant(clazz, signatureAttribute.u2attributeNameIndex),
+                                   constantAdder.addConstant(clazz, signatureAttribute.u2signatureIndex));
+
+        newSignatureAttribute.referencedClasses = signatureAttribute.referencedClasses;
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newSignatureAttribute);
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        // Create a copy of the attribute.
+        ConstantValueAttribute newConstantValueAttribute =
+            new ConstantValueAttribute(constantAdder.addConstant(clazz, constantValueAttribute.u2attributeNameIndex),
+                                       constantAdder.addConstant(clazz, constantValueAttribute.u2constantValueIndex));
+
+        // Add it to the target field.
+        attributesEditor.addAttribute(newConstantValueAttribute);
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        // Create a new exceptions attribute.
+        ExceptionsAttribute newExceptionsAttribute =
+            new ExceptionsAttribute(constantAdder.addConstant(clazz, exceptionsAttribute.u2attributeNameIndex),
+                                    0,
+                                    exceptionsAttribute.u2exceptionIndexTableLength > 0 ?
+                                        new int[exceptionsAttribute.u2exceptionIndexTableLength] :
+                                        EMPTY_INTS);
+
+        // Add the exceptions.
+        exceptionsAttribute.exceptionEntriesAccept((ProgramClass)clazz,
+                                                   new ExceptionAdder(targetClass,
+                                                                      newExceptionsAttribute));
+
+        // Add it to the target method.
+        attributesEditor.addAttribute(newExceptionsAttribute);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Create a new code attribute.
+        CodeAttribute newCodeAttribute =
+            new CodeAttribute(constantAdder.addConstant(clazz, codeAttribute.u2attributeNameIndex),
+                              codeAttribute.u2maxStack,
+                              codeAttribute.u2maxLocals,
+                              0,
+                              EMPTY_BYTES,
+                              0,
+                              codeAttribute.u2exceptionTableLength > 0 ?
+                                  new ExceptionInfo[codeAttribute.u2exceptionTableLength] :
+                                  EMPTY_EXCEPTIONS,
+                              0,
+                              codeAttribute.u2attributesCount > 0 ?
+                                  new Attribute[codeAttribute.u2attributesCount] :
+                                  EMPTY_ATTRIBUTES);
+
+        CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer();
+
+        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
+
+        // Add the instructions.
+        codeAttribute.instructionsAccept(clazz,
+                                         method,
+                                         new InstructionAdder(targetClass,
+                                                              codeAttributeComposer));
+
+        // Append a label just after the code.
+        codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
+
+        // Add the exceptions.
+        codeAttribute.exceptionsAccept(clazz,
+                                       method,
+                                       new ExceptionInfoAdder(targetClass,
+                                                              codeAttributeComposer));
+
+        codeAttributeComposer.endCodeFragment();
+
+        // Add the attributes.
+        codeAttribute.attributesAccept(clazz,
+                                       method,
+                                       new AttributeAdder(targetClass,
+                                                          targetMember,
+                                                          newCodeAttribute,
+                                                          replaceAttributes));
+
+        // Apply these changes to the new code attribute.
+        codeAttributeComposer.visitCodeAttribute(targetClass,
+                                                 (Method)targetMember,
+                                                 newCodeAttribute);
+
+        // Add the completed code attribute to the target method.
+        attributesEditor.addAttribute(newCodeAttribute);
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        // TODO: Implement method.
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        // TODO: Implement method.
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        // Create a new line number table attribute.
+        LineNumberTableAttribute newLineNumberTableAttribute =
+            new LineNumberTableAttribute(constantAdder.addConstant(clazz, lineNumberTableAttribute.u2attributeNameIndex),
+                                         0,
+                                         new LineNumberInfo[lineNumberTableAttribute.u2lineNumberTableLength]);
+
+        // Add the line numbers.
+        lineNumberTableAttribute.lineNumbersAccept(clazz,
+                                                   method,
+                                                   codeAttribute,
+                                                   new LineNumberInfoAdder(newLineNumberTableAttribute));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newLineNumberTableAttribute);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Create a new local variable table attribute.
+        LocalVariableTableAttribute newLocalVariableTableAttribute =
+            new LocalVariableTableAttribute(constantAdder.addConstant(clazz, localVariableTableAttribute.u2attributeNameIndex),
+                                            0,
+                                            new LocalVariableInfo[localVariableTableAttribute.u2localVariableTableLength]);
+
+        // Add the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz,
+                                                         method,
+                                                         codeAttribute,
+                                                         new LocalVariableInfoAdder(targetClass, newLocalVariableTableAttribute));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newLocalVariableTableAttribute);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Create a new local variable type table attribute.
+        LocalVariableTypeTableAttribute newLocalVariableTypeTableAttribute =
+            new LocalVariableTypeTableAttribute(constantAdder.addConstant(clazz, localVariableTypeTableAttribute.u2attributeNameIndex),
+                                            0,
+                                            new LocalVariableTypeInfo[localVariableTypeTableAttribute.u2localVariableTypeTableLength]);
+
+        // Add the local variable types.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz,
+                                                             method,
+                                                             codeAttribute,
+                                                             new LocalVariableTypeInfoAdder(targetClass, newLocalVariableTypeTableAttribute));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newLocalVariableTypeTableAttribute);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        // Create a new annotations attribute.
+        RuntimeVisibleAnnotationsAttribute newAnnotationsAttribute =
+            new RuntimeVisibleAnnotationsAttribute(constantAdder.addConstant(clazz, runtimeVisibleAnnotationsAttribute.u2attributeNameIndex),
+                                                   0,
+                                                   new Annotation[runtimeVisibleAnnotationsAttribute.u2annotationsCount]);
+
+        // Add the annotations.
+        runtimeVisibleAnnotationsAttribute.annotationsAccept(clazz,
+                                                             new AnnotationAdder(targetClass,
+                                                                                 newAnnotationsAttribute));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        // Create a new annotations attribute.
+        RuntimeInvisibleAnnotationsAttribute newAnnotationsAttribute =
+            new RuntimeInvisibleAnnotationsAttribute(constantAdder.addConstant(clazz, runtimeInvisibleAnnotationsAttribute.u2attributeNameIndex),
+                                                     0,
+                                                     new Annotation[runtimeInvisibleAnnotationsAttribute.u2annotationsCount]);
+
+        // Add the annotations.
+        runtimeInvisibleAnnotationsAttribute.annotationsAccept(clazz,
+                                                               new AnnotationAdder(targetClass,
+                                                                                   newAnnotationsAttribute));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute)
+    {
+        // Create a new annotations attribute.
+        RuntimeVisibleParameterAnnotationsAttribute newParameterAnnotationsAttribute =
+            new RuntimeVisibleParameterAnnotationsAttribute(constantAdder.addConstant(clazz, runtimeVisibleParameterAnnotationsAttribute.u2attributeNameIndex),
+                                                            0,
+                                                            new int[runtimeVisibleParameterAnnotationsAttribute.u2parametersCount],
+                                                            new Annotation[runtimeVisibleParameterAnnotationsAttribute.u2parametersCount][]);
+
+        // Add the annotations.
+        runtimeVisibleParameterAnnotationsAttribute.annotationsAccept(clazz,
+                                                                      method,
+                                                                      new AnnotationAdder(targetClass,
+                                                                                          newParameterAnnotationsAttribute));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newParameterAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute)
+    {
+        // Create a new annotations attribute.
+        RuntimeInvisibleParameterAnnotationsAttribute newParameterAnnotationsAttribute =
+            new RuntimeInvisibleParameterAnnotationsAttribute(constantAdder.addConstant(clazz, runtimeInvisibleParameterAnnotationsAttribute.u2attributeNameIndex),
+                                                              0,
+                                                              new int[runtimeInvisibleParameterAnnotationsAttribute.u2parametersCount],
+                                                              new Annotation[runtimeInvisibleParameterAnnotationsAttribute.u2parametersCount][]);
+
+        // Add the annotations.
+        runtimeInvisibleParameterAnnotationsAttribute.annotationsAccept(clazz,
+                                                                        method,
+                                                                        new AnnotationAdder(targetClass,
+                                                                                            newParameterAnnotationsAttribute));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newParameterAnnotationsAttribute);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Create a new annotation default attribute.
+        AnnotationDefaultAttribute newAnnotationDefaultAttribute =
+            new AnnotationDefaultAttribute(constantAdder.addConstant(clazz, annotationDefaultAttribute.u2attributeNameIndex),
+                                           null);
+
+        // Add the annotations.
+        annotationDefaultAttribute.defaultValueAccept(clazz,
+                                                      new ElementValueAdder(targetClass,
+                                                                            newAnnotationDefaultAttribute,
+                                                                            false));
+
+        // Add it to the target.
+        attributesEditor.addAttribute(newAnnotationDefaultAttribute);
+    }
+}
diff --git a/src/proguard/classfile/editor/AttributeSorter.java b/src/proguard/classfile/editor/AttributeSorter.java
new file mode 100644
index 0000000..d8e3367
--- /dev/null
+++ b/src/proguard/classfile/editor/AttributeSorter.java
@@ -0,0 +1,89 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+import java.util.*;
+
+/**
+ * This ClassVisitor sorts the attributes of the classes that it visits.
+ * The sorting order is based on the types of the attributes.
+ *
+ * @author Eric Lafortune
+ */
+public class AttributeSorter
+extends      SimplifiedVisitor
+implements   ClassVisitor, MemberVisitor, AttributeVisitor, Comparator
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Sort the attributes.
+        Arrays.sort(programClass.attributes, 0, programClass.u2attributesCount, this);
+
+        // Sort the attributes of the class members.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        // Sort the attributes.
+        Arrays.sort(programMember.attributes, 0, programMember.u2attributesCount, this);
+
+        // Sort the attributes of the attributes.
+        programMember.attributesAccept(programClass, this);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Sort the attributes.
+        Arrays.sort(codeAttribute.attributes, 0, codeAttribute.u2attributesCount, this);
+    }
+
+
+    // Implementations for Comparator.
+
+    public int compare(Object object1, Object object2)
+    {
+        Attribute attribute1 = (Attribute)object1;
+        Attribute attribute2 = (Attribute)object2;
+
+        return attribute1.u2attributeNameIndex < attribute2.u2attributeNameIndex ? -1 :
+               attribute1.u2attributeNameIndex > attribute2.u2attributeNameIndex ?  1 :
+                                                                                    0;
+    }
+}
diff --git a/src/proguard/classfile/editor/AttributesEditor.java b/src/proguard/classfile/editor/AttributesEditor.java
new file mode 100644
index 0000000..10846cc
--- /dev/null
+++ b/src/proguard/classfile/editor/AttributesEditor.java
@@ -0,0 +1,269 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+
+/**
+ * This class can add and delete attributes to and from classes, fields,
+ * methods, and code attributes. Attributes to be added must be filled out
+ * beforehand, including their references to the constant pool. Existing
+ * attributes of the same type are always replaced.
+ *
+ * @author Eric Lafortune
+ */
+public class AttributesEditor
+{
+    private final ProgramClass  targetClass;
+    private final ProgramMember targetMember;
+    private final CodeAttribute targetAttribute;
+    private final boolean       replaceAttributes;
+
+
+    /**
+     * Creates a new AttributeAdder that will edit attributes in the given
+     * target class.
+     */
+    public AttributesEditor(ProgramClass targetClass,
+                            boolean      replaceAttributes)
+    {
+        this(targetClass, null, null, replaceAttributes);
+    }
+
+
+    /**
+     * Creates a new AttributeAdder that will edit attributes in the given
+     * target class member.
+     */
+    public AttributesEditor(ProgramClass  targetClass,
+                            ProgramMember targetMember,
+                            boolean       replaceAttributes)
+    {
+        this(targetClass, targetMember, null, replaceAttributes);
+    }
+
+
+    /**
+     * Creates a new AttributeAdder that will edit attributes in the given
+     * target code attribute.
+     */
+    public AttributesEditor(ProgramClass  targetClass,
+                            ProgramMember targetMember,
+                            CodeAttribute targetAttribute,
+                            boolean       replaceAttributes)
+    {
+        this.targetClass       = targetClass;
+        this.targetMember      = targetMember;
+        this.targetAttribute   = targetAttribute;
+        this.replaceAttributes = replaceAttributes;
+    }
+
+
+    /**
+     * Adds the given attribute to the target.
+     */
+    public void addAttribute(Attribute attribute)
+    {
+        // What's the target?
+        if (targetAttribute != null)
+        {
+            // Try to replace an existing attribute.
+            if (!replaceAttributes ||
+                !replaceAttribute(targetAttribute.u2attributesCount,
+                                  targetAttribute.attributes,
+                                  attribute))
+            {
+                // Otherwise append the attribute.
+                targetAttribute.attributes =
+                    addAttribute(targetAttribute.u2attributesCount,
+                                 targetAttribute.attributes,
+                                 attribute);
+
+                targetAttribute.u2attributesCount++;
+            }
+        }
+        else if (targetMember != null)
+        {
+            // Try to replace an existing attribute.
+            if (!replaceAttributes ||
+                !replaceAttribute(targetMember.u2attributesCount,
+                                  targetMember.attributes,
+                                  attribute))
+            {
+                // Otherwise append the attribute.
+                targetMember.attributes =
+                    addAttribute(targetMember.u2attributesCount,
+                                 targetMember.attributes,
+                                 attribute);
+
+                targetMember.u2attributesCount++;
+            }
+        }
+        else
+        {
+            // Try to replace an existing attribute.
+            if (!replaceAttributes ||
+                !replaceAttribute(targetClass.u2attributesCount,
+                                  targetClass.attributes,
+                                  attribute))
+            {
+                // Otherwise append the attribute.
+                targetClass.attributes =
+                    addAttribute(targetClass.u2attributesCount,
+                                 targetClass.attributes,
+                                 attribute);
+
+                targetClass.u2attributesCount++;
+            }
+        }
+    }
+
+
+    /**
+     * Deletes the specified attribute from the target.
+     */
+    public void deleteAttribute(String attributeName)
+    {
+        // What's the target?
+        if (targetAttribute != null)
+        {
+            targetAttribute.u2attributesCount =
+                deleteAttribute(targetAttribute.u2attributesCount,
+                                targetAttribute.attributes,
+                                attributeName);
+        }
+        else if (targetMember != null)
+        {
+            targetMember.u2attributesCount =
+                deleteAttribute(targetMember.u2attributesCount,
+                                targetMember.attributes,
+                                attributeName);
+        }
+        else
+        {
+            targetClass.u2attributesCount =
+                deleteAttribute(targetClass.u2attributesCount,
+                                targetClass.attributes,
+                                attributeName);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Tries put the given attribute in place of an existing attribute of the
+     * same name, returning whether it was present.
+     */
+    private boolean replaceAttribute(int         attributesCount,
+                                     Attribute[] attributes,
+                                     Attribute   attribute)
+    {
+        // Find the attribute with the same name.
+        int index = findAttribute(attributesCount,
+                                  attributes,
+                                  attribute.getAttributeName(targetClass));
+        if (index < 0)
+        {
+            return false;
+        }
+
+        attributes[index] = attribute;
+
+        return true;
+    }
+
+
+    /**
+     * Appends the given attribute to the given array of attributes, creating a
+     * new array if necessary.
+     */
+    private Attribute[] addAttribute(int         attributesCount,
+                                     Attribute[] attributes,
+                                     Attribute   attribute)
+    {
+        // Is the array too small to contain the additional attribute?
+        if (attributes.length <= attributesCount)
+        {
+            // Create a new array and copy the attributes into it.
+            Attribute[] newAttributes = new Attribute[attributesCount + 1];
+            System.arraycopy(attributes, 0,
+                             newAttributes, 0,
+                             attributesCount);
+            attributes = newAttributes;
+        }
+
+        // Append the attribute.
+        attributes[attributesCount] = attribute;
+
+        return attributes;
+    }
+
+
+    /**
+     * Deletes the attributes with the given name from the given array of
+     * attributes, returning the new number of attributes.
+     */
+    private int deleteAttribute(int         attributesCount,
+                                Attribute[] attributes,
+                                String      attributeName)
+    {
+        // Find the attribute.
+        int index = findAttribute(attributesCount,
+                                  attributes,
+                                  attributeName);
+        if (index < 0)
+        {
+            return attributesCount;
+        }
+
+        // Shift the other attributes in the array.
+        System.arraycopy(attributes, index + 1,
+                         attributes, index,
+                         attributesCount - index - 1);
+
+        // Clear the last entry in the array.
+        attributes[--attributesCount] = null;
+
+        return attributesCount;
+    }
+
+
+    /**
+     * Finds the index of the attribute with the given name in the given
+     * array of attributes.
+     */
+    private int findAttribute(int         attributesCount,
+                              Attribute[] attributes,
+                              String      attributeName)
+    {
+        for (int index = 0; index < attributesCount; index++)
+        {
+            if (attributes[index].getAttributeName(targetClass).equals(attributeName))
+            {
+                return index;
+            }
+        }
+
+        return -1;
+    }
+}
diff --git a/src/proguard/classfile/editor/ClassEditor.java b/src/proguard/classfile/editor/ClassEditor.java
new file mode 100644
index 0000000..e503ea3
--- /dev/null
+++ b/src/proguard/classfile/editor/ClassEditor.java
@@ -0,0 +1,255 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+
+/**
+ * This class can add interfaces and class members to a given class.
+ * Elements to be added must be filled out beforehand, including their
+ * references to the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassEditor
+{
+    private static final boolean DEBUG = false;
+
+    private ProgramClass targetClass;
+
+
+    /**
+     * Creates a new ClassEditor that will edit elements in the given
+     * target class.
+     */
+    public ClassEditor(ProgramClass targetClass)
+    {
+        this.targetClass = targetClass;
+    }
+
+
+    /**
+     * Adds the given interface.
+     */
+    public void addInterface(int interfaceConstantIndex)
+    {
+        int   interfacesCount = targetClass.u2interfacesCount;
+        int[] interfaces      = targetClass.u2interfaces;
+
+        // Make sure there is enough space for the new interface.
+        if (interfaces.length <= interfacesCount)
+        {
+            targetClass.u2interfaces = new int[interfacesCount+1];
+            System.arraycopy(interfaces, 0,
+                             targetClass.u2interfaces, 0,
+                             interfacesCount);
+            interfaces = targetClass.u2interfaces;
+        }
+
+        if (DEBUG)
+        {
+            System.out.println(targetClass.getName()+": adding interface ["+targetClass.getClassName(interfaceConstantIndex)+"]");
+        }
+
+        // Add the interface.
+        interfaces[targetClass.u2interfacesCount++] = interfaceConstantIndex;
+    }
+
+    /**
+     * Removes the given interface.
+     */
+    public void removeInterface(int interfaceConstantIndex)
+    {
+        int   interfacesCount = targetClass.u2interfacesCount;
+        int[] interfaces      = targetClass.u2interfaces;
+
+        int interfaceIndex = findInterfaceIndex(interfaceConstantIndex);
+
+        // Shift the interface entries.
+        System.arraycopy(interfaces, interfaceIndex+1,
+                         interfaces, interfaceIndex,
+                         interfacesCount - interfaceIndex - 1);
+
+        // Clear the last entry.
+        interfaces[--targetClass.u2interfacesCount] = 0;
+    }
+
+
+    /**
+     * Finds the index of the given interface in the target class.
+     */
+
+    private int findInterfaceIndex(int interfaceConstantIndex)
+    {
+        int   interfacesCount = targetClass.u2interfacesCount;
+        int[] interfaces      = targetClass.u2interfaces;
+
+        for (int index = 0; index < interfacesCount; index++)
+        {
+            if (interfaces[index] == interfaceConstantIndex)
+            {
+                return index;
+            }
+        }
+
+        return interfacesCount;
+    }
+
+
+    /**
+     * Adds the given field.
+     */
+    public void addField(Field field)
+    {
+        int     fieldsCount = targetClass.u2fieldsCount;
+        Field[] fields      = targetClass.fields;
+
+        // Make sure there is enough space for the new field.
+        if (fields.length <= fieldsCount)
+        {
+            targetClass.fields = new ProgramField[fieldsCount+1];
+            System.arraycopy(fields, 0,
+                             targetClass.fields, 0,
+                             fieldsCount);
+            fields = targetClass.fields;
+        }
+
+        if (DEBUG)
+        {
+            System.out.println(targetClass.getName()+": adding field ["+field.getName(targetClass)+" "+field.getDescriptor(targetClass)+"]");
+        }
+
+        // Add the field.
+        fields[targetClass.u2fieldsCount++] = field;
+    }
+
+
+    /**
+     * Removes the given field. Note that removing a field that is still being
+     * referenced can cause unpredictable effects.
+     */
+    public void removeField(Field field)
+    {
+        int     fieldsCount = targetClass.u2fieldsCount;
+        Field[] fields      = targetClass.fields;
+
+        int fieldIndex = findFieldIndex(field);
+
+        // Shift the field entries.
+        System.arraycopy(fields, fieldIndex+1,
+                         fields, fieldIndex,
+                         fieldsCount - fieldIndex - 1);
+
+        // Clear the last entry.
+        fields[--targetClass.u2fieldsCount] = null;
+    }
+
+
+    /**
+     * Finds the index of the given field in the target class.
+     */
+
+    private int findFieldIndex(Field field)
+    {
+        int     fieldsCount = targetClass.u2fieldsCount;
+        Field[] fields      = targetClass.fields;
+
+        for (int index = 0; index < fieldsCount; index++)
+        {
+            if (fields[index].equals(field))
+            {
+                return index;
+            }
+        }
+
+        return fieldsCount;
+    }
+
+
+    /**
+     * Adds the given method.
+     */
+    public void addMethod(Method method)
+    {
+        int      methodsCount = targetClass.u2methodsCount;
+        Method[] methods      = targetClass.methods;
+
+        // Make sure there is enough space for the new method.
+        if (methods.length <= methodsCount)
+        {
+            targetClass.methods = new ProgramMethod[methodsCount+1];
+            System.arraycopy(methods, 0,
+                             targetClass.methods, 0,
+                             methodsCount);
+            methods = targetClass.methods;
+        }
+
+        if (DEBUG)
+        {
+            System.out.println(targetClass.getName()+": adding method ["+method.getName(targetClass)+method.getDescriptor(targetClass)+"]");
+        }
+
+        // Add the method.
+        methods[targetClass.u2methodsCount++] = method;
+    }
+
+
+    /**
+     * Removes the given method. Note that removing a method that is still being
+     * referenced can cause unpredictable effects.
+     */
+    public void removeMethod(Method method)
+    {
+        int      methodsCount = targetClass.u2methodsCount;
+        Method[] methods      = targetClass.methods;
+
+        int methodIndex = findMethodIndex(method);
+
+        // Shift the method entries.
+        System.arraycopy(methods, methodIndex+1,
+                         methods, methodIndex,
+                         methodsCount - methodIndex - 1);
+
+        // Clear the last entry.
+        methods[--targetClass.u2methodsCount] = null;
+    }
+
+
+    /**
+     * Finds the index of the given method in the target class.
+     */
+
+    private int findMethodIndex(Method method)
+    {
+        int      methodsCount = targetClass.u2methodsCount;
+        Method[] methods      = targetClass.methods;
+
+        for (int index = 0; index < methodsCount; index++)
+        {
+            if (methods[index].equals(method))
+            {
+                return index;
+            }
+        }
+
+        return methodsCount;
+    }
+}
diff --git a/src/proguard/classfile/editor/ClassElementSorter.java b/src/proguard/classfile/editor/ClassElementSorter.java
new file mode 100644
index 0000000..3256c88
--- /dev/null
+++ b/src/proguard/classfile/editor/ClassElementSorter.java
@@ -0,0 +1,52 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.ProgramClass;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor sorts the various elements of the classes that it visits:
+ * interfaces, constants, fields, methods, and attributes.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassElementSorter
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private final ClassVisitor interfaceSorter    = new InterfaceSorter();
+    private final ClassVisitor constantPoolSorter = new ConstantPoolSorter();
+//  private ClassVisitor classMemberSorter  = new ClassMemberSorter();
+    private final ClassVisitor attributeSorter    = new AttributeSorter();
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.accept(constantPoolSorter);
+        programClass.accept(interfaceSorter);
+//      programClass.accept(classMemberSorter);
+        programClass.accept(attributeSorter);
+    }
+}
diff --git a/src/proguard/classfile/editor/ClassMemberSorter.java b/src/proguard/classfile/editor/ClassMemberSorter.java
new file mode 100644
index 0000000..f31fcd0
--- /dev/null
+++ b/src/proguard/classfile/editor/ClassMemberSorter.java
@@ -0,0 +1,69 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+import java.util.*;
+
+/**
+ * This ClassVisitor sorts the class members of the classes that it visits.
+ * The sorting order is based on the access flags, the names, and the
+ * descriptors.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassMemberSorter implements ClassVisitor, Comparator
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Sort the fields.
+        Arrays.sort(programClass.fields, 0, programClass.u2fieldsCount, this);
+
+        // Sort the methods.
+        Arrays.sort(programClass.methods, 0, programClass.u2methodsCount, this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+    }
+
+
+    // Implementations for Comparator.
+
+    public int compare(Object object1, Object object2)
+    {
+        ProgramMember member1 = (ProgramMember)object1;
+        ProgramMember member2 = (ProgramMember)object2;
+
+        return member1.u2accessFlags     < member2.u2accessFlags     ? -1 :
+               member1.u2accessFlags     > member2.u2accessFlags     ?  1 :
+               member1.u2nameIndex       < member2.u2nameIndex       ? -1 :
+               member1.u2nameIndex       > member2.u2nameIndex       ?  1 :
+               member1.u2descriptorIndex < member2.u2descriptorIndex ? -1 :
+               member1.u2descriptorIndex > member2.u2descriptorIndex ?  1 :
+                                                                        0;
+    }
+}
diff --git a/src/proguard/classfile/editor/ClassReferenceFixer.java b/src/proguard/classfile/editor/ClassReferenceFixer.java
new file mode 100644
index 0000000..9857903
--- /dev/null
+++ b/src/proguard/classfile/editor/ClassReferenceFixer.java
@@ -0,0 +1,546 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor fixes references of constant pool entries, fields,
+ * methods, and attributes to classes whose names have changed. Descriptors
+ * of member references are not updated yet.
+ *
+ * @see MemberReferenceFixer
+ * @author Eric Lafortune
+ */
+public class ClassReferenceFixer
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             MemberVisitor,
+             AttributeVisitor,
+             InnerClassesInfoVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    private final boolean ensureUniqueMemberNames;
+
+
+    /**
+     * Creates a new ClassReferenceFixer.
+     * @param ensureUniqueMemberNames specifies whether class members whose
+     *                                descriptor changes should get new, unique
+     *                                names, in order to avoid naming conflicts
+     *                                with similar methods.
+     */
+    public ClassReferenceFixer(boolean ensureUniqueMemberNames)
+    {
+        this.ensureUniqueMemberNames = ensureUniqueMemberNames;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Fix the constant pool.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Fix class members.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+
+        // Fix the attributes.
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Fix class members.
+        libraryClass.fieldsAccept(this);
+        libraryClass.methodsAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Has the descriptor changed?
+        String descriptor    = programField.getDescriptor(programClass);
+        String newDescriptor = newDescriptor(descriptor,
+                                             programField.referencedClass);
+
+        if (!descriptor.equals(newDescriptor))
+        {
+            ConstantPoolEditor constantPoolEditor =
+                new ConstantPoolEditor(programClass);
+
+            // Update the descriptor.
+            programField.u2descriptorIndex =
+                constantPoolEditor.addUtf8Constant(newDescriptor);
+
+            // Update the name, if requested.
+            if (ensureUniqueMemberNames)
+            {
+                String name    = programField.getName(programClass);
+                String newName = newUniqueMemberName(name, descriptor);
+                programField.u2nameIndex =
+                    constantPoolEditor.addUtf8Constant(newName);
+            }
+        }
+
+        // Fix the attributes.
+        programField.attributesAccept(programClass, this);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Has the descriptor changed?
+        String descriptor    = programMethod.getDescriptor(programClass);
+        String newDescriptor = newDescriptor(descriptor,
+                                             programMethod.referencedClasses);
+
+        if (!descriptor.equals(newDescriptor))
+        {
+            ConstantPoolEditor constantPoolEditor =
+                new ConstantPoolEditor(programClass);
+
+            // Update the descriptor.
+            programMethod.u2descriptorIndex =
+                constantPoolEditor.addUtf8Constant(newDescriptor);
+
+            // Update the name, if requested.
+            if (ensureUniqueMemberNames)
+            {
+                String name    = programMethod.getName(programClass);
+                String newName = newUniqueMemberName(name, descriptor);
+                programMethod.u2nameIndex =
+                    constantPoolEditor.addUtf8Constant(newName);
+            }
+        }
+
+        // Fix the attributes.
+        programMethod.attributesAccept(programClass, this);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        // Has the descriptor changed?
+        String descriptor    = libraryField.getDescriptor(libraryClass);
+        String newDescriptor = newDescriptor(descriptor,
+                                             libraryField.referencedClass);
+
+        // Update the descriptor.
+        libraryField.descriptor = newDescriptor;
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        // Has the descriptor changed?
+        String descriptor    = libraryMethod.getDescriptor(libraryClass);
+        String newDescriptor = newDescriptor(descriptor,
+                                             libraryMethod.referencedClasses);
+
+        // Update the descriptor.
+        libraryMethod.descriptor = newDescriptor;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Does the string refer to a class, due to a Class.forName construct?
+        Clazz  referencedClass  = stringConstant.referencedClass;
+        Member referencedMember = stringConstant.referencedMember;
+        if (referencedClass  != null &&
+            referencedMember == null)
+        {
+            // Reconstruct the new class name.
+            String externalClassName    = stringConstant.getString(clazz);
+            String internalClassName    = ClassUtil.internalClassName(externalClassName);
+            String newInternalClassName = newClassName(internalClassName,
+                                                       referencedClass);
+
+            // Update the String entry if required.
+            if (!newInternalClassName.equals(internalClassName))
+            {
+                String newExternalClassName = ClassUtil.externalClassName(newInternalClassName);
+
+                // Refer to a new Utf8 entry.
+                stringConstant.u2stringIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newExternalClassName);
+            }
+        }
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Do we know the referenced class?
+        Clazz referencedClass = classConstant.referencedClass;
+        if (referencedClass != null)
+        {
+            // Has the class name changed?
+            String className    = classConstant.getName(clazz);
+            String newClassName = newClassName(className, referencedClass);
+            if (!className.equals(newClassName))
+            {
+                // Refer to a new Utf8 entry.
+                classConstant.u2nameIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName);
+            }
+        }
+    }
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // Fix the inner class names.
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Fix the attributes.
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Fix the types of the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Fix the signatures of the local variables.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        // Compute the new signature.
+        String signature    = clazz.getString(signatureAttribute.u2signatureIndex);
+        String newSignature = newDescriptor(signature,
+                                            signatureAttribute.referencedClasses);
+
+        if (!signature.equals(newSignature))
+        {
+            signatureAttribute.u2signatureIndex =
+                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature);
+        }
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Fix the annotations.
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Fix the annotations.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Fix the annotation.
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        // Fix the inner class name.
+        int innerClassIndex = innerClassesInfo.u2innerClassIndex;
+        int innerNameIndex  = innerClassesInfo.u2innerNameIndex;
+        if (innerClassIndex != 0 &&
+            innerNameIndex  != 0)
+        {
+            String newInnerName = clazz.getClassName(innerClassIndex);
+            int index = newInnerName.lastIndexOf(ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR);
+            if (index >= 0)
+            {
+                innerClassesInfo.u2innerNameIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newInnerName.substring(index + 1));
+            }
+        }
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        // Has the descriptor changed?
+        String descriptor    = clazz.getString(localVariableInfo.u2descriptorIndex);
+        String newDescriptor = newDescriptor(descriptor,
+                                             localVariableInfo.referencedClass);
+
+        if (!descriptor.equals(newDescriptor))
+        {
+            // Refer to a new Utf8 entry.
+            localVariableInfo.u2descriptorIndex =
+                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newDescriptor);
+        }
+    }
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        // Has the signature changed?
+        String signature    = clazz.getString(localVariableTypeInfo.u2signatureIndex);
+        String newSignature = newDescriptor(signature,
+                                            localVariableTypeInfo.referencedClasses);
+
+        if (!signature.equals(newSignature))
+        {
+            localVariableTypeInfo.u2signatureIndex =
+                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature);
+        }
+    }
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        // Compute the new type name.
+        String typeName    = clazz.getString(annotation.u2typeIndex);
+        String newTypeName = newDescriptor(typeName,
+                                           annotation.referencedClasses);
+
+        if (!typeName.equals(newTypeName))
+        {
+            // Refer to a new Utf8 entry.
+            annotation.u2typeIndex =
+                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName);
+        }
+
+        // Fix the element values.
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        // Compute the new type name.
+        String typeName    = clazz.getString(enumConstantElementValue.u2typeNameIndex);
+        String newTypeName = newDescriptor(typeName,
+                                           enumConstantElementValue.referencedClasses);
+
+        if (!typeName.equals(newTypeName))
+        {
+            // Refer to a new Utf8 entry.
+            enumConstantElementValue.u2typeNameIndex =
+                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newTypeName);
+        }
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        // Compute the new class name.
+        String className    = clazz.getString(classElementValue.u2classInfoIndex);
+        String newClassName = newDescriptor(className,
+                                            classElementValue.referencedClasses);
+
+        if (!className.equals(newClassName))
+        {
+            // Refer to a new Utf8 entry.
+            classElementValue.u2classInfoIndex =
+                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newClassName);
+        }
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        // Fix the annotation.
+        annotationElementValue.annotationAccept(clazz, this);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        // Fix the element values.
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+    }
+
+
+    // Small utility methods.
+
+    private static String newDescriptor(String descriptor,
+                                        Clazz  referencedClass)
+    {
+        // If there is no referenced class, the descriptor won't change.
+        if (referencedClass == null)
+        {
+            return descriptor;
+        }
+
+        // Unravel and reconstruct the class element of the descriptor.
+        DescriptorClassEnumeration descriptorClassEnumeration =
+            new DescriptorClassEnumeration(descriptor);
+
+        StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length());
+        newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff());
+
+        // Only if the descriptor contains a class name (e.g. with an array of
+        // primitive types), the descriptor can change.
+        if (descriptorClassEnumeration.hasMoreClassNames())
+        {
+            String className = descriptorClassEnumeration.nextClassName();
+            String fluff     = descriptorClassEnumeration.nextFluff();
+
+            String newClassName = newClassName(className,
+                                               referencedClass);
+
+            newDescriptorBuffer.append(newClassName);
+            newDescriptorBuffer.append(fluff);
+        }
+
+        return newDescriptorBuffer.toString();
+    }
+
+
+    private static String newDescriptor(String  descriptor,
+                                        Clazz[] referencedClasses)
+    {
+        // If there are no referenced classes, the descriptor won't change.
+        if (referencedClasses == null ||
+            referencedClasses.length == 0)
+        {
+            return descriptor;
+        }
+
+        // Unravel and reconstruct the class elements of the descriptor.
+        DescriptorClassEnumeration descriptorClassEnumeration =
+            new DescriptorClassEnumeration(descriptor);
+
+        StringBuffer newDescriptorBuffer = new StringBuffer(descriptor.length());
+        newDescriptorBuffer.append(descriptorClassEnumeration.nextFluff());
+
+        int index = 0;
+        while (descriptorClassEnumeration.hasMoreClassNames())
+        {
+            String  className        = descriptorClassEnumeration.nextClassName();
+            boolean isInnerClassName = descriptorClassEnumeration.isInnerClassName();
+            String  fluff            = descriptorClassEnumeration.nextFluff();
+
+            String newClassName = newClassName(className,
+                                               referencedClasses[index++]);
+
+            // Strip the outer class name again, if it's an inner class.
+            if (isInnerClassName)
+            {
+                newClassName =
+                    newClassName.substring(newClassName.lastIndexOf(ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR)+1);
+            }
+
+            newDescriptorBuffer.append(newClassName);
+            newDescriptorBuffer.append(fluff);
+        }
+
+        return newDescriptorBuffer.toString();
+    }
+
+
+    /**
+     * Returns a unique class member name, based on the given name and descriptor.
+     */
+    private String newUniqueMemberName(String name, String descriptor)
+    {
+        return name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ?
+            ClassConstants.INTERNAL_METHOD_NAME_INIT :
+            name + ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode()));
+    }
+
+
+    /**
+     * Returns the new class name based on the given class name and the new
+     * name of the given referenced class. Class names of array types
+     * are handled properly.
+     */
+    private static String newClassName(String className,
+                                       Clazz  referencedClass)
+    {
+        // If there is no referenced class, the class name won't change.
+        if (referencedClass == null)
+        {
+            return className;
+        }
+
+        // Reconstruct the class name.
+        String newClassName = referencedClass.getName();
+
+        // Is it an array type?
+        if (className.charAt(0) == ClassConstants.INTERNAL_TYPE_ARRAY)
+        {
+            // Add the array prefixes and suffix "[L...;".
+            newClassName =
+                 className.substring(0, className.indexOf(ClassConstants.INTERNAL_TYPE_CLASS_START)+1) +
+                 newClassName +
+                 ClassConstants.INTERNAL_TYPE_CLASS_END;
+        }
+
+        return newClassName;
+    }
+}
diff --git a/src/proguard/classfile/editor/CodeAttributeComposer.java b/src/proguard/classfile/editor/CodeAttributeComposer.java
new file mode 100644
index 0000000..e783203
--- /dev/null
+++ b/src/proguard/classfile/editor/CodeAttributeComposer.java
@@ -0,0 +1,845 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.preverification.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor accumulates instructions and exceptions, and then
+ * copies them into code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class CodeAttributeComposer
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ExceptionInfoVisitor,
+             StackMapFrameVisitor,
+             VerificationTypeVisitor,
+             LineNumberInfoVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    public  static       boolean DEBUG = true;
+    //*/
+
+
+    private static final int MAXIMUM_LEVELS = 32;
+    private static final int INVALID        = -1;
+
+
+    private boolean allowExternalExceptionHandlers;
+
+    private int maximumCodeLength;
+    private int codeLength;
+    private int exceptionTableLength;
+    private int level = -1;
+
+    private byte[]  code                  = new byte[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int[]   oldInstructionOffsets = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+
+    private final int[]   codeFragmentOffsets  = new int[MAXIMUM_LEVELS];
+    private final int[]   codeFragmentLengths  = new int[MAXIMUM_LEVELS];
+    private final int[][] instructionOffsetMap = new int[MAXIMUM_LEVELS][ClassConstants.TYPICAL_CODE_LENGTH + 1];
+
+    private ExceptionInfo[] exceptionTable = new ExceptionInfo[ClassConstants.TYPICAL_EXCEPTION_TABLE_LENGTH];
+
+    private int expectedStackMapFrameOffset;
+
+    private final StackSizeUpdater    stackSizeUpdater    = new StackSizeUpdater();
+    private final VariableSizeUpdater variableSizeUpdater = new VariableSizeUpdater();
+//    private final InstructionWriter   instructionWriter   = new InstructionWriter();
+
+
+    /**
+     * Creates a new CodeAttributeComposer that doesn't allow external exception
+     * handlers.
+     */
+    public CodeAttributeComposer()
+    {
+        this(false);
+    }
+
+
+    /**
+     * Creates a new CodeAttributeComposer that optionally allows external
+     * exception handlers.
+     */
+    public CodeAttributeComposer(boolean allowExternalExceptionHandlers)
+    {
+        this.allowExternalExceptionHandlers = allowExternalExceptionHandlers;
+    }
+
+
+    /**
+     * Starts a new code definition.
+     */
+    public void reset()
+    {
+        maximumCodeLength    = 0;
+        codeLength           = 0;
+        exceptionTableLength = 0;
+        level                = -1;
+    }
+
+
+    /**
+     * Starts a new code fragment. Branch instructions that are added are
+     * assumed to be relative within such code fragments.
+     * @param maximumCodeFragmentLength the maximum length of the code that will
+     *                                  be added as part of this fragment.
+     */
+    public void beginCodeFragment(int maximumCodeFragmentLength)
+    {
+        level++;
+
+        if (level >= MAXIMUM_LEVELS)
+        {
+            throw new IllegalArgumentException("Maximum number of code fragment levels exceeded ["+level+"]");
+        }
+
+//        // TODO: Figure out some length.
+//        if (level == 0)
+//        {
+//            // Prepare for possible widening of instructions.
+//            instructionWriter.reset(2 * maximumCodeFragmentLength);
+//        }
+
+        // Make sure there is sufficient space for adding the code fragment.
+        maximumCodeLength += maximumCodeFragmentLength;
+
+        ensureCodeLength(maximumCodeLength);
+
+        // Try to reuse the previous array for this code fragment.
+        if (instructionOffsetMap[level].length <= maximumCodeFragmentLength)
+        {
+            instructionOffsetMap[level] = new int[maximumCodeFragmentLength + 1];
+        }
+
+        // Initialize the offset map.
+        for (int index = 0; index <= maximumCodeFragmentLength; index++)
+        {
+            instructionOffsetMap[level][index] = INVALID;
+        }
+
+        // Remember the location of the code fragment.
+        codeFragmentOffsets[level] = codeLength;
+        codeFragmentLengths[level] = maximumCodeFragmentLength;
+    }
+
+
+    /**
+     * Appends the given instruction with the given old offset.
+     * @param oldInstructionOffset the old offset of the instruction, to which
+     *                             branches and other references in the current
+     *                             code fragment are pointing.
+     * @param instruction          the instruction to be appended.
+     */
+    public void appendInstruction(int         oldInstructionOffset,
+                                  Instruction instruction)
+    {
+        if (DEBUG)
+        {
+            println("["+codeLength+"] <- ", instruction.toString(oldInstructionOffset));
+        }
+
+        // Make sure the code array is large enough.
+        int newCodeLength = codeLength + instruction.length(codeLength);
+
+        ensureCodeLength(newCodeLength);
+
+        // Remember the old offset of the appended instruction.
+        oldInstructionOffsets[codeLength] = oldInstructionOffset;
+
+        // Write the instruction.
+//        instruction.accept(null,
+//                           null,
+//                           new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null),
+//                           codeLength,
+//                           instructionWriter);
+        instruction.write(code, codeLength);
+
+        // Fill out the new offset of the appended instruction.
+        instructionOffsetMap[level][oldInstructionOffset] = codeLength;
+
+        // Continue appending at the next instruction offset.
+        codeLength = newCodeLength;
+    }
+
+
+    /**
+     * Appends the given label with the given old offset.
+     * @param oldInstructionOffset the old offset of the label, to which
+     *                             branches and other references in the current
+     *                             code fragment are pointing.
+     */
+    public void appendLabel(int oldInstructionOffset)
+    {
+        if (DEBUG)
+        {
+            println("["+codeLength+"] <- ", "[" + oldInstructionOffset + "] (label)");
+        }
+
+        // Fill out the new offset of the appended instruction.
+        instructionOffsetMap[level][oldInstructionOffset] = codeLength;
+    }
+
+
+    /**
+     * Appends the given exception to the exception table.
+     * @param exceptionInfo the exception to be appended.
+     */
+    public void appendException(ExceptionInfo exceptionInfo)
+    {
+        if (DEBUG)
+        {
+            print("         ", "Exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+": "+exceptionInfo.u2handlerPC+"]");
+        }
+
+        // Remap the exception right away.
+        visitExceptionInfo(null, null, null, exceptionInfo);
+
+        if (DEBUG)
+        {
+            System.out.println(" -> ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+": "+exceptionInfo.u2handlerPC+"]");
+        }
+
+        // Don't add the exception if its instruction range is empty.
+        if (exceptionInfo.u2startPC == exceptionInfo.u2endPC)
+        {
+            if (DEBUG)
+            {
+                println("         ", "  (not added because of empty instruction range)");
+            }
+
+            return;
+        }
+
+        // Make sure there is sufficient space in the exception table.
+        if (exceptionTable.length <= exceptionTableLength)
+        {
+            ExceptionInfo[] newExceptionTable = new ExceptionInfo[exceptionTableLength+1];
+            System.arraycopy(exceptionTable, 0, newExceptionTable, 0, exceptionTableLength);
+            exceptionTable = newExceptionTable;
+        }
+
+        // Add the exception.
+        exceptionTable[exceptionTableLength++] = exceptionInfo;
+    }
+
+
+    /**
+     * Wraps up the current code fragment, continuing with the previous one on
+     * the stack.
+     */
+    public void endCodeFragment()
+    {
+        if (level < 0)
+        {
+            throw new IllegalArgumentException("Code fragment not begun ["+level+"]");
+        }
+
+        // Remap the instructions of the code fragment.
+        int instructionOffset = codeFragmentOffsets[level];
+        while (instructionOffset < codeLength)
+        {
+            // Get the next instruction.
+            Instruction instruction = InstructionFactory.create(code, instructionOffset);
+
+            // Does this instruction still have to be remapped?
+            if (oldInstructionOffsets[instructionOffset] >= 0)
+            {
+                // Adapt the instruction for its new offset.
+                instruction.accept(null, null, null, instructionOffset, this);
+
+                // Write the instruction back.
+//                instruction.accept(null,
+//                                   null,
+//                                   new CodeAttribute(0, 0, 0, 0, code, 0, null, 0, null),
+//                                   instructionOffset,
+//                                   instructionWriter);
+                instruction.write(code, instructionOffset);
+
+                // Don't remap this instruction again.
+                oldInstructionOffsets[instructionOffset] = -1;
+            }
+
+            // Continue remapping at the next instruction offset.
+            instructionOffset += instruction.length(instructionOffset);
+        }
+
+        // Correct the estimated maximum code length, now that we know the
+        // actual length of this code fragment.
+        maximumCodeLength += codeLength - codeFragmentOffsets[level] -
+                             codeFragmentLengths[level];
+
+        // Try to remap the exception handlers that couldn't be remapped before.
+        if (allowExternalExceptionHandlers)
+        {
+            for (int index = 0; index < exceptionTableLength; index++)
+            {
+                ExceptionInfo exceptionInfo = exceptionTable[index];
+
+                // Unmapped exception handlers are still negated.
+                int handlerPC = -exceptionInfo.u2handlerPC;
+                if (handlerPC > 0)
+                {
+                    if (remappableInstructionOffset(handlerPC))
+                    {
+                        exceptionInfo.u2handlerPC = remapInstructionOffset(handlerPC);
+                    }
+                    else if (level == 0)
+                    {
+                        throw new IllegalStateException("Couldn't remap exception handler offset ["+handlerPC+"]");
+                    }
+                }
+            }
+        }
+
+        level--;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (DEBUG)
+        {
+            System.out.println("CodeAttributeComposer: putting results in ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+        }
+
+        if (level != -1)
+        {
+            throw new IllegalArgumentException("Code fragment not ended ["+level+"]");
+        }
+
+        level++;
+
+        // Make sure the code attribute has sufficient space for the composed
+        // code.
+        if (codeAttribute.u4codeLength < codeLength)
+        {
+            codeAttribute.code = new byte[codeLength];
+        }
+
+        // Copy the composed code over into the code attribute.
+        codeAttribute.u4codeLength = codeLength;
+        System.arraycopy(code, 0, codeAttribute.code, 0, codeLength);
+
+        // Remove exceptions with empty code blocks (done before).
+        //exceptionTableLength =
+        //    removeEmptyExceptions(exceptionTable, exceptionTableLength);
+
+        // Make sure the exception table has sufficient space for the composed
+        // exceptions.
+        if (codeAttribute.exceptionTable.length < exceptionTableLength)
+        {
+            codeAttribute.exceptionTable = new ExceptionInfo[exceptionTableLength];
+        }
+
+        // Copy the exception table.
+        codeAttribute.u2exceptionTableLength = exceptionTableLength;
+        System.arraycopy(exceptionTable, 0, codeAttribute.exceptionTable, 0, exceptionTableLength);
+
+        // Update the maximum stack size and local variable frame size.
+        stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
+        variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Remap  the line number table and the local variable table.
+        codeAttribute.attributesAccept(clazz, method, this);
+
+        // Remap the exception table.
+        //codeAttribute.exceptionsAccept(clazz, method, this);
+
+        // Remove exceptions with empty code blocks (done before).
+        //codeAttribute.u2exceptionTableLength =
+        //    removeEmptyExceptions(codeAttribute.exceptionTable,
+        //                          codeAttribute.u2exceptionTableLength);
+
+//        // Make sure instructions are widened if necessary.
+//        instructionWriter.visitCodeAttribute(clazz, method, codeAttribute);
+
+        level--;
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        // Remap all stack map entries.
+        expectedStackMapFrameOffset = -1;
+        stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        // Remap all stack map table entries.
+        expectedStackMapFrameOffset = 0;
+        stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        // Remap all line number table entries.
+        lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
+
+        // Remove line numbers with empty code blocks.
+        lineNumberTableAttribute.u2lineNumberTableLength =
+           removeEmptyLineNumbers(lineNumberTableAttribute.lineNumberTable,
+                                  lineNumberTableAttribute.u2lineNumberTableLength,
+                                  codeAttribute.u4codeLength);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Remap all local variable table entries.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+
+        // Remove local variables with empty code blocks.
+        localVariableTableAttribute.u2localVariableTableLength =
+            removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable,
+                                      localVariableTableAttribute.u2localVariableTableLength,
+                                      codeAttribute.u2maxLocals);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Remap all local variable table entries.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+
+        // Remove local variables with empty code blocks.
+        localVariableTypeTableAttribute.u2localVariableTypeTableLength =
+            removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable,
+                                          localVariableTypeTableAttribute.u2localVariableTypeTableLength,
+                                          codeAttribute.u2maxLocals);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        // Adjust the branch offset.
+        branchInstruction.branchOffset = remapBranchOffset(offset,
+                                                           branchInstruction.branchOffset);
+    }
+
+
+    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
+    {
+        // Adjust the default jump offset.
+        switchInstruction.defaultOffset = remapBranchOffset(offset,
+                                                            switchInstruction.defaultOffset);
+
+        // Adjust the jump offsets.
+        remapJumpOffsets(offset,
+                         switchInstruction.jumpOffsets);
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        // Remap the code offsets. Note that the instruction offset map also has
+        // an entry for the first offset after the code, for u2endPC.
+        exceptionInfo.u2startPC = remapInstructionOffset(exceptionInfo.u2startPC);
+        exceptionInfo.u2endPC   = remapInstructionOffset(exceptionInfo.u2endPC);
+
+        // See if we can remap the handler right away. Unmapped exception
+        // handlers are negated, in order to mark them as external.
+        int handlerPC = exceptionInfo.u2handlerPC;
+        exceptionInfo.u2handlerPC =
+            !allowExternalExceptionHandlers ||
+            remappableInstructionOffset(handlerPC) ?
+                remapInstructionOffset(handlerPC) :
+                -handlerPC;
+    }
+
+
+    // Implementations for StackMapFrameVisitor.
+
+    public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame)
+    {
+        // Remap the stack map frame offset.
+        int stackMapFrameOffset = remapInstructionOffset(offset);
+
+        int offsetDelta = stackMapFrameOffset;
+
+        // Compute the offset delta if the frame is part of a stack map frame
+        // table (for JDK 6.0) instead of a stack map (for Java Micro Edition).
+        if (expectedStackMapFrameOffset >= 0)
+        {
+            offsetDelta -= expectedStackMapFrameOffset;
+
+            expectedStackMapFrameOffset = stackMapFrameOffset + 1;
+        }
+
+        stackMapFrame.u2offsetDelta = offsetDelta;
+    }
+
+
+    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+    {
+        // Remap the stack map frame offset.
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame);
+
+        // Remap the verification type offset.
+        sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+    {
+        // Remap the stack map frame offset.
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame);
+
+        // Remap the verification type offsets.
+        moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+    {
+        // Remap the stack map frame offset.
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame);
+
+        // Remap the verification type offsets.
+        fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
+        fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for VerificationTypeVisitor.
+
+    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {}
+
+
+    public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)
+    {
+        // Remap the offset of the 'new' instruction.
+        uninitializedType.u2newInstructionOffset = remapInstructionOffset(uninitializedType.u2newInstructionOffset);
+    }
+
+
+    // Implementations for LineNumberInfoVisitor.
+
+    public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
+    {
+        // Remap the code offset.
+        lineNumberInfo.u2startPC = remapInstructionOffset(lineNumberInfo.u2startPC);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        // Remap the code offset and length.
+        // TODO: The local variable frame might not be strictly preserved.
+        int startPC = remapInstructionOffset(localVariableInfo.u2startPC);
+        int endPC   = remapInstructionOffset(localVariableInfo.u2startPC + localVariableInfo.u2length);
+
+        localVariableInfo.u2startPC = startPC;
+        localVariableInfo.u2length  = endPC - startPC;
+    }
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        // Remap the code offset and length.
+        // TODO: The local variable frame might not be strictly preserved.
+        int startPC = remapInstructionOffset(localVariableTypeInfo.u2startPC);
+        int endPC   = remapInstructionOffset(localVariableTypeInfo.u2startPC + localVariableTypeInfo.u2length);
+
+        localVariableTypeInfo.u2startPC = startPC;
+        localVariableTypeInfo.u2length  = endPC - startPC;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Make sure the code arrays have at least the given size.
+     */
+    private void ensureCodeLength(int newCodeLength)
+    {
+        if (code.length < newCodeLength)
+        {
+            // Add 20% to avoid extending the arrays too often.
+            newCodeLength = newCodeLength * 6 / 5;
+
+            byte[] newCode = new byte[newCodeLength];
+            System.arraycopy(code, 0, newCode, 0, codeLength);
+            code = newCode;
+
+            int[] newOldInstructionOffsets = new int[newCodeLength];
+            System.arraycopy(oldInstructionOffsets, 0, newOldInstructionOffsets, 0, codeLength);
+            oldInstructionOffsets = newOldInstructionOffsets;
+        }
+    }
+
+
+    /**
+     * Adjusts the given jump offsets for the instruction at the given offset.
+     */
+    private void remapJumpOffsets(int offset, int[] jumpOffsets)
+    {
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            jumpOffsets[index] = remapBranchOffset(offset, jumpOffsets[index]);
+        }
+    }
+
+
+    /**
+     * Computes the new branch offset for the instruction at the given new offset
+     * with the given old branch offset.
+     */
+    private int remapBranchOffset(int newInstructionOffset, int branchOffset)
+    {
+        if (newInstructionOffset < 0 ||
+            newInstructionOffset > codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+newInstructionOffset +"] in code with length ["+codeLength+"]");
+        }
+
+        int oldInstructionOffset = oldInstructionOffsets[newInstructionOffset];
+
+        return remapInstructionOffset(oldInstructionOffset + branchOffset) -
+               remapInstructionOffset(oldInstructionOffset);
+    }
+
+
+    /**
+     * Computes the new instruction offset for the instruction at the given old
+     * offset.
+     */
+    private int remapInstructionOffset(int oldInstructionOffset)
+    {
+        if (oldInstructionOffset < 0 ||
+            oldInstructionOffset > codeFragmentLengths[level])
+        {
+            throw new IllegalArgumentException("Instruction offset ["+oldInstructionOffset +"] out of range in code fragment with length ["+codeFragmentLengths[level]+"] at level "+level);
+        }
+
+        int newInstructionOffset = instructionOffsetMap[level][oldInstructionOffset];
+        if (newInstructionOffset == INVALID)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+oldInstructionOffset +"] in code fragment at level "+level);
+        }
+
+        return newInstructionOffset;
+    }
+
+
+    /**
+     * Returns whether the given old instruction offset can be remapped at the
+     */
+    private boolean remappableInstructionOffset(int oldInstructionOffset)
+    {
+        return
+            oldInstructionOffset <= codeFragmentLengths[level] &&
+            instructionOffsetMap[level][oldInstructionOffset] > INVALID;
+    }
+
+
+    /**
+     * Returns the given list of exceptions, without the ones that have empty
+     * code blocks.
+     */
+    private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos,
+                                      int             exceptionInfoCount)
+    {
+        // Overwrite all empty exceptions.
+        int newIndex = 0;
+        for (int index = 0; index < exceptionInfoCount; index++)
+        {
+            ExceptionInfo exceptionInfo = exceptionInfos[index];
+            if (exceptionInfo.u2startPC < exceptionInfo.u2endPC)
+            {
+                exceptionInfos[newIndex++] = exceptionInfo;
+            }
+        }
+
+        // Clear the unused array entries.
+        for (int index = newIndex; index < exceptionInfoCount; index++)
+        {
+            exceptionInfos[index] = null;
+        }
+
+        return newIndex;
+    }
+
+
+    /**
+     * Returns the given list of line numbers, without the ones that have empty
+     * code blocks or that exceed the code size.
+     */
+    private int removeEmptyLineNumbers(LineNumberInfo[] lineNumberInfos,
+                                       int              lineNumberInfoCount,
+                                       int              codeLength)
+    {
+        // Overwrite all empty line number entries.
+        int newIndex = 0;
+        for (int index = 0; index < lineNumberInfoCount; index++)
+        {
+            LineNumberInfo lineNumberInfo = lineNumberInfos[index];
+            int startPC = lineNumberInfo.u2startPC;
+            if (startPC < codeLength &&
+                (index == 0 || startPC > lineNumberInfos[index-1].u2startPC))
+            {
+                lineNumberInfos[newIndex++] = lineNumberInfo;
+            }
+        }
+
+        // Clear the unused array entries.
+        for (int index = newIndex; index < lineNumberInfoCount; index++)
+        {
+            lineNumberInfos[index] = null;
+        }
+
+        return newIndex;
+    }
+
+
+    /**
+     * Returns the given list of local variables, without the ones that have empty
+     * code blocks or that exceed the actual number of local variables.
+     */
+    private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos,
+                                          int                 localVariableInfoCount,
+                                          int                 maxLocals)
+    {
+        // Overwrite all empty local variable entries.
+        int newIndex = 0;
+        for (int index = 0; index < localVariableInfoCount; index++)
+        {
+            LocalVariableInfo localVariableInfo = localVariableInfos[index];
+            if (localVariableInfo.u2length > 0 &&
+                localVariableInfo.u2index < maxLocals)
+            {
+                localVariableInfos[newIndex++] = localVariableInfo;
+            }
+        }
+
+        // Clear the unused array entries.
+        for (int index = newIndex; index < localVariableInfoCount; index++)
+        {
+            localVariableInfos[index] = null;
+        }
+
+        return newIndex;
+    }
+
+
+    /**
+     * Returns the given list of local variable types, without the ones that
+     * have empty code blocks or that exceed the actual number of local variables.
+     */
+    private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos,
+                                              int                     localVariableTypeInfoCount,
+                                              int                     maxLocals)
+    {
+        // Overwrite all empty local variable type entries.
+        int newIndex = 0;
+        for (int index = 0; index < localVariableTypeInfoCount; index++)
+        {
+            LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeInfos[index];
+            if (localVariableTypeInfo.u2length > 0 &&
+                localVariableTypeInfo.u2index < maxLocals)
+            {
+                localVariableTypeInfos[newIndex++] = localVariableTypeInfo;
+            }
+        }
+
+        // Clear the unused array entries.
+        for (int index = newIndex; index < localVariableTypeInfoCount; index++)
+        {
+            localVariableTypeInfos[index] = null;
+        }
+
+        return newIndex;
+    }
+
+
+    private void println(String string1, String string2)
+    {
+        print(string1, string2);
+
+        System.out.println();
+    }
+
+    private void print(String string1, String string2)
+    {
+        System.out.print(string1);
+
+        for (int index = 0; index < level; index++)
+        {
+            System.out.print("  ");
+        }
+
+        System.out.print(string2);
+    }
+
+
+    public static void main(String[] args)
+    {
+        CodeAttributeComposer composer = new CodeAttributeComposer();
+
+        composer.beginCodeFragment(4);
+        composer.appendInstruction(0, new SimpleInstruction(InstructionConstants.OP_ICONST_0));
+        composer.appendInstruction(1, new VariableInstruction(InstructionConstants.OP_ISTORE, 0));
+        composer.appendInstruction(2, new BranchInstruction(InstructionConstants.OP_GOTO, 1));
+
+        composer.beginCodeFragment(4);
+        composer.appendInstruction(0, new VariableInstruction(InstructionConstants.OP_IINC, 0, 1));
+        composer.appendInstruction(1, new VariableInstruction(InstructionConstants.OP_ILOAD, 0));
+        composer.appendInstruction(2, new SimpleInstruction(InstructionConstants.OP_ICONST_5));
+        composer.appendInstruction(3, new BranchInstruction(InstructionConstants.OP_IFICMPLT, -3));
+        composer.endCodeFragment();
+
+        composer.appendInstruction(3, new SimpleInstruction(InstructionConstants.OP_RETURN));
+        composer.endCodeFragment();
+    }
+}
diff --git a/src/proguard/classfile/editor/CodeAttributeEditor.java b/src/proguard/classfile/editor/CodeAttributeEditor.java
new file mode 100644
index 0000000..9658c98
--- /dev/null
+++ b/src/proguard/classfile/editor/CodeAttributeEditor.java
@@ -0,0 +1,1163 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.preverification.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassPrinter;
+
+/**
+ * This AttributeVisitor accumulates specified changes to code, and then applies
+ * these accumulated changes to the code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class CodeAttributeEditor
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ExceptionInfoVisitor,
+             StackMapFrameVisitor,
+             VerificationTypeVisitor,
+             LineNumberInfoVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+    private boolean updateFrameSizes;
+
+    private int     codeLength;
+    private boolean modified;
+    private boolean simple;
+
+    /*private*/public Instruction[]    preInsertions  = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH];
+    /*private*/public Instruction[]    replacements   = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH];
+    /*private*/public Instruction[]    postInsertions = new Instruction[ClassConstants.TYPICAL_CODE_LENGTH];
+    /*private*/public boolean[]        deleted        = new boolean[ClassConstants.TYPICAL_CODE_LENGTH];
+
+    private int[]   instructionOffsetMap = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int     newOffset;
+    private boolean lengthIncreased;
+
+    private int expectedStackMapFrameOffset;
+
+    private final StackSizeUpdater    stackSizeUpdater    = new StackSizeUpdater();
+    private final VariableSizeUpdater variableSizeUpdater = new VariableSizeUpdater();
+    private final InstructionWriter   instructionWriter   = new InstructionWriter();
+
+
+    public CodeAttributeEditor()
+    {
+        this(true);
+    }
+
+
+    public CodeAttributeEditor(boolean updateFrameSizes)
+    {
+        this.updateFrameSizes = updateFrameSizes;
+    }
+
+
+    /**
+     * Resets the accumulated code changes.
+     * @param codeLength the length of the code that will be edited next.
+     */
+    public void reset(int codeLength)
+    {
+        this.codeLength = codeLength;
+
+        // Try to reuse the previous arrays.
+        if (preInsertions.length < codeLength)
+        {
+            preInsertions  = new Instruction[codeLength];
+            replacements   = new Instruction[codeLength];
+            postInsertions = new Instruction[codeLength];
+            deleted        = new boolean[codeLength];
+        }
+        else
+        {
+            for (int index = 0; index < codeLength; index++)
+            {
+                preInsertions[index]  = null;
+                replacements[index]   = null;
+                postInsertions[index] = null;
+                deleted[index]        = false;
+            }
+        }
+
+        modified = false;
+        simple   = true;
+
+    }
+
+
+    /**
+     * Remembers to place the given instruction right before the instruction
+     * at the given offset.
+     * @param instructionOffset the offset of the instruction.
+     * @param instruction       the new instruction.
+     */
+    public void insertBeforeInstruction(int instructionOffset, Instruction instruction)
+    {
+        if (instructionOffset < 0 ||
+            instructionOffset >= codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]");
+        }
+
+        preInsertions[instructionOffset] = instruction;
+
+        modified = true;
+        simple   = false;
+
+    }
+
+
+    /**
+     * Remembers to place the given instructions right before the instruction
+     * at the given offset.
+     * @param instructionOffset the offset of the instruction.
+     * @param instructions      the new instructions.
+     */
+    public void insertBeforeInstruction(int instructionOffset, Instruction[] instructions)
+    {
+        if (instructionOffset < 0 ||
+            instructionOffset >= codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]");
+        }
+
+        preInsertions[instructionOffset] = new CompositeInstruction(instructions);
+
+        modified = true;
+        simple   = false;
+
+    }
+
+
+    /**
+     * Remembers to replace the instruction at the given offset by the given
+     * instruction.
+     * @param instructionOffset the offset of the instruction to be replaced.
+     * @param instruction       the new instruction.
+     */
+    public void replaceInstruction(int instructionOffset, Instruction instruction)
+    {
+        if (instructionOffset < 0 ||
+            instructionOffset >= codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]");
+        }
+
+        replacements[instructionOffset] = instruction;
+
+        modified = true;
+    }
+
+
+    /**
+     * Remembers to replace the instruction at the given offset by the given
+     * instructions.
+     * @param instructionOffset the offset of the instruction to be replaced.
+     * @param instructions      the new instructions.
+     */
+    public void replaceInstruction(int instructionOffset, Instruction[] instructions)
+    {
+        if (instructionOffset < 0 ||
+            instructionOffset >= codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]");
+        }
+
+        replacements[instructionOffset] = new CompositeInstruction(instructions);
+
+        modified = true;
+    }
+
+
+    /**
+     * Remembers to place the given instruction right after the instruction
+     * at the given offset.
+     * @param instructionOffset the offset of the instruction.
+     * @param instruction       the new instruction.
+     */
+    public void insertAfterInstruction(int instructionOffset, Instruction instruction)
+    {
+        if (instructionOffset < 0 ||
+            instructionOffset >= codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]");
+        }
+
+        postInsertions[instructionOffset] = instruction;
+
+        modified = true;
+        simple   = false;
+    }
+
+
+    /**
+     * Remembers to place the given instructions right after the instruction
+     * at the given offset.
+     * @param instructionOffset the offset of the instruction.
+     * @param instructions      the new instructions.
+     */
+    public void insertAfterInstruction(int instructionOffset, Instruction[] instructions)
+    {
+        if (instructionOffset < 0 ||
+            instructionOffset >= codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]");
+        }
+
+        postInsertions[instructionOffset] = new CompositeInstruction(instructions);
+
+        modified = true;
+        simple   = false;
+    }
+
+
+    /**
+     * Remembers to delete the instruction at the given offset.
+     * @param instructionOffset the offset of the instruction to be deleted.
+     */
+    public void deleteInstruction(int instructionOffset)
+    {
+        if (instructionOffset < 0 ||
+            instructionOffset >= codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]");
+        }
+
+        deleted[instructionOffset] = true;
+
+        modified = true;
+        simple   = false;
+    }
+
+
+    /**
+     * Remembers not to delete the instruction at the given offset.
+     * @param instructionOffset the offset of the instruction not to be deleted.
+     */
+    public void undeleteInstruction(int instructionOffset)
+    {
+        if (instructionOffset < 0 ||
+            instructionOffset >= codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+instructionOffset+"] in code with length ["+codeLength+"]");
+        }
+
+        deleted[instructionOffset] = false;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset has been modified
+     * in any way.
+     */
+    public boolean isModified(int instructionOffset)
+    {
+        return preInsertions[instructionOffset]  != null ||
+               replacements[instructionOffset]   != null ||
+               postInsertions[instructionOffset] != null ||
+               deleted[instructionOffset];
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // TODO: Remove this when the code has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            // Process the code.
+            visitCodeAttribute0(clazz, method, codeAttribute);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while editing code:");
+            System.err.println("  Class       = ["+clazz.getName()+"]");
+            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+
+            throw ex;
+        }
+    }
+
+
+    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (DEBUG)
+        {
+            System.out.println("CodeAttributeEditor: ["+clazz.getName()+"."+method.getName(clazz)+"]");
+        }
+
+        // Avoid doing any work if nothing is changing anyway.
+        if (!modified)
+        {
+            return;
+        }
+
+        // Check if we can perform a faster simple replacement of instructions.
+        if (canPerformSimpleReplacements(codeAttribute))
+        {
+            // Simply overwrite the instructions.
+            performSimpleReplacements(codeAttribute);
+
+            // Update the maximum stack size and local variable frame size.
+            updateFrameSizes(clazz, method, codeAttribute);
+        }
+        else
+        {
+            // Move and remap the instructions.
+            codeAttribute.u4codeLength =
+                updateInstructions(clazz, method, codeAttribute);
+
+            // Remap the exception table.
+            codeAttribute.exceptionsAccept(clazz, method, this);
+
+            // Remove exceptions with empty code blocks.
+            codeAttribute.u2exceptionTableLength =
+                removeEmptyExceptions(codeAttribute.exceptionTable,
+                                      codeAttribute.u2exceptionTableLength);
+
+            // Update the maximum stack size and local variable frame size.
+            updateFrameSizes(clazz, method, codeAttribute);
+
+            // Remap the line number table and the local variable table.
+            codeAttribute.attributesAccept(clazz, method, this);
+
+            // Make sure instructions are widened if necessary.
+            instructionWriter.visitCodeAttribute(clazz, method, codeAttribute);
+        }
+    }
+
+
+    private void updateFrameSizes(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (updateFrameSizes)
+        {
+            stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
+            variableSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
+        }
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        // Remap all stack map entries.
+        expectedStackMapFrameOffset = -1;
+        stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        // Remap all stack map table entries.
+        expectedStackMapFrameOffset = 0;
+        stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        // Remap all line number table entries.
+        lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
+
+        // Remove line numbers with empty code blocks.
+        lineNumberTableAttribute.u2lineNumberTableLength =
+           removeEmptyLineNumbers(lineNumberTableAttribute.lineNumberTable,
+                                  lineNumberTableAttribute.u2lineNumberTableLength,
+                                  codeAttribute.u4codeLength);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Remap all local variable table entries.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+
+        // Remove local variables with empty code blocks.
+        localVariableTableAttribute.u2localVariableTableLength =
+            removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable,
+                                      localVariableTableAttribute.u2localVariableTableLength,
+                                      codeAttribute.u2maxLocals);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Remap all local variable table entries.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+
+        // Remove local variables with empty code blocks.
+        localVariableTypeTableAttribute.u2localVariableTypeTableLength =
+            removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable,
+                                          localVariableTypeTableAttribute.u2localVariableTypeTableLength,
+                                          codeAttribute.u2maxLocals);
+    }
+
+
+    /**
+     * Checks if it is possible to modifies the given code without having to
+     * update any offsets.
+     * @param codeAttribute the code to be changed.
+     * @return the new code length.
+     */
+    private boolean canPerformSimpleReplacements(CodeAttribute codeAttribute)
+    {
+        if (!simple)
+        {
+            return false;
+        }
+
+        byte[] code       = codeAttribute.code;
+        int    codeLength = codeAttribute.u4codeLength;
+
+        // Go over all replacement instructions.
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            // Check if the replacement instruction, if any, has a different
+            // length than the original instruction.
+            Instruction replacementInstruction = replacements[offset];
+            if (replacementInstruction != null &&
+                replacementInstruction.length(offset) !=
+                    InstructionFactory.create(code, offset).length(offset))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Modifies the given code without updating any offsets.
+     * @param codeAttribute the code to be changed.
+     */
+    private void performSimpleReplacements(CodeAttribute codeAttribute)
+    {
+        int codeLength = codeAttribute.u4codeLength;
+
+        // Go over all replacement instructions.
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            // Overwrite the original instruction with the replacement
+            // instruction if any.
+            Instruction replacementInstruction = replacements[offset];
+            if (replacementInstruction != null)
+            {
+                replacementInstruction.write(codeAttribute, offset);
+
+                if (DEBUG)
+                {
+                    System.out.println("  Replaced "+replacementInstruction.toString(newOffset));
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Modifies the given code based on the previously specified changes.
+     * @param clazz         the class file of the code to be changed.
+     * @param method        the method of the code to be changed.
+     * @param codeAttribute the code to be changed.
+     * @return the new code length.
+     */
+    private int updateInstructions(Clazz         clazz,
+                                   Method        method,
+                                   CodeAttribute codeAttribute)
+    {
+        byte[] oldCode   = codeAttribute.code;
+        int    oldLength = codeAttribute.u4codeLength;
+
+        // Make sure there is a sufficiently large instruction offset map.
+        if (instructionOffsetMap.length < oldLength + 1)
+        {
+            instructionOffsetMap = new int[oldLength + 1];
+        }
+
+        // Fill out the instruction offset map.
+        int newLength = mapInstructions(oldCode,
+                                        oldLength);
+
+        // Create a new code array if necessary.
+        if (lengthIncreased)
+        {
+            codeAttribute.code = new byte[newLength];
+        }
+
+        // Prepare for possible widening of instructions.
+        instructionWriter.reset(newLength);
+
+        // Move the instructions into the new code array.
+        moveInstructions(clazz,
+                         method,
+                         codeAttribute,
+                         oldCode,
+                         oldLength);
+
+        // We can return the new length.
+        return newLength;
+    }
+
+
+    /**
+     * Fills out the instruction offset map for the given code block.
+     * @param oldCode   the instructions to be moved.
+     * @param oldLength the code length.
+     * @return the new code length.
+     */
+    private int mapInstructions(byte[] oldCode, int oldLength)
+    {
+        // Start mapping instructions at the beginning.
+        newOffset       = 0;
+        lengthIncreased = false;
+
+        int oldOffset = 0;
+        do
+        {
+            // Get the next instruction.
+            Instruction instruction = InstructionFactory.create(oldCode, oldOffset);
+
+            // Compute the mapping of the instruction.
+            mapInstruction(oldOffset, instruction);
+
+            oldOffset += instruction.length(oldOffset);
+
+            if (newOffset > oldOffset)
+            {
+                lengthIncreased = true;
+            }
+        }
+        while (oldOffset < oldLength);
+
+        // Also add an entry for the first offset after the code.
+        instructionOffsetMap[oldOffset] = newOffset;
+
+        return newOffset;
+    }
+
+
+    /**
+     * Fills out the instruction offset map for the given instruction.
+     * @param oldOffset   the instruction's old offset.
+     * @param instruction the instruction to be moved.
+     */
+    private void mapInstruction(int         oldOffset,
+                                Instruction instruction)
+    {
+        instructionOffsetMap[oldOffset] = newOffset;
+
+        // Account for the pre-inserted instruction, if any.
+        Instruction preInstruction = preInsertions[oldOffset];
+        if (preInstruction != null)
+        {
+            newOffset += preInstruction.length(newOffset);
+        }
+
+        // Account for the replacement instruction, or for the current
+        // instruction, if it shouldn't be  deleted.
+        Instruction replacementInstruction = replacements[oldOffset];
+        if (replacementInstruction != null)
+        {
+            newOffset += replacementInstruction.length(newOffset);
+        }
+        else if (!deleted[oldOffset])
+        {
+            // Note that the instruction's length may change at its new offset,
+            // e.g. if it is a switch instruction.
+            newOffset += instruction.length(newOffset);
+        }
+
+        // Account for the post-inserted instruction, if any.
+        Instruction postInstruction = postInsertions[oldOffset];
+        if (postInstruction != null)
+        {
+            newOffset += postInstruction.length(newOffset);
+        }
+    }
+
+
+    /**
+     * Moves the given code block to the new offsets.
+     * @param clazz         the class file of the code to be changed.
+     * @param method        the method of the code to be changed.
+     * @param codeAttribute the code to be changed.
+     * @param oldCode       the original code to be moved.
+     * @param oldLength     the original code length.
+     */
+    private void moveInstructions(Clazz         clazz,
+                                  Method        method,
+                                  CodeAttribute codeAttribute,
+                                  byte[]        oldCode,
+                                  int           oldLength)
+    {
+        // Start writing instructions at the beginning.
+        newOffset = 0;
+
+        int oldOffset = 0;
+        do
+        {
+            // Get the next instruction.
+            Instruction instruction = InstructionFactory.create(oldCode, oldOffset);
+
+            // Move the instruction to its new offset.
+            moveInstruction(clazz,
+                            method,
+                            codeAttribute,
+                            oldOffset,
+                            instruction);
+
+            oldOffset += instruction.length(oldOffset);
+        }
+        while (oldOffset < oldLength);
+    }
+
+
+    /**
+     * Moves the given instruction to its new offset.
+     * @param clazz         the class file of the code to be changed.
+     * @param method        the method of the code to be changed.
+     * @param codeAttribute the code to be changed.
+     * @param oldOffset     the original instruction offset.
+     * @param instruction   the original instruction.
+     */
+    private void moveInstruction(Clazz         clazz,
+                                 Method        method,
+                                 CodeAttribute codeAttribute,
+                                 int           oldOffset,
+                                 Instruction   instruction)
+    {
+        // Remap and insert the pre-inserted instruction, if any.
+        Instruction preInstruction = preInsertions[oldOffset];
+        if (preInstruction != null)
+        {
+            if (DEBUG)
+            {
+                System.out.println("  Pre-inserted  "+preInstruction.toString(newOffset));
+            }
+
+            // Remap the instruction.
+            preInstruction.accept(clazz, method, codeAttribute, oldOffset, this);
+        }
+
+        // Remap and insert the replacement instruction, or the current
+        // instruction, if it shouldn't be deleted.
+        Instruction replacementInstruction = replacements[oldOffset];
+        if (replacementInstruction != null)
+        {
+            if (DEBUG)
+            {
+                System.out.println("  Replaced      "+replacementInstruction.toString(newOffset));
+            }
+            // Remap the instruction.
+            replacementInstruction.accept(clazz, method, codeAttribute, oldOffset, this);
+        }
+        else if (!deleted[oldOffset])
+        {
+            if (DEBUG)
+            {
+                System.out.println("  Copied        "+instruction.toString(newOffset));
+            }
+
+            // Remap the instruction.
+            instruction.accept(clazz, method, codeAttribute, oldOffset, this);
+        }
+
+        // Remap and insert the post-inserted instruction, if any.
+        Instruction postInstruction = postInsertions[oldOffset];
+        if (postInstruction != null)
+        {
+            if (DEBUG)
+            {
+                System.out.println("  Post-inserted "+postInstruction.toString(newOffset));
+            }
+
+            // Remap the instruction.
+            postInstruction.accept(clazz, method, codeAttribute, oldOffset, this);
+        }
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        // Write out the instruction.
+        instructionWriter.visitSimpleInstruction(clazz,
+                                                 method,
+                                                 codeAttribute,
+                                                 newOffset,
+                                                 simpleInstruction);
+
+        newOffset += simpleInstruction.length(newOffset);
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        // Write out the instruction.
+        instructionWriter.visitConstantInstruction(clazz,
+                                                   method,
+                                                   codeAttribute,
+                                                   newOffset,
+                                                   constantInstruction);
+
+        newOffset += constantInstruction.length(newOffset);
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        // Write out the instruction.
+        instructionWriter.visitVariableInstruction(clazz,
+                                                   method,
+                                                   codeAttribute,
+                                                   newOffset,
+                                                   variableInstruction);
+
+        newOffset += variableInstruction.length(newOffset);
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        // Adjust the branch offset.
+        branchInstruction.branchOffset = remapBranchOffset(offset,
+                                                           branchInstruction.branchOffset);
+
+        // Write out the instruction.
+        instructionWriter.visitBranchInstruction(clazz,
+                                                 method,
+                                                 codeAttribute,
+                                                 newOffset,
+                                                 branchInstruction);
+
+        newOffset += branchInstruction.length(newOffset);
+    }
+
+
+    public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction)
+    {
+        // Adjust the default jump offset.
+        tableSwitchInstruction.defaultOffset = remapBranchOffset(offset,
+                                                                 tableSwitchInstruction.defaultOffset);
+
+        // Adjust the jump offsets.
+        remapJumpOffsets(offset,
+                         tableSwitchInstruction.jumpOffsets);
+
+        // Write out the instruction.
+        instructionWriter.visitTableSwitchInstruction(clazz,
+                                                      method,
+                                                      codeAttribute,
+                                                      newOffset,
+                                                      tableSwitchInstruction);
+
+        newOffset += tableSwitchInstruction.length(newOffset);
+    }
+
+
+    public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction)
+    {
+        // Adjust the default jump offset.
+        lookUpSwitchInstruction.defaultOffset = remapBranchOffset(offset,
+                                                                  lookUpSwitchInstruction.defaultOffset);
+
+        // Adjust the jump offsets.
+        remapJumpOffsets(offset,
+                         lookUpSwitchInstruction.jumpOffsets);
+
+        // Write out the instruction.
+        instructionWriter.visitLookUpSwitchInstruction(clazz,
+                                                       method,
+                                                       codeAttribute,
+                                                       newOffset,
+                                                       lookUpSwitchInstruction);
+
+        newOffset += lookUpSwitchInstruction.length(newOffset);
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        // Remap the code offsets. Note that the instruction offset map also has
+        // an entry for the first offset after the code, for u2endPC.
+        exceptionInfo.u2startPC   = remapInstructionOffset(exceptionInfo.u2startPC);
+        exceptionInfo.u2endPC     = remapInstructionOffset(exceptionInfo.u2endPC);
+        exceptionInfo.u2handlerPC = remapInstructionOffset(exceptionInfo.u2handlerPC);
+    }
+
+
+    // Implementations for StackMapFrameVisitor.
+
+    public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame)
+    {
+        // Remap the stack map frame offset.
+        int stackMapFrameOffset = remapInstructionOffset(offset);
+
+        int offsetDelta = stackMapFrameOffset;
+
+        // Compute the offset delta if the frame is part of a stack map frame
+        // table (for JDK 6.0) instead of a stack map (for Java Micro Edition).
+        if (expectedStackMapFrameOffset >= 0)
+        {
+            offsetDelta -= expectedStackMapFrameOffset;
+
+            expectedStackMapFrameOffset = stackMapFrameOffset + 1;
+        }
+
+        stackMapFrame.u2offsetDelta = offsetDelta;
+    }
+
+
+    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+    {
+        // Remap the stack map frame offset.
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame);
+
+        // Remap the verification type offset.
+        sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+    {
+        // Remap the stack map frame offset.
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame);
+
+        // Remap the verification type offsets.
+        moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+    {
+        // Remap the stack map frame offset.
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame);
+
+        // Remap the verification type offsets.
+        fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
+        fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for VerificationTypeVisitor.
+
+    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {}
+
+
+    public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)
+    {
+        // Remap the offset of the 'new' instruction.
+        uninitializedType.u2newInstructionOffset = remapInstructionOffset(uninitializedType.u2newInstructionOffset);
+    }
+
+
+    // Implementations for LineNumberInfoVisitor.
+
+    public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
+    {
+        // Remap the code offset.
+        lineNumberInfo.u2startPC = remapInstructionOffset(lineNumberInfo.u2startPC);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        // Remap the code offset and length.
+        // TODO: The local variable frame might not be strictly preserved.
+        localVariableInfo.u2length  = remapBranchOffset(localVariableInfo.u2startPC,
+                                                        localVariableInfo.u2length);
+        localVariableInfo.u2startPC = remapInstructionOffset(localVariableInfo.u2startPC);
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        // Remap the code offset and length.
+        // TODO: The local variable frame might not be strictly preserved.
+        localVariableTypeInfo.u2length  = remapBranchOffset(localVariableTypeInfo.u2startPC,
+                                                            localVariableTypeInfo.u2length);
+        localVariableTypeInfo.u2startPC = remapInstructionOffset(localVariableTypeInfo.u2startPC);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Adjusts the given jump offsets for the instruction at the given offset.
+     */
+    private void remapJumpOffsets(int offset, int[] jumpOffsets)
+    {
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            jumpOffsets[index] = remapBranchOffset(offset, jumpOffsets[index]);
+        }
+    }
+
+
+    /**
+     * Computes the new branch offset for the instruction at the given offset
+     * with the given branch offset.
+     */
+    private int remapBranchOffset(int offset, int branchOffset)
+    {
+        return remapInstructionOffset(offset + branchOffset) - newOffset;
+    }
+
+
+    /**
+     * Computes the new instruction offset for the instruction at the given offset.
+     */
+    private int remapInstructionOffset(int offset)
+    {
+        if (offset < 0 ||
+            offset > codeLength)
+        {
+            throw new IllegalArgumentException("Invalid instruction offset ["+offset+"] in code with length ["+codeLength+"]");
+        }
+
+        return instructionOffsetMap[offset];
+    }
+
+
+    /**
+     * Returns the given list of exceptions, without the ones that have empty
+     * code blocks.
+     */
+    private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos,
+                                      int             exceptionInfoCount)
+    {
+        // Overwrite all empty exceptions.
+        int newIndex = 0;
+        for (int index = 0; index < exceptionInfoCount; index++)
+        {
+            ExceptionInfo exceptionInfo = exceptionInfos[index];
+            if (exceptionInfo.u2startPC < exceptionInfo.u2endPC)
+            {
+                exceptionInfos[newIndex++] = exceptionInfo;
+            }
+        }
+
+        return newIndex;
+    }
+
+
+    /**
+     * Returns the given list of line numbers, without the ones that have empty
+     * code blocks or that exceed the code size.
+     */
+    private int removeEmptyLineNumbers(LineNumberInfo[] lineNumberInfos,
+                                       int              lineNumberInfoCount,
+                                       int              codeLength)
+    {
+        // Overwrite all empty line number entries.
+        int newIndex = 0;
+        for (int index = 0; index < lineNumberInfoCount; index++)
+        {
+            LineNumberInfo lineNumberInfo = lineNumberInfos[index];
+            int startPC = lineNumberInfo.u2startPC;
+            if (startPC < codeLength &&
+                (index == 0 || startPC > lineNumberInfos[index-1].u2startPC))
+            {
+                lineNumberInfos[newIndex++] = lineNumberInfo;
+            }
+        }
+
+        return newIndex;
+    }
+
+
+    /**
+     * Returns the given list of local variables, without the ones that have empty
+     * code blocks or that exceed the actual number of local variables.
+     */
+    private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos,
+                                          int                 localVariableInfoCount,
+                                          int                 maxLocals)
+    {
+        // Overwrite all empty local variable entries.
+        int newIndex = 0;
+        for (int index = 0; index < localVariableInfoCount; index++)
+        {
+            LocalVariableInfo localVariableInfo = localVariableInfos[index];
+            if (localVariableInfo.u2length > 0 &&
+                localVariableInfo.u2index < maxLocals)
+            {
+                localVariableInfos[newIndex++] = localVariableInfo;
+            }
+        }
+
+        return newIndex;
+    }
+
+
+    /**
+     * Returns the given list of local variable types, without the ones that
+     * have empty code blocks or that exceed the actual number of local variables.
+     */
+    private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos,
+                                              int                     localVariableTypeInfoCount,
+                                              int                     maxLocals)
+    {
+        // Overwrite all empty local variable type entries.
+        int newIndex = 0;
+        for (int index = 0; index < localVariableTypeInfoCount; index++)
+        {
+            LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeInfos[index];
+            if (localVariableTypeInfo.u2length > 0 &&
+                localVariableTypeInfo.u2index < maxLocals)
+            {
+                localVariableTypeInfos[newIndex++] = localVariableTypeInfo;
+            }
+        }
+
+        return newIndex;
+    }
+
+
+    private class CompositeInstruction
+    extends       Instruction
+    {
+        private Instruction[] instructions;
+
+
+        private CompositeInstruction(Instruction[] instructions)
+        {
+            this.instructions = instructions;
+        }
+
+
+        // Implementations for Instruction.
+
+        public Instruction shrink()
+        {
+            for (int index = 0; index < instructions.length; index++)
+            {
+                instructions[index] = instructions[index].shrink();
+            }
+
+            return this;
+        }
+
+
+        public void write(byte[] code, int offset)
+        {
+            for (int index = 0; index < instructions.length; index++)
+            {
+                Instruction instruction = instructions[index];
+
+                instruction.write(code, offset);
+
+                offset += instruction.length(offset);
+            }
+        }
+
+
+        protected void readInfo(byte[] code, int offset)
+        {
+            throw new UnsupportedOperationException("Can't read composite instruction");
+        }
+
+
+        protected void writeInfo(byte[] code, int offset)
+        {
+            throw new UnsupportedOperationException("Can't write composite instruction");
+        }
+
+
+        public int length(int offset)
+        {
+            int newOffset = offset;
+
+            for (int index = 0; index < instructions.length; index++)
+            {
+                newOffset += instructions[index].length(newOffset);
+            }
+
+            return newOffset - offset;
+        }
+
+
+        public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor)
+        {
+            if (instructionVisitor != CodeAttributeEditor.this)
+            {
+                throw new UnsupportedOperationException("Unexpected visitor ["+instructionVisitor+"]");
+            }
+
+            for (int index = 0; index < instructions.length; index++)
+            {
+                Instruction instruction = instructions[index];
+
+                instruction.accept(clazz, method, codeAttribute, offset, CodeAttributeEditor.this);
+
+                offset += instruction.length(offset);
+            }
+        }
+
+
+        // Implementations for Object.
+
+        public String toString()
+        {
+            StringBuffer stringBuffer = new StringBuffer();
+
+            for (int index = 0; index < instructions.length; index++)
+            {
+                stringBuffer.append(instructions[index].toString()).append("; ");
+            }
+
+            return stringBuffer.toString();
+        }
+    }
+}
diff --git a/src/proguard/classfile/editor/CodeAttributeEditorResetter.java b/src/proguard/classfile/editor/CodeAttributeEditorResetter.java
new file mode 100644
index 0000000..9962ea5
--- /dev/null
+++ b/src/proguard/classfile/editor/CodeAttributeEditorResetter.java
@@ -0,0 +1,60 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor resets it CodeAttributeEditor whenever it visits a
+ * code attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class CodeAttributeEditorResetter
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private final CodeAttributeEditor codeAttributeEditor;
+
+
+    /**
+     * Creates a new CodeAttributeEditorResetter.
+     * @param codeAttributeEditor the code attribute editor that will be reset.
+     */
+    public CodeAttributeEditorResetter(CodeAttributeEditor codeAttributeEditor)
+    {
+        this.codeAttributeEditor = codeAttributeEditor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        codeAttributeEditor.reset(codeAttribute.u4codeLength);
+    }
+}
diff --git a/src/proguard/classfile/editor/ComparableConstant.java b/src/proguard/classfile/editor/ComparableConstant.java
new file mode 100644
index 0000000..bb81221
--- /dev/null
+++ b/src/proguard/classfile/editor/ComparableConstant.java
@@ -0,0 +1,200 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+
+/**
+ * This class is a <code>Comparable</code> wrapper of <code>Constant</code>
+ * objects. It can store an index, in order to identify the constant   pool
+ * entry after it has been sorted. The comparison is primarily based   on the
+ * types of the constant pool entries, and secondarily on the contents of
+ * the constant pool entries.
+ *
+ * @author Eric Lafortune
+ */
+class      ComparableConstant
+extends    SimplifiedVisitor
+implements Comparable, ConstantVisitor
+{
+    private static final int[] PRIORITIES = new int[13];
+    static
+    {
+        PRIORITIES[ClassConstants.CONSTANT_Integer]            = 0; // Possibly byte index (ldc).
+        PRIORITIES[ClassConstants.CONSTANT_Float]              = 1;
+        PRIORITIES[ClassConstants.CONSTANT_String]             = 2;
+        PRIORITIES[ClassConstants.CONSTANT_Class]              = 3;
+        PRIORITIES[ClassConstants.CONSTANT_Long]               = 4; // Always wide index (ldc2_w).
+        PRIORITIES[ClassConstants.CONSTANT_Double]             = 5;
+        PRIORITIES[ClassConstants.CONSTANT_Fieldref]           = 6; // Always wide index.
+        PRIORITIES[ClassConstants.CONSTANT_Methodref]          = 7;
+        PRIORITIES[ClassConstants.CONSTANT_InterfaceMethodref] = 8;
+        PRIORITIES[ClassConstants.CONSTANT_NameAndType]        = 9;
+        PRIORITIES[ClassConstants.CONSTANT_Utf8]               = 10;
+    }
+
+    private final Clazz    clazz;
+    private final int      thisIndex;
+    private final Constant thisConstant;
+
+    private Constant otherConstant;
+    private int      result;
+
+
+    public ComparableConstant(Clazz clazz, int index, Constant constant)
+    {
+        this.clazz        = clazz;
+        this.thisIndex    = index;
+        this.thisConstant = constant;
+    }
+
+
+    public int getIndex()
+    {
+        return thisIndex;
+    }
+
+
+    public Constant getConstant()
+    {
+        return thisConstant;
+    }
+
+
+    // Implementations for Comparable.
+
+    public int compareTo(Object other)
+    {
+        ComparableConstant otherComparableConstant = (ComparableConstant)other;
+
+        otherConstant = otherComparableConstant.thisConstant;
+
+        // Compare based on the original indices, if the actual constant pool
+        // entries are the same.
+        if (thisConstant == otherConstant)
+        {
+            int otherIndex = otherComparableConstant.thisIndex;
+
+            return thisIndex <  otherIndex ? -1 :
+                   thisIndex == otherIndex ?  0 :
+                                              1;
+        }
+
+        // Compare based on the tags, if they are different.
+        int thisTag  = thisConstant.getTag();
+        int otherTag = otherConstant.getTag();
+
+        if (thisTag != otherTag)
+        {
+            return PRIORITIES[thisTag] < PRIORITIES[otherTag] ? -1 : 1;
+        }
+
+        // Otherwise compare based on the contents of the Constant objects.
+        thisConstant.accept(clazz, this);
+
+        return result;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        // In JDK 1.4, we can use Integer.compare(a,b).
+        result = new Integer(integerConstant.getValue()).compareTo(new Integer(((IntegerConstant)otherConstant).getValue()));
+    }
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        // In JDK 1.4, we can use Long.compare(a,b).
+        result = new Long(longConstant.getValue()).compareTo(new Long(((LongConstant)otherConstant).getValue()));
+    }
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        // In JDK 1.4, we can use Float.compare(a,b).
+        result = new Float(floatConstant.getValue()).compareTo(new Float(((FloatConstant)otherConstant).getValue()));
+    }
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        // In JDK 1.4, we can use Double.compare(a,b).
+        result = new Double(doubleConstant.getValue()).compareTo(new Double(((DoubleConstant)otherConstant).getValue()));
+    }
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        result = stringConstant.getString(clazz).compareTo(((StringConstant)otherConstant).getString(clazz));
+    }
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        result = utf8Constant.getString().compareTo(((Utf8Constant)otherConstant).getString());
+    }
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        RefConstant otherRefConstant = (RefConstant)otherConstant;
+        result = (refConstant.getClassName(clazz) + ' ' +
+                  refConstant.getName(clazz)      + ' ' +
+                  refConstant.getType(clazz))
+                 .compareTo
+                 (otherRefConstant.getClassName(clazz) + ' ' +
+                  otherRefConstant.getName(clazz)      + ' ' +
+                  otherRefConstant.getType(clazz));
+    }
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        result = classConstant.getName(clazz).compareTo(((ClassConstant)otherConstant).getName(clazz));
+    }
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        NameAndTypeConstant otherNameAndTypeConstant = (NameAndTypeConstant)otherConstant;
+        result = (nameAndTypeConstant.getName(clazz) + ' ' +
+                  nameAndTypeConstant.getType(clazz))
+                 .compareTo
+                 (otherNameAndTypeConstant.getName(clazz) + ' ' +
+                  otherNameAndTypeConstant.getType(clazz));
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object other)
+    {
+        return other != null &&
+               this.getClass().equals(other.getClass()) &&
+               this.getConstant().getClass().equals(((ComparableConstant)other).getConstant().getClass()) &&
+               this.compareTo(other) == 0;
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+}
diff --git a/src/proguard/classfile/editor/ConstantAdder.java b/src/proguard/classfile/editor/ConstantAdder.java
new file mode 100644
index 0000000..2b74f5f
--- /dev/null
+++ b/src/proguard/classfile/editor/ConstantAdder.java
@@ -0,0 +1,194 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This ConstantVisitor adds all constants that it visits to the constant pool
+ * of a given target class.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantAdder
+implements   ConstantVisitor
+{
+    private final ConstantPoolEditor constantPoolEditor;
+
+    private int constantIndex;
+
+
+    /**
+     * Creates a new ConstantAdder that will copy constants into the given
+     * target class.
+     */
+    public ConstantAdder(ProgramClass targetClass)
+    {
+        constantPoolEditor = new ConstantPoolEditor(targetClass);
+    }
+
+
+    /**
+     * Adds a copy of the specified constant in the given class and returns
+     * its index. If the specified index is 0, the returned value is 0 too.
+     */
+    public int addConstant(Clazz clazz, int constantIndex)
+    {
+        clazz.constantPoolEntryAccept(constantIndex, this);
+
+        return this.constantIndex;
+    }
+
+
+    /**
+     * Adds a copy of the given constant in the given class and returns
+     * its index.
+     */
+    public int addConstant(Clazz clazz, Constant constant)
+    {
+        constant.accept(clazz, this);
+
+        return this.constantIndex;
+    }
+
+
+    /**
+     * Returns the index of the most recently created constant in the constant
+     * pool of the target class.
+     */
+    public int getConstantIndex()
+    {
+        return constantIndex;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        constantIndex =
+            constantPoolEditor.addIntegerConstant(integerConstant.getValue());
+    }
+
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        constantIndex =
+            constantPoolEditor.addLongConstant(longConstant.getValue());
+    }
+
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        constantIndex =
+            constantPoolEditor.addFloatConstant(floatConstant.getValue());
+    }
+
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        constantIndex =
+            constantPoolEditor.addDoubleConstant(doubleConstant.getValue());
+    }
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        constantIndex =
+            constantPoolEditor.addStringConstant(stringConstant.getString(clazz),
+                                                 stringConstant.referencedClass,
+                                                 stringConstant.referencedMember);
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        constantIndex =
+            constantPoolEditor.addUtf8Constant(utf8Constant.getString());
+    }
+
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        // First add the referenced class constant, with its own referenced class.
+        clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this);
+
+        // Then add the actual field reference constant, with its referenced
+        // class and class member.
+        constantIndex =
+            constantPoolEditor.addFieldrefConstant(constantIndex,
+                                                   fieldrefConstant.getName(clazz),
+                                                   fieldrefConstant.getType(clazz),
+                                                   fieldrefConstant.referencedClass,
+                                                   fieldrefConstant.referencedMember);
+    }
+
+
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant)
+    {
+        // First add the referenced class constant, with its own referenced class.
+        clazz.constantPoolEntryAccept(interfaceMethodrefConstant.u2classIndex, this);
+
+        // Then add the actual interface method reference constant, with its
+        // referenced class and class member.
+        constantIndex =
+            constantPoolEditor.addInterfaceMethodrefConstant(constantIndex,
+                                                             interfaceMethodrefConstant.getName(clazz),
+                                                             interfaceMethodrefConstant.getType(clazz),
+                                                             interfaceMethodrefConstant.referencedClass,
+                                                             interfaceMethodrefConstant.referencedMember);
+    }
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        // First add the referenced class constant, with its own referenced class.
+        clazz.constantPoolEntryAccept(methodrefConstant.u2classIndex, this);
+
+        // Then add the actual method reference constant, with its referenced
+        // class and class member.
+        constantIndex =
+            constantPoolEditor.addMethodrefConstant(constantIndex,
+                                                    methodrefConstant.getName(clazz),
+                                                    methodrefConstant.getType(clazz),
+                                                    methodrefConstant.referencedClass,
+                                                    methodrefConstant.referencedMember);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Add the class constant, with its referenced class..
+        constantIndex =
+            constantPoolEditor.addClassConstant(classConstant.getName(clazz),
+                                                classConstant.referencedClass);
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        constantIndex =
+            constantPoolEditor.addNameAndTypeConstant(nameAndTypeConstant.getName(clazz),
+                                                      nameAndTypeConstant.getType(clazz));
+    }
+}
diff --git a/src/proguard/classfile/editor/ConstantPoolEditor.java b/src/proguard/classfile/editor/ConstantPoolEditor.java
new file mode 100644
index 0000000..8663dee
--- /dev/null
+++ b/src/proguard/classfile/editor/ConstantPoolEditor.java
@@ -0,0 +1,665 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.*;
+
+/**
+ * This class can add constant pool entries to a given class.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantPoolEditor
+{
+    private static final boolean DEBUG = false;
+
+    private ProgramClass targetClass;
+
+
+    /**
+     * Creates a new ConstantPoolEditor that will edit constants in the given
+     * target class.
+     */
+    public ConstantPoolEditor(ProgramClass targetClass)
+    {
+        this.targetClass = targetClass;
+    }
+
+
+    /**
+     * Finds or creates a IntegerConstant constant pool entry with the given
+     * value.
+     * @return the constant pool index of the Utf8Constant.
+     */
+    public int addIntegerConstant(int value)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_Integer)
+            {
+                IntegerConstant integerConstant = (IntegerConstant)constant;
+                if (integerConstant.getValue() == value)
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new IntegerConstant(value));
+    }
+
+
+    /**
+     * Finds or creates a LongConstant constant pool entry with the given value.
+     * @return the constant pool index of the LongConstant.
+     */
+    public int addLongConstant(long value)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_Long)
+            {
+                LongConstant longConstant = (LongConstant)constant;
+                if (longConstant.getValue() == value)
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new LongConstant(value));
+    }
+
+
+    /**
+     * Finds or creates a FloatConstant constant pool entry with the given
+     * value.
+     * @return the constant pool index of the FloatConstant.
+     */
+    public int addFloatConstant(float value)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_Float)
+            {
+                FloatConstant floatConstant = (FloatConstant)constant;
+                if (floatConstant.getValue() == value)
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new FloatConstant(value));
+    }
+
+
+    /**
+     * Finds or creates a DoubleConstant constant pool entry with the given
+     * value.
+     * @return the constant pool index of the DoubleConstant.
+     */
+    public int addDoubleConstant(double value)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_Double)
+            {
+                DoubleConstant doubleConstant = (DoubleConstant)constant;
+                if (doubleConstant.getValue() == value)
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new DoubleConstant(value));
+    }
+
+
+    /**
+     * Finds or creates a StringConstant constant pool entry with the given
+     * value.
+     * @return the constant pool index of the StringConstant.
+     */
+    public int addStringConstant(String string,
+                                 Clazz  referencedClass,
+                                 Member referencedMember)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_String)
+            {
+                StringConstant stringConstant = (StringConstant)constant;
+                if (stringConstant.getString(targetClass).equals(string))
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new StringConstant(addUtf8Constant(string),
+                                              referencedClass,
+                                              referencedMember));
+    }
+
+
+    /**
+     * Finds or creates a FieldrefConstant constant pool entry for the given
+     * class and field.
+     * @return the constant pool index of the FieldrefConstant.
+     */
+    public int addFieldrefConstant(Clazz  referencedClass,
+                                   Member referencedMember)
+    {
+        return addFieldrefConstant(referencedClass.getName(),
+                                   referencedMember.getName(referencedClass),
+                                   referencedMember.getDescriptor(referencedClass),
+                                   referencedClass,
+                                   referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a FieldrefConstant constant pool entry with the given
+     * class name, field name, and descriptor.
+     * @return the constant pool index of the FieldrefConstant.
+     */
+    public int addFieldrefConstant(String className,
+                                   String name,
+                                   String descriptor,
+                                   Clazz  referencedClass,
+                                   Member referencedMember)
+    {
+        return addFieldrefConstant(className,
+                                   addNameAndTypeConstant(name, descriptor),
+                                   referencedClass,
+                                   referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a FieldrefConstant constant pool entry with the given
+     * class name, field name, and descriptor.
+     * @return the constant pool index of the FieldrefConstant.
+     */
+    public int addFieldrefConstant(String className,
+                                   int    nameAndTypeIndex,
+                                   Clazz  referencedClass,
+                                   Member referencedMember)
+    {
+        return addFieldrefConstant(addClassConstant(className, referencedClass),
+                                   nameAndTypeIndex,
+                                   referencedClass,
+                                   referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a FieldrefConstant constant pool entry with the given
+     * class constant pool entry index, field name, and descriptor.
+     * @return the constant pool index of the FieldrefConstant.
+     */
+    public int addFieldrefConstant(int    classIndex,
+                                   String name,
+                                   String descriptor,
+                                   Clazz  referencedClass,
+                                   Member referencedMember)
+    {
+        return addFieldrefConstant(classIndex,
+                                   addNameAndTypeConstant(name, descriptor),
+                                   referencedClass,
+                                   referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a FieldrefConstant constant pool entry with the given
+     * class constant pool entry index and name and type constant pool entry
+     * index.
+     * @return the constant pool index of the FieldrefConstant.
+     */
+    public int addFieldrefConstant(int    classIndex,
+                                   int    nameAndTypeIndex,
+                                   Clazz  referencedClass,
+                                   Member referencedMember)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_Fieldref)
+            {
+                FieldrefConstant fieldrefConstant = (FieldrefConstant)constant;
+                if (fieldrefConstant.u2classIndex         == classIndex &&
+                    fieldrefConstant.u2nameAndTypeIndex   == nameAndTypeIndex)
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new FieldrefConstant(classIndex,
+                                                nameAndTypeIndex,
+                                                referencedClass,
+                                                referencedMember));
+    }
+
+
+    /**
+     * Finds or creates a InterfaceMethodrefConstant constant pool entry with the
+     * given class name, method name, and descriptor.
+     * @return the constant pool index of the InterfaceMethodrefConstant.
+     */
+    public int addInterfaceMethodrefConstant(String className,
+                                             String name,
+                                             String descriptor,
+                                             Clazz  referencedClass,
+                                             Member referencedMember)
+    {
+        return addInterfaceMethodrefConstant(className,
+                                             addNameAndTypeConstant(name, descriptor),
+                                             referencedClass,
+                                             referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a InterfaceMethodrefConstant constant pool entry with the
+     * given class name, method name, and descriptor.
+     * @return the constant pool index of the InterfaceMethodrefConstant.
+     */
+    public int addInterfaceMethodrefConstant(String className,
+                                             int    nameAndTypeIndex,
+                                             Clazz  referencedClass,
+                                             Member referencedMember)
+    {
+        return addInterfaceMethodrefConstant(addClassConstant(className, referencedClass),
+                                             nameAndTypeIndex,
+                                             referencedClass,
+                                             referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a InterfaceMethodrefConstant constant pool entry for the
+     * given class and method.
+     * @return the constant pool index of the InterfaceMethodrefConstant.
+     */
+    public int addInterfaceMethodrefConstant(Clazz  referencedClass,
+                                             Member referencedMember)
+    {
+        return addInterfaceMethodrefConstant(referencedClass.getName(),
+                                             referencedMember.getName(referencedClass),
+                                             referencedMember.getDescriptor(referencedClass),
+                                             referencedClass,
+                                             referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a InterfaceMethodrefConstant constant pool entry with the
+     * given class constant pool entry index, method name, and descriptor.
+     * @return the constant pool index of the InterfaceMethodrefConstant.
+     */
+    public int addInterfaceMethodrefConstant(int    classIndex,
+                                             String name,
+                                             String descriptor,
+                                             Clazz  referencedClass,
+                                             Member referencedMember)
+    {
+        return addInterfaceMethodrefConstant(classIndex,
+                                             addNameAndTypeConstant(name, descriptor),
+                                             referencedClass,
+                                             referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a InterfaceMethodrefConstant constant pool entry with the
+     * given class constant pool entry index and name and type constant pool
+     * entry index.
+     * @return the constant pool index of the InterfaceMethodrefConstant.
+     */
+    public int addInterfaceMethodrefConstant(int    classIndex,
+                                             int    nameAndTypeIndex,
+                                             Clazz  referencedClass,
+                                             Member referencedMember)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                            constant.getTag() == ClassConstants.CONSTANT_InterfaceMethodref)
+            {
+                InterfaceMethodrefConstant methodrefConstant = (InterfaceMethodrefConstant)constant;
+                if (methodrefConstant.u2classIndex       == classIndex &&
+                    methodrefConstant.u2nameAndTypeIndex == nameAndTypeIndex)
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new InterfaceMethodrefConstant(classIndex,
+                                                          nameAndTypeIndex,
+                                                          referencedClass,
+                                                          referencedMember));
+    }
+
+
+    /**
+     * Finds or creates a MethodrefConstant constant pool entry for the given
+     * class and method.
+     * @return the constant pool index of the MethodrefConstant.
+     */
+    public int addMethodrefConstant(Clazz  referencedClass,
+                                    Member referencedMember)
+    {
+        return addMethodrefConstant(referencedClass.getName(),
+                                    referencedMember.getName(referencedClass),
+                                    referencedMember.getDescriptor(referencedClass),
+                                    referencedClass,
+                                    referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a MethodrefConstant constant pool entry with the given
+     * class name, method name, and descriptor.
+     * @return the constant pool index of the MethodrefConstant.
+     */
+    public int addMethodrefConstant(String className,
+                                    String name,
+                                    String descriptor,
+                                    Clazz  referencedClass,
+                                    Member referencedMember)
+    {
+        return addMethodrefConstant(className,
+                                    addNameAndTypeConstant(name, descriptor),
+                                    referencedClass,
+                                    referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a MethodrefConstant constant pool entry with the given
+     * class name, method name, and descriptor.
+     * @return the constant pool index of the MethodrefConstant.
+     */
+    public int addMethodrefConstant(String className,
+                                    int    nameAndTypeIndex,
+                                    Clazz  referencedClass,
+                                    Member referencedMember)
+    {
+        return addMethodrefConstant(addClassConstant(className, referencedClass),
+                                    nameAndTypeIndex,
+                                    referencedClass,
+                                    referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a MethodrefConstant constant pool entry with the given
+     * class constant pool entry index, method name, and descriptor.
+     * @return the constant pool index of the MethodrefConstant.
+     */
+    public int addMethodrefConstant(int    classIndex,
+                                    String name,
+                                    String descriptor,
+                                    Clazz  referencedClass,
+                                    Member referencedMember)
+    {
+        return addMethodrefConstant(classIndex,
+                                    addNameAndTypeConstant(name, descriptor),
+                                    referencedClass,
+                                    referencedMember);
+    }
+
+
+    /**
+     * Finds or creates a MethodrefConstant constant pool entry with the given
+     * class constant pool entry index and name and type constant pool entry
+     * index.
+     * @return the constant pool index of the MethodrefConstant.
+     */
+    public int addMethodrefConstant(int    classIndex,
+                                    int    nameAndTypeIndex,
+                                    Clazz  referencedClass,
+                                    Member referencedMember)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_Methodref)
+            {
+                MethodrefConstant methodrefConstant = (MethodrefConstant)constant;
+                if (methodrefConstant.u2classIndex       == classIndex &&
+                    methodrefConstant.u2nameAndTypeIndex == nameAndTypeIndex)
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new MethodrefConstant(classIndex,
+                                                 nameAndTypeIndex,
+                                                 referencedClass,
+                                                 referencedMember));
+    }
+
+
+    /**
+     * Finds or creates a ClassConstant constant pool entry for the given class.
+     * @return the constant pool index of the ClassConstant.
+     */
+    public int addClassConstant(Clazz referencedClass)
+    {
+        return addClassConstant(referencedClass.getName(),
+                                referencedClass);
+    }
+
+
+    /**
+     * Finds or creates a ClassConstant constant pool entry with the given name.
+     * @return the constant pool index of the ClassConstant.
+     */
+    public int addClassConstant(String name,
+                                Clazz  referencedClass)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_Class)
+            {
+                ClassConstant classConstant = (ClassConstant)constant;
+                if (classConstant.getName(targetClass).equals(name))
+                {
+                    return index;
+                }
+            }
+        }
+
+        int nameIndex = addUtf8Constant(name);
+
+        return addConstant(new ClassConstant(nameIndex, referencedClass));
+    }
+
+
+    /**
+     * Finds or creates a NameAndTypeConstant constant pool entry with the given
+     * name and type.
+     * @return the constant pool index of the NameAndTypeConstant.
+     */
+    public int addNameAndTypeConstant(String name,
+                                      String type)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_NameAndType)
+            {
+                NameAndTypeConstant nameAndTypeConstant = (NameAndTypeConstant)constant;
+                if (nameAndTypeConstant.getName(targetClass).equals(name) &&
+                    nameAndTypeConstant.getType(targetClass).equals(type))
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new NameAndTypeConstant(addUtf8Constant(name),
+                                                   addUtf8Constant(type)));
+    }
+
+
+    /**
+     * Finds or creates a Utf8Constant constant pool entry for the given string.
+     * @return the constant pool index of the Utf8Constant.
+     */
+    public int addUtf8Constant(String string)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Check if the entry already exists.
+        for (int index = 1; index < constantPoolCount; index++)
+        {
+            Constant constant = constantPool[index];
+
+            if (constant != null &&
+                constant.getTag() == ClassConstants.CONSTANT_Utf8)
+            {
+                Utf8Constant utf8Constant = (Utf8Constant)constant;
+                if (utf8Constant.getString().equals(string))
+                {
+                    return index;
+                }
+            }
+        }
+
+        return addConstant(new Utf8Constant(string));
+    }
+
+
+    /**
+     * Adds a given constant pool entry to the end of the constant pool/
+     * @return the constant pool index for the added entry.
+     */
+    public int addConstant(Constant constant)
+    {
+        int        constantPoolCount = targetClass.u2constantPoolCount;
+        Constant[] constantPool      = targetClass.constantPool;
+
+        // Make sure there is enough space for another constant pool entry.
+        if (constantPool.length < constantPoolCount+2)
+        {
+            targetClass.constantPool = new Constant[constantPoolCount+2];
+            System.arraycopy(constantPool, 0,
+                             targetClass.constantPool, 0,
+                             constantPoolCount);
+            constantPool = targetClass.constantPool;
+        }
+
+        if (DEBUG)
+        {
+            System.out.println(targetClass.getName()+": adding ["+constant.getClass().getName()+"] at index "+targetClass.u2constantPoolCount);
+        }
+
+        // Create a new Utf8Constant for the given string.
+        constantPool[targetClass.u2constantPoolCount++] = constant;
+
+        // Long constants and double constants take up two entries in the
+        // constant pool.
+        int tag = constant.getTag();
+        if (tag == ClassConstants.CONSTANT_Long ||
+            tag == ClassConstants.CONSTANT_Double)
+        {
+            constantPool[targetClass.u2constantPoolCount++] = null;
+        }
+
+        return constantPoolCount;
+    }
+}
diff --git a/src/proguard/classfile/editor/ConstantPoolRemapper.java b/src/proguard/classfile/editor/ConstantPoolRemapper.java
new file mode 100644
index 0000000..7430d3d
--- /dev/null
+++ b/src/proguard/classfile/editor/ConstantPoolRemapper.java
@@ -0,0 +1,617 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.preverification.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor remaps all possible references to constant pool entries
+ * of the classes that it visits, based on a given index map. It is assumed that
+ * the constant pool entries themselves have already been remapped.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantPoolRemapper
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             MemberVisitor,
+             AttributeVisitor,
+             InstructionVisitor,
+             InnerClassesInfoVisitor,
+             ExceptionInfoVisitor,
+             StackMapFrameVisitor,
+             VerificationTypeVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
+
+    private int[] constantIndexMap;
+
+
+    /**
+     * Sets the given mapping of old constant pool entry indexes to their new
+     * indexes.
+     */
+    public void setConstantIndexMap(int[] constantIndexMap)
+    {
+        this.constantIndexMap = constantIndexMap;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Remap the local constant pool references.
+        programClass.u2thisClass  = remapConstantIndex(programClass.u2thisClass);
+        programClass.u2superClass = remapConstantIndex(programClass.u2superClass);
+
+        remapConstantIndexArray(programClass.u2interfaces,
+                                programClass.u2interfacesCount);
+
+        // Remap the references of the contant pool entries themselves.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Remap the references in all fields, methods, and attributes.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classConstant.u2nameIndex =
+            remapConstantIndex(classConstant.u2nameIndex);
+    }
+
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        // Nothing to do.
+    }
+
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        fieldrefConstant.u2classIndex =
+            remapConstantIndex(fieldrefConstant.u2classIndex);
+        fieldrefConstant.u2nameAndTypeIndex =
+            remapConstantIndex(fieldrefConstant.u2nameAndTypeIndex);
+    }
+
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        // Nothing to do.
+    }
+
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        // Nothing to do.
+    }
+
+
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant)
+    {
+        interfaceMethodrefConstant.u2classIndex =
+            remapConstantIndex(interfaceMethodrefConstant.u2classIndex);
+        interfaceMethodrefConstant.u2nameAndTypeIndex =
+            remapConstantIndex(interfaceMethodrefConstant.u2nameAndTypeIndex);
+    }
+
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        // Nothing to do.
+    }
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        methodrefConstant.u2classIndex =
+            remapConstantIndex(methodrefConstant.u2classIndex);
+        methodrefConstant.u2nameAndTypeIndex =
+            remapConstantIndex(methodrefConstant.u2nameAndTypeIndex);
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        nameAndTypeConstant.u2nameIndex =
+            remapConstantIndex(nameAndTypeConstant.u2nameIndex);
+        nameAndTypeConstant.u2descriptorIndex =
+            remapConstantIndex(nameAndTypeConstant.u2descriptorIndex);
+    }
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        stringConstant.u2stringIndex =
+            remapConstantIndex(stringConstant.u2stringIndex);
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        // Nothing to do.
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        visitMember(programClass, programField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        visitMember(programClass, programMethod);
+    }
+
+
+    private void visitMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        // Remap the local constant pool references.
+        programMember.u2nameIndex =
+            remapConstantIndex(programMember.u2nameIndex);
+        programMember.u2descriptorIndex =
+            remapConstantIndex(programMember.u2descriptorIndex);
+
+        // Remap the constant pool references of the remaining attributes.
+        programMember.attributesAccept(programClass, this);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        // Library classes are left unchanged.
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        // Library classes are left unchanged.
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        unknownAttribute.u2attributeNameIndex =
+            remapConstantIndex(unknownAttribute.u2attributeNameIndex);
+
+        // There's not much else we can do with unknown attributes.
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        sourceFileAttribute.u2attributeNameIndex =
+            remapConstantIndex(sourceFileAttribute.u2attributeNameIndex);
+        sourceFileAttribute.u2sourceFileIndex =
+            remapConstantIndex(sourceFileAttribute.u2sourceFileIndex);
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        sourceDirAttribute.u2attributeNameIndex =
+            remapConstantIndex(sourceDirAttribute.u2attributeNameIndex);
+        sourceDirAttribute.u2sourceDirIndex       =
+            remapConstantIndex(sourceDirAttribute.u2sourceDirIndex);
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        innerClassesAttribute.u2attributeNameIndex =
+            remapConstantIndex(innerClassesAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the inner classes.
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        enclosingMethodAttribute.u2attributeNameIndex =
+            remapConstantIndex(enclosingMethodAttribute.u2attributeNameIndex);
+        enclosingMethodAttribute.u2classIndex =
+            remapConstantIndex(enclosingMethodAttribute.u2classIndex);
+        enclosingMethodAttribute.u2nameAndTypeIndex =
+            remapConstantIndex(enclosingMethodAttribute.u2nameAndTypeIndex);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        deprecatedAttribute.u2attributeNameIndex =
+            remapConstantIndex(deprecatedAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        syntheticAttribute.u2attributeNameIndex =
+            remapConstantIndex(syntheticAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        signatureAttribute.u2attributeNameIndex =
+            remapConstantIndex(signatureAttribute.u2attributeNameIndex);
+        signatureAttribute.u2signatureIndex       =
+            remapConstantIndex(signatureAttribute.u2signatureIndex);
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        constantValueAttribute.u2attributeNameIndex =
+            remapConstantIndex(constantValueAttribute.u2attributeNameIndex);
+        constantValueAttribute.u2constantValueIndex =
+            remapConstantIndex(constantValueAttribute.u2constantValueIndex);
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        exceptionsAttribute.u2attributeNameIndex =
+            remapConstantIndex(exceptionsAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the exceptions.
+        remapConstantIndexArray(exceptionsAttribute.u2exceptionIndexTable,
+                                exceptionsAttribute.u2exceptionIndexTableLength);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        codeAttribute.u2attributeNameIndex =
+            remapConstantIndex(codeAttribute.u2attributeNameIndex);
+
+        // Initially, the code attribute editor doesn't contain any changes.
+        codeAttributeEditor.reset(codeAttribute.u4codeLength);
+
+        // Remap the constant pool references of the instructions.
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        // Apply the code atribute editor. It will only contain any changes if
+        // the code length is changing at any point.
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Remap the constant pool references of the exceptions and attributes.
+        codeAttribute.exceptionsAccept(clazz, method, this);
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        stackMapAttribute.u2attributeNameIndex =
+            remapConstantIndex(stackMapAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the stack map frames.
+        stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        stackMapTableAttribute.u2attributeNameIndex =
+            remapConstantIndex(stackMapTableAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the stack map frames.
+        stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        lineNumberTableAttribute.u2attributeNameIndex =
+            remapConstantIndex(lineNumberTableAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        localVariableTableAttribute.u2attributeNameIndex =
+            remapConstantIndex(localVariableTableAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        localVariableTypeTableAttribute.u2attributeNameIndex =
+            remapConstantIndex(localVariableTypeTableAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the local variables.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        annotationsAttribute.u2attributeNameIndex =
+            remapConstantIndex(annotationsAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the annotations.
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        parameterAnnotationsAttribute.u2attributeNameIndex =
+            remapConstantIndex(parameterAnnotationsAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the annotations.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        annotationDefaultAttribute.u2attributeNameIndex =
+            remapConstantIndex(annotationDefaultAttribute.u2attributeNameIndex);
+
+        // Remap the constant pool references of the annotations.
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        if (innerClassesInfo.u2innerClassIndex != 0)
+        {
+            innerClassesInfo.u2innerClassIndex =
+                remapConstantIndex(innerClassesInfo.u2innerClassIndex);
+        }
+
+        if (innerClassesInfo.u2outerClassIndex != 0)
+        {
+            innerClassesInfo.u2outerClassIndex =
+                remapConstantIndex(innerClassesInfo.u2outerClassIndex);
+        }
+
+        if (innerClassesInfo.u2innerNameIndex != 0)
+        {
+            innerClassesInfo.u2innerNameIndex =
+                remapConstantIndex(innerClassesInfo.u2innerNameIndex);
+        }
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        if (exceptionInfo.u2catchType != 0)
+        {
+            exceptionInfo.u2catchType =
+                remapConstantIndex(exceptionInfo.u2catchType);
+        }
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        // Is the new constant pool index different from the original one?
+        int newConstantIndex = remapConstantIndex(constantInstruction.constantIndex);
+        if (newConstantIndex != constantInstruction.constantIndex)
+        {
+            // Replace the instruction.
+            Instruction replacementInstruction =
+                new ConstantInstruction(constantInstruction.opcode,
+                                        newConstantIndex,
+                                        constantInstruction.constant).shrink();
+
+            codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+        }
+    }
+
+
+    // Implementations for StackMapFrameVisitor.
+
+    public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame) {}
+
+
+    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+    {
+        // Remap the constant pool references of the verification types.
+        sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+    {
+        // Remap the constant pool references of the verification types.
+        moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+    {
+        // Remap the constant pool references of the verification types.
+        fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
+        fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for VerificationTypeVisitor.
+
+    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {}
+
+
+    public void visitObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType objectType)
+    {
+        objectType.u2classIndex =
+            remapConstantIndex(objectType.u2classIndex);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        localVariableInfo.u2nameIndex =
+            remapConstantIndex(localVariableInfo.u2nameIndex);
+        localVariableInfo.u2descriptorIndex =
+            remapConstantIndex(localVariableInfo.u2descriptorIndex);
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        localVariableTypeInfo.u2nameIndex =
+            remapConstantIndex(localVariableTypeInfo.u2nameIndex);
+        localVariableTypeInfo.u2signatureIndex       =
+            remapConstantIndex(localVariableTypeInfo.u2signatureIndex);
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        annotation.u2typeIndex =
+            remapConstantIndex(annotation.u2typeIndex);
+
+        // Remap the constant pool references of the element values.
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        constantElementValue.u2elementNameIndex =
+            remapConstantIndex(constantElementValue.u2elementNameIndex);
+        constantElementValue.u2constantValueIndex =
+            remapConstantIndex(constantElementValue.u2constantValueIndex);
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        enumConstantElementValue.u2elementNameIndex =
+            remapConstantIndex(enumConstantElementValue.u2elementNameIndex);
+        enumConstantElementValue.u2typeNameIndex =
+            remapConstantIndex(enumConstantElementValue.u2typeNameIndex);
+        enumConstantElementValue.u2constantNameIndex =
+            remapConstantIndex(enumConstantElementValue.u2constantNameIndex);
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        classElementValue.u2elementNameIndex =
+            remapConstantIndex(classElementValue.u2elementNameIndex);
+        classElementValue.u2classInfoIndex       =
+            remapConstantIndex(classElementValue.u2classInfoIndex);
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        annotationElementValue.u2elementNameIndex =
+            remapConstantIndex(annotationElementValue.u2elementNameIndex);
+
+        // Remap the constant pool references of the annotation.
+        annotationElementValue.annotationAccept(clazz, this);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        arrayElementValue.u2elementNameIndex =
+            remapConstantIndex(arrayElementValue.u2elementNameIndex);
+
+        // Remap the constant pool references of the element values.
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Remaps all constant pool indices in the given array.
+     */
+    private void remapConstantIndexArray(int[] array, int length)
+    {
+        for (int index = 0; index < length; index++)
+        {
+            array[index] = remapConstantIndex(array[index]);
+        }
+    }
+
+
+    /**
+     * Returns the new constant pool index of the entry at the
+     * given index.
+     */
+    private int remapConstantIndex(int constantIndex)
+    {
+        return constantIndexMap[constantIndex];
+    }
+}
diff --git a/src/proguard/classfile/editor/ConstantPoolSorter.java b/src/proguard/classfile/editor/ConstantPoolSorter.java
new file mode 100644
index 0000000..faae318
--- /dev/null
+++ b/src/proguard/classfile/editor/ConstantPoolSorter.java
@@ -0,0 +1,126 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.Constant;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+import java.util.Arrays;
+
+/**
+ * This ClassVisitor sorts the constant pool entries of the program classes
+ * that it visits. The sorting order is based on the types of the constant pool
+ * entries in the first place, and on their contents in the second place.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantPoolSorter
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private int[]                constantIndexMap       = new int[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE];
+    private ComparableConstant[] comparableConstantPool = new ComparableConstant[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE];
+    private Constant[]           newConstantPool        = new Constant[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE];
+
+    private final ConstantPoolRemapper constantPoolRemapper = new ConstantPoolRemapper();
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        int constantPoolCount = programClass.u2constantPoolCount;
+
+        // Sort the constant pool and set up an index map.
+        if (constantIndexMap.length < constantPoolCount)
+        {
+            constantIndexMap       = new int[constantPoolCount];
+            comparableConstantPool = new ComparableConstant[constantPoolCount];
+            newConstantPool        = new Constant[constantPoolCount];
+        }
+
+        // Initialize an array whose elements can be compared.
+        int sortLength = 0;
+        for (int oldIndex = 1; oldIndex < constantPoolCount; oldIndex++)
+        {
+            Constant constant = programClass.constantPool[oldIndex];
+            if (constant != null)
+            {
+                comparableConstantPool[sortLength++] =
+                    new ComparableConstant(programClass, oldIndex, constant);
+            }
+        }
+
+        // Sort the array.
+        Arrays.sort(comparableConstantPool, 0, sortLength);
+
+        // Save the sorted elements.
+        int newLength = 1;
+        int newIndex  = 1;
+        ComparableConstant previousComparableConstant = null;
+        for (int sortIndex = 0; sortIndex < sortLength; sortIndex++)
+        {
+            ComparableConstant comparableConstant = comparableConstantPool[sortIndex];
+
+            // Isn't this a duplicate of the previous constant?
+            if (!comparableConstant.equals(previousComparableConstant))
+            {
+                // Remember the index of the new entry.
+                newIndex = newLength;
+
+                // Copy the sorted constant pool entry over to the constant pool.
+                Constant constant = comparableConstant.getConstant();
+
+                newConstantPool[newLength++] = constant;
+
+                // Long entries take up two slots, the second of which is null.
+                int tag = constant.getTag();
+                if (tag == ClassConstants.CONSTANT_Long ||
+                    tag == ClassConstants.CONSTANT_Double)
+                {
+                    newConstantPool[newLength++] = null;
+                }
+
+                previousComparableConstant = comparableConstant;
+            }
+
+            // Fill out the map array.
+            constantIndexMap[comparableConstant.getIndex()] = newIndex;
+        }
+
+        // Copy the new constant pool over.
+        System.arraycopy(newConstantPool, 0, programClass.constantPool, 0, newLength);
+
+        // Clear any remaining entries.
+        for (int index = newLength; index < constantPoolCount; index++)
+        {
+            programClass.constantPool[index] = null;
+        }
+
+        programClass.u2constantPoolCount = newLength;
+
+        // Remap all constant pool references.
+        constantPoolRemapper.setConstantIndexMap(constantIndexMap);
+        constantPoolRemapper.visitProgramClass(programClass);
+    }
+}
diff --git a/src/proguard/classfile/editor/ElementValueAdder.java b/src/proguard/classfile/editor/ElementValueAdder.java
new file mode 100644
index 0000000..8cbd11d
--- /dev/null
+++ b/src/proguard/classfile/editor/ElementValueAdder.java
@@ -0,0 +1,217 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+
+/**
+ * This AnnotationVisitor adds all element values that it visits to the given
+ * target annotation default attribute, annotation, or element value.
+ *
+ * @author Eric Lafortune
+ */
+public class ElementValueAdder
+implements   ElementValueVisitor
+{
+    private static final ElementValue[] EMPTY_ELEMENT_VALUES = new ElementValue[0];
+
+
+    private final ProgramClass               targetClass;
+    private final AnnotationDefaultAttribute targetAnnotationDefaultAttribute;
+
+    private final ConstantAdder       constantAdder;
+    private final ElementValuesEditor elementValuesEditor;
+
+
+    /**
+     * Creates a new ElementValueAdder that will copy element values into the
+     * given target annotation default attribute value.
+     */
+    public ElementValueAdder(ProgramClass               targetClass,
+                             AnnotationDefaultAttribute targetAnnotationDefaultAttribute,
+                             boolean                    replaceElementValues)
+    {
+        this.targetClass                      = targetClass;
+        this.targetAnnotationDefaultAttribute = targetAnnotationDefaultAttribute;
+
+        constantAdder       = new ConstantAdder(targetClass);
+        elementValuesEditor = null;
+    }
+
+
+    /**
+     * Creates a new ElementValueAdder that will copy element values into the
+     * given target annotation.
+     */
+    public ElementValueAdder(ProgramClass targetClass,
+                             Annotation   targetAnnotation,
+                             boolean      replaceElementValues)
+    {
+        this.targetClass                      = targetClass;
+        this.targetAnnotationDefaultAttribute = null;
+
+        constantAdder       = new ConstantAdder(targetClass);
+        elementValuesEditor = new ElementValuesEditor(targetClass,
+                                                      targetAnnotation,
+                                                      replaceElementValues);
+    }
+
+
+    /**
+     * Creates a new ElementValueAdder that will copy element values into the
+     * given target element value.
+     */
+    public ElementValueAdder(ProgramClass      targetClass,
+                             ArrayElementValue targetArrayElementValue,
+                             boolean           replaceElementValues)
+    {
+        this.targetClass                      = targetClass;
+        this.targetAnnotationDefaultAttribute = null;
+
+        constantAdder       = new ConstantAdder(targetClass);
+        elementValuesEditor = new ElementValuesEditor(targetClass,
+                                                      targetArrayElementValue,
+                                                      replaceElementValues);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        // Create a copy of the element value.
+        ConstantElementValue newConstantElementValue =
+            new ConstantElementValue(constantElementValue.u1tag,
+                                     constantElementValue.u2elementNameIndex == 0 ? 0 :
+                                     constantAdder.addConstant(clazz, constantElementValue.u2elementNameIndex),
+                                     constantAdder.addConstant(clazz, constantElementValue.u2constantValueIndex));
+
+        newConstantElementValue.referencedClass  = constantElementValue.referencedClass;
+        newConstantElementValue.referencedMethod = constantElementValue.referencedMethod;
+
+        // Add it to the target.
+        addElementValue(newConstantElementValue);
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        // Create a copy of the element value.
+        EnumConstantElementValue newEnumConstantElementValue =
+            new EnumConstantElementValue(enumConstantElementValue.u2elementNameIndex == 0 ? 0 :
+                                         constantAdder.addConstant(clazz, enumConstantElementValue.u2elementNameIndex),
+                                         constantAdder.addConstant(clazz, enumConstantElementValue.u2typeNameIndex),
+                                         constantAdder.addConstant(clazz, enumConstantElementValue.u2constantNameIndex));
+
+        newEnumConstantElementValue.referencedClass  = enumConstantElementValue.referencedClass;
+        newEnumConstantElementValue.referencedMethod = enumConstantElementValue.referencedMethod;
+
+        // TODO: Clone array.
+        newEnumConstantElementValue.referencedClasses = enumConstantElementValue.referencedClasses;
+
+        // Add it to the target.
+        addElementValue(newEnumConstantElementValue);
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        // Create a copy of the element value.
+        ClassElementValue newClassElementValue =
+            new ClassElementValue(classElementValue.u2elementNameIndex == 0 ? 0 :
+                                  constantAdder.addConstant(clazz, classElementValue.u2elementNameIndex),
+                                  constantAdder.addConstant(clazz, classElementValue.u2classInfoIndex));
+
+        newClassElementValue.referencedClass  = classElementValue.referencedClass;
+        newClassElementValue.referencedMethod = classElementValue.referencedMethod;
+
+        // TODO: Clone array.
+        newClassElementValue.referencedClasses = classElementValue.referencedClasses;
+
+        // Add it to the target.
+        addElementValue(newClassElementValue);
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        // Create a copy of the element value.
+        AnnotationElementValue newAnnotationElementValue =
+            new AnnotationElementValue(annotationElementValue.u2elementNameIndex == 0 ? 0 :
+                                       constantAdder.addConstant(clazz, annotationElementValue.u2elementNameIndex),
+                                       new Annotation());
+
+        newAnnotationElementValue.referencedClass  = annotationElementValue.referencedClass;
+        newAnnotationElementValue.referencedMethod = annotationElementValue.referencedMethod;
+
+        annotationElementValue.annotationAccept(clazz,
+                                                new AnnotationAdder(targetClass,
+                                                                    newAnnotationElementValue));
+
+        // Add it to the target.
+        addElementValue(newAnnotationElementValue);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        // Create a copy of the element value.
+        ArrayElementValue newArrayElementValue =
+            new ArrayElementValue(arrayElementValue.u2elementNameIndex == 0 ? 0 :
+                                  constantAdder.addConstant(clazz, arrayElementValue.u2elementNameIndex),
+                                  0,
+                                  arrayElementValue.u2elementValuesCount > 0 ?
+                                      new ElementValue[arrayElementValue.u2elementValuesCount] :
+                                      EMPTY_ELEMENT_VALUES);
+
+        newArrayElementValue.referencedClass  = arrayElementValue.referencedClass;
+        newArrayElementValue.referencedMethod = arrayElementValue.referencedMethod;
+
+        arrayElementValue.elementValuesAccept(clazz,
+                                              annotation,
+                                              new ElementValueAdder(targetClass,
+                                                                    newArrayElementValue,
+                                                                    false));
+
+        // Add it to the target.
+        addElementValue(newArrayElementValue);
+    }
+
+
+    // Small utility methods.
+
+    private void addElementValue(ElementValue newElementValue)
+    {
+        // What's the target?
+        if (targetAnnotationDefaultAttribute != null)
+        {
+            // Simply set the completed element value.
+            targetAnnotationDefaultAttribute.defaultValue = newElementValue;
+        }
+        else
+        {
+            // Add it to the target.
+            elementValuesEditor.addElementValue(newElementValue);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/ElementValuesEditor.java b/src/proguard/classfile/editor/ElementValuesEditor.java
new file mode 100644
index 0000000..bfc4e9f
--- /dev/null
+++ b/src/proguard/classfile/editor/ElementValuesEditor.java
@@ -0,0 +1,238 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.annotation.*;
+
+/**
+ * This class can add and delete element values to and from a given target
+ * annotation default attribute, annotation, or array element value. Element
+ * values to be added must be filled out beforehand, including their references
+ * to the constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class ElementValuesEditor
+{
+    private final ProgramClass      targetClass;
+    private final Annotation        targetAnnotation;
+    private final ArrayElementValue targetArrayElementValue;
+    private final boolean           replaceElementValues;
+
+
+    /**
+     * Creates a new ElementValuesEditor that will edit element values in the
+     * given target annotation.
+     */
+    public ElementValuesEditor(ProgramClass targetClass,
+                               Annotation   targetAnnotation,
+                               boolean      replaceElementValues)
+    {
+        this.targetClass             = targetClass;
+        this.targetAnnotation        = targetAnnotation;
+        this.targetArrayElementValue = null;
+        this.replaceElementValues    = replaceElementValues;
+    }
+
+
+    /**
+     * Creates a new ElementValuesEditor that will edit element values in the
+     * given target array element value.
+     */
+    public ElementValuesEditor(ProgramClass      targetClass,
+                               ArrayElementValue targetArrayElementValue,
+                               boolean           replaceElementValues)
+    {
+        this.targetClass             = targetClass;
+        this.targetAnnotation        = null;
+        this.targetArrayElementValue = targetArrayElementValue;
+        this.replaceElementValues    = replaceElementValues;
+    }
+
+
+    /**
+     * Adds the given elementValue to the target.
+     */
+    public void addElementValue(ElementValue elementValue)
+    {
+        // What's the target?
+        if (targetAnnotation != null)
+        {
+            // Try to replace an existing element value.
+            if (!replaceElementValues ||
+                !replaceElementValue(targetAnnotation.u2elementValuesCount,
+                                     targetAnnotation.elementValues,
+                                     elementValue))
+            {
+                // Otherwise append the element value.
+                targetAnnotation.elementValues =
+                    addElementValue(targetAnnotation.u2elementValuesCount,
+                                    targetAnnotation.elementValues,
+                                    elementValue);
+
+                targetAnnotation.u2elementValuesCount++;
+            }
+        }
+        else
+        {
+            // Try to replace an existing element value.
+            if (!replaceElementValues ||
+                !replaceElementValue(targetArrayElementValue.u2elementValuesCount,
+                                     targetArrayElementValue.elementValues,
+                                     elementValue))
+            {
+                // Otherwise append the element value.
+                targetArrayElementValue.elementValues =
+                    addElementValue(targetArrayElementValue.u2elementValuesCount,
+                                    targetArrayElementValue.elementValues,
+                                    elementValue);
+
+                targetArrayElementValue.u2elementValuesCount++;
+            }
+        }
+    }
+
+
+    /**
+     * Deletes the given elementValue to the target.
+     */
+    public void deleteElementValue(String elementValueMethodName)
+    {
+        // What's the target?
+        if (targetAnnotation != null)
+        {
+            // Delete the element value to the target annotation.
+            targetAnnotation.u2elementValuesCount =
+                deleteElementValue(targetAnnotation.u2elementValuesCount,
+                                   targetAnnotation.elementValues,
+                                   elementValueMethodName);
+        }
+        else
+        {
+            // Delete the element value to the target array element value.
+            targetArrayElementValue.u2elementValuesCount =
+                deleteElementValue(targetArrayElementValue.u2elementValuesCount,
+                                   targetArrayElementValue.elementValues,
+                                   elementValueMethodName);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Tries put the given element value in place of an existing element value
+     * of the same name, returning whether it was present.
+     */
+    private boolean replaceElementValue(int            elementValuesCount,
+                                        ElementValue[] elementValues,
+                                        ElementValue   elementValue)
+    {
+        // Find the element value with the same name.
+        int index = findElementValue(elementValuesCount,
+                                     elementValues,
+                                     elementValue.getMethodName(targetClass));
+        if (index < 0)
+        {
+            return false;
+        }
+
+        elementValues[index] = elementValue;
+
+        return true;
+    }
+
+
+    /**
+     * Appends the given element value to the given array of element values,
+     * creating a new array if necessary.
+     */
+    private ElementValue[] addElementValue(int            elementValuesCount,
+                                           ElementValue[] elementValues,
+                                           ElementValue   elementValue)
+    {
+        // Is the array too small to contain the additional elementValue?
+        if (elementValues.length <= elementValuesCount)
+        {
+            // Create a new array and copy the elementValues into it.
+            ElementValue[] newElementValues = new ElementValue[elementValuesCount + 1];
+            System.arraycopy(elementValues, 0,
+                             newElementValues, 0,
+                             elementValuesCount);
+            elementValues = newElementValues;
+        }
+
+        // Append the elementValue.
+        elementValues[elementValuesCount] = elementValue;
+
+        return elementValues;
+    }
+
+
+    /**
+     * Deletes the element values with the given name from the given array of
+     * element values, returning the new number of element values.
+     */
+    private int deleteElementValue(int            elementValuesCount,
+                                   ElementValue[] elementValues,
+                                   String         elementValueMethodName)
+    {
+        // Find the element value.
+        int index = findElementValue(elementValuesCount,
+                                     elementValues,
+                                     elementValueMethodName);
+        if (index < 0)
+        {
+            return elementValuesCount;
+        }
+
+        // Shift the other element values in the array.
+        System.arraycopy(elementValues, index + 1,
+                         elementValues, index,
+                         elementValuesCount - index - 1);
+
+        // Clear the last entry in the array.
+        elementValues[--elementValuesCount] = null;
+
+        return elementValuesCount;
+    }
+
+
+    /**
+     * Finds the index of the element value with the given name in the given
+     * array of element values.
+     */
+    private int findElementValue(int            elementValuesCount,
+                                 ElementValue[] elementValues,
+                                 String         elementValueName)
+    {
+        for (int index = 0; index < elementValuesCount; index++)
+        {
+            if (elementValues[index].getMethodName(targetClass).equals(elementValueName))
+            {
+                return index;
+            }
+        }
+
+        return -1;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/ExceptionAdder.java b/src/proguard/classfile/editor/ExceptionAdder.java
new file mode 100644
index 0000000..1ccb1a6
--- /dev/null
+++ b/src/proguard/classfile/editor/ExceptionAdder.java
@@ -0,0 +1,65 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.ExceptionsAttribute;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This ConstantVisitor adds all class constants that it visits to the given
+ * target exceptions attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionAdder
+extends      SimplifiedVisitor
+implements   ConstantVisitor
+{
+    private final ConstantAdder             constantAdder;
+    private final ExceptionsAttributeEditor exceptionsAttributeEditor;
+
+
+    /**
+     * Creates a new ExceptionAdder that will copy classes into the given
+     * target exceptions attribute.
+     */
+    public ExceptionAdder(ProgramClass        targetClass,
+                          ExceptionsAttribute targetExceptionsAttribute)
+    {
+        constantAdder             = new ConstantAdder(targetClass);
+        exceptionsAttributeEditor = new ExceptionsAttributeEditor(targetExceptionsAttribute);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Add a class constant to the constant pool.
+        constantAdder.visitClassConstant(clazz, classConstant);
+
+        // Add the index of the class constant to the list of exceptions.
+        exceptionsAttributeEditor.addException(constantAdder.getConstantIndex());
+    }
+}
diff --git a/src/proguard/classfile/editor/ExceptionInfoAdder.java b/src/proguard/classfile/editor/ExceptionInfoAdder.java
new file mode 100644
index 0000000..e0cc9c5
--- /dev/null
+++ b/src/proguard/classfile/editor/ExceptionInfoAdder.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.ExceptionInfoVisitor;
+
+/**
+ * This ExceptionInfoVisitor adds all exception information that it visits to
+ * the given target code attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionInfoAdder
+implements   ExceptionInfoVisitor
+{
+    private final ConstantAdder         constantAdder;
+    private final CodeAttributeComposer codeAttributeComposer;
+
+
+    /**
+     * Creates a new ExceptionAdder that will copy exceptions into the given
+     * target code attribute.
+     */
+    public ExceptionInfoAdder(ProgramClass          targetClass,
+                              CodeAttributeComposer targetComposer)
+    {
+        constantAdder         = new ConstantAdder(targetClass);
+        codeAttributeComposer = targetComposer;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        // Create a copy of the exception info.
+        ExceptionInfo newExceptionInfo =
+            new ExceptionInfo(exceptionInfo.u2startPC,
+                              exceptionInfo.u2endPC,
+                              exceptionInfo.u2handlerPC,
+                              exceptionInfo.u2catchType == 0 ? 0 :
+                                  constantAdder.addConstant(clazz, exceptionInfo.u2catchType));
+
+        // Add the completed exception info.
+        codeAttributeComposer.appendException(newExceptionInfo);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/ExceptionsAttributeEditor.java b/src/proguard/classfile/editor/ExceptionsAttributeEditor.java
new file mode 100644
index 0000000..4509a9a
--- /dev/null
+++ b/src/proguard/classfile/editor/ExceptionsAttributeEditor.java
@@ -0,0 +1,68 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.ExceptionsAttribute;
+
+/**
+ * This class can add exceptions to a given exceptions attribute.
+ * Exceptions to be added must have been added to the constant pool and filled
+ * out beforehand.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionsAttributeEditor
+{
+    private ExceptionsAttribute targetExceptionsAttribute;
+
+
+    /**
+     * Creates a new ExceptionsAttributeEditor that will edit exceptions in the
+     * given exceptions attribute.
+     */
+    public ExceptionsAttributeEditor(ExceptionsAttribute targetExceptionsAttribute)
+    {
+        this.targetExceptionsAttribute = targetExceptionsAttribute;
+    }
+
+
+    /**
+     * Adds a given exception to the exceptions attribute.
+     */
+    public void addException(int exceptionIndex)
+    {
+        int   exceptionIndexTableLength = targetExceptionsAttribute.u2exceptionIndexTableLength;
+        int[] exceptionIndexTable       = targetExceptionsAttribute.u2exceptionIndexTable;
+
+        // Make sure there is enough space for the new exception.
+        if (exceptionIndexTable.length <= exceptionIndexTableLength)
+        {
+            targetExceptionsAttribute.u2exceptionIndexTable = new int[exceptionIndexTableLength+1];
+            System.arraycopy(exceptionIndexTable, 0,
+                             targetExceptionsAttribute.u2exceptionIndexTable, 0,
+                             exceptionIndexTableLength);
+            exceptionIndexTable = targetExceptionsAttribute.u2exceptionIndexTable;
+        }
+
+        // Add the exception.
+        exceptionIndexTable[targetExceptionsAttribute.u2exceptionIndexTableLength++] = exceptionIndex;
+    }
+}
diff --git a/src/proguard/classfile/editor/InstructionAdder.java b/src/proguard/classfile/editor/InstructionAdder.java
new file mode 100644
index 0000000..60fde6d
--- /dev/null
+++ b/src/proguard/classfile/editor/InstructionAdder.java
@@ -0,0 +1,76 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor adds all instructions that it visits to the given
+ * target code attribute.
+ *
+ * @author Eric Lafortune
+ */
+public class InstructionAdder
+extends      SimplifiedVisitor
+implements   InstructionVisitor
+{
+    private final ConstantAdder         constantAdder;
+    private final CodeAttributeComposer codeAttributeComposer;
+
+
+    /**
+     * Creates a new InstructionAdder that will copy classes into the given
+     * target code attribute.
+     */
+    public InstructionAdder(ProgramClass          targetClass,
+                            CodeAttributeComposer targetComposer)
+    {
+        constantAdder         = new ConstantAdder(targetClass);
+        codeAttributeComposer = targetComposer;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        // Add the instruction.
+        codeAttributeComposer.appendInstruction(offset, instruction);
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        // Create a copy of the instruction.
+        Instruction newConstantInstruction =
+            new ConstantInstruction(constantInstruction.opcode,
+                                    constantAdder.addConstant(clazz, constantInstruction.constantIndex),
+                                    constantInstruction.constant).shrink();
+
+        // Add the instruction.
+        codeAttributeComposer.appendInstruction(offset, newConstantInstruction);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/InstructionWriter.java b/src/proguard/classfile/editor/InstructionWriter.java
new file mode 100644
index 0000000..d842358
--- /dev/null
+++ b/src/proguard/classfile/editor/InstructionWriter.java
@@ -0,0 +1,278 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor writes out the instructions that it visits,
+ * collecting instructions that have to be widened. As an AttributeVisitor,
+ * it then applies the collected changes. The process will be repeated
+ * recursively, if necessary.
+ *
+ * @author Eric Lafortune
+ */
+public class InstructionWriter
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             AttributeVisitor
+{
+    private int codeLength;
+
+    private CodeAttributeEditor codeAttributeEditor;
+
+
+    /**
+     * Resets the accumulated code changes.
+     * @param codeLength the length of the code that will be edited next.
+     */
+    public void reset(int codeLength)
+    {
+        this.codeLength = codeLength;
+
+        // The code attribute editor has to be created lazily.
+        if (codeAttributeEditor != null)
+        {
+            codeAttributeEditor.reset(codeLength);
+        }
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        // Try to write out the instruction.
+        // Simple instructions should always fit.
+        simpleInstruction.write(codeAttribute, offset);
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        try
+        {
+            // Try to write out the instruction.
+            constantInstruction.write(codeAttribute, offset);
+        }
+        catch (IllegalArgumentException exception)
+        {
+            // Create a new constant instruction that will fit.
+            Instruction replacementInstruction =
+                new ConstantInstruction(constantInstruction.opcode,
+                                        constantInstruction.constantIndex,
+                                        constantInstruction.constant).shrink();
+
+            replaceInstruction(offset, replacementInstruction);
+
+            // Write out a dummy constant instruction for now.
+            constantInstruction.constantIndex = 0;
+            constantInstruction.constant      = 0;
+            constantInstruction.write(codeAttribute, offset);
+        }
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        try
+        {
+            // Try to write out the instruction.
+            variableInstruction.write(codeAttribute, offset);
+        }
+        catch (IllegalArgumentException exception)
+        {
+            // Create a new variable instruction that will fit.
+            Instruction replacementInstruction =
+                new VariableInstruction(variableInstruction.opcode,
+                                        variableInstruction.variableIndex,
+                                        variableInstruction.constant).shrink();
+
+            replaceInstruction(offset, replacementInstruction);
+
+            // Write out a dummy variable instruction for now.
+            variableInstruction.variableIndex = 0;
+            variableInstruction.constant      = 0;
+            variableInstruction.write(codeAttribute, offset);
+        }
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        try
+        {
+            // Try to write out the instruction.
+            branchInstruction.write(codeAttribute, offset);
+        }
+        catch (IllegalArgumentException exception)
+        {
+            // Create a new unconditional branch that will fit.
+            Instruction replacementInstruction =
+                new BranchInstruction(InstructionConstants.OP_GOTO_W,
+                                      branchInstruction.branchOffset);
+
+            // Create a new instruction that will fit.
+            switch (branchInstruction.opcode)
+            {
+                default:
+                {
+                    // Create a new branch instruction that will fit.
+                    replacementInstruction =
+                        new BranchInstruction(branchInstruction.opcode,
+                                              branchInstruction.branchOffset).shrink();
+
+                    break;
+                }
+
+                // Some special cases, for which a wide branch doesn't exist.
+                case InstructionConstants.OP_IFEQ:
+                case InstructionConstants.OP_IFNE:
+                case InstructionConstants.OP_IFLT:
+                case InstructionConstants.OP_IFGE:
+                case InstructionConstants.OP_IFGT:
+                case InstructionConstants.OP_IFLE:
+                case InstructionConstants.OP_IFICMPEQ:
+                case InstructionConstants.OP_IFICMPNE:
+                case InstructionConstants.OP_IFICMPLT:
+                case InstructionConstants.OP_IFICMPGE:
+                case InstructionConstants.OP_IFICMPGT:
+                case InstructionConstants.OP_IFICMPLE:
+                case InstructionConstants.OP_IFACMPEQ:
+                case InstructionConstants.OP_IFACMPNE:
+                {
+                    // Insert the complementary conditional branch.
+                    Instruction complementaryConditionalBranch =
+                        new BranchInstruction((byte)(((branchInstruction.opcode+1) ^ 1) - 1),
+                                              (1+2) + (1+4));
+
+                    insertBeforeInstruction(offset, complementaryConditionalBranch);
+
+                    // Create a new unconditional branch that will fit.
+                    break;
+                }
+
+                case InstructionConstants.OP_IFNULL:
+                case InstructionConstants.OP_IFNONNULL:
+                {
+                    // Insert the complementary conditional branch.
+                    Instruction complementaryConditionalBranch =
+                        new BranchInstruction((byte)(branchInstruction.opcode ^ 1),
+                                              (1+2) + (1+4));
+
+                    insertBeforeInstruction(offset, complementaryConditionalBranch);
+
+                    // Create a new unconditional branch that will fit.
+                    break;
+                }
+            }
+
+            replaceInstruction(offset, replacementInstruction);
+
+            // Write out a dummy branch instruction for now.
+            branchInstruction.branchOffset = 0;
+            branchInstruction.write(codeAttribute, offset);
+        }
+    }
+
+
+    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
+    {
+        // Try to write out the instruction.
+        // Switch instructions should always fit.
+        switchInstruction.write(codeAttribute, offset);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Avoid doing any work if nothing is changing anyway.
+        if (codeAttributeEditor != null)
+        {
+            // Apply the collected expansions.
+            codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+
+            // Clear the modifications for the next run.
+            codeAttributeEditor = null;
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Remembers to place the given instruction right before the instruction
+     * at the given offset.
+     */
+    private void insertBeforeInstruction(int instructionOffset, Instruction instruction)
+    {
+        ensureCodeAttributeEditor();
+
+        // Replace the instruction.
+        codeAttributeEditor.insertBeforeInstruction(instructionOffset, instruction);
+    }
+
+
+    /**
+     * Remembers to replace the instruction at the given offset by the given
+     * instruction.
+     */
+    private void replaceInstruction(int instructionOffset, Instruction instruction)
+    {
+        ensureCodeAttributeEditor();
+
+        // Replace the instruction.
+        codeAttributeEditor.replaceInstruction(instructionOffset, instruction);
+    }
+
+
+    /**
+     * Remembers to place the given instruction right after the instruction
+     * at the given offset.
+     */
+    private void insertAfterInstruction(int instructionOffset, Instruction instruction)
+    {
+        ensureCodeAttributeEditor();
+
+        // Replace the instruction.
+        codeAttributeEditor.insertAfterInstruction(instructionOffset, instruction);
+    }
+
+
+    /**
+     * Makes sure there is a code attribute editor for the given code attribute.
+     */
+    private void ensureCodeAttributeEditor()
+    {
+        if (codeAttributeEditor == null)
+        {
+            codeAttributeEditor = new CodeAttributeEditor();
+            codeAttributeEditor.reset(codeLength);
+        }
+    }
+}
diff --git a/src/proguard/classfile/editor/InterfaceAdder.java b/src/proguard/classfile/editor/InterfaceAdder.java
new file mode 100644
index 0000000..e095af6
--- /dev/null
+++ b/src/proguard/classfile/editor/InterfaceAdder.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This ConstantVisitor adds all interfaces that it visits to the given
+ * target class.
+ *
+ * @author Eric Lafortune
+ */
+public class InterfaceAdder
+extends      SimplifiedVisitor
+implements   ConstantVisitor
+{
+    private final ConstantAdder    constantAdder;
+    private final InterfacesEditor interfacesEditor;
+
+
+    /**
+     * Creates a new InterfaceAdder that will add interfaces to the given
+     * target class.
+     */
+    public InterfaceAdder(ProgramClass targetClass)
+    {
+        constantAdder    = new ConstantAdder(targetClass);
+        interfacesEditor = new InterfacesEditor(targetClass);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        interfacesEditor.addInterface(constantAdder.addConstant(clazz, classConstant));
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/InterfaceSorter.java b/src/proguard/classfile/editor/InterfaceSorter.java
new file mode 100644
index 0000000..6521369
--- /dev/null
+++ b/src/proguard/classfile/editor/InterfaceSorter.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+import java.util.Arrays;
+
+/**
+ * This ClassVisitor sorts the interfaces of the program classes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class InterfaceSorter
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        int[] interfaces      = programClass.u2interfaces;
+        int   interfacesCount = programClass.u2interfacesCount;
+
+        // Sort the interfaces.
+        Arrays.sort(interfaces, 0, interfacesCount);
+
+        // Remove any duplicate entries.
+        int newInterfacesCount     = 0;
+        int previousInterfaceIndex = 0;
+        for (int index = 0; index < interfacesCount; index++)
+        {
+            int interfaceIndex = interfaces[index];
+
+            // Isn't this a duplicate of the previous interface?
+            if (interfaceIndex != previousInterfaceIndex)
+            {
+                interfaces[newInterfacesCount++] = interfaceIndex;
+
+                // Remember the interface.
+                previousInterfaceIndex = interfaceIndex;
+            }
+        }
+
+        programClass.u2interfacesCount = newInterfacesCount;
+    }
+}
diff --git a/src/proguard/classfile/editor/InterfacesEditor.java b/src/proguard/classfile/editor/InterfacesEditor.java
new file mode 100644
index 0000000..d3170e1
--- /dev/null
+++ b/src/proguard/classfile/editor/InterfacesEditor.java
@@ -0,0 +1,122 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+
+import java.util.Arrays;
+
+/**
+ * This class can add and delete interfaces to and from classes. References to
+ * the constant pool must be filled out beforehand.
+ *
+ * @author Eric Lafortune
+ */
+public class InterfacesEditor
+{
+    private final ProgramClass targetClass;
+
+
+    /**
+     * Creates a new InterfacesEditor that will edit interfaces in the given
+     * target class.
+     */
+    public InterfacesEditor(ProgramClass targetClass)
+    {
+        this.targetClass = targetClass;
+    }
+
+
+    /**
+     * Adds the specified interface to the target class, if it isn't present yet.
+     */
+    public void addInterface(int interfaceConstantIndex)
+    {
+        // Is the interface not yet present?
+        if (findInterfaceIndex(interfaceConstantIndex) < 0)
+        {
+            int   interfacesCount = targetClass.u2interfacesCount++;
+            int[] interfaces      = targetClass.u2interfaces;
+
+            // Is the array too small to contain the additional interface?
+            if (interfaces.length <= interfacesCount)
+            {
+                // Create a new array and copy the interfaces into it.
+                int[] newinterfaces = new int[interfacesCount + 1];
+                System.arraycopy(interfaces, 0, newinterfaces, 0, interfacesCount);
+                interfaces = newinterfaces;
+
+                targetClass.u2interfaces = interfaces;
+            }
+
+            // Append the interface.
+            interfaces[interfacesCount] = interfaceConstantIndex;
+        }
+    }
+
+
+    /**
+     * Deletes the given interface from the target class, if it is present.
+     */
+    public void deleteInterface(int interfaceConstantIndex)
+    {
+        // Is the interface already present?
+        int interfaceIndex = findInterfaceIndex(interfaceConstantIndex);
+        if (interfaceIndex >= 0)
+        {
+            int   interfacesCount = --targetClass.u2interfacesCount;
+            int[] interfaces      = targetClass.u2interfaces;
+
+            // Shift the other interfaces in the array.
+            for (int index = interfaceIndex; index < interfacesCount; index++)
+            {
+                interfaces[index] = interfaces[index + 1];
+            }
+
+            // Clear the remaining entry in the array.
+            interfaces[interfacesCount] = 0;
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Finds the index of the specified interface in the list of interfaces of
+     * the target class.
+     */
+    private int findInterfaceIndex(int interfaceConstantIndex)
+    {
+        int   interfacesCount = targetClass.u2interfacesCount;
+        int[] interfaces      = targetClass.u2interfaces;
+
+        for (int index = 0; index < interfacesCount; index++)
+        {
+            if (interfaces[index] == interfaceConstantIndex)
+              {
+                  return index;
+              }
+        }
+
+        return -1;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/LineNumberInfoAdder.java b/src/proguard/classfile/editor/LineNumberInfoAdder.java
new file mode 100644
index 0000000..aa8c0c4
--- /dev/null
+++ b/src/proguard/classfile/editor/LineNumberInfoAdder.java
@@ -0,0 +1,59 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.visitor.LineNumberInfoVisitor;
+import proguard.classfile.attribute.*;
+import proguard.classfile.*;
+
+/**
+ * This LineNumberInfoVisitor adds all line numbers that it visits to the given
+ * target line number attribute.
+ */
+public class LineNumberInfoAdder
+implements   LineNumberInfoVisitor
+{
+    private final LineNumberTableAttributeEditor lineNumberTableAttributeEditor;
+
+
+    /**
+     * Creates a new LineNumberInfoAdder that will copy line numbers into the
+     * given target line number table.
+     */
+    public LineNumberInfoAdder(LineNumberTableAttribute targetLineNumberTableAttribute)
+    {
+        this.lineNumberTableAttributeEditor = new LineNumberTableAttributeEditor(targetLineNumberTableAttribute);
+    }
+
+
+    // Implementations for LineNumberInfoVisitor.
+
+    public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
+    {
+        // Create a new line number.
+        LineNumberInfo newLineNumberInfo =
+            new LineNumberInfo(lineNumberInfo.u2startPC,
+                               lineNumberInfo.u2lineNumber);
+
+        // Add it to the target.
+        lineNumberTableAttributeEditor.addLineNumberInfo(newLineNumberInfo);
+    }
+}
diff --git a/src/proguard/classfile/editor/LineNumberTableAttributeEditor.java b/src/proguard/classfile/editor/LineNumberTableAttributeEditor.java
new file mode 100644
index 0000000..ab96b38
--- /dev/null
+++ b/src/proguard/classfile/editor/LineNumberTableAttributeEditor.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.*;
+
+/**
+ * This class can add line numbers to a given line number table attribute.
+ * Line numbers to be added must have been filled out beforehand.
+ *
+ * @author Eric Lafortune
+ */
+public class LineNumberTableAttributeEditor
+{
+    private LineNumberTableAttribute targetLineNumberTableAttribute;
+
+
+    /**
+     * Creates a new LineNumberTableAttributeEditor that will edit line numbers
+     * in the given line number table attribute.
+     */
+    public LineNumberTableAttributeEditor(LineNumberTableAttribute targetLineNumberTableAttribute)
+    {
+        this.targetLineNumberTableAttribute = targetLineNumberTableAttribute;
+    }
+
+
+    /**
+     * Adds a given line number to the line number table attribute.
+     */
+    public void addLineNumberInfo(LineNumberInfo lineNumberInfo)
+    {
+        int              lineNumberTableLength = targetLineNumberTableAttribute.u2lineNumberTableLength;
+        LineNumberInfo[] lineNumberTable       = targetLineNumberTableAttribute.lineNumberTable;
+
+        // Make sure there is enough space for the new lineNumberInfo.
+        if (lineNumberTable.length <= lineNumberTableLength)
+        {
+            targetLineNumberTableAttribute.lineNumberTable = new LineNumberInfo[lineNumberTableLength+1];
+            System.arraycopy(lineNumberTable, 0,
+                             targetLineNumberTableAttribute.lineNumberTable, 0,
+                             lineNumberTableLength);
+            lineNumberTable = targetLineNumberTableAttribute.lineNumberTable;
+        }
+
+        // Add the lineNumberInfo.
+        lineNumberTable[targetLineNumberTableAttribute.u2lineNumberTableLength++] = lineNumberInfo;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/LocalVariableInfoAdder.java b/src/proguard/classfile/editor/LocalVariableInfoAdder.java
new file mode 100644
index 0000000..f285e4f
--- /dev/null
+++ b/src/proguard/classfile/editor/LocalVariableInfoAdder.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.visitor.LocalVariableInfoVisitor;
+import proguard.classfile.attribute.*;
+import proguard.classfile.*;
+
+/**
+ * This LocalVariableInfoVisitor adds all line numbers that it visits to the given
+ * target line number attribute.
+ */
+public class LocalVariableInfoAdder
+implements   LocalVariableInfoVisitor
+{
+    private final ConstantAdder                     constantAdder;
+    private final LocalVariableTableAttributeEditor localVariableTableAttributeEditor;
+
+
+    /**
+     * Creates a new LocalVariableInfoAdder that will copy line numbers into the
+     * given target line number table.
+     */
+    public LocalVariableInfoAdder(ProgramClass                targetClass,
+                                  LocalVariableTableAttribute targetLocalVariableTableAttribute)
+    {
+        this.constantAdder                     = new ConstantAdder(targetClass);
+        this.localVariableTableAttributeEditor = new LocalVariableTableAttributeEditor(targetLocalVariableTableAttribute);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        // Create a new line number.
+        LocalVariableInfo newLocalVariableInfo =
+            new LocalVariableInfo(localVariableInfo.u2startPC,
+                                  localVariableInfo.u2length,
+                                  constantAdder.addConstant(clazz, localVariableInfo.u2nameIndex),
+                                  constantAdder.addConstant(clazz, localVariableInfo.u2descriptorIndex),
+                                  localVariableInfo.u2index);
+
+        newLocalVariableInfo.referencedClass = localVariableInfo.referencedClass;
+
+        // Add it to the target.
+        localVariableTableAttributeEditor.addLocalVariableInfo(newLocalVariableInfo);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/LocalVariableTableAttributeEditor.java b/src/proguard/classfile/editor/LocalVariableTableAttributeEditor.java
new file mode 100644
index 0000000..053b628
--- /dev/null
+++ b/src/proguard/classfile/editor/LocalVariableTableAttributeEditor.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.*;
+
+/**
+ * This class can add local variables to a given local variable table attribute.
+ * Local variables to be added must have been filled out beforehand.
+ *
+ * @author Eric Lafortune
+ */
+public class LocalVariableTableAttributeEditor
+{
+    private LocalVariableTableAttribute targetLocalVariableTableAttribute;
+
+
+    /**
+     * Creates a new LocalVariableTableAttributeEditor that will edit line numbers
+     * in the given line number table attribute.
+     */
+    public LocalVariableTableAttributeEditor(LocalVariableTableAttribute targetLocalVariableTableAttribute)
+    {
+        this.targetLocalVariableTableAttribute = targetLocalVariableTableAttribute;
+    }
+
+
+    /**
+     * Adds a given line number to the line number table attribute.
+     */
+    public void addLocalVariableInfo(LocalVariableInfo localVariableInfo)
+    {
+        int                 localVariableTableLength = targetLocalVariableTableAttribute.u2localVariableTableLength;
+        LocalVariableInfo[] localVariableTable       = targetLocalVariableTableAttribute.localVariableTable;
+
+        // Make sure there is enough space for the new localVariableInfo.
+        if (localVariableTable.length <= localVariableTableLength)
+        {
+            targetLocalVariableTableAttribute.localVariableTable = new LocalVariableInfo[localVariableTableLength+1];
+            System.arraycopy(localVariableTable, 0,
+                             targetLocalVariableTableAttribute.localVariableTable, 0,
+                             localVariableTableLength);
+            localVariableTable = targetLocalVariableTableAttribute.localVariableTable;
+        }
+
+        // Add the localVariableInfo.
+        localVariableTable[targetLocalVariableTableAttribute.u2localVariableTableLength++] = localVariableInfo;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/LocalVariableTypeInfoAdder.java b/src/proguard/classfile/editor/LocalVariableTypeInfoAdder.java
new file mode 100644
index 0000000..ca50f3f
--- /dev/null
+++ b/src/proguard/classfile/editor/LocalVariableTypeInfoAdder.java
@@ -0,0 +1,68 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.visitor.LocalVariableTypeInfoVisitor;
+import proguard.classfile.attribute.*;
+import proguard.classfile.*;
+
+/**
+ * This LocalVariableTypeInfoVisitor adds all line numbers that it visits to the given
+ * target line number attribute.
+ */
+public class LocalVariableTypeInfoAdder
+implements   LocalVariableTypeInfoVisitor
+{
+    private final ConstantAdder                     constantAdder;
+    private final LocalVariableTypeTableAttributeEditor localVariableTypeTableAttributeEditor;
+
+
+    /**
+     * Creates a new LocalVariableTypeInfoAdder that will copy line numbers into the
+     * given target line number table.
+     */
+    public LocalVariableTypeInfoAdder(ProgramClass                    targetClass,
+                                      LocalVariableTypeTableAttribute targetLocalVariableTypeTableAttribute)
+    {
+        this.constantAdder                         = new ConstantAdder(targetClass);
+        this.localVariableTypeTableAttributeEditor = new LocalVariableTypeTableAttributeEditor(targetLocalVariableTypeTableAttribute);
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        // Create a new line number.
+        LocalVariableTypeInfo newLocalVariableTypeInfo =
+            new LocalVariableTypeInfo(localVariableTypeInfo.u2startPC,
+                                      localVariableTypeInfo.u2length,
+                                      constantAdder.addConstant(clazz, localVariableTypeInfo.u2nameIndex),
+                                      constantAdder.addConstant(clazz, localVariableTypeInfo.u2signatureIndex),
+                                      localVariableTypeInfo.u2index);
+
+        // TODO: Clone array.
+        newLocalVariableTypeInfo.referencedClasses = localVariableTypeInfo.referencedClasses;
+
+        // Add it to the target.
+        localVariableTypeTableAttributeEditor.addLocalVariableTypeInfo(newLocalVariableTypeInfo);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/LocalVariableTypeTableAttributeEditor.java b/src/proguard/classfile/editor/LocalVariableTypeTableAttributeEditor.java
new file mode 100644
index 0000000..fe5a64d
--- /dev/null
+++ b/src/proguard/classfile/editor/LocalVariableTypeTableAttributeEditor.java
@@ -0,0 +1,68 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.*;
+
+/**
+ * This class can add local variables to a given local variable type table
+ * attribute.
+ * Local variable types to be added must have been filled out beforehand.
+ *
+ * @author Eric Lafortune
+ */
+public class LocalVariableTypeTableAttributeEditor
+{
+    private LocalVariableTypeTableAttribute targetLocalVariableTypeTableAttribute;
+
+
+    /**
+     * Creates a new LocalVariableTypeTableAttributeEditor that will edit line numbers
+     * in the given line number table attribute.
+     */
+    public LocalVariableTypeTableAttributeEditor(LocalVariableTypeTableAttribute targetLocalVariableTypeTableAttribute)
+    {
+        this.targetLocalVariableTypeTableAttribute = targetLocalVariableTypeTableAttribute;
+    }
+
+
+    /**
+     * Adds a given line number to the line number table attribute.
+     */
+    public void addLocalVariableTypeInfo(LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        int                     localVariableTypeTableLength = targetLocalVariableTypeTableAttribute.u2localVariableTypeTableLength;
+        LocalVariableTypeInfo[] localVariableTypeTable       = targetLocalVariableTypeTableAttribute.localVariableTypeTable;
+
+        // Make sure there is enough space for the new localVariableTypeInfo.
+        if (localVariableTypeTable.length <= localVariableTypeTableLength)
+        {
+            targetLocalVariableTypeTableAttribute.localVariableTypeTable = new LocalVariableTypeInfo[localVariableTypeTableLength+1];
+            System.arraycopy(localVariableTypeTable, 0,
+                             targetLocalVariableTypeTableAttribute.localVariableTypeTable, 0,
+                             localVariableTypeTableLength);
+            localVariableTypeTable = targetLocalVariableTypeTableAttribute.localVariableTypeTable;
+        }
+
+        // Add the localVariableTypeInfo.
+        localVariableTypeTable[targetLocalVariableTypeTableAttribute.u2localVariableTypeTableLength++] = localVariableTypeInfo;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/MemberAdder.java b/src/proguard/classfile/editor/MemberAdder.java
new file mode 100644
index 0000000..5f939bb
--- /dev/null
+++ b/src/proguard/classfile/editor/MemberAdder.java
@@ -0,0 +1,257 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This ConstantVisitor adds all class members that it visits to the given
+ * target class.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberAdder
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    private static final Attribute[] EMPTY_ATTRIBUTES = new Attribute[0];
+
+
+    private final ProgramClass targetClass;
+//    private final boolean      addFields;
+
+    private final ConstantAdder      constantAdder;
+    private final ClassEditor        classEditor;
+    private final ConstantPoolEditor constantPoolEditor;
+
+
+    /**
+     * Creates a new MemberAdder that will copy methods into the given target
+     * class.
+     * @param targetClass the class to which all visited class members will be
+     *                    added.
+     */
+//     * @param addFields   specifies whether fields should be added, or fused
+//     *                    with the present fields.
+    public MemberAdder(ProgramClass targetClass)//),
+//                       boolean      addFields)
+    {
+        this.targetClass = targetClass;
+//        this.addFields   = addFields;
+
+        constantAdder      = new ConstantAdder(targetClass);
+        classEditor        = new ClassEditor(targetClass);
+        constantPoolEditor = new ConstantPoolEditor(targetClass);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        String name        = programField.getName(programClass);
+        String descriptor  = programField.getDescriptor(programClass);
+        int    accessFlags = programField.getAccessFlags();
+
+        // Does the target class already have such a field?
+        ProgramField targetField = (ProgramField)targetClass.findField(name, descriptor);
+        if (targetField != null)
+        {
+            // Is the field private or static?
+            int targetAccessFlags = targetField.getAccessFlags();
+            if ((targetAccessFlags &
+                 (ClassConstants.INTERNAL_ACC_PRIVATE |
+                  ClassConstants.INTERNAL_ACC_STATIC)) != 0)
+            {
+                if (DEBUG)
+                {
+                    System.out.println("MemberAdder: renaming field ["+targetClass+"."+targetField.getName(targetClass)+" "+targetField.getDescriptor(targetClass)+"]");
+                }
+
+                // Rename the private or static field.
+                targetField.u2nameIndex =
+                    constantPoolEditor.addUtf8Constant(newUniqueMemberName(name, targetClass.getName()));
+            }
+//            else
+//            {
+//                // Keep the non-private and non-static field, but update its
+//                // contents, in order to keep any references to it valid.
+//                if (DEBUG)
+//                {
+//                    System.out.println("MemberAdder: updating field ["+programClass+"."+programField.getName(programClass)+" "+programField.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]");
+//                }
+//
+//                // Combine the access flags.
+//                targetField.u2accessFlags = accessFlags | targetAccessFlags;
+//
+//                // Add and replace any attributes.
+//                programField.attributesAccept(programClass,
+//                                              new AttributeAdder(targetClass,
+//                                                                 targetField,
+//                                                                 true));
+//
+//                // Don't add a new field.
+//                return;
+//            }
+        }
+
+        if (DEBUG)
+        {
+            System.out.println("MemberAdder: copying field ["+programClass+"."+programField.getName(programClass)+" "+programField.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]");
+        }
+
+        // Create a copy of the field.
+        ProgramField newProgramField =
+            new ProgramField(accessFlags,
+                             constantAdder.addConstant(programClass, programField.u2nameIndex),
+                             constantAdder.addConstant(programClass, programField.u2descriptorIndex),
+                             0,
+                             programField.u2attributesCount > 0 ?
+                                 new Attribute[programField.u2attributesCount] :
+                                 EMPTY_ATTRIBUTES,
+                             programField.referencedClass);
+
+        // Link to its visitor info.
+        newProgramField.setVisitorInfo(programField);
+
+        // Copy its attributes.
+        programField.attributesAccept(programClass,
+                                      new AttributeAdder(targetClass,
+                                                         newProgramField,
+                                                         false));
+
+        // Add the completed field.
+        classEditor.addField(newProgramField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        String name        = programMethod.getName(programClass);
+        String descriptor  = programMethod.getDescriptor(programClass);
+        int    accessFlags = programMethod.getAccessFlags();
+
+        // Does the target class already have such a method?
+        ProgramMethod targetMethod = (ProgramMethod)targetClass.findMethod(name, descriptor);
+        if (targetMethod != null)
+        {
+            // is this source method abstract?
+            if ((accessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0)
+            {
+                // Keep the target method.
+                if (DEBUG)
+                {
+                    System.out.println("MemberAdder: skipping abstract method ["+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]");
+                }
+
+                // Don't add a new method.
+                return;
+            }
+
+            // Is the target method abstract?
+            int targetAccessFlags = targetMethod.getAccessFlags();
+            if ((targetAccessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0)
+            {
+                // Keep the abstract method, but update its contents, in order
+                // to keep any references to it valid.
+                if (DEBUG)
+                {
+                    System.out.println("MemberAdder: updating method ["+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]");
+                }
+
+                // Replace the access flags.
+                targetMethod.u2accessFlags =
+                    accessFlags & ~ClassConstants.INTERNAL_ACC_FINAL;
+
+                // Add and replace the attributes.
+                programMethod.attributesAccept(programClass,
+                                               new AttributeAdder(targetClass,
+                                                                  targetMethod,
+                                                                  true));
+
+                // Don't add a new method.
+                return;
+            }
+
+            if (DEBUG)
+            {
+                System.out.println("MemberAdder: renaming method ["+targetClass.getName()+"."+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+"]");
+            }
+
+            // Rename the private (non-abstract) or static method.
+            targetMethod.u2nameIndex =
+                constantPoolEditor.addUtf8Constant(newUniqueMemberName(name, descriptor));
+        }
+
+        if (DEBUG)
+        {
+            System.out.println("MemberAdder: copying method ["+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]");
+        }
+
+        // Create a copy of the method.
+        ProgramMethod newProgramMethod =
+            new ProgramMethod(accessFlags & ~ClassConstants.INTERNAL_ACC_FINAL,
+                              constantAdder.addConstant(programClass, programMethod.u2nameIndex),
+                              constantAdder.addConstant(programClass, programMethod.u2descriptorIndex),
+                              0,
+                              programMethod.u2attributesCount > 0 ?
+                                  new Attribute[programMethod.u2attributesCount] :
+                                  EMPTY_ATTRIBUTES,
+                              programMethod.referencedClasses != null ?
+                                  (Clazz[])programMethod.referencedClasses.clone() :
+                                  null);
+
+        // Link to its visitor info.
+        newProgramMethod.setVisitorInfo(programMethod);
+
+        // Copy its attributes.
+        programMethod.attributesAccept(programClass,
+                                       new AttributeAdder(targetClass,
+                                                          newProgramMethod,
+                                                          false));
+
+        // Add the completed method.
+        classEditor.addMethod(newProgramMethod);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns a unique class member name, based on the given name and descriptor.
+     */
+    private String newUniqueMemberName(String name, String descriptor)
+    {
+        return name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ?
+            ClassConstants.INTERNAL_METHOD_NAME_INIT :
+            name + ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode()));
+    }
+}
diff --git a/src/proguard/classfile/editor/MemberReferenceFixer.java b/src/proguard/classfile/editor/MemberReferenceFixer.java
new file mode 100644
index 0000000..4bd8af5
--- /dev/null
+++ b/src/proguard/classfile/editor/MemberReferenceFixer.java
@@ -0,0 +1,456 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor fixes constant pool field and method references to fields
+ * and methods whose names or descriptors have changed.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberReferenceFixer
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             MemberVisitor,
+             AttributeVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    private static final boolean DEBUG = false;
+
+
+    private final StackSizeUpdater stackSizeUpdater = new StackSizeUpdater();
+
+    // Parameter for the visitor methods.
+    private int constantIndex;
+
+    // Return values for the visitor methods.
+    private boolean isInterfaceMethod;
+    private boolean stackSizesMayHaveChanged;
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        stackSizesMayHaveChanged = false;
+
+        // Fix the constant pool entries.
+        for (int index = 1; index < programClass.u2constantPoolCount; index++)
+        {
+            Constant constant = programClass.constantPool[index];
+            if (constant != null)
+            {
+                // Fix the entry, replacing it entirely if needed.
+                this.constantIndex = index;
+
+                constant.accept(programClass, this);
+            }
+        }
+
+        // Fix the class members.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+
+        // Fix the attributes.
+        programClass.attributesAccept(this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Does the string refer to a class member, due to a
+        // Class.get[Declared]{Field,Method} construct?
+        Member referencedMember = stringConstant.referencedMember;
+        if (referencedMember != null)
+        {
+            Clazz referencedClass = stringConstant.referencedClass;
+
+            // Does it have a new name?
+            String newName = referencedMember.getName(referencedClass);
+
+            if (!stringConstant.getString(clazz).equals(newName))
+            {
+                if (DEBUG)
+                {
+                    debug(clazz, stringConstant, referencedClass, referencedMember);
+                }
+
+                // Update the name.
+                stringConstant.u2stringIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newName);
+            }
+        }
+    }
+
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        // Do we know the referenced field?
+        Member referencedMember = fieldrefConstant.referencedMember;
+        if (referencedMember != null)
+        {
+            Clazz referencedClass = fieldrefConstant.referencedClass;
+
+            // Does it have a new name or type?
+            String newName = referencedMember.getName(referencedClass);
+            String newType = referencedMember.getDescriptor(referencedClass);
+
+            if (!fieldrefConstant.getName(clazz).equals(newName) ||
+                !fieldrefConstant.getType(clazz).equals(newType))
+            {
+                if (DEBUG)
+                {
+                    debug(clazz, fieldrefConstant, referencedClass, referencedMember);
+                }
+
+                // Update the name and type index.
+                fieldrefConstant.u2nameAndTypeIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(newName, newType);
+            }
+        }
+    }
+
+
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant)
+    {
+        // Do we know the referenced interface method?
+        Member referencedMember = interfaceMethodrefConstant.referencedMember;
+        if (referencedMember != null)
+        {
+            Clazz referencedClass = interfaceMethodrefConstant.referencedClass;
+
+            // Does it have a new name or type?
+            String newName = referencedMember.getName(referencedClass);
+            String newType = referencedMember.getDescriptor(referencedClass);
+
+            if (!interfaceMethodrefConstant.getName(clazz).equals(newName) ||
+                !interfaceMethodrefConstant.getType(clazz).equals(newType))
+            {
+                if (DEBUG)
+                {
+                    debug(clazz, interfaceMethodrefConstant, referencedClass, referencedMember);
+                }
+
+                // Update the name and type index.
+                interfaceMethodrefConstant.u2nameAndTypeIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(newName, newType);
+
+                // Remember that the stack sizes of the methods in this class
+                // may have changed.
+                stackSizesMayHaveChanged = true;
+            }
+
+            // Check if this is an interface method.
+            isInterfaceMethod = true;
+            clazz.constantPoolEntryAccept(interfaceMethodrefConstant.u2classIndex, this);
+
+            // Has the method become a non-interface method?
+            if (!isInterfaceMethod)
+            {
+                if (DEBUG)
+                {
+                    System.out.println("MemberReferenceFixer:");
+                    System.out.println("  Class file     = "+clazz.getName());
+                    System.out.println("  Ref class      = "+referencedClass.getName());
+                    System.out.println("  Ref method     = "+interfaceMethodrefConstant.getName(clazz)+interfaceMethodrefConstant.getType(clazz));
+                    System.out.println("    -> ordinary method");
+                }
+
+                // Replace the interface method reference by a method reference.
+                ((ProgramClass)clazz).constantPool[this.constantIndex] =
+                    new MethodrefConstant(interfaceMethodrefConstant.u2classIndex,
+                                          interfaceMethodrefConstant.u2nameAndTypeIndex,
+                                          referencedClass,
+                                          referencedMember);
+            }
+        }
+    }
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        // Do we know the referenced method?
+        Member referencedMember = methodrefConstant.referencedMember;
+        if (referencedMember != null)
+        {
+            Clazz referencedClass = methodrefConstant.referencedClass;
+
+            // Does it have a new name or type?
+            String newName = referencedMember.getName(referencedClass);
+            String newType = referencedMember.getDescriptor(referencedClass);
+
+            if (!methodrefConstant.getName(clazz).equals(newName) ||
+                !methodrefConstant.getType(clazz).equals(newType))
+            {
+                if (DEBUG)
+                {
+                    debug(clazz, methodrefConstant, referencedClass, referencedMember);
+                }
+
+                // Update the name and type index.
+                methodrefConstant.u2nameAndTypeIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(newName, newType);
+
+                // Remember that the stack sizes of the methods in this class
+                // may have changed.
+                stackSizesMayHaveChanged = true;
+            }
+
+            // Check if this is an interface method.
+            isInterfaceMethod = false;
+            clazz.constantPoolEntryAccept(methodrefConstant.u2classIndex, this);
+
+            // Has the method become an interface method?
+            if (isInterfaceMethod)
+            {
+                if (DEBUG)
+                {
+                    System.out.println("MemberReferenceFixer:");
+                    System.out.println("  Class file     = "+clazz.getName());
+                    System.out.println("  Ref class      = "+referencedClass.getName());
+                    System.out.println("  Ref method     = "+methodrefConstant.getName(clazz)+methodrefConstant.getType(clazz));
+                    System.out.println("    -> interface method");
+                }
+
+                // Replace the method reference by an interface method reference.
+                ((ProgramClass)clazz).constantPool[this.constantIndex] =
+                    new InterfaceMethodrefConstant(methodrefConstant.u2classIndex,
+                                                   methodrefConstant.u2nameAndTypeIndex,
+                                                   referencedClass,
+                                                   referencedMember);
+            }
+        }
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Check if this class entry is an array type.
+        if (ClassUtil.isInternalArrayType(classConstant.getName(clazz)))
+        {
+            isInterfaceMethod = false;
+        }
+        else
+        {
+            // Check if this class entry refers to an interface class.
+            Clazz referencedClass = classConstant.referencedClass;
+            if (referencedClass != null)
+            {
+                isInterfaceMethod = (referencedClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE) != 0;
+            }
+        }
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        // Fix the attributes.
+        programMember.attributesAccept(programClass, this);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        Member referencedMember = enclosingMethodAttribute.referencedMethod;
+        if (referencedMember != null)
+        {
+            Clazz referencedClass = enclosingMethodAttribute.referencedClass;
+
+            // Does it have a new class?
+            if (!enclosingMethodAttribute.getClassName(clazz).equals(referencedClass.getName()))
+            {
+                // Update the class index.
+                enclosingMethodAttribute.u2classIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addClassConstant(referencedClass);
+            }
+
+            // Does it have a new name or type?
+            if (!enclosingMethodAttribute.getName(clazz).equals(referencedMember.getName(referencedClass)) ||
+                !enclosingMethodAttribute.getType(clazz).equals(referencedMember.getDescriptor(referencedClass)))
+            {
+                // Update the name and type index.
+                enclosingMethodAttribute.u2nameAndTypeIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addNameAndTypeConstant(referencedMember.getName(referencedClass),
+                                                                                       referencedMember.getDescriptor(referencedClass));
+            }
+        }
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Recompute the maximum stack size if necessary.
+        if (stackSizesMayHaveChanged)
+        {
+                stackSizeUpdater.visitCodeAttribute(clazz, method, codeAttribute);
+        }
+
+        // Fix the nested attributes.
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Fix the annotations.
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Fix the annotations.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Fix the annotation.
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        // Fix the element values.
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        fixElementValue(clazz, annotation, constantElementValue);
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        fixElementValue(clazz, annotation, enumConstantElementValue);
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        fixElementValue(clazz, annotation, classElementValue);
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        fixElementValue(clazz, annotation, annotationElementValue);
+
+        // Fix the annotation.
+        annotationElementValue.annotationAccept(clazz, this);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        fixElementValue(clazz, annotation, arrayElementValue);
+
+        // Fix the element values.
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Fixes the method reference of the element value, if any.
+     */
+    private void fixElementValue(Clazz        clazz,
+                                 Annotation   annotation,
+                                 ElementValue elementValue)
+    {
+        // Do we know the referenced method?
+        Member referencedMember = elementValue.referencedMethod;
+        if (referencedMember != null)
+        {
+            // Does it have a new name or type?
+            String methodName    = elementValue.getMethodName(clazz);
+            String newMethodName = referencedMember.getName(elementValue.referencedClass);
+
+            if (!methodName.equals(newMethodName))
+            {
+                // Update the element name index.
+                elementValue.u2elementNameIndex =
+                    new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newMethodName);
+            }
+        }
+    }
+
+
+    private void debug(Clazz          clazz,
+                       StringConstant stringConstant,
+                       Clazz          referencedClass,
+                       Member         referencedMember)
+    {
+        System.out.println("MemberReferenceFixer:");
+        System.out.println("  Class file      = "+clazz.getName());
+        System.out.println("  Ref class       = "+referencedClass.getName());
+        System.out.println("  Ref member name = "+stringConstant.getString(clazz));
+        System.out.println("                 -> "+referencedMember.getName(referencedClass));
+    }
+
+
+    private void debug(Clazz       clazz,
+                       RefConstant refConstant,
+                       Clazz       referencedClass,
+                       Member      referencedMember)
+    {
+        System.out.println("MemberReferenceFixer:");
+        System.out.println("  Class file      = "+clazz.getName());
+        System.out.println("  Ref class       = "+referencedClass.getName());
+        System.out.println("  Ref member name = "+refConstant.getName(clazz));
+        System.out.println("                 -> "+referencedMember.getName(referencedClass));
+        System.out.println("  Ref descriptor  = "+refConstant.getType(clazz));
+        System.out.println("                 -> "+referencedMember.getDescriptor(referencedClass));
+    }
+}
diff --git a/src/proguard/classfile/editor/MethodInvocationFixer.java b/src/proguard/classfile/editor/MethodInvocationFixer.java
new file mode 100644
index 0000000..ef76012
--- /dev/null
+++ b/src/proguard/classfile/editor/MethodInvocationFixer.java
@@ -0,0 +1,254 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This AttributeVisitor fixes all inappropriate special/virtual/static/interface
+ * invocations of the code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class MethodInvocationFixer
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ConstantVisitor,
+             ClassVisitor,
+             MemberVisitor
+{
+    private static final boolean DEBUG = false;
+
+
+    private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
+
+    // Return values for the visitor methods.
+    private Clazz  referencedClass;
+    private Clazz  referencedMethodClass;
+    private Member referencedMethod;
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Reset the code attribute editor.
+        codeAttributeEditor.reset(codeAttribute.u4codeLength);
+
+        // Remap the variables of the instructions.
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        // Apply the code atribute editor.
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        int constantIndex = constantInstruction.constantIndex;
+
+        // Get information on the called class and method, if present.
+        referencedMethod = null;
+
+        clazz.constantPoolEntryAccept(constantIndex, this);
+
+        // Did we find the called class and method?
+        if (referencedMethod != null)
+        {
+            // Do we need to update the opcode?
+            byte opcode = constantInstruction.opcode;
+
+            // Is the method static?
+            if ((referencedMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0)
+            {
+                // But is it not a static invocation?
+                if (opcode != InstructionConstants.OP_INVOKESTATIC)
+                {
+                    // Replace the invocation by an invokestatic instruction.
+                    Instruction replacementInstruction =
+                        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC,
+                                                constantIndex).shrink();
+
+                    codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+
+                    if (DEBUG)
+                    {
+                        debug(clazz, method, offset, constantInstruction, replacementInstruction);
+                    }
+                }
+            }
+
+            // Is the method private, or an instance initializer?
+            else if ((referencedMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_PRIVATE) != 0 ||
+                     referencedMethod.getName(referencedMethodClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+            {
+                // But is it not a special invocation?
+                if (opcode != InstructionConstants.OP_INVOKESPECIAL)
+                {
+                    // Replace the invocation by an invokespecial instruction.
+                    Instruction replacementInstruction =
+                        new ConstantInstruction(InstructionConstants.OP_INVOKESPECIAL,
+                                                constantIndex).shrink();
+
+                    codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+
+                    if (DEBUG)
+                    {
+                        debug(clazz, method, offset, constantInstruction, replacementInstruction);
+                    }
+                }
+            }
+
+            // Is the method an interface method?
+            else if ((referencedClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE) != 0)
+            {
+                int invokeinterfaceConstant =
+                    (ClassUtil.internalMethodParameterSize(referencedMethod.getDescriptor(referencedMethodClass), false)) << 8;
+
+                // But is it not an interface invocation, or is the parameter
+                // size incorrect?
+                if (opcode != InstructionConstants.OP_INVOKEINTERFACE ||
+                    constantInstruction.constant != invokeinterfaceConstant)
+                {
+                    // Fix the parameter size of the interface invocation.
+                    Instruction replacementInstruction =
+                        new ConstantInstruction(InstructionConstants.OP_INVOKEINTERFACE,
+                                                constantIndex,
+                                                invokeinterfaceConstant).shrink();
+
+                    codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+
+                    if (DEBUG)
+                    {
+                        debug(clazz, method, offset, constantInstruction, replacementInstruction);
+                    }
+                }
+            }
+
+            // The method is not static, private, an instance initializer, or
+            // an interface method.
+            else
+            {
+                // But is it not a virtual invocation (or a special invocation,
+                // but not a super call)?
+                if (opcode != InstructionConstants.OP_INVOKEVIRTUAL &&
+                    (opcode != InstructionConstants.OP_INVOKESPECIAL ||
+                     !clazz.extends_(referencedClass)))
+                {
+                    // Replace the invocation by an invokevirtual instruction.
+                    Instruction replacementInstruction =
+                        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL,
+                                                constantIndex).shrink();
+
+                    codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+
+                    if (DEBUG)
+                    {
+                        debug(clazz, method, offset, constantInstruction, replacementInstruction);
+                    }
+                }
+            }
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        // Check if this is an interface method. Note that we're interested in
+        // the class of the method reference, not in the class in which the
+        // method was actually found.
+        //refConstant.referencedClassAccept(this);
+        clazz.constantPoolEntryAccept(refConstant.u2classIndex, this);
+
+        // Get the referenced access flags and names.
+        refConstant.referencedMemberAccept(this);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Check if this is an interface class.
+       classConstant.referencedClassAccept(this);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitAnyClass(Clazz clazz)
+    {
+        // Remember the referenced class.
+        referencedClass = clazz;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        // Remember the referenced method.
+        referencedMethodClass = clazz;
+        referencedMethod      = member;
+    }
+
+
+    // Small utility methods.
+
+    private void debug(Clazz               clazz,
+                       Method              method,
+                       int                 offset,
+                       ConstantInstruction constantInstruction,
+                       Instruction         replacementInstruction)
+    {
+        System.out.println("MethodInvocationFixer:");
+        System.out.println("  Class       = "+clazz.getName());
+        System.out.println("  Method      = "+method.getName(clazz)+method.getDescriptor(clazz));
+        System.out.println("  Instruction = "+constantInstruction.toString(offset));
+        System.out.println("  -> Class    = "+referencedClass);
+        System.out.println("     Method   = "+referencedMethod);
+        if ((referencedClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE) != 0)
+        {
+            System.out.println("     Parameter size   = "+(ClassUtil.internalMethodParameterSize(referencedMethod.getDescriptor(referencedMethodClass), false)));
+        }
+        System.out.println("  Replacement instruction = "+replacementInstruction.toString(offset));
+    }
+}
diff --git a/src/proguard/classfile/editor/NamedAttributeDeleter.java b/src/proguard/classfile/editor/NamedAttributeDeleter.java
new file mode 100644
index 0000000..0c4d339
--- /dev/null
+++ b/src/proguard/classfile/editor/NamedAttributeDeleter.java
@@ -0,0 +1,54 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.*;
+import proguard.util.StringMatcher;
+
+
+/**
+ * This ClassVisitor deletes attributes with a given name in the program
+ * classes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class NamedAttributeDeleter implements ClassVisitor
+{
+    private final String attributeName;
+
+
+    public NamedAttributeDeleter(String attributeName)
+    {
+        this.attributeName = attributeName;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        new AttributesEditor(programClass, false).deleteAttribute(attributeName);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/ParameterAnnotationsAttributeEditor.java b/src/proguard/classfile/editor/ParameterAnnotationsAttributeEditor.java
new file mode 100644
index 0000000..4cad6b8
--- /dev/null
+++ b/src/proguard/classfile/editor/ParameterAnnotationsAttributeEditor.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.attribute.annotation.*;
+
+/**
+ * This class can add annotations to a given parameter annotations attribute.
+ * Annotations to be added must have been filled out beforehand.
+ *
+ * @author Eric Lafortune
+ */
+public class ParameterAnnotationsAttributeEditor
+{
+    private ParameterAnnotationsAttribute targetParameterAnnotationsAttribute;
+
+
+    /**
+     * Creates a new ParameterAnnotationsAttributeEditor that will edit
+     * annotations in the given parameter annotations attribute.
+     */
+    public ParameterAnnotationsAttributeEditor(ParameterAnnotationsAttribute targetParameterAnnotationsAttribute)
+    {
+        this.targetParameterAnnotationsAttribute = targetParameterAnnotationsAttribute;
+    }
+
+
+    /**
+     * Adds a given annotation to the annotations attribute.
+     */
+    public void addAnnotation(int parameterIndex, Annotation annotation)
+    {
+        int          annotationsCount = targetParameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex];
+        Annotation[] annotations      = targetParameterAnnotationsAttribute.parameterAnnotations[parameterIndex];
+
+        // Make sure there is enough space for the new annotation.
+        if (annotations == null ||
+            annotations.length <= annotationsCount)
+        {
+            targetParameterAnnotationsAttribute.parameterAnnotations[parameterIndex] = new Annotation[annotationsCount+1];
+            if (annotations != null)
+            {
+                System.arraycopy(annotations, 0,
+                                 targetParameterAnnotationsAttribute.parameterAnnotations[parameterIndex], 0,
+                                 annotationsCount);
+            }
+            annotations = targetParameterAnnotationsAttribute.parameterAnnotations[parameterIndex];
+        }
+
+        // Add the annotation.
+        annotations[targetParameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex]++] = annotation;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/StackSizeUpdater.java b/src/proguard/classfile/editor/StackSizeUpdater.java
new file mode 100644
index 0000000..94e0519
--- /dev/null
+++ b/src/proguard/classfile/editor/StackSizeUpdater.java
@@ -0,0 +1,54 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor computes and updates the maximum stack size of the
+ * code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class StackSizeUpdater
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private final StackSizeComputer stackSizeComputer = new StackSizeComputer();
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Compute the stack sizes.
+        stackSizeComputer.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Update the maximum stack size.
+        codeAttribute.u2maxStack = stackSizeComputer.getMaxStackSize();
+    }
+}
diff --git a/src/proguard/classfile/editor/SubclassAdder.java b/src/proguard/classfile/editor/SubclassAdder.java
new file mode 100644
index 0000000..6b9fd64
--- /dev/null
+++ b/src/proguard/classfile/editor/SubclassAdder.java
@@ -0,0 +1,59 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor adds the given class to the list of subclasses of the
+ * classes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class SubclassAdder
+implements   ClassVisitor
+{
+    private final Clazz subclass;
+
+
+    /**
+     * Creates a new SubclassAdder that will add the given subclass.
+     */
+    public SubclassAdder(Clazz subclass)
+    {
+        this.subclass = subclass;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.addSubClass(subclass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.addSubClass(subclass);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/SubclassToAdder.java b/src/proguard/classfile/editor/SubclassToAdder.java
new file mode 100644
index 0000000..deb242f
--- /dev/null
+++ b/src/proguard/classfile/editor/SubclassToAdder.java
@@ -0,0 +1,60 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor adds all classes that it visits to the list of subclasses
+ * of the given target class.
+ *
+ * @author Eric Lafortune
+ */
+public class SubclassToAdder
+implements   ClassVisitor
+{
+    private final Clazz targetClass;
+
+
+    /**
+     * Creates a new SubclassAdder that will add subclasses to the given
+     * target class.
+     */
+    public SubclassToAdder(Clazz targetClass)
+    {
+        this.targetClass = targetClass;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        targetClass.addSubClass(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        targetClass.addSubClass(libraryClass);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/VariableCleaner.java b/src/proguard/classfile/editor/VariableCleaner.java
new file mode 100644
index 0000000..1e93c15
--- /dev/null
+++ b/src/proguard/classfile/editor/VariableCleaner.java
@@ -0,0 +1,135 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.optimize.info.VariableUsageMarker;
+
+/**
+ * This AttributeVisitor cleans up unused variables in all attributes that it
+ * visits.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableCleaner
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private final VariableUsageMarker variableUsageMarker = new VariableUsageMarker();
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Figure out the local variables that are used by the code.
+        variableUsageMarker.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Clean up the variables of the attributes.
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Clean up local variables that aren't used.
+        localVariableTableAttribute.u2localVariableTableLength =
+            removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable,
+                                      localVariableTableAttribute.u2localVariableTableLength,
+                                      codeAttribute.u2maxLocals);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Clean up local variables that aren't used.
+        localVariableTypeTableAttribute.u2localVariableTypeTableLength =
+            removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable,
+                                          localVariableTypeTableAttribute.u2localVariableTypeTableLength,
+                                          codeAttribute.u2maxLocals);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the given list of local variables, without the ones that aren't
+     * used
+     */
+    private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos,
+                                          int                 localVariableInfoCount,
+                                          int                 maxLocals)
+    {
+        // Overwrite all empty local variable entries.
+        int newIndex = 0;
+        for (int index = 0; index < localVariableInfoCount && index < maxLocals; index++)
+        {
+            if (variableUsageMarker.isVariableUsed(index))
+            {
+                localVariableInfos[newIndex++] = localVariableInfos[index];
+            }
+        }
+
+        // Clean up any remaining array elements.
+        for (int index = newIndex; index < localVariableInfoCount; index++)
+        {
+            localVariableInfos[index] = null;
+        }
+
+        return newIndex;
+    }
+
+
+    /**
+     * Returns the given list of local variable types, without the ones that
+     * aren't used
+     */
+    private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos,
+                                              int                     localVariableTypeInfoCount,
+                                              int                     maxLocals)
+    {
+        // Overwrite all empty local variable type entries.
+        int newIndex = 0;
+        for (int index = 0; index < localVariableTypeInfoCount && index < maxLocals; index++)
+        {
+            if (variableUsageMarker.isVariableUsed(index))
+            {
+                localVariableTypeInfos[newIndex++] = localVariableTypeInfos[index];
+            }
+        }
+
+        // Clean up any remaining array elements.
+        for (int index = newIndex; index < localVariableTypeInfoCount; index++)
+        {
+            localVariableTypeInfos[index] = null;
+        }
+
+        return newIndex;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/editor/VariableEditor.java b/src/proguard/classfile/editor/VariableEditor.java
new file mode 100644
index 0000000..a583b49
--- /dev/null
+++ b/src/proguard/classfile/editor/VariableEditor.java
@@ -0,0 +1,129 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor accumulates specified changes to local variables, and
+ * then applies these accumulated changes to the code attributes that it visits.
+ *
+ * @author Eric  Lafortune
+ */
+public class VariableEditor
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private boolean   modified;
+
+    private boolean[] deleted     = new boolean[ClassConstants.TYPICAL_VARIABLES_SIZE];
+    private int[]     variableMap = new int[ClassConstants.TYPICAL_VARIABLES_SIZE];
+
+    private final VariableRemapper variableRemapper = new VariableRemapper();
+
+
+    /**
+     * Resets the accumulated code changes.
+     * @param maxLocals the length of the local variable frame that will be
+     *                  edited next.
+     */
+    public void reset(int maxLocals)
+    {
+        // Try to reuse the previous array.
+        if (deleted.length < maxLocals)
+        {
+            deleted = new boolean[maxLocals];
+        }
+        else
+        {
+            for (int index = 0; index < maxLocals; index++)
+            {
+                deleted[index] = false;
+            }
+        }
+
+        modified = false;
+    }
+
+
+    /**
+     * Remembers to delete the given variable.
+     * @param variableIndex the index of the variable to be deleted.
+     */
+    public void deleteVariable(int variableIndex)
+    {
+        deleted[variableIndex] = true;
+
+        modified = true;
+    }
+
+
+    /**
+     * Returns whether the given variable at the given offset has deleted.
+     */
+    public boolean isDeleted(int instructionOffset)
+    {
+        return deleted[instructionOffset];
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Avoid doing any work if nothing is changing anyway.
+        if (!modified)
+        {
+            return;
+        }
+
+        int oldMaxLocals = codeAttribute.u2maxLocals;
+
+        // Make sure there is a sufficiently large variable map.
+        if (variableMap.length < oldMaxLocals)
+        {
+            variableMap = new int[oldMaxLocals];
+        }
+
+        // Fill out the variable map.
+        int newVariableIndex = 0;
+        for (int oldVariableIndex = 0; oldVariableIndex < oldMaxLocals; oldVariableIndex++)
+        {
+            variableMap[oldVariableIndex] = deleted[oldVariableIndex] ?
+                -1 : newVariableIndex++;
+        }
+
+        // Set the map.
+        variableRemapper.setVariableMap(variableMap);
+
+        // Remap the variables.
+        variableRemapper.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Update the length of local variable frame.
+        codeAttribute.u2maxLocals = newVariableIndex;
+    }
+}
diff --git a/src/proguard/classfile/editor/VariableRemapper.java b/src/proguard/classfile/editor/VariableRemapper.java
new file mode 100644
index 0000000..590cd4e
--- /dev/null
+++ b/src/proguard/classfile/editor/VariableRemapper.java
@@ -0,0 +1,197 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor remaps variable indexes in all attributes that it
+ * visits, based on a given index map.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableRemapper
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor
+{
+    private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
+
+    private int[] variableMap;
+
+
+    /**
+     * Sets the given mapping of old variable indexes to their new indexes.
+     * Variables that should disappear can be mapped to -1.
+     */
+    public void setVariableMap(int[] variableMap)
+    {
+        this.variableMap = variableMap;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Initially, the code attribute editor doesn't contain any changes.
+        codeAttributeEditor.reset(codeAttribute.u4codeLength);
+
+        // Remap the variables of the instructions.
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        // Apply the code atribute editor.
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Remap the variables of the attributes.
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Remap the variable references of the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+
+        // Remove local variables that haven't been mapped.
+        localVariableTableAttribute.u2localVariableTableLength =
+            removeEmptyLocalVariables(localVariableTableAttribute.localVariableTable,
+                                      localVariableTableAttribute.u2localVariableTableLength);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Remap the variable references of the local variables.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+
+        // Remove local variables that haven't been mapped.
+        localVariableTypeTableAttribute.u2localVariableTypeTableLength =
+            removeEmptyLocalVariableTypes(localVariableTypeTableAttribute.localVariableTypeTable,
+                                          localVariableTypeTableAttribute.u2localVariableTypeTableLength);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        localVariableInfo.u2index =
+            remapVariable(localVariableInfo.u2index);
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        localVariableTypeInfo.u2index =
+            remapVariable(localVariableTypeInfo.u2index);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        // Is the new variable index different from the original one?
+        int oldVariableIndex = variableInstruction.variableIndex;
+        int newVariableIndex = remapVariable(oldVariableIndex);
+        if (newVariableIndex != oldVariableIndex)
+        {
+            // Replace the instruction.
+            Instruction replacementInstruction =
+                new VariableInstruction(variableInstruction.opcode,
+                                        newVariableIndex,
+                                        variableInstruction.constant).shrink();
+
+            codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the new variable index of the given variable.
+     */
+    private int remapVariable(int variableIndex)
+    {
+        return variableMap[variableIndex];
+    }
+
+
+    /**
+     * Returns the given list of local variables, without the ones that have
+     * been removed.
+     */
+    private int removeEmptyLocalVariables(LocalVariableInfo[] localVariableInfos,
+                                          int                 localVariableInfoCount)
+    {
+        // Overwrite all empty local variable entries.
+        int newIndex = 0;
+        for (int index = 0; index < localVariableInfoCount; index++)
+        {
+            LocalVariableInfo localVariableInfo = localVariableInfos[index];
+            if (localVariableInfo.u2index >= 0)
+            {
+                localVariableInfos[newIndex++] = localVariableInfo;
+            }
+        }
+
+        return newIndex;
+    }
+
+
+    /**
+     * Returns the given list of local variable types, without the ones that
+     * have been removed.
+     */
+    private int removeEmptyLocalVariableTypes(LocalVariableTypeInfo[] localVariableTypeInfos,
+                                              int                     localVariableTypeInfoCount)
+    {
+        // Overwrite all empty local variable type entries.
+        int newIndex = 0;
+        for (int index = 0; index < localVariableTypeInfoCount; index++)
+        {
+            LocalVariableTypeInfo localVariableTypeInfo = localVariableTypeInfos[index];
+            if (localVariableTypeInfo.u2index >= 0)
+            {
+                localVariableTypeInfos[newIndex++] = localVariableTypeInfo;
+            }
+        }
+
+        return newIndex;
+    }
+}
diff --git a/src/proguard/classfile/editor/VariableSizeUpdater.java b/src/proguard/classfile/editor/VariableSizeUpdater.java
new file mode 100644
index 0000000..18958c5
--- /dev/null
+++ b/src/proguard/classfile/editor/VariableSizeUpdater.java
@@ -0,0 +1,98 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.editor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.*;
+
+/**
+ * This AttributeVisitor computes and updates the maximum local variable frame
+ * size of the code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableSizeUpdater
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // The minimum variable size is determined by the arguments.
+        codeAttribute.u2maxLocals =
+            ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz),
+                                                  method.getAccessFlags());
+
+        if (DEBUG)
+        {
+            System.out.println("VariableSizeUpdater: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
+            System.out.println("  Max locals: "+codeAttribute.u2maxLocals+" <- parameters");
+        }
+
+        // Go over all instructions.
+        codeAttribute.instructionsAccept(clazz, method, this);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        int variableSize = variableInstruction.variableIndex + 1;
+        if (variableInstruction.isCategory2())
+        {
+            variableSize++;
+        }
+
+        if (codeAttribute.u2maxLocals < variableSize)
+        {
+            codeAttribute.u2maxLocals = variableSize;
+
+            if (DEBUG)
+            {
+                System.out.println("Max locals: "+codeAttribute.u2maxLocals+" <- "+variableInstruction.toString(offset));
+            }
+        }
+    }
+}
diff --git a/src/proguard/classfile/editor/package.html b/src/proguard/classfile/editor/package.html
new file mode 100644
index 0000000..d37f541
--- /dev/null
+++ b/src/proguard/classfile/editor/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains visitors to edit byte code.
+</body>
diff --git a/src/proguard/classfile/instruction/BranchInstruction.java b/src/proguard/classfile/instruction/BranchInstruction.java
new file mode 100644
index 0000000..2baa917
--- /dev/null
+++ b/src/proguard/classfile/instruction/BranchInstruction.java
@@ -0,0 +1,180 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+
+/**
+ * This interface describes an instruction that branches to a given offset in
+ * the code.
+ *
+ * @author Eric Lafortune
+ */
+public class BranchInstruction extends Instruction
+{
+    public int branchOffset;
+
+
+    /**
+     * Creates an uninitialized BranchInstruction.
+     */
+    public BranchInstruction() {}
+
+
+    public BranchInstruction(byte opcode, int branchOffset)
+    {
+        this.opcode       = opcode;
+        this.branchOffset = branchOffset;
+    }
+
+
+    /**
+     * Copies the given instruction into this instruction.
+     * @param branchInstruction the instruction to be copied.
+     * @return this instruction.
+     */
+    public BranchInstruction copy(BranchInstruction branchInstruction)
+    {
+        this.opcode       = branchInstruction.opcode;
+        this.branchOffset = branchInstruction.branchOffset;
+
+        return this;
+    }
+
+
+    // Implementations for Instruction.
+
+    public byte canonicalOpcode()
+    {
+        // Remove the _w extension, if any.
+        switch (opcode)
+        {
+            case InstructionConstants.OP_GOTO_W: return InstructionConstants.OP_GOTO;
+
+            case InstructionConstants.OP_JSR_W: return InstructionConstants.OP_JSR;
+
+            default: return opcode;
+        }
+    }
+
+    public Instruction shrink()
+    {
+        // Do we need an ordinary branch or a wide branch?
+        if (requiredBranchOffsetSize() == 2)
+        {
+            // Can we replace the wide branch by an ordinary branch?
+            if      (opcode == InstructionConstants.OP_GOTO_W)
+            {
+                opcode = InstructionConstants.OP_GOTO;
+            }
+            else if (opcode == InstructionConstants.OP_JSR_W)
+            {
+                opcode = InstructionConstants.OP_JSR;
+            }
+        }
+        else
+        {
+            // Should we replace the ordinary branch by a wide branch?
+            if      (opcode == InstructionConstants.OP_GOTO)
+            {
+                opcode = InstructionConstants.OP_GOTO_W;
+            }
+            else if (opcode == InstructionConstants.OP_JSR)
+            {
+                opcode = InstructionConstants.OP_JSR_W;
+            }
+            else
+            {
+                throw new IllegalArgumentException("Branch instruction can't be widened ("+this.toString()+")");
+            }
+        }
+
+        return this;
+    }
+
+    protected void readInfo(byte[] code, int offset)
+    {
+        branchOffset = readSignedValue(code, offset, branchOffsetSize());
+    }
+
+
+    protected void writeInfo(byte[] code, int offset)
+    {
+        if (requiredBranchOffsetSize() > branchOffsetSize())
+        {
+            throw new IllegalArgumentException("Instruction has invalid branch offset size ("+this.toString(offset)+")");
+        }
+
+        writeSignedValue(code, offset, branchOffset, branchOffsetSize());
+    }
+
+
+    public int length(int offset)
+    {
+        return 1 + branchOffsetSize();
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor)
+    {
+        instructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public String toString(int offset)
+    {
+        return "["+offset+"] "+toString()+" (target="+(offset+branchOffset)+")";
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return getName()+" "+(branchOffset >= 0 ? "+" : "")+branchOffset;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the branch offset size for this instruction.
+     */
+    private int branchOffsetSize()
+    {
+        return opcode == InstructionConstants.OP_GOTO_W ||
+               opcode == InstructionConstants.OP_JSR_W  ? 4 :
+                                                          2;
+    }
+
+
+    /**
+     * Computes the required branch offset size for this instruction's branch
+     * offset.
+     */
+    private int requiredBranchOffsetSize()
+    {
+        return branchOffset << 16 >> 16 == branchOffset ? 2 :
+                                                          4;
+    }
+}
diff --git a/src/proguard/classfile/instruction/ConstantInstruction.java b/src/proguard/classfile/instruction/ConstantInstruction.java
new file mode 100644
index 0000000..6c2d1a3
--- /dev/null
+++ b/src/proguard/classfile/instruction/ConstantInstruction.java
@@ -0,0 +1,303 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.ClassUtil;
+
+/**
+ * This Instruction represents an instruction that refers to an entry in the
+ * constant pool.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantInstruction extends Instruction
+implements   ConstantVisitor
+{
+    public int constantIndex;
+    public int constant;
+
+
+    // Fields acting as return parameters for the ConstantVisitor methods.
+    private int parameterStackDelta;
+    private int typeStackDelta;
+
+
+    /**
+     * Creates an uninitialized ConstantInstruction.
+     */
+    public ConstantInstruction() {}
+
+
+    /**
+     * Creates a new ConstantInstruction with the given opcode and constant pool
+     * index.
+     */
+    public ConstantInstruction(byte opcode, int constantIndex)
+    {
+        this(opcode, constantIndex, 0);
+    }
+
+
+    /**
+     * Creates a new ConstantInstruction with the given opcode, constant pool
+     * index, and constant.
+     */
+    public ConstantInstruction(byte opcode, int constantIndex, int constant)
+    {
+        this.opcode        = opcode;
+        this.constantIndex = constantIndex;
+        this.constant      = constant;
+    }
+
+
+    /**
+     * Copies the given instruction into this instruction.
+     * @param constantInstruction the instruction to be copied.
+     * @return this instruction.
+     */
+    public ConstantInstruction copy(ConstantInstruction constantInstruction)
+    {
+        this.opcode        = constantInstruction.opcode;
+        this.constantIndex = constantInstruction.constantIndex;
+        this.constant      = constantInstruction.constant;
+
+        return this;
+    }
+
+
+    // Implementations for Instruction.
+
+    public byte canonicalOpcode()
+    {
+        // Remove the _w extension, if any.
+        switch (opcode)
+        {
+            case InstructionConstants.OP_LDC_W:
+            case InstructionConstants.OP_LDC2_W: return InstructionConstants.OP_LDC;
+
+            default: return opcode;
+        }
+    }
+
+    public Instruction shrink()
+    {
+        // Do we need a short index or a long index?
+        if (requiredConstantIndexSize() == 1)
+        {
+            // Can we replace the long instruction by a short instruction?
+            if (opcode == InstructionConstants.OP_LDC_W)
+            {
+                opcode = InstructionConstants.OP_LDC;
+            }
+        }
+        else
+        {
+            // Should we replace the short instruction by a long instruction?
+            if (opcode == InstructionConstants.OP_LDC)
+            {
+                opcode = InstructionConstants.OP_LDC_W;
+            }
+        }
+
+        return this;
+    }
+
+    protected void readInfo(byte[] code, int offset)
+    {
+        int constantIndexSize = constantIndexSize();
+        int constantSize      = constantSize();
+
+        constantIndex = readValue(code, offset, constantIndexSize);  offset += constantIndexSize;
+        constant      = readValue(code, offset, constantSize);
+    }
+
+
+    protected void writeInfo(byte[] code, int offset)
+    {
+        int constantIndexSize = constantIndexSize();
+        int constantSize      = constantSize();
+
+        if (requiredConstantIndexSize() > constantIndexSize)
+        {
+            throw new IllegalArgumentException("Instruction has invalid constant index size ("+this.toString(offset)+")");
+        }
+
+        writeValue(code, offset, constantIndex, constantIndexSize); offset += constantIndexSize;
+        writeValue(code, offset, constant,      constantSize);
+    }
+
+
+    public int length(int offset)
+    {
+        return 1 + constantIndexSize() + constantSize();
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor)
+    {
+        instructionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public int stackPopCount(Clazz clazz)
+    {
+        int stackPopCount = super.stackPopCount(clazz);
+
+        // Some special cases.
+        switch (opcode)
+        {
+            case InstructionConstants.OP_MULTIANEWARRAY:
+                // For each dimension, an integer size is popped from the stack.
+                stackPopCount += constant;
+                break;
+
+            case InstructionConstants.OP_PUTSTATIC:
+            case InstructionConstants.OP_PUTFIELD:
+                // The field value is be popped from the stack.
+                clazz.constantPoolEntryAccept(constantIndex, this);
+                stackPopCount += typeStackDelta;
+                break;
+
+            case InstructionConstants.OP_INVOKEVIRTUAL:
+            case InstructionConstants.OP_INVOKESPECIAL:
+            case InstructionConstants.OP_INVOKESTATIC:
+            case InstructionConstants.OP_INVOKEINTERFACE:
+                // The some parameters may be popped from the stack.
+                clazz.constantPoolEntryAccept(constantIndex, this);
+                stackPopCount += parameterStackDelta;
+                break;
+        }
+
+        return stackPopCount;
+    }
+
+
+    public int stackPushCount(Clazz clazz)
+    {
+        int stackPushCount = super.stackPushCount(clazz);
+
+        // Some special cases.
+        switch (opcode)
+        {
+            case InstructionConstants.OP_GETSTATIC:
+            case InstructionConstants.OP_GETFIELD:
+            case InstructionConstants.OP_INVOKEVIRTUAL:
+            case InstructionConstants.OP_INVOKESPECIAL:
+            case InstructionConstants.OP_INVOKESTATIC:
+            case InstructionConstants.OP_INVOKEINTERFACE:
+                // The field value or a return value may be pushed onto the stack.
+                clazz.constantPoolEntryAccept(constantIndex, this);
+                stackPushCount += typeStackDelta;
+                break;
+        }
+
+        return stackPushCount;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant) {}
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant) {}
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant) {}
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant) {}
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant) {}
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) {}
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant) {}
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) {}
+
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        String type = fieldrefConstant.getType(clazz);
+
+        typeStackDelta = ClassUtil.internalTypeSize(ClassUtil.internalMethodReturnType(type));
+    }
+
+
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant)
+    {
+        visitRefConstant(clazz, interfaceMethodrefConstant);
+    }
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        visitRefConstant(clazz, methodrefConstant);
+    }
+
+
+    private void visitRefConstant(Clazz clazz, RefConstant methodrefConstant)
+    {
+        String type = methodrefConstant.getType(clazz);
+
+        parameterStackDelta = ClassUtil.internalMethodParameterSize(type);
+        typeStackDelta      = ClassUtil.internalTypeSize(ClassUtil.internalMethodReturnType(type));
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return getName()+" #"+constantIndex;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the constant pool index size for this instruction.
+     */
+    private int constantIndexSize()
+    {
+        return opcode == InstructionConstants.OP_LDC ? 1 :
+                                                       2;
+    }
+
+
+    /**
+     * Returns the constant size for this instruction.
+     */
+    private int constantSize()
+    {
+        return opcode == InstructionConstants.OP_MULTIANEWARRAY  ? 1 :
+               opcode == InstructionConstants.OP_INVOKEINTERFACE ? 2 :
+                                                                   0;
+    }
+
+
+    /**
+     * Computes the required constant pool index size for this instruction's
+     * constant pool index.
+     */
+    private int requiredConstantIndexSize()
+    {
+        return (constantIndex &   0xff) == constantIndex ? 1 :
+               (constantIndex & 0xffff) == constantIndex ? 2 :
+                                                           4;
+    }
+}
diff --git a/src/proguard/classfile/instruction/Instruction.java b/src/proguard/classfile/instruction/Instruction.java
new file mode 100644
index 0000000..8437495
--- /dev/null
+++ b/src/proguard/classfile/instruction/Instruction.java
@@ -0,0 +1,920 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+
+/**
+ * Base class for representing instructions.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class Instruction
+{
+    // An array for marking Category 2 instructions.
+    private static final boolean[] IS_CATEGORY2 = new boolean[]
+    {
+        false, // nop
+        false, // aconst_null
+        false, // iconst_m1
+        false, // iconst_0
+        false, // iconst_1
+        false, // iconst_2
+        false, // iconst_3
+        false, // iconst_4
+        false, // iconst_5
+        true,  // lconst_0
+        true,  // lconst_1
+        false, // fconst_0
+        false, // fconst_1
+        false, // fconst_2
+        true,  // dconst_0
+        true,  // dconst_1
+        false, // bipush
+        false, // sipush
+        false, // ldc
+        false, // ldc_w
+        true,  // ldc2_w
+        false, // iload
+        true,  // lload
+        false, // fload
+        true,  // dload
+        false, // aload
+        false, // iload_0
+        false, // iload_1
+        false, // iload_2
+        false, // iload_3
+        true,  // lload_0
+        true,  // lload_1
+        true,  // lload_2
+        true,  // lload_3
+        false, // fload_0
+        false, // fload_1
+        false, // fload_2
+        false, // fload_3
+        true,  // dload_0
+        true,  // dload_1
+        true,  // dload_2
+        true,  // dload_3
+        false, // aload_0
+        false, // aload_1
+        false, // aload_2
+        false, // aload_3
+        false, // iaload
+        true,  // laload
+        false, // faload
+        true,  // daload
+        false, // aaload
+        false, // baload
+        false, // caload
+        false, // saload
+        false, // istore
+        true,  // lstore
+        false, // fstore
+        true,  // dstore
+        false, // astore
+        false, // istore_0
+        false, // istore_1
+        false, // istore_2
+        false, // istore_3
+        true,  // lstore_0
+        true,  // lstore_1
+        true,  // lstore_2
+        true,  // lstore_3
+        false, // fstore_0
+        false, // fstore_1
+        false, // fstore_2
+        false, // fstore_3
+        true,  // dstore_0
+        true,  // dstore_1
+        true,  // dstore_2
+        true,  // dstore_3
+        false, // astore_0
+        false, // astore_1
+        false, // astore_2
+        false, // astore_3
+        false, // iastore
+        true,  // lastore
+        false, // fastore
+        true,  // dastore
+        false, // aastore
+        false, // bastore
+        false, // castore
+        false, // sastore
+        false, // pop
+        true,  // pop2
+        false, // dup
+        false, // dup_x1
+        false, // dup_x2
+        true,  // dup2
+        true,  // dup2_x1
+        true,  // dup2_x2
+        false, // swap
+        false, // iadd
+        true,  // ladd
+        false, // fadd
+        true,  // dadd
+        false, // isub
+        true,  // lsub
+        false, // fsub
+        true,  // dsub
+        false, // imul
+        true,  // lmul
+        false, // fmul
+        true,  // dmul
+        false, // idiv
+        true,  // ldiv
+        false, // fdiv
+        true,  // ddiv
+        false, // irem
+        true,  // lrem
+        false, // frem
+        true,  // drem
+        false, // ineg
+        true,  // lneg
+        false, // fneg
+        true,  // dneg
+        false, // ishl
+        true,  // lshl
+        false, // ishr
+        true,  // lshr
+        false, // iushr
+        true,  // lushr
+        false, // iand
+        true,  // land
+        false, // ior
+        true,  // lor
+        false, // ixor
+        true,  // lxor
+        false, // iinc
+        false, // i2l
+        false, // i2f
+        false, // i2d
+        true,  // l2i
+        true,  // l2f
+        true,  // l2d
+        false, // f2i
+        false, // f2l
+        false, // f2d
+        true,  // d2i
+        true,  // d2l
+        true,  // d2f
+        false, // i2b
+        false, // i2c
+        false, // i2s
+        true,  // lcmp
+        false, // fcmpl
+        false, // fcmpg
+        true,  // dcmpl
+        true,  // dcmpg
+        false, // ifeq
+        false, // ifne
+        false, // iflt
+        false, // ifge
+        false, // ifgt
+        false, // ifle
+        false, // ificmpeq
+        false, // ificmpne
+        false, // ificmplt
+        false, // ificmpge
+        false, // ificmpgt
+        false, // ificmple
+        false, // ifacmpeq
+        false, // ifacmpne
+        false, // goto
+        false, // jsr
+        false, // ret
+        false, // tableswitch
+        false, // lookupswitch
+        false, // ireturn
+        true,  // lreturn
+        false, // freturn
+        true,  // dreturn
+        false, // areturn
+        false, // return
+        false, // getstatic
+        false, // putstatic
+        false, // getfield
+        false, // putfield
+        false, // invokevirtual
+        false, // invokespecial
+        false, // invokestatic
+        false, // invokeinterface
+        false, // unused
+        false, // new
+        false, // newarray
+        false, // anewarray
+        false, // arraylength
+        false, // athrow
+        false, // checkcast
+        false, // instanceof
+        false, // monitorenter
+        false, // monitorexit
+        false, // wide
+        false, // multianewarray
+        false, // ifnull
+        false, // ifnonnull
+        false, // goto_w
+        false, // jsr_w
+    };
+
+
+    // An array containing the fixed number of entries popped from the stack,
+    // for all instructions.
+    private static final int[] STACK_POP_COUNTS = new int[]
+    {
+        0, // nop
+        0, // aconst_null
+        0, // iconst_m1
+        0, // iconst_0
+        0, // iconst_1
+        0, // iconst_2
+        0, // iconst_3
+        0, // iconst_4
+        0, // iconst_5
+        0, // lconst_0
+        0, // lconst_1
+        0, // fconst_0
+        0, // fconst_1
+        0, // fconst_2
+        0, // dconst_0
+        0, // dconst_1
+        0, // bipush
+        0, // sipush
+        0, // ldc
+        0, // ldc_w
+        0, // ldc2_w
+        0, // iload
+        0, // lload
+        0, // fload
+        0, // dload
+        0, // aload
+        0, // iload_0
+        0, // iload_1
+        0, // iload_2
+        0, // iload_3
+        0, // lload_0
+        0, // lload_1
+        0, // lload_2
+        0, // lload_3
+        0, // fload_0
+        0, // fload_1
+        0, // fload_2
+        0, // fload_3
+        0, // dload_0
+        0, // dload_1
+        0, // dload_2
+        0, // dload_3
+        0, // aload_0
+        0, // aload_1
+        0, // aload_2
+        0, // aload_3
+        2, // iaload
+        2, // laload
+        2, // faload
+        2, // daload
+        2, // aaload
+        2, // baload
+        2, // caload
+        2, // saload
+        1, // istore
+        2, // lstore
+        1, // fstore
+        2, // dstore
+        1, // astore
+        1, // istore_0
+        1, // istore_1
+        1, // istore_2
+        1, // istore_3
+        2, // lstore_0
+        2, // lstore_1
+        2, // lstore_2
+        2, // lstore_3
+        1, // fstore_0
+        1, // fstore_1
+        1, // fstore_2
+        1, // fstore_3
+        2, // dstore_0
+        2, // dstore_1
+        2, // dstore_2
+        2, // dstore_3
+        1, // astore_0
+        1, // astore_1
+        1, // astore_2
+        1, // astore_3
+        3, // iastore
+        4, // lastore
+        3, // fastore
+        4, // dastore
+        3, // aastore
+        3, // bastore
+        3, // castore
+        3, // sastore
+        1, // pop
+        2, // pop2
+        1, // dup
+        2, // dup_x1
+        3, // dup_x2
+        2, // dup2
+        3, // dup2_x1
+        4, // dup2_x2
+        2, // swap
+        2, // iadd
+        4, // ladd
+        2, // fadd
+        4, // dadd
+        2, // isub
+        4, // lsub
+        2, // fsub
+        4, // dsub
+        2, // imul
+        4, // lmul
+        2, // fmul
+        4, // dmul
+        2, // idiv
+        4, // ldiv
+        2, // fdiv
+        4, // ddiv
+        2, // irem
+        4, // lrem
+        2, // frem
+        4, // drem
+        1, // ineg
+        2, // lneg
+        1, // fneg
+        2, // dneg
+        2, // ishl
+        3, // lshl
+        2, // ishr
+        3, // lshr
+        2, // iushr
+        3, // lushr
+        2, // iand
+        4, // land
+        2, // ior
+        4, // lor
+        2, // ixor
+        4, // lxor
+        0, // iinc
+        1, // i2l
+        1, // i2f
+        1, // i2d
+        2, // l2i
+        2, // l2f
+        2, // l2d
+        1, // f2i
+        1, // f2l
+        1, // f2d
+        2, // d2i
+        2, // d2l
+        2, // d2f
+        1, // i2b
+        1, // i2c
+        1, // i2s
+        4, // lcmp
+        2, // fcmpl
+        2, // fcmpg
+        4, // dcmpl
+        4, // dcmpg
+        1, // ifeq
+        1, // ifne
+        1, // iflt
+        1, // ifge
+        1, // ifgt
+        1, // ifle
+        2, // ificmpeq
+        2, // ificmpne
+        2, // ificmplt
+        2, // ificmpge
+        2, // ificmpgt
+        2, // ificmple
+        2, // ifacmpeq
+        2, // ifacmpne
+        0, // goto
+        0, // jsr
+        0, // ret
+        1, // tableswitch
+        1, // lookupswitch
+        1, // ireturn
+        2, // lreturn
+        1, // freturn
+        2, // dreturn
+        1, // areturn
+        0, // return
+        0, // getstatic
+        0, // putstatic
+        1, // getfield
+        1, // putfield
+        1, // invokevirtual
+        1, // invokespecial
+        0, // invokestatic
+        1, // invokeinterface
+        0, // unused
+        0, // new
+        1, // newarray
+        1, // anewarray
+        1, // arraylength
+        1, // athrow
+        1, // checkcast
+        1, // instanceof
+        1, // monitorenter
+        1, // monitorexit
+        0, // wide
+        0, // multianewarray
+        1, // ifnull
+        1, // ifnonnull
+        0, // goto_w
+        0, // jsr_w
+    };
+
+
+    // An array containing the fixed number of entries pushed onto the stack,
+    // for all instructions.
+    private static final int[] STACK_PUSH_COUNTS = new int[]
+    {
+        0, // nop
+        1, // aconst_null
+        1, // iconst_m1
+        1, // iconst_0
+        1, // iconst_1
+        1, // iconst_2
+        1, // iconst_3
+        1, // iconst_4
+        1, // iconst_5
+        2, // lconst_0
+        2, // lconst_1
+        1, // fconst_0
+        1, // fconst_1
+        1, // fconst_2
+        2, // dconst_0
+        2, // dconst_1
+        1, // bipush
+        1, // sipush
+        1, // ldc
+        1, // ldc_w
+        2, // ldc2_w
+        1, // iload
+        2, // lload
+        1, // fload
+        2, // dload
+        1, // aload
+        1, // iload_0
+        1, // iload_1
+        1, // iload_2
+        1, // iload_3
+        2, // lload_0
+        2, // lload_1
+        2, // lload_2
+        2, // lload_3
+        1, // fload_0
+        1, // fload_1
+        1, // fload_2
+        1, // fload_3
+        2, // dload_0
+        2, // dload_1
+        2, // dload_2
+        2, // dload_3
+        1, // aload_0
+        1, // aload_1
+        1, // aload_2
+        1, // aload_3
+        1, // iaload
+        2, // laload
+        1, // faload
+        2, // daload
+        1, // aaload
+        1, // baload
+        1, // caload
+        1, // saload
+        0, // istore
+        0, // lstore
+        0, // fstore
+        0, // dstore
+        0, // astore
+        0, // istore_0
+        0, // istore_1
+        0, // istore_2
+        0, // istore_3
+        0, // lstore_0
+        0, // lstore_1
+        0, // lstore_2
+        0, // lstore_3
+        0, // fstore_0
+        0, // fstore_1
+        0, // fstore_2
+        0, // fstore_3
+        0, // dstore_0
+        0, // dstore_1
+        0, // dstore_2
+        0, // dstore_3
+        0, // astore_0
+        0, // astore_1
+        0, // astore_2
+        0, // astore_3
+        0, // iastore
+        0, // lastore
+        0, // fastore
+        0, // dastore
+        0, // aastore
+        0, // bastore
+        0, // castore
+        0, // sastore
+        0, // pop
+        0, // pop2
+        2, // dup
+        3, // dup_x1
+        4, // dup_x2
+        4, // dup2
+        5, // dup2_x1
+        6, // dup2_x2
+        2, // swap
+        1, // iadd
+        2, // ladd
+        1, // fadd
+        2, // dadd
+        1, // isub
+        2, // lsub
+        1, // fsub
+        2, // dsub
+        1, // imul
+        2, // lmul
+        1, // fmul
+        2, // dmul
+        1, // idiv
+        2, // ldiv
+        1, // fdiv
+        2, // ddiv
+        1, // irem
+        2, // lrem
+        1, // frem
+        2, // drem
+        1, // ineg
+        2, // lneg
+        1, // fneg
+        2, // dneg
+        1, // ishl
+        2, // lshl
+        1, // ishr
+        2, // lshr
+        1, // iushr
+        2, // lushr
+        1, // iand
+        2, // land
+        1, // ior
+        2, // lor
+        1, // ixor
+        2, // lxor
+        0, // iinc
+        2, // i2l
+        1, // i2f
+        2, // i2d
+        1, // l2i
+        1, // l2f
+        2, // l2d
+        1, // f2i
+        2, // f2l
+        2, // f2d
+        1, // d2i
+        2, // d2l
+        1, // d2f
+        1, // i2b
+        1, // i2c
+        1, // i2s
+        1, // lcmp
+        1, // fcmpl
+        1, // fcmpg
+        1, // dcmpl
+        1, // dcmpg
+        0, // ifeq
+        0, // ifne
+        0, // iflt
+        0, // ifge
+        0, // ifgt
+        0, // ifle
+        0, // ificmpeq
+        0, // ificmpne
+        0, // ificmplt
+        0, // ificmpge
+        0, // ificmpgt
+        0, // ificmple
+        0, // ifacmpeq
+        0, // ifacmpne
+        0, // goto
+        1, // jsr
+        0, // ret
+        0, // tableswitch
+        0, // lookupswitch
+        0, // ireturn
+        0, // lreturn
+        0, // freturn
+        0, // dreturn
+        0, // areturn
+        0, // return
+        0, // getstatic
+        0, // putstatic
+        0, // getfield
+        0, // putfield
+        0, // invokevirtual
+        0, // invokespecial
+        0, // invokestatic
+        0, // invokeinterface
+        0, // unused
+        1, // new
+        1, // newarray
+        1, // anewarray
+        1, // arraylength
+        0, // athrow
+        1, // checkcast
+        1, // instanceof
+        0, // monitorenter
+        0, // monitorexit
+        0, // wide
+        1, // multianewarray
+        0, // ifnull
+        0, // ifnonnull
+        0, // goto_w
+        1, // jsr_w
+    };
+
+
+    public byte opcode;
+
+
+    /**
+     * Returns the canonical opcode of this instruction, i.e. typically the
+     * opcode whose extension has been removed.
+     */
+    public byte canonicalOpcode()
+    {
+        return opcode;
+    }
+
+
+    /**
+     * Shrinks this instruction to its shortest possible form.
+     * @return this instruction.
+     */
+    public abstract Instruction shrink();
+
+
+
+    /**
+     * Writes the Instruction at the given offset in the given code attribute.
+     */
+    public final void write(CodeAttribute codeAttribute, int offset)
+    {
+        write(codeAttribute.code, offset);
+    }
+
+
+    /**
+     * Writes the Instruction at the given offset in the given code array.
+     */
+    public void write(byte[] code, int offset)
+    {
+        // Write the wide opcode, if necessary.
+        if (isWide())
+        {
+            code[offset++] = InstructionConstants.OP_WIDE;
+        }
+
+        // Write the opcode.
+        code[offset++] = opcode;
+
+        // Write any additional arguments.
+        writeInfo(code, offset);
+    }
+
+
+    /**
+     * Returns whether the instruction is wide, i.e. preceded by a wide opcode.
+     * With the current specifications, only variable instructions can be wide.
+     */
+    protected boolean isWide()
+    {
+        return false;
+    }
+
+
+    /**
+     * Reads the data following the instruction opcode.
+     */
+    protected abstract void readInfo(byte[] code, int offset);
+
+
+    /**
+     * Writes data following the instruction opcode.
+     */
+    protected abstract void writeInfo(byte[] code, int offset);
+
+
+    /**
+     * Returns the length in bytes of the instruction.
+     */
+    public abstract int length(int offset);
+
+
+    /**
+     * Accepts the given visitor.
+     */
+    public abstract void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor);
+
+
+    /**
+     * Returns a description of the instruction, at the given offset.
+     */
+    public String toString(int offset)
+    {
+        return "["+offset+"] "+ this.toString();
+    }
+
+
+    /**
+     * Returns the name of the instruction.
+     */
+    public String getName()
+    {
+        return InstructionConstants.NAMES[opcode & 0xff];
+    }
+
+
+    /**
+     * Returns whether the instruction is a Category 2 instruction. This means
+     * that it operates on long or double arguments.
+     */
+    public boolean isCategory2()
+    {
+        return IS_CATEGORY2[opcode & 0xff];
+    }
+
+
+    /**
+     * Returns the number of entries popped from the stack during the execution
+     * of the instruction.
+     */
+    public int stackPopCount(Clazz clazz)
+    {
+        return STACK_POP_COUNTS[opcode & 0xff];
+    }
+
+
+    /**
+     * Returns the number of entries pushed onto the stack during the execution
+     * of the instruction.
+     */
+    public int stackPushCount(Clazz clazz)
+    {
+        return STACK_PUSH_COUNTS[opcode & 0xff];
+    }
+
+
+    // Small utility methods.
+
+    protected static int readByte(byte[] code, int offset)
+    {
+        return code[offset] & 0xff;
+    }
+
+    protected static int readShort(byte[] code, int offset)
+    {
+        return ((code[offset++] & 0xff) << 8) |
+               ( code[offset  ] & 0xff      );
+    }
+
+    protected static int readInt(byte[] code, int offset)
+    {
+        return ( code[offset++]         << 24) |
+               ((code[offset++] & 0xff) << 16) |
+               ((code[offset++] & 0xff) <<  8) |
+               ( code[offset  ] & 0xff       );
+    }
+
+    protected static int readValue(byte[] code, int offset, int valueSize)
+    {
+        switch (valueSize)
+        {
+            case 0: return 0;
+            case 1: return readByte( code, offset);
+            case 2: return readShort(code, offset);
+            case 4: return readInt(  code, offset);
+            default: throw new IllegalArgumentException("Unsupported value size ["+valueSize+"]");
+        }
+    }
+
+    protected static int readSignedByte(byte[] code, int offset)
+    {
+        return code[offset];
+    }
+
+    protected static int readSignedShort(byte[] code, int offset)
+    {
+        return (code[offset++] <<   8) |
+               (code[offset  ] & 0xff);
+    }
+
+    protected static int readSignedValue(byte[] code, int offset, int valueSize)
+    {
+        switch (valueSize)
+        {
+            case 0: return 0;
+            case 1: return readSignedByte( code, offset);
+            case 2: return readSignedShort(code, offset);
+            case 4: return readInt(        code, offset);
+            default: throw new IllegalArgumentException("Unsupported value size ["+valueSize+"]");
+        }
+    }
+
+    protected static void writeByte(byte[] code, int offset, int value)
+    {
+        if (value > 0xff)
+        {
+            throw new IllegalArgumentException("Unsigned byte value larger than 0xff ["+value+"]");
+        }
+
+        code[offset] = (byte)value;
+    }
+
+    protected static void writeShort(byte[] code, int offset, int value)
+    {
+        if (value > 0xffff)
+        {
+            throw new IllegalArgumentException("Unsigned short value larger than 0xffff ["+value+"]");
+        }
+
+        code[offset++] = (byte)(value >> 8);
+        code[offset  ] = (byte)(value     );
+    }
+
+    protected static void writeInt(byte[] code, int offset, int value)
+    {
+        code[offset++] = (byte)(value >> 24);
+        code[offset++] = (byte)(value >> 16);
+        code[offset++] = (byte)(value >>  8);
+        code[offset  ] = (byte)(value      );
+    }
+
+    protected static void writeValue(byte[] code, int offset, int value, int valueSize)
+    {
+        switch (valueSize)
+        {
+            case 0:                                  break;
+            case 1: writeByte( code, offset, value); break;
+            case 2: writeShort(code, offset, value); break;
+            case 4: writeInt(  code, offset, value); break;
+            default: throw new IllegalArgumentException("Unsupported value size ["+valueSize+"]");
+        }
+    }
+
+    protected static void writeSignedByte(byte[] code, int offset, int value)
+    {
+        if (value << 24 >> 24 != value)
+        {
+            throw new IllegalArgumentException("Signed byte value out of range ["+value+"]");
+        }
+
+        code[offset] = (byte)value;
+    }
+
+    protected static void writeSignedShort(byte[] code, int offset, int value)
+    {
+        if (value << 16 >> 16 != value)
+        {
+            throw new IllegalArgumentException("Signed short value out of range ["+value+"]");
+        }
+
+        code[offset++] = (byte)(value >> 8);
+        code[offset  ] = (byte)(value     );
+    }
+
+    protected static void writeSignedValue(byte[] code, int offset, int value, int valueSize)
+    {
+        switch (valueSize)
+        {
+            case 0:                                        break;
+            case 1: writeSignedByte( code, offset, value); break;
+            case 2: writeSignedShort(code, offset, value); break;
+            case 4: writeInt(        code, offset, value); break;
+            default: throw new IllegalArgumentException("Unsupported value size ["+valueSize+"]");
+        }
+    }
+}
diff --git a/src/proguard/classfile/instruction/InstructionConstants.java b/src/proguard/classfile/instruction/InstructionConstants.java
new file mode 100644
index 0000000..78730b3
--- /dev/null
+++ b/src/proguard/classfile/instruction/InstructionConstants.java
@@ -0,0 +1,449 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+/**
+ * Representation of an instruction.
+ *
+ * @author Eric Lafortune
+ */
+public interface InstructionConstants
+{
+    public static final byte OP_NOP             = 0;
+    public static final byte OP_ACONST_NULL     = 1;
+    public static final byte OP_ICONST_M1       = 2;
+    public static final byte OP_ICONST_0        = 3;
+    public static final byte OP_ICONST_1        = 4;
+    public static final byte OP_ICONST_2        = 5;
+    public static final byte OP_ICONST_3        = 6;
+    public static final byte OP_ICONST_4        = 7;
+    public static final byte OP_ICONST_5        = 8;
+    public static final byte OP_LCONST_0        = 9;
+    public static final byte OP_LCONST_1        = 10;
+    public static final byte OP_FCONST_0        = 11;
+    public static final byte OP_FCONST_1        = 12;
+    public static final byte OP_FCONST_2        = 13;
+    public static final byte OP_DCONST_0        = 14;
+    public static final byte OP_DCONST_1        = 15;
+    public static final byte OP_BIPUSH          = 16;
+    public static final byte OP_SIPUSH          = 17;
+    public static final byte OP_LDC             = 18;
+    public static final byte OP_LDC_W           = 19;
+    public static final byte OP_LDC2_W          = 20;
+    public static final byte OP_ILOAD           = 21;
+    public static final byte OP_LLOAD           = 22;
+    public static final byte OP_FLOAD           = 23;
+    public static final byte OP_DLOAD           = 24;
+    public static final byte OP_ALOAD           = 25;
+    public static final byte OP_ILOAD_0         = 26;
+    public static final byte OP_ILOAD_1         = 27;
+    public static final byte OP_ILOAD_2         = 28;
+    public static final byte OP_ILOAD_3         = 29;
+    public static final byte OP_LLOAD_0         = 30;
+    public static final byte OP_LLOAD_1         = 31;
+    public static final byte OP_LLOAD_2         = 32;
+    public static final byte OP_LLOAD_3         = 33;
+    public static final byte OP_FLOAD_0         = 34;
+    public static final byte OP_FLOAD_1         = 35;
+    public static final byte OP_FLOAD_2         = 36;
+    public static final byte OP_FLOAD_3         = 37;
+    public static final byte OP_DLOAD_0         = 38;
+    public static final byte OP_DLOAD_1         = 39;
+    public static final byte OP_DLOAD_2         = 40;
+    public static final byte OP_DLOAD_3         = 41;
+    public static final byte OP_ALOAD_0         = 42;
+    public static final byte OP_ALOAD_1         = 43;
+    public static final byte OP_ALOAD_2         = 44;
+    public static final byte OP_ALOAD_3         = 45;
+    public static final byte OP_IALOAD          = 46;
+    public static final byte OP_LALOAD          = 47;
+    public static final byte OP_FALOAD          = 48;
+    public static final byte OP_DALOAD          = 49;
+    public static final byte OP_AALOAD          = 50;
+    public static final byte OP_BALOAD          = 51;
+    public static final byte OP_CALOAD          = 52;
+    public static final byte OP_SALOAD          = 53;
+    public static final byte OP_ISTORE          = 54;
+    public static final byte OP_LSTORE          = 55;
+    public static final byte OP_FSTORE          = 56;
+    public static final byte OP_DSTORE          = 57;
+    public static final byte OP_ASTORE          = 58;
+    public static final byte OP_ISTORE_0        = 59;
+    public static final byte OP_ISTORE_1        = 60;
+    public static final byte OP_ISTORE_2        = 61;
+    public static final byte OP_ISTORE_3        = 62;
+    public static final byte OP_LSTORE_0        = 63;
+    public static final byte OP_LSTORE_1        = 64;
+    public static final byte OP_LSTORE_2        = 65;
+    public static final byte OP_LSTORE_3        = 66;
+    public static final byte OP_FSTORE_0        = 67;
+    public static final byte OP_FSTORE_1        = 68;
+    public static final byte OP_FSTORE_2        = 69;
+    public static final byte OP_FSTORE_3        = 70;
+    public static final byte OP_DSTORE_0        = 71;
+    public static final byte OP_DSTORE_1        = 72;
+    public static final byte OP_DSTORE_2        = 73;
+    public static final byte OP_DSTORE_3        = 74;
+    public static final byte OP_ASTORE_0        = 75;
+    public static final byte OP_ASTORE_1        = 76;
+    public static final byte OP_ASTORE_2        = 77;
+    public static final byte OP_ASTORE_3        = 78;
+    public static final byte OP_IASTORE         = 79;
+    public static final byte OP_LASTORE         = 80;
+    public static final byte OP_FASTORE         = 81;
+    public static final byte OP_DASTORE         = 82;
+    public static final byte OP_AASTORE         = 83;
+    public static final byte OP_BASTORE         = 84;
+    public static final byte OP_CASTORE         = 85;
+    public static final byte OP_SASTORE         = 86;
+    public static final byte OP_POP             = 87;
+    public static final byte OP_POP2            = 88;
+    public static final byte OP_DUP             = 89;
+    public static final byte OP_DUP_X1          = 90;
+    public static final byte OP_DUP_X2          = 91;
+    public static final byte OP_DUP2            = 92;
+    public static final byte OP_DUP2_X1         = 93;
+    public static final byte OP_DUP2_X2         = 94;
+    public static final byte OP_SWAP            = 95;
+    public static final byte OP_IADD            = 96;
+    public static final byte OP_LADD            = 97;
+    public static final byte OP_FADD            = 98;
+    public static final byte OP_DADD            = 99;
+    public static final byte OP_ISUB            = 100;
+    public static final byte OP_LSUB            = 101;
+    public static final byte OP_FSUB            = 102;
+    public static final byte OP_DSUB            = 103;
+    public static final byte OP_IMUL            = 104;
+    public static final byte OP_LMUL            = 105;
+    public static final byte OP_FMUL            = 106;
+    public static final byte OP_DMUL            = 107;
+    public static final byte OP_IDIV            = 108;
+    public static final byte OP_LDIV            = 109;
+    public static final byte OP_FDIV            = 110;
+    public static final byte OP_DDIV            = 111;
+    public static final byte OP_IREM            = 112;
+    public static final byte OP_LREM            = 113;
+    public static final byte OP_FREM            = 114;
+    public static final byte OP_DREM            = 115;
+    public static final byte OP_INEG            = 116;
+    public static final byte OP_LNEG            = 117;
+    public static final byte OP_FNEG            = 118;
+    public static final byte OP_DNEG            = 119;
+    public static final byte OP_ISHL            = 120;
+    public static final byte OP_LSHL            = 121;
+    public static final byte OP_ISHR            = 122;
+    public static final byte OP_LSHR            = 123;
+    public static final byte OP_IUSHR           = 124;
+    public static final byte OP_LUSHR           = 125;
+    public static final byte OP_IAND            = 126;
+    public static final byte OP_LAND            = 127;
+    public static final byte OP_IOR             = -128;
+    public static final byte OP_LOR             = -127;
+    public static final byte OP_IXOR            = -126;
+    public static final byte OP_LXOR            = -125;
+    public static final byte OP_IINC            = -124;
+    public static final byte OP_I2L             = -123;
+    public static final byte OP_I2F             = -122;
+    public static final byte OP_I2D             = -121;
+    public static final byte OP_L2I             = -120;
+    public static final byte OP_L2F             = -119;
+    public static final byte OP_L2D             = -118;
+    public static final byte OP_F2I             = -117;
+    public static final byte OP_F2L             = -116;
+    public static final byte OP_F2D             = -115;
+    public static final byte OP_D2I             = -114;
+    public static final byte OP_D2L             = -113;
+    public static final byte OP_D2F             = -112;
+    public static final byte OP_I2B             = -111;
+    public static final byte OP_I2C             = -110;
+    public static final byte OP_I2S             = -109;
+    public static final byte OP_LCMP            = -108;
+    public static final byte OP_FCMPL           = -107;
+    public static final byte OP_FCMPG           = -106;
+    public static final byte OP_DCMPL           = -105;
+    public static final byte OP_DCMPG           = -104;
+    public static final byte OP_IFEQ            = -103;
+    public static final byte OP_IFNE            = -102;
+    public static final byte OP_IFLT            = -101;
+    public static final byte OP_IFGE            = -100;
+    public static final byte OP_IFGT            = -99;
+    public static final byte OP_IFLE            = -98;
+    public static final byte OP_IFICMPEQ        = -97;
+    public static final byte OP_IFICMPNE        = -96;
+    public static final byte OP_IFICMPLT        = -95;
+    public static final byte OP_IFICMPGE        = -94;
+    public static final byte OP_IFICMPGT        = -93;
+    public static final byte OP_IFICMPLE        = -92;
+    public static final byte OP_IFACMPEQ        = -91;
+    public static final byte OP_IFACMPNE        = -90;
+    public static final byte OP_GOTO            = -89;
+    public static final byte OP_JSR             = -88;
+    public static final byte OP_RET             = -87;
+    public static final byte OP_TABLESWITCH     = -86;
+    public static final byte OP_LOOKUPSWITCH    = -85;
+    public static final byte OP_IRETURN         = -84;
+    public static final byte OP_LRETURN         = -83;
+    public static final byte OP_FRETURN         = -82;
+    public static final byte OP_DRETURN         = -81;
+    public static final byte OP_ARETURN         = -80;
+    public static final byte OP_RETURN          = -79;
+    public static final byte OP_GETSTATIC       = -78;
+    public static final byte OP_PUTSTATIC       = -77;
+    public static final byte OP_GETFIELD        = -76;
+    public static final byte OP_PUTFIELD        = -75;
+    public static final byte OP_INVOKEVIRTUAL   = -74;
+    public static final byte OP_INVOKESPECIAL   = -73;
+    public static final byte OP_INVOKESTATIC    = -72;
+    public static final byte OP_INVOKEINTERFACE = -71;
+//  public static final byte OP_UNUSED           = -70;
+    public static final byte OP_NEW            = -69;
+    public static final byte OP_NEWARRAY       = -68;
+    public static final byte OP_ANEWARRAY      = -67;
+    public static final byte OP_ARRAYLENGTH    = -66;
+    public static final byte OP_ATHROW         = -65;
+    public static final byte OP_CHECKCAST      = -64;
+    public static final byte OP_INSTANCEOF     = -63;
+    public static final byte OP_MONITORENTER   = -62;
+    public static final byte OP_MONITOREXIT    = -61;
+    public static final byte OP_WIDE           = -60;
+    public static final byte OP_MULTIANEWARRAY = -59;
+    public static final byte OP_IFNULL         = -58;
+    public static final byte OP_IFNONNULL      = -57;
+    public static final byte OP_GOTO_W         = -56;
+    public static final byte OP_JSR_W          = -55;
+
+
+    public static final String[] NAMES =
+    {
+        "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",
+        "sipush",
+        "ldc",
+        "ldc_w",
+        "ldc2_w",
+        "iload",
+        "lload",
+        "fload",
+        "dload",
+        "aload",
+        "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",
+        "lstore",
+        "fstore",
+        "dstore",
+        "astore",
+        "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",
+        "i2l",
+        "i2f",
+        "i2d",
+        "l2i",
+        "l2f",
+        "l2d",
+        "f2i",
+        "f2l",
+        "f2d",
+        "d2i",
+        "d2l",
+        "d2f",
+        "i2b",
+        "i2c",
+        "i2s",
+        "lcmp",
+        "fcmpl",
+        "fcmpg",
+        "dcmpl",
+        "dcmpg",
+        "ifeq",
+        "ifne",
+        "iflt",
+        "ifge",
+        "ifgt",
+        "ifle",
+        "ificmpeq",
+        "ificmpne",
+        "ificmplt",
+        "ificmpge",
+        "ificmpgt",
+        "ificmple",
+        "ifacmpeq",
+        "ifacmpne",
+        "goto",
+        "jsr",
+        "ret",
+        "tableswitch",
+        "lookupswitch",
+        "ireturn",
+        "lreturn",
+        "freturn",
+        "dreturn",
+        "areturn",
+        "return",
+        "getstatic",
+        "putstatic",
+        "getfield",
+        "putfield",
+        "invokevirtual",
+        "invokespecial",
+        "invokestatic",
+        "invokeinterface",
+        "unused",
+        "new",
+        "newarray",
+        "anewarray",
+        "arraylength",
+        "athrow",
+        "checkcast",
+        "instanceof",
+        "monitorenter",
+        "monitorexit",
+        "wide",
+        "multianewarray",
+        "ifnull",
+        "ifnonnull",
+        "goto_w",
+        "jsr_w",
+    };
+
+
+    public static final byte ARRAY_T_BOOLEAN = 4;
+    public static final byte ARRAY_T_CHAR    = 5;
+    public static final byte ARRAY_T_FLOAT   = 6;
+    public static final byte ARRAY_T_DOUBLE  = 7;
+    public static final byte ARRAY_T_BYTE    = 8;
+    public static final byte ARRAY_T_SHORT   = 9;
+    public static final byte ARRAY_T_INT     = 10;
+    public static final byte ARRAY_T_LONG    = 11;
+}
diff --git a/src/proguard/classfile/instruction/InstructionFactory.java b/src/proguard/classfile/instruction/InstructionFactory.java
new file mode 100644
index 0000000..f898471
--- /dev/null
+++ b/src/proguard/classfile/instruction/InstructionFactory.java
@@ -0,0 +1,299 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+/**
+ * This class provides methods to create and reuse Instruction objects.
+ *
+ * @author Eric Lafortune
+ */
+public class InstructionFactory
+{
+    /**
+     * Creates a new Instruction from the data in the byte array, starting
+     * at the given index.
+     */
+    public static Instruction create(byte[] code, int offset)
+    {
+        Instruction instruction;
+
+        int  index  = offset;
+        byte opcode = code[index++];
+
+        boolean wide = false;
+        if (opcode == InstructionConstants.OP_WIDE)
+        {
+            opcode = code[index++];
+            wide   = true;
+        }
+
+        switch (opcode)
+        {
+            // Simple instructions.
+            case InstructionConstants.OP_NOP:
+            case InstructionConstants.OP_ACONST_NULL:
+            case InstructionConstants.OP_ICONST_M1:
+            case InstructionConstants.OP_ICONST_0:
+            case InstructionConstants.OP_ICONST_1:
+            case InstructionConstants.OP_ICONST_2:
+            case InstructionConstants.OP_ICONST_3:
+            case InstructionConstants.OP_ICONST_4:
+            case InstructionConstants.OP_ICONST_5:
+            case InstructionConstants.OP_LCONST_0:
+            case InstructionConstants.OP_LCONST_1:
+            case InstructionConstants.OP_FCONST_0:
+            case InstructionConstants.OP_FCONST_1:
+            case InstructionConstants.OP_FCONST_2:
+            case InstructionConstants.OP_DCONST_0:
+            case InstructionConstants.OP_DCONST_1:
+
+            case InstructionConstants.OP_BIPUSH:
+            case InstructionConstants.OP_SIPUSH:
+
+            case InstructionConstants.OP_IALOAD:
+            case InstructionConstants.OP_LALOAD:
+            case InstructionConstants.OP_FALOAD:
+            case InstructionConstants.OP_DALOAD:
+            case InstructionConstants.OP_AALOAD:
+            case InstructionConstants.OP_BALOAD:
+            case InstructionConstants.OP_CALOAD:
+            case InstructionConstants.OP_SALOAD:
+
+            case InstructionConstants.OP_IASTORE:
+            case InstructionConstants.OP_LASTORE:
+            case InstructionConstants.OP_FASTORE:
+            case InstructionConstants.OP_DASTORE:
+            case InstructionConstants.OP_AASTORE:
+            case InstructionConstants.OP_BASTORE:
+            case InstructionConstants.OP_CASTORE:
+            case InstructionConstants.OP_SASTORE:
+            case InstructionConstants.OP_POP:
+            case InstructionConstants.OP_POP2:
+            case InstructionConstants.OP_DUP:
+            case InstructionConstants.OP_DUP_X1:
+            case InstructionConstants.OP_DUP_X2:
+            case InstructionConstants.OP_DUP2:
+            case InstructionConstants.OP_DUP2_X1:
+            case InstructionConstants.OP_DUP2_X2:
+            case InstructionConstants.OP_SWAP:
+            case InstructionConstants.OP_IADD:
+            case InstructionConstants.OP_LADD:
+            case InstructionConstants.OP_FADD:
+            case InstructionConstants.OP_DADD:
+            case InstructionConstants.OP_ISUB:
+            case InstructionConstants.OP_LSUB:
+            case InstructionConstants.OP_FSUB:
+            case InstructionConstants.OP_DSUB:
+            case InstructionConstants.OP_IMUL:
+            case InstructionConstants.OP_LMUL:
+            case InstructionConstants.OP_FMUL:
+            case InstructionConstants.OP_DMUL:
+            case InstructionConstants.OP_IDIV:
+            case InstructionConstants.OP_LDIV:
+            case InstructionConstants.OP_FDIV:
+            case InstructionConstants.OP_DDIV:
+            case InstructionConstants.OP_IREM:
+            case InstructionConstants.OP_LREM:
+            case InstructionConstants.OP_FREM:
+            case InstructionConstants.OP_DREM:
+            case InstructionConstants.OP_INEG:
+            case InstructionConstants.OP_LNEG:
+            case InstructionConstants.OP_FNEG:
+            case InstructionConstants.OP_DNEG:
+            case InstructionConstants.OP_ISHL:
+            case InstructionConstants.OP_LSHL:
+            case InstructionConstants.OP_ISHR:
+            case InstructionConstants.OP_LSHR:
+            case InstructionConstants.OP_IUSHR:
+            case InstructionConstants.OP_LUSHR:
+            case InstructionConstants.OP_IAND:
+            case InstructionConstants.OP_LAND:
+            case InstructionConstants.OP_IOR:
+            case InstructionConstants.OP_LOR:
+            case InstructionConstants.OP_IXOR:
+            case InstructionConstants.OP_LXOR:
+
+            case InstructionConstants.OP_I2L:
+            case InstructionConstants.OP_I2F:
+            case InstructionConstants.OP_I2D:
+            case InstructionConstants.OP_L2I:
+            case InstructionConstants.OP_L2F:
+            case InstructionConstants.OP_L2D:
+            case InstructionConstants.OP_F2I:
+            case InstructionConstants.OP_F2L:
+            case InstructionConstants.OP_F2D:
+            case InstructionConstants.OP_D2I:
+            case InstructionConstants.OP_D2L:
+            case InstructionConstants.OP_D2F:
+            case InstructionConstants.OP_I2B:
+            case InstructionConstants.OP_I2C:
+            case InstructionConstants.OP_I2S:
+            case InstructionConstants.OP_LCMP:
+            case InstructionConstants.OP_FCMPL:
+            case InstructionConstants.OP_FCMPG:
+            case InstructionConstants.OP_DCMPL:
+            case InstructionConstants.OP_DCMPG:
+
+            case InstructionConstants.OP_IRETURN:
+            case InstructionConstants.OP_LRETURN:
+            case InstructionConstants.OP_FRETURN:
+            case InstructionConstants.OP_DRETURN:
+            case InstructionConstants.OP_ARETURN:
+            case InstructionConstants.OP_RETURN:
+
+            case InstructionConstants.OP_NEWARRAY:
+            case InstructionConstants.OP_ARRAYLENGTH:
+            case InstructionConstants.OP_ATHROW:
+
+            case InstructionConstants.OP_MONITORENTER:
+            case InstructionConstants.OP_MONITOREXIT:
+                instruction = new SimpleInstruction();
+                break;
+
+            // Instructions with a contant pool index.
+            case InstructionConstants.OP_LDC:
+            case InstructionConstants.OP_LDC_W:
+            case InstructionConstants.OP_LDC2_W:
+
+            case InstructionConstants.OP_GETSTATIC:
+            case InstructionConstants.OP_PUTSTATIC:
+            case InstructionConstants.OP_GETFIELD:
+            case InstructionConstants.OP_PUTFIELD:
+
+            case InstructionConstants.OP_INVOKEVIRTUAL:
+            case InstructionConstants.OP_INVOKESPECIAL:
+            case InstructionConstants.OP_INVOKESTATIC:
+            case InstructionConstants.OP_INVOKEINTERFACE:
+
+            case InstructionConstants.OP_NEW:
+            case InstructionConstants.OP_ANEWARRAY:
+            case InstructionConstants.OP_CHECKCAST:
+            case InstructionConstants.OP_INSTANCEOF:
+            case InstructionConstants.OP_MULTIANEWARRAY:
+                instruction = new ConstantInstruction();
+                break;
+
+            // Instructions with a local variable index.
+            case InstructionConstants.OP_ILOAD:
+            case InstructionConstants.OP_LLOAD:
+            case InstructionConstants.OP_FLOAD:
+            case InstructionConstants.OP_DLOAD:
+            case InstructionConstants.OP_ALOAD:
+            case InstructionConstants.OP_ILOAD_0:
+            case InstructionConstants.OP_ILOAD_1:
+            case InstructionConstants.OP_ILOAD_2:
+            case InstructionConstants.OP_ILOAD_3:
+            case InstructionConstants.OP_LLOAD_0:
+            case InstructionConstants.OP_LLOAD_1:
+            case InstructionConstants.OP_LLOAD_2:
+            case InstructionConstants.OP_LLOAD_3:
+            case InstructionConstants.OP_FLOAD_0:
+            case InstructionConstants.OP_FLOAD_1:
+            case InstructionConstants.OP_FLOAD_2:
+            case InstructionConstants.OP_FLOAD_3:
+            case InstructionConstants.OP_DLOAD_0:
+            case InstructionConstants.OP_DLOAD_1:
+            case InstructionConstants.OP_DLOAD_2:
+            case InstructionConstants.OP_DLOAD_3:
+            case InstructionConstants.OP_ALOAD_0:
+            case InstructionConstants.OP_ALOAD_1:
+            case InstructionConstants.OP_ALOAD_2:
+            case InstructionConstants.OP_ALOAD_3:
+
+            case InstructionConstants.OP_ISTORE:
+            case InstructionConstants.OP_LSTORE:
+            case InstructionConstants.OP_FSTORE:
+            case InstructionConstants.OP_DSTORE:
+            case InstructionConstants.OP_ASTORE:
+            case InstructionConstants.OP_ISTORE_0:
+            case InstructionConstants.OP_ISTORE_1:
+            case InstructionConstants.OP_ISTORE_2:
+            case InstructionConstants.OP_ISTORE_3:
+            case InstructionConstants.OP_LSTORE_0:
+            case InstructionConstants.OP_LSTORE_1:
+            case InstructionConstants.OP_LSTORE_2:
+            case InstructionConstants.OP_LSTORE_3:
+            case InstructionConstants.OP_FSTORE_0:
+            case InstructionConstants.OP_FSTORE_1:
+            case InstructionConstants.OP_FSTORE_2:
+            case InstructionConstants.OP_FSTORE_3:
+            case InstructionConstants.OP_DSTORE_0:
+            case InstructionConstants.OP_DSTORE_1:
+            case InstructionConstants.OP_DSTORE_2:
+            case InstructionConstants.OP_DSTORE_3:
+            case InstructionConstants.OP_ASTORE_0:
+            case InstructionConstants.OP_ASTORE_1:
+            case InstructionConstants.OP_ASTORE_2:
+            case InstructionConstants.OP_ASTORE_3:
+
+            case InstructionConstants.OP_IINC:
+
+            case InstructionConstants.OP_RET:
+                instruction = new VariableInstruction(wide);
+                break;
+
+            // Instructions with a branch offset operand.
+            case InstructionConstants.OP_IFEQ:
+            case InstructionConstants.OP_IFNE:
+            case InstructionConstants.OP_IFLT:
+            case InstructionConstants.OP_IFGE:
+            case InstructionConstants.OP_IFGT:
+            case InstructionConstants.OP_IFLE:
+            case InstructionConstants.OP_IFICMPEQ:
+            case InstructionConstants.OP_IFICMPNE:
+            case InstructionConstants.OP_IFICMPLT:
+            case InstructionConstants.OP_IFICMPGE:
+            case InstructionConstants.OP_IFICMPGT:
+            case InstructionConstants.OP_IFICMPLE:
+            case InstructionConstants.OP_IFACMPEQ:
+            case InstructionConstants.OP_IFACMPNE:
+            case InstructionConstants.OP_GOTO:
+            case InstructionConstants.OP_JSR:
+
+            case InstructionConstants.OP_IFNULL:
+            case InstructionConstants.OP_IFNONNULL:
+
+            case InstructionConstants.OP_GOTO_W:
+            case InstructionConstants.OP_JSR_W:
+                instruction = new BranchInstruction();
+                break;
+
+            //  The tableswitch instruction.
+            case InstructionConstants.OP_TABLESWITCH:
+                instruction = new TableSwitchInstruction();
+                break;
+
+            //  The lookupswitch instruction.
+            case InstructionConstants.OP_LOOKUPSWITCH:
+                instruction = new LookUpSwitchInstruction();
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown instruction opcode ["+opcode+"] at offset "+offset);
+        }
+
+        instruction.opcode = opcode;
+
+        instruction.readInfo(code, index);
+
+        return instruction;
+    }
+}
diff --git a/src/proguard/classfile/instruction/InstructionUtil.java b/src/proguard/classfile/instruction/InstructionUtil.java
new file mode 100644
index 0000000..a3a328a
--- /dev/null
+++ b/src/proguard/classfile/instruction/InstructionUtil.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+import proguard.classfile.ClassConstants;
+
+/**
+ * Utility methods for converting between representations of names and
+ * descriptions.
+ *
+ * @author Eric Lafortune
+ */
+public class InstructionUtil
+{
+    /**
+     * Returns the internal type corresponding to the given 'newarray' type.
+     * @param arrayType <code>InstructionConstants.ARRAY_T_BOOLEAN</code>,
+     *                  <code>InstructionConstants.ARRAY_T_BYTE</code>,
+     *                  <code>InstructionConstants.ARRAY_T_CHAR</code>,
+     *                  <code>InstructionConstants.ARRAY_T_SHORT</code>,
+     *                  <code>InstructionConstants.ARRAY_T_INT</code>,
+     *                  <code>InstructionConstants.ARRAY_T_LONG</code>,
+     *                  <code>InstructionConstants.ARRAY_T_FLOAT</code>, or
+     *                  <code>InstructionConstants.ARRAY_T_DOUBLE</code>.
+     * @return <code>ClassConstants.INTERNAL_TYPE_BOOLEAN</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_BYTE</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_CHAR</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_SHORT</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_INT</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_LONG</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_FLOAT</code>, or
+     *         <code>ClassConstants.INTERNAL_TYPE_DOUBLE</code>.
+     */
+    public static char internalTypeFromArrayType(byte arrayType)
+    {
+        switch (arrayType)
+        {
+            case InstructionConstants.ARRAY_T_BOOLEAN: return ClassConstants.INTERNAL_TYPE_BOOLEAN;
+            case InstructionConstants.ARRAY_T_CHAR:    return ClassConstants.INTERNAL_TYPE_CHAR;
+            case InstructionConstants.ARRAY_T_FLOAT:   return ClassConstants.INTERNAL_TYPE_FLOAT;
+            case InstructionConstants.ARRAY_T_DOUBLE:  return ClassConstants.INTERNAL_TYPE_DOUBLE;
+            case InstructionConstants.ARRAY_T_BYTE:    return ClassConstants.INTERNAL_TYPE_BYTE;
+            case InstructionConstants.ARRAY_T_SHORT:   return ClassConstants.INTERNAL_TYPE_SHORT;
+            case InstructionConstants.ARRAY_T_INT:     return ClassConstants.INTERNAL_TYPE_INT;
+            case InstructionConstants.ARRAY_T_LONG:    return ClassConstants.INTERNAL_TYPE_LONG;
+            default: throw new IllegalArgumentException("Unknown array type ["+arrayType+"]");
+        }
+    }
+}
diff --git a/src/proguard/classfile/instruction/LookUpSwitchInstruction.java b/src/proguard/classfile/instruction/LookUpSwitchInstruction.java
new file mode 100644
index 0000000..178cce5
--- /dev/null
+++ b/src/proguard/classfile/instruction/LookUpSwitchInstruction.java
@@ -0,0 +1,135 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+
+/**
+ * This Instruction represents a simple instruction without variable arguments
+ * or constant pool references.
+ *
+ * @author Eric Lafortune
+ */
+public class LookUpSwitchInstruction extends SwitchInstruction
+{
+    public int[] cases;
+
+
+    /**
+     * Creates an uninitialized LookUpSwitchInstruction.
+     */
+    public LookUpSwitchInstruction() {}
+
+
+    /**
+     * Creates a new LookUpSwitchInstruction with the given arguments.
+     */
+    public LookUpSwitchInstruction(byte  opcode,
+                                   int   defaultOffset,
+                                   int[] cases,
+                                   int[] jumpOffsets)
+    {
+        this.opcode        = opcode;
+        this.defaultOffset = defaultOffset;
+        this.cases         = cases;
+        this.jumpOffsets   = jumpOffsets;
+    }
+
+
+    /**
+     * Copies the given instruction into this instruction.
+     * @param lookUpSwitchInstruction the instruction to be copied.
+     * @return this instruction.
+     */
+    public LookUpSwitchInstruction copy(LookUpSwitchInstruction lookUpSwitchInstruction)
+    {
+        this.opcode        = lookUpSwitchInstruction.opcode;
+        this.defaultOffset = lookUpSwitchInstruction.defaultOffset;
+        this.cases         = lookUpSwitchInstruction.cases;
+        this.jumpOffsets   = lookUpSwitchInstruction.jumpOffsets;
+
+        return this;
+    }
+
+
+    // Implementations for Instruction.
+
+    public Instruction shrink()
+    {
+        // There aren't any ways to shrink this instruction.
+        return this;
+    }
+
+    protected void readInfo(byte[] code, int offset)
+    {
+        // Skip up to three padding bytes.
+        offset += -offset & 3;
+
+        // Read the two 32-bit arguments.
+        defaultOffset       = readInt(code, offset); offset += 4;
+        int jumpOffsetCount = readInt(code, offset); offset += 4;
+
+        // Read the matches-offset pairs.
+        cases       = new int[jumpOffsetCount];
+        jumpOffsets = new int[jumpOffsetCount];
+
+        for (int index = 0; index < jumpOffsetCount; index++)
+        {
+            cases[index]       = readInt(code, offset); offset += 4;
+            jumpOffsets[index] = readInt(code, offset); offset += 4;
+        }
+    }
+
+
+    protected void writeInfo(byte[] code, int offset)
+    {
+        // Write up to three padding bytes.
+        while ((offset & 3) != 0)
+        {
+            writeByte(code, offset++, 0);
+        }
+
+        // Write the two 32-bit arguments.
+        writeInt(code, offset, defaultOffset); offset += 4;
+        writeInt(code, offset, cases.length);  offset += 4;
+
+        // Write the matches-offset pairs.
+        for (int index = 0; index < cases.length; index++)
+        {
+            writeInt(code, offset, cases[index]);       offset += 4;
+            writeInt(code, offset, jumpOffsets[index]); offset += 4;
+        }
+    }
+
+
+    public int length(int offset)
+    {
+        return 1 + (-(offset+1) & 3) + 8 + cases.length * 8;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor)
+    {
+        instructionVisitor.visitLookUpSwitchInstruction(clazz, method, codeAttribute, offset, this);
+    }
+}
diff --git a/src/proguard/classfile/instruction/SimpleInstruction.java b/src/proguard/classfile/instruction/SimpleInstruction.java
new file mode 100644
index 0000000..84e6344
--- /dev/null
+++ b/src/proguard/classfile/instruction/SimpleInstruction.java
@@ -0,0 +1,255 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+
+/**
+ * This Instruction represents a simple instruction without variable arguments
+ * or constant pool references.
+ *
+ * @author Eric Lafortune
+ */
+public class SimpleInstruction extends Instruction
+{
+    public int constant;
+
+
+    /**
+     * Creates an uninitialized SimpleInstruction.
+     */
+    public SimpleInstruction() {}
+
+
+    /**
+     * Creates a new SimpleInstruction with the given opcode.
+     */
+    public SimpleInstruction(byte opcode)
+    {
+        this(opcode, embeddedConstant(opcode));
+    }
+
+
+    /**
+     * Creates a new SimpleInstruction with the given opcode and constant.
+     */
+    public SimpleInstruction(byte opcode, int constant)
+    {
+        this.opcode   = opcode;
+        this.constant = constant;
+    }
+
+
+    /**
+     * Copies the given instruction into this instruction.
+     * @param simpleInstruction the instruction to be copied.
+     * @return this instruction.
+     */
+    public SimpleInstruction copy(SimpleInstruction simpleInstruction)
+    {
+        this.opcode   = simpleInstruction.opcode;
+        this.constant = simpleInstruction.constant;
+
+        return this;
+    }
+
+
+    /**
+     * Return the embedded constant of the given opcode, or 0 if the opcode
+     * doesn't have one.
+     */
+    private static int embeddedConstant(byte opcode)
+    {
+        switch (opcode)
+        {
+            case InstructionConstants.OP_ICONST_M1: return -1;
+
+            case InstructionConstants.OP_ICONST_1:
+            case InstructionConstants.OP_LCONST_1:
+            case InstructionConstants.OP_FCONST_1:
+            case InstructionConstants.OP_DCONST_1: return 1;
+
+            case InstructionConstants.OP_ICONST_2:
+            case InstructionConstants.OP_FCONST_2: return 2;
+
+            case InstructionConstants.OP_ICONST_3: return 3;
+
+            case InstructionConstants.OP_ICONST_4: return 4;
+
+            case InstructionConstants.OP_ICONST_5: return 5;
+
+            default: return 0;
+        }
+    }
+
+
+    // Implementations for Instruction.
+
+    public byte canonicalOpcode()
+    {
+        // Replace any _1, _2, _3,... extension by _0.
+        switch (opcode)
+        {
+            case InstructionConstants.OP_ICONST_M1:
+            case InstructionConstants.OP_ICONST_0:
+            case InstructionConstants.OP_ICONST_1:
+            case InstructionConstants.OP_ICONST_2:
+            case InstructionConstants.OP_ICONST_3:
+            case InstructionConstants.OP_ICONST_4:
+            case InstructionConstants.OP_ICONST_5:
+            case InstructionConstants.OP_BIPUSH:
+            case InstructionConstants.OP_SIPUSH:   return InstructionConstants.OP_ICONST_0;
+
+            case InstructionConstants.OP_LCONST_0:
+            case InstructionConstants.OP_LCONST_1: return InstructionConstants.OP_LCONST_0;
+
+            case InstructionConstants.OP_FCONST_0:
+            case InstructionConstants.OP_FCONST_1:
+            case InstructionConstants.OP_FCONST_2: return InstructionConstants.OP_FCONST_0;
+
+            case InstructionConstants.OP_DCONST_0:
+            case InstructionConstants.OP_DCONST_1: return InstructionConstants.OP_DCONST_0;
+
+            default: return opcode;
+        }
+    }
+
+    public Instruction shrink()
+    {
+        // Reconstruct the opcode of the shortest instruction, if there are
+        // any alternatives.
+        switch (opcode)
+        {
+            case InstructionConstants.OP_ICONST_M1:
+            case InstructionConstants.OP_ICONST_0:
+            case InstructionConstants.OP_ICONST_1:
+            case InstructionConstants.OP_ICONST_2:
+            case InstructionConstants.OP_ICONST_3:
+            case InstructionConstants.OP_ICONST_4:
+            case InstructionConstants.OP_ICONST_5:
+            case InstructionConstants.OP_BIPUSH:
+            case InstructionConstants.OP_SIPUSH:
+                switch (requiredConstantSize())
+                {
+                    case 0:
+                        opcode = (byte)(InstructionConstants.OP_ICONST_0 + constant);
+                        break;
+                    case 1:
+                        opcode = InstructionConstants.OP_BIPUSH;
+                        break;
+                    case 2:
+                        opcode = InstructionConstants.OP_SIPUSH;
+                        break;
+                }
+                break;
+
+            case InstructionConstants.OP_LCONST_0:
+            case InstructionConstants.OP_LCONST_1:
+                opcode = (byte)(InstructionConstants.OP_LCONST_0 + constant);
+                break;
+
+            case InstructionConstants.OP_FCONST_0:
+            case InstructionConstants.OP_FCONST_1:
+            case InstructionConstants.OP_FCONST_2:
+                opcode = (byte)(InstructionConstants.OP_FCONST_0 + constant);
+                break;
+
+            case InstructionConstants.OP_DCONST_0:
+            case InstructionConstants.OP_DCONST_1:
+                opcode = (byte)(InstructionConstants.OP_DCONST_0 + constant);
+                break;
+        }
+
+        return this;
+    }
+
+    protected void readInfo(byte[] code, int offset)
+    {
+        int constantSize = constantSize();
+
+        // Also initialize embedded constants that are different from 0.
+        constant = constantSize == 0 ?
+            embeddedConstant(opcode) :
+            readSignedValue(code, offset, constantSize);
+    }
+
+
+    protected void writeInfo(byte[] code, int offset)
+    {
+        int constantSize = constantSize();
+
+        if (requiredConstantSize() > constantSize)
+        {
+            throw new IllegalArgumentException("Instruction has invalid constant size ("+this.toString(offset)+")");
+        }
+
+        writeSignedValue(code, offset, constant, constantSize);
+    }
+
+
+    public int length(int offset)
+    {
+        return 1 + constantSize();
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor)
+    {
+        instructionVisitor.visitSimpleInstruction(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return getName() +
+               (constantSize() > 0 ? " "+constant : "");
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the constant size for this instruction.
+     */
+    private int constantSize()
+    {
+        return opcode == InstructionConstants.OP_BIPUSH ||
+               opcode == InstructionConstants.OP_NEWARRAY ? 1 :
+               opcode == InstructionConstants.OP_SIPUSH   ? 2 :
+                                                            0;
+    }
+
+
+    /**
+     * Computes the required constant size for this instruction.
+     */
+    private int requiredConstantSize()
+    {
+        return constant >= -1 && constant <= 5  ? 0 :
+               constant << 24 >> 24 == constant ? 1 :
+               constant << 16 >> 16 == constant ? 2 :
+                                                  4;
+    }
+}
diff --git a/src/proguard/classfile/instruction/SwitchInstruction.java b/src/proguard/classfile/instruction/SwitchInstruction.java
new file mode 100644
index 0000000..b98c2fb
--- /dev/null
+++ b/src/proguard/classfile/instruction/SwitchInstruction.java
@@ -0,0 +1,83 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+/**
+ * This Instruction represents a simple instruction without variable arguments
+ * or constant pool references.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class SwitchInstruction extends Instruction
+{
+    public int   defaultOffset;
+    public int[] jumpOffsets;
+
+
+    /**
+     * Creates an uninitialized SwitchInstruction.
+     */
+    public SwitchInstruction() {}
+
+
+    /**
+     * Creates a new SwitchInstruction with the given arguments.
+     */
+    public SwitchInstruction(byte  opcode,
+                             int   defaultOffset,
+                             int[] jumpOffsets)
+    {
+        this.opcode        = opcode;
+        this.defaultOffset = defaultOffset;
+        this.jumpOffsets   = jumpOffsets;
+    }
+
+
+    /**
+     * Copies the given instruction into this instruction.
+     * @param switchInstruction the instruction to be copied.
+     * @return this instruction.
+     */
+    public SwitchInstruction copy(SwitchInstruction switchInstruction)
+    {
+        this.opcode        = switchInstruction.opcode;
+        this.defaultOffset = switchInstruction.defaultOffset;
+        this.jumpOffsets   = switchInstruction.jumpOffsets;
+
+        return this;
+    }
+
+
+    // Implementations for Instruction.
+
+    public String toString(int offset)
+    {
+        return "["+offset+"] "+toString()+" (target="+(offset+defaultOffset)+")";
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return getName()+" ("+jumpOffsets.length+" offsets, default="+defaultOffset+")";
+    }
+}
diff --git a/src/proguard/classfile/instruction/TableSwitchInstruction.java b/src/proguard/classfile/instruction/TableSwitchInstruction.java
new file mode 100644
index 0000000..ee81af5
--- /dev/null
+++ b/src/proguard/classfile/instruction/TableSwitchInstruction.java
@@ -0,0 +1,139 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+
+/**
+ * This Instruction represents a simple instruction without variable arguments
+ * or constant pool references.
+ *
+ * @author Eric Lafortune
+ */
+public class TableSwitchInstruction extends SwitchInstruction
+{
+    public int lowCase;
+    public int highCase;
+
+
+    /**
+     * Creates an uninitialized TableSwitchInstruction.
+     */
+    public TableSwitchInstruction() {}
+
+
+    /**
+     * Creates a new TableSwitchInstruction with the given arguments.
+     */
+    public TableSwitchInstruction(byte  opcode,
+                                  int   defaultOffset,
+                                  int   lowCase,
+                                  int   highCase,
+                                  int[] jumpOffsets)
+    {
+        this.opcode        = opcode;
+        this.defaultOffset = defaultOffset;
+        this.lowCase       = lowCase;
+        this.highCase      = highCase;
+        this.jumpOffsets   = jumpOffsets;
+    }
+
+
+    /**
+     * Copies the given instruction into this instruction.
+     * @param tableSwitchInstruction the instruction to be copied.
+     * @return this instruction.
+     */
+    public TableSwitchInstruction copy(TableSwitchInstruction tableSwitchInstruction)
+    {
+        this.opcode        = tableSwitchInstruction.opcode;
+        this.defaultOffset = tableSwitchInstruction.defaultOffset;
+        this.lowCase       = tableSwitchInstruction.lowCase;
+        this.highCase      = tableSwitchInstruction.highCase;
+        this.jumpOffsets   = tableSwitchInstruction.jumpOffsets;
+
+        return this;
+    }
+
+
+    // Implementations for Instruction.
+
+    public Instruction shrink()
+    {
+        // There aren't any ways to shrink this instruction.
+        return this;
+    }
+
+    protected void readInfo(byte[] code, int offset)
+    {
+        // Skip up to three padding bytes.
+        offset += -offset & 3;
+
+        // Read the three 32-bit arguments.
+        defaultOffset = readInt(code, offset); offset += 4;
+        lowCase       = readInt(code, offset); offset += 4;
+        highCase      = readInt(code, offset); offset += 4;
+
+        // Read the jump offsets.
+        jumpOffsets = new int[highCase - lowCase + 1];
+
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            jumpOffsets[index] = readInt(code, offset); offset += 4;
+        }
+    }
+
+
+    protected void writeInfo(byte[] code, int offset)
+    {
+        // Write up to three padding bytes.
+        while ((offset & 3) != 0)
+        {
+            writeByte(code, offset++, 0);
+        }
+
+        // Write the three 32-bit arguments.
+        writeInt(code, offset, defaultOffset); offset += 4;
+        writeInt(code, offset, lowCase);       offset += 4;
+        writeInt(code, offset, highCase);      offset += 4;
+
+        // Write the jump offsets.
+        int length = highCase - lowCase + 1;
+        for (int index = 0; index < length; index++)
+        {
+            writeInt(code, offset, jumpOffsets[index]); offset += 4;
+        }
+    }
+
+
+    public int length(int offset)
+    {
+        return 1 + (-(offset+1) & 3) + 12 + (highCase - lowCase + 1) * 4;
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor)
+    {
+        instructionVisitor.visitTableSwitchInstruction(clazz, method, codeAttribute, offset, this);
+    }
+}
diff --git a/src/proguard/classfile/instruction/VariableInstruction.java b/src/proguard/classfile/instruction/VariableInstruction.java
new file mode 100644
index 0000000..309f802
--- /dev/null
+++ b/src/proguard/classfile/instruction/VariableInstruction.java
@@ -0,0 +1,372 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+
+/**
+ * This Instruction represents an instruction that refers to a variable on the
+ * local variable stack.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableInstruction extends Instruction
+{
+    public boolean wide;
+    public int     variableIndex;
+    public int     constant;
+
+
+    /**
+     * Creates an uninitialized VariableInstruction.
+     */
+    public VariableInstruction() {}
+
+
+    public VariableInstruction(boolean wide)
+    {
+        this.wide = wide;
+    }
+
+
+    public VariableInstruction(byte opcode)
+    {
+        this(opcode, embeddedVariable(opcode), 0);
+    }
+
+
+    public VariableInstruction(byte opcode,
+                               int  variableIndex)
+    {
+        this(opcode, variableIndex, 0);
+    }
+
+
+    public VariableInstruction(byte opcode,
+                               int  variableIndex,
+                               int  constant)
+    {
+        this.opcode        = opcode;
+        this.variableIndex = variableIndex;
+        this.constant      = constant;
+        this.wide          = requiredVariableIndexSize() > 1 ||
+                             requiredConstantSize()      > 1;
+    }
+
+
+    /**
+     * Copies the given instruction into this instruction.
+     * @param variableInstruction the instruction to be copied.
+     * @return this instruction.
+     */
+    public VariableInstruction copy(VariableInstruction variableInstruction)
+    {
+        this.opcode        = variableInstruction.opcode;
+        this.variableIndex = variableInstruction.variableIndex;
+        this.constant      = variableInstruction.constant;
+        this.wide          = variableInstruction.wide;
+
+        return this;
+    }
+
+
+    /**
+     * Return the embedded variable of the given opcode, or 0 if the opcode
+     * doesn't have one.
+     */
+    private static int embeddedVariable(byte opcode)
+    {
+        switch (opcode)
+        {
+            case InstructionConstants.OP_ILOAD_1:
+            case InstructionConstants.OP_LLOAD_1:
+            case InstructionConstants.OP_FLOAD_1:
+            case InstructionConstants.OP_DLOAD_1:
+            case InstructionConstants.OP_ALOAD_1:
+            case InstructionConstants.OP_ISTORE_1:
+            case InstructionConstants.OP_LSTORE_1:
+            case InstructionConstants.OP_FSTORE_1:
+            case InstructionConstants.OP_DSTORE_1:
+            case InstructionConstants.OP_ASTORE_1: return 1;
+
+            case InstructionConstants.OP_ILOAD_2:
+            case InstructionConstants.OP_LLOAD_2:
+            case InstructionConstants.OP_FLOAD_2:
+            case InstructionConstants.OP_DLOAD_2:
+            case InstructionConstants.OP_ALOAD_2:
+            case InstructionConstants.OP_ISTORE_2:
+            case InstructionConstants.OP_LSTORE_2:
+            case InstructionConstants.OP_FSTORE_2:
+            case InstructionConstants.OP_DSTORE_2:
+            case InstructionConstants.OP_ASTORE_2: return 2;
+
+            case InstructionConstants.OP_ILOAD_3:
+            case InstructionConstants.OP_LLOAD_3:
+            case InstructionConstants.OP_FLOAD_3:
+            case InstructionConstants.OP_DLOAD_3:
+            case InstructionConstants.OP_ALOAD_3:
+            case InstructionConstants.OP_ISTORE_3:
+            case InstructionConstants.OP_LSTORE_3:
+            case InstructionConstants.OP_FSTORE_3:
+            case InstructionConstants.OP_DSTORE_3:
+            case InstructionConstants.OP_ASTORE_3: return 3;
+
+            default: return 0;
+        }
+    }
+
+
+    /**
+     * Returns whether this instruction stores the value of a variable.
+     * The value is false for the ret instruction, but true for the iinc
+     * instruction.
+     */
+    public boolean isStore()
+    {
+        // A store instruction can be recognized as follows. Note that this
+        // excludes the ret instruction, which has a negative opcode.
+        return opcode >= InstructionConstants.OP_ISTORE ||
+               opcode == InstructionConstants.OP_IINC;
+    }
+
+
+    /**
+     * Returns whether this instruction loads the value of a variable.
+     * The value is true for the ret instruction and for the iinc
+     * instruction.
+     */
+    public boolean isLoad()
+    {
+        // A load instruction can be recognized as follows. Note that this
+        // includes the ret instruction, which has a negative opcode.
+        return opcode < InstructionConstants.OP_ISTORE;
+    }
+
+
+    // Implementations for Instruction.
+
+    public byte canonicalOpcode()
+    {
+        // Remove the _0, _1, _2, _3 extension, if any.
+        switch (opcode)
+        {
+            case InstructionConstants.OP_ILOAD_0:
+            case InstructionConstants.OP_ILOAD_1:
+            case InstructionConstants.OP_ILOAD_2:
+            case InstructionConstants.OP_ILOAD_3: return InstructionConstants.OP_ILOAD;
+            case InstructionConstants.OP_LLOAD_0:
+            case InstructionConstants.OP_LLOAD_1:
+            case InstructionConstants.OP_LLOAD_2:
+            case InstructionConstants.OP_LLOAD_3: return InstructionConstants.OP_LLOAD;
+            case InstructionConstants.OP_FLOAD_0:
+            case InstructionConstants.OP_FLOAD_1:
+            case InstructionConstants.OP_FLOAD_2:
+            case InstructionConstants.OP_FLOAD_3: return InstructionConstants.OP_FLOAD;
+            case InstructionConstants.OP_DLOAD_0:
+            case InstructionConstants.OP_DLOAD_1:
+            case InstructionConstants.OP_DLOAD_2:
+            case InstructionConstants.OP_DLOAD_3: return InstructionConstants.OP_DLOAD;
+            case InstructionConstants.OP_ALOAD_0:
+            case InstructionConstants.OP_ALOAD_1:
+            case InstructionConstants.OP_ALOAD_2:
+            case InstructionConstants.OP_ALOAD_3: return InstructionConstants.OP_ALOAD;
+
+            case InstructionConstants.OP_ISTORE_0:
+            case InstructionConstants.OP_ISTORE_1:
+            case InstructionConstants.OP_ISTORE_2:
+            case InstructionConstants.OP_ISTORE_3: return InstructionConstants.OP_ISTORE;
+            case InstructionConstants.OP_LSTORE_0:
+            case InstructionConstants.OP_LSTORE_1:
+            case InstructionConstants.OP_LSTORE_2:
+            case InstructionConstants.OP_LSTORE_3: return InstructionConstants.OP_LSTORE;
+            case InstructionConstants.OP_FSTORE_0:
+            case InstructionConstants.OP_FSTORE_1:
+            case InstructionConstants.OP_FSTORE_2:
+            case InstructionConstants.OP_FSTORE_3: return InstructionConstants.OP_FSTORE;
+            case InstructionConstants.OP_DSTORE_0:
+            case InstructionConstants.OP_DSTORE_1:
+            case InstructionConstants.OP_DSTORE_2:
+            case InstructionConstants.OP_DSTORE_3: return InstructionConstants.OP_DSTORE;
+            case InstructionConstants.OP_ASTORE_0:
+            case InstructionConstants.OP_ASTORE_1:
+            case InstructionConstants.OP_ASTORE_2:
+            case InstructionConstants.OP_ASTORE_3: return InstructionConstants.OP_ASTORE;
+
+            default: return opcode;
+        }
+    }
+
+    public Instruction shrink()
+    {
+        opcode = canonicalOpcode();
+
+        // Is this instruction pointing to a variable with index from 0 to 3?
+        if (variableIndex <= 3)
+        {
+            switch (opcode)
+            {
+                case InstructionConstants.OP_ILOAD: opcode = (byte)(InstructionConstants.OP_ILOAD_0 + variableIndex); break;
+                case InstructionConstants.OP_LLOAD: opcode = (byte)(InstructionConstants.OP_LLOAD_0 + variableIndex); break;
+                case InstructionConstants.OP_FLOAD: opcode = (byte)(InstructionConstants.OP_FLOAD_0 + variableIndex); break;
+                case InstructionConstants.OP_DLOAD: opcode = (byte)(InstructionConstants.OP_DLOAD_0 + variableIndex); break;
+                case InstructionConstants.OP_ALOAD: opcode = (byte)(InstructionConstants.OP_ALOAD_0 + variableIndex); break;
+
+                case InstructionConstants.OP_ISTORE: opcode = (byte)(InstructionConstants.OP_ISTORE_0 + variableIndex); break;
+                case InstructionConstants.OP_LSTORE: opcode = (byte)(InstructionConstants.OP_LSTORE_0 + variableIndex); break;
+                case InstructionConstants.OP_FSTORE: opcode = (byte)(InstructionConstants.OP_FSTORE_0 + variableIndex); break;
+                case InstructionConstants.OP_DSTORE: opcode = (byte)(InstructionConstants.OP_DSTORE_0 + variableIndex); break;
+                case InstructionConstants.OP_ASTORE: opcode = (byte)(InstructionConstants.OP_ASTORE_0 + variableIndex); break;
+            }
+        }
+
+        // Only make the instruction wide if necessary.
+        wide = requiredVariableIndexSize() > 1 ||
+               requiredConstantSize()      > 1;
+
+        return this;
+    }
+
+
+    protected boolean isWide()
+    {
+        return wide;
+    }
+
+
+    protected void readInfo(byte[] code, int offset)
+    {
+        int variableIndexSize = variableIndexSize();
+        int constantSize      = constantSize();
+
+        // Also initialize embedded variable indexes.
+        if (variableIndexSize == 0)
+        {
+            // An embedded variable index can be decoded as follows.
+            variableIndex = opcode < InstructionConstants.OP_ISTORE_0 ?
+                (opcode - InstructionConstants.OP_ILOAD_0 ) & 3 :
+                (opcode - InstructionConstants.OP_ISTORE_0) & 3;
+        }
+        else
+        {
+            variableIndex = readValue(code, offset, variableIndexSize); offset += variableIndexSize;
+        }
+
+        constant = readSignedValue(code, offset, constantSize);
+    }
+
+
+    protected void writeInfo(byte[] code, int offset)
+    {
+        int variableIndexSize = variableIndexSize();
+        int constantSize      = constantSize();
+
+        if (requiredVariableIndexSize() > variableIndexSize)
+        {
+            throw new IllegalArgumentException("Instruction has invalid variable index size ("+this.toString(offset)+")");
+        }
+
+        if (requiredConstantSize() > constantSize)
+        {
+            throw new IllegalArgumentException("Instruction has invalid constant size ("+this.toString(offset)+")");
+        }
+
+        writeValue(code, offset, variableIndex, variableIndexSize); offset += variableIndexSize;
+        writeSignedValue(code, offset, constant, constantSize);
+    }
+
+
+    public int length(int offset)
+    {
+        return (wide ? 2 : 1) + variableIndexSize() + constantSize();
+    }
+
+
+    public void accept(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, InstructionVisitor instructionVisitor)
+    {
+        instructionVisitor.visitVariableInstruction(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return getName() +
+               (wide ? "_w" : "") +
+               " v"+variableIndex +
+               (constantSize() > 0 ? ", "+constant : "");
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the variable index size for this instruction.
+     */
+    private int variableIndexSize()
+    {
+        return (opcode >= InstructionConstants.OP_ILOAD_0 &&
+                opcode <= InstructionConstants.OP_ALOAD_3) ||
+               (opcode >= InstructionConstants.OP_ISTORE_0 &&
+                opcode <= InstructionConstants.OP_ASTORE_3) ? 0 :
+               wide                                         ? 2 :
+                                                              1;
+    }
+
+
+    /**
+     * Computes the required variable index size for this instruction's variable
+     * index.
+     */
+    private int requiredVariableIndexSize()
+    {
+        return (variableIndex &    0x3) == variableIndex ? 0 :
+               (variableIndex &   0xff) == variableIndex ? 1 :
+               (variableIndex & 0xffff) == variableIndex ? 2 :
+                                                           4;
+
+    }
+
+
+    /**
+     * Returns the constant size for this instruction.
+     */
+    private int constantSize()
+    {
+        return opcode != InstructionConstants.OP_IINC ? 0 :
+               wide                                   ? 2 :
+                                                        1;
+    }
+
+
+    /**
+     * Computes the required constant size for this instruction's constant.
+     */
+    private int requiredConstantSize()
+    {
+        return opcode != InstructionConstants.OP_IINC ? 0 :
+               constant << 24 >> 24 == constant       ? 1 :
+               constant << 16 >> 16 == constant       ? 2 :
+                                                        4;
+    }
+}
diff --git a/src/proguard/classfile/instruction/package.html b/src/proguard/classfile/instruction/package.html
new file mode 100644
index 0000000..48c234e
--- /dev/null
+++ b/src/proguard/classfile/instruction/package.html
@@ -0,0 +1,9 @@
+<body>
+This package contains classes to represent Java bytecode instructions.
+<p>
+Not every instruction currently has its own class. Only groups of instructions
+that refer to the constant pool get their own representations.
+<p>
+While the package is sufficient for the current needs of the ProGuard
+application, it may very well be reorganised and extended in the future.
+</body>
diff --git a/src/proguard/classfile/instruction/visitor/AllInstructionVisitor.java b/src/proguard/classfile/instruction/visitor/AllInstructionVisitor.java
new file mode 100644
index 0000000..71b2cde
--- /dev/null
+++ b/src/proguard/classfile/instruction/visitor/AllInstructionVisitor.java
@@ -0,0 +1,56 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor lets a given InstructionVisitor visit all Instruction
+ * objects of the CodeAttribute objects it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllInstructionVisitor
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private final InstructionVisitor instructionVisitor;
+
+
+    public AllInstructionVisitor(InstructionVisitor instructionVisitor)
+    {
+        this.instructionVisitor = instructionVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        codeAttribute.instructionsAccept(clazz, method, instructionVisitor);
+    }
+}
diff --git a/src/proguard/classfile/instruction/visitor/InstructionCounter.java b/src/proguard/classfile/instruction/visitor/InstructionCounter.java
new file mode 100644
index 0000000..1d10980
--- /dev/null
+++ b/src/proguard/classfile/instruction/visitor/InstructionCounter.java
@@ -0,0 +1,59 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.Instruction;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor counts the number of instructions that has been visited.
+ *
+ * @author Eric Lafortune
+ */
+public class InstructionCounter
+extends      SimplifiedVisitor
+implements   InstructionVisitor
+{
+    private int count;
+
+
+    /**
+     * Returns the number of instructions that has been visited so far.
+     */
+    public int getCount()
+    {
+        return count;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz         clazz,
+                                    Method        method,
+                                    CodeAttribute codeAttribute,
+                                    int           offset,
+                                    Instruction   instruction)
+    {
+        count++;
+    }
+}
diff --git a/src/proguard/classfile/instruction/visitor/InstructionVisitor.java b/src/proguard/classfile/instruction/visitor/InstructionVisitor.java
new file mode 100644
index 0000000..11af131
--- /dev/null
+++ b/src/proguard/classfile/instruction/visitor/InstructionVisitor.java
@@ -0,0 +1,42 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.*;
+
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>Instruction</code> objects.
+ *
+ * @author Eric Lafortune
+ */
+public interface InstructionVisitor
+{
+    public void visitSimpleInstruction(      Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction       simpleInstruction);
+    public void visitVariableInstruction(    Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction     variableInstruction);
+    public void visitConstantInstruction(    Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction     constantInstruction);
+    public void visitBranchInstruction(      Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction       branchInstruction);
+    public void visitTableSwitchInstruction( Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction  tableSwitchInstruction);
+    public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction);
+}
diff --git a/src/proguard/classfile/instruction/visitor/MultiInstructionVisitor.java b/src/proguard/classfile/instruction/visitor/MultiInstructionVisitor.java
new file mode 100644
index 0000000..aada455
--- /dev/null
+++ b/src/proguard/classfile/instruction/visitor/MultiInstructionVisitor.java
@@ -0,0 +1,131 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.instruction.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.*;
+
+
+/**
+ * This InstructionVisitor delegates all visits to each InstructionVisitor
+ * in a given list.
+ *
+ * @author Eric Lafortune
+ */
+public class MultiInstructionVisitor implements InstructionVisitor
+{
+    private static final int ARRAY_SIZE_INCREMENT = 5;
+
+
+    private InstructionVisitor[] instructionVisitors;
+    private int                  instructionVisitorCount;
+
+
+    public MultiInstructionVisitor()
+    {
+    }
+
+
+    public MultiInstructionVisitor(InstructionVisitor[] instructionVisitors)
+    {
+        this.instructionVisitors     = instructionVisitors;
+        this.instructionVisitorCount = instructionVisitors.length;
+    }
+
+
+    public void addInstructionVisitor(InstructionVisitor instructionVisitor)
+    {
+        ensureArraySize();
+
+        instructionVisitors[instructionVisitorCount++] = instructionVisitor;
+    }
+
+
+    private void ensureArraySize()
+    {
+        if (instructionVisitors == null)
+        {
+            instructionVisitors = new InstructionVisitor[ARRAY_SIZE_INCREMENT];
+        }
+        else if (instructionVisitors.length == instructionVisitorCount)
+        {
+            InstructionVisitor[] newInstructionVisitors =
+                new InstructionVisitor[instructionVisitorCount +
+                                     ARRAY_SIZE_INCREMENT];
+            System.arraycopy(instructionVisitors, 0,
+                             newInstructionVisitors, 0,
+                             instructionVisitorCount);
+            instructionVisitors = newInstructionVisitors;
+        }
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        for (int index = 0; index < instructionVisitorCount; index++)
+        {
+            instructionVisitors[index].visitSimpleInstruction(clazz, method, codeAttribute, offset, simpleInstruction);
+        }
+    }
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        for (int index = 0; index < instructionVisitorCount; index++)
+        {
+            instructionVisitors[index].visitVariableInstruction(clazz, method, codeAttribute, offset, variableInstruction);
+        }
+    }
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        for (int index = 0; index < instructionVisitorCount; index++)
+        {
+            instructionVisitors[index].visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction);
+        }
+    }
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        for (int index = 0; index < instructionVisitorCount; index++)
+        {
+            instructionVisitors[index].visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction);
+        }
+    }
+
+    public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction)
+    {
+        for (int index = 0; index < instructionVisitorCount; index++)
+        {
+            instructionVisitors[index].visitTableSwitchInstruction(clazz, method, codeAttribute, offset, tableSwitchInstruction);
+        }
+    }
+
+    public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction)
+    {
+        for (int index = 0; index < instructionVisitorCount; index++)
+        {
+            instructionVisitors[index].visitLookUpSwitchInstruction(clazz, method, codeAttribute, offset, lookUpSwitchInstruction);
+        }
+    }
+}
diff --git a/src/proguard/classfile/instruction/visitor/package.html b/src/proguard/classfile/instruction/visitor/package.html
new file mode 100644
index 0000000..a31a408
--- /dev/null
+++ b/src/proguard/classfile/instruction/visitor/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains visitors for instructions.
+</body>
diff --git a/src/proguard/classfile/io/LibraryClassReader.java b/src/proguard/classfile/io/LibraryClassReader.java
new file mode 100644
index 0000000..f14471c
--- /dev/null
+++ b/src/proguard/classfile/io/LibraryClassReader.java
@@ -0,0 +1,362 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.io;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+import java.io.DataInput;
+
+/**
+ * This ClassVisitor fills out the LibraryClass objects that it visits with data
+ * from the given DataInput object.
+ *
+ * @author Eric Lafortune
+ */
+public class LibraryClassReader
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             ConstantVisitor
+{
+    private static final LibraryField[]  EMPTY_LIBRARY_FIELDS  = new LibraryField[0];
+    private static final LibraryMethod[] EMPTY_LIBRARY_METHODS = new LibraryMethod[0];
+
+
+    private final RuntimeDataInput dataInput;
+    private final boolean          skipNonPublicClasses;
+    private final boolean          skipNonPublicClassMembers;
+
+    // A global array that acts as a parameter for the visitor methods.
+    private Constant[]      constantPool;
+
+
+    /**
+     * Creates a new ProgramClassReader for reading from the given DataInput.
+     */
+    public LibraryClassReader(DataInput dataInput,
+                              boolean   skipNonPublicClasses,
+                              boolean   skipNonPublicClassMembers)
+    {
+        this.dataInput                 = new RuntimeDataInput(dataInput);
+        this.skipNonPublicClasses      = skipNonPublicClasses;
+        this.skipNonPublicClassMembers = skipNonPublicClassMembers;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass libraryClass)
+    {
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Read and check the magic number.
+        int u4magic = dataInput.readInt();
+
+        ClassUtil.checkMagicNumber(u4magic);
+
+        // Read and check the version numbers.
+        int u2minorVersion = dataInput.readUnsignedShort();
+        int u2majorVersion = dataInput.readUnsignedShort();
+
+        int u4version = ClassUtil.internalClassVersion(u2majorVersion,
+                                                       u2minorVersion);
+
+        ClassUtil.checkVersionNumbers(u4version);
+
+        // Read the constant pool. Note that the first entry is not used.
+        int u2constantPoolCount = dataInput.readUnsignedShort();
+
+        // Create the constant pool array.
+        constantPool = new Constant[u2constantPoolCount];
+
+        for (int index = 1; index < u2constantPoolCount; index++)
+        {
+            Constant constant = createConstant();
+            constant.accept(libraryClass, this);
+
+            int tag = constant.getTag();
+            if (tag == ClassConstants.CONSTANT_Class ||
+                tag == ClassConstants.CONSTANT_Utf8)
+            {
+                constantPool[index] = constant;
+            }
+
+            // Long constants and double constants take up two entries in the
+            // constant pool.
+            if (tag == ClassConstants.CONSTANT_Long ||
+                tag == ClassConstants.CONSTANT_Double)
+            {
+                index++;
+            }
+        }
+
+        // Read the general class information.
+        libraryClass.u2accessFlags = dataInput.readUnsignedShort();
+
+        // We may stop parsing this library class if it's not public anyway.
+        // E.g. only about 60% of all rt.jar classes need to be parsed.
+        if (skipNonPublicClasses &&
+            AccessUtil.accessLevel(libraryClass.getAccessFlags()) < AccessUtil.PUBLIC)
+        {
+            return;
+        }
+
+        // Read the class and super class indices.
+        int u2thisClass  = dataInput.readUnsignedShort();
+        int u2superClass = dataInput.readUnsignedShort();
+
+        // Store their actual names.
+        libraryClass.thisClassName  = getClassName(u2thisClass);
+        libraryClass.superClassName = (u2superClass == 0) ? null :
+                                      getClassName(u2superClass);
+
+        // Read the interfaces
+        int u2interfacesCount = dataInput.readUnsignedShort();
+
+        libraryClass.interfaceNames = new String[u2interfacesCount];
+        for (int index = 0; index < u2interfacesCount; index++)
+        {
+            // Store the actual interface name.
+            int u2interface = dataInput.readUnsignedShort();
+            libraryClass.interfaceNames[index] = getClassName(u2interface);
+        }
+
+        // Read the fields.
+        int u2fieldsCount = dataInput.readUnsignedShort();
+
+        // Create the fields array.
+        LibraryField[] reusableFields = new LibraryField[u2fieldsCount];
+
+        int visibleFieldsCount = 0;
+        for (int index = 0; index < u2fieldsCount; index++)
+        {
+            LibraryField field = new LibraryField();
+            this.visitLibraryMember(libraryClass, field);
+
+            // Only store fields that are visible.
+            if (AccessUtil.accessLevel(field.getAccessFlags()) >=
+                (skipNonPublicClassMembers ? AccessUtil.PROTECTED :
+                                             AccessUtil.PACKAGE_VISIBLE))
+            {
+                reusableFields[visibleFieldsCount++] = field;
+            }
+        }
+
+        // Copy the visible fields (if any) into a fields array of the right size.
+        if (visibleFieldsCount == 0)
+        {
+            libraryClass.fields = EMPTY_LIBRARY_FIELDS;
+        }
+        else
+        {
+            libraryClass.fields = new LibraryField[visibleFieldsCount];
+            System.arraycopy(reusableFields, 0, libraryClass.fields, 0, visibleFieldsCount);
+        }
+
+        // Read the methods.
+        int u2methodsCount = dataInput.readUnsignedShort();
+
+        // Create the methods array.
+        LibraryMethod[] reusableMethods = new LibraryMethod[u2methodsCount];
+
+        int visibleMethodsCount = 0;
+        for (int index = 0; index < u2methodsCount; index++)
+        {
+            LibraryMethod method = new LibraryMethod();
+            this.visitLibraryMember(libraryClass, method);
+
+            // Only store methods that are visible.
+            if (AccessUtil.accessLevel(method.getAccessFlags()) >=
+                (skipNonPublicClassMembers ? AccessUtil.PROTECTED :
+                                             AccessUtil.PACKAGE_VISIBLE))
+            {
+                reusableMethods[visibleMethodsCount++] = method;
+            }
+        }
+
+        // Copy the visible methods (if any) into a methods array of the right size.
+        if (visibleMethodsCount == 0)
+        {
+            libraryClass.methods = EMPTY_LIBRARY_METHODS;
+        }
+        else
+        {
+            libraryClass.methods = new LibraryMethod[visibleMethodsCount];
+            System.arraycopy(reusableMethods, 0, libraryClass.methods, 0, visibleMethodsCount);
+        }
+
+        // Skip the class attributes.
+        skipAttributes();
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass libraryClass, ProgramMember libraryMember)
+    {
+    }
+
+
+    public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember)
+    {
+        // Read the general field information.
+        libraryMember.u2accessFlags = dataInput.readUnsignedShort();
+        libraryMember.name          = getString(dataInput.readUnsignedShort());
+        libraryMember.descriptor    = getString(dataInput.readUnsignedShort());
+
+        // Skip the field attributes.
+        skipAttributes();
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        dataInput.skipBytes(4);
+    }
+
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        dataInput.skipBytes(8);
+    }
+
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        dataInput.skipBytes(4);
+    }
+
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        dataInput.skipBytes(8);
+    }
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        dataInput.skipBytes(2);
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        int u2length = dataInput.readUnsignedShort();
+
+        // Read the UTF-8 bytes.
+        byte[] bytes = new byte[u2length];
+        dataInput.readFully(bytes);
+        utf8Constant.setBytes(bytes);
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        dataInput.skipBytes(4);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classConstant.u2nameIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        dataInput.skipBytes(4);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the class name of the ClassConstant at the specified index in the
+     * reusable constant pool.
+     */
+    private String getClassName(int constantIndex)
+    {
+        ClassConstant classEntry = (ClassConstant)constantPool[constantIndex];
+
+        return getString(classEntry.u2nameIndex);
+    }
+
+
+    /**
+     * Returns the string of the Utf8Constant at the specified index in the
+     * reusable constant pool.
+     */
+    private String getString(int constantIndex)
+    {
+        return ((Utf8Constant)constantPool[constantIndex]).getString();
+    }
+
+
+    private Constant createConstant()
+    {
+        int u1tag = dataInput.readUnsignedByte();
+
+        switch (u1tag)
+        {
+            case ClassConstants.CONSTANT_Utf8:               return new Utf8Constant();
+            case ClassConstants.CONSTANT_Integer:            return new IntegerConstant();
+            case ClassConstants.CONSTANT_Float:              return new FloatConstant();
+            case ClassConstants.CONSTANT_Long:               return new LongConstant();
+            case ClassConstants.CONSTANT_Double:             return new DoubleConstant();
+            case ClassConstants.CONSTANT_String:             return new StringConstant();
+            case ClassConstants.CONSTANT_Fieldref:           return new FieldrefConstant();
+            case ClassConstants.CONSTANT_Methodref:          return new MethodrefConstant();
+            case ClassConstants.CONSTANT_InterfaceMethodref: return new InterfaceMethodrefConstant();
+            case ClassConstants.CONSTANT_Class:              return new ClassConstant();
+            case ClassConstants.CONSTANT_NameAndType:        return new NameAndTypeConstant();
+
+            default: throw new RuntimeException("Unknown constant type ["+u1tag+"] in constant pool");
+        }
+    }
+
+
+    private void skipAttributes()
+    {
+        int u2attributesCount = dataInput.readUnsignedShort();
+
+        for (int index = 0; index < u2attributesCount; index++)
+        {
+            skipAttribute();
+        }
+    }
+
+
+    private void skipAttribute()
+    {
+        dataInput.skipBytes(2);
+        int u4attributeLength = dataInput.readInt();
+        dataInput.skipBytes(u4attributeLength);
+    }
+}
diff --git a/src/proguard/classfile/io/ProgramClassReader.java b/src/proguard/classfile/io/ProgramClassReader.java
new file mode 100644
index 0000000..476a346
--- /dev/null
+++ b/src/proguard/classfile/io/ProgramClassReader.java
@@ -0,0 +1,862 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.io;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.preverification.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+import java.io.DataInput;
+
+/**
+ * This ClassVisitor fills out the ProgramClass objects that it visits with data
+ * from the given DataInput object.
+ *
+ * @author Eric Lafortune
+ */
+public class ProgramClassReader
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             ConstantVisitor,
+             AttributeVisitor,
+             InnerClassesInfoVisitor,
+             ExceptionInfoVisitor,
+             StackMapFrameVisitor,
+             VerificationTypeVisitor,
+             LineNumberInfoVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    private final RuntimeDataInput dataInput;
+
+
+    /**
+     * Creates a new ProgramClassReader for reading from the given DataInput.
+     */
+    public ProgramClassReader(DataInput dataInput)
+    {
+        this.dataInput = new RuntimeDataInput(dataInput);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Read and check the magic number.
+        programClass.u4magic = dataInput.readInt();
+
+        ClassUtil.checkMagicNumber(programClass.u4magic);
+
+        // Read and check the version numbers.
+        int u2minorVersion = dataInput.readUnsignedShort();
+        int u2majorVersion = dataInput.readUnsignedShort();
+
+        programClass.u4version = ClassUtil.internalClassVersion(u2majorVersion,
+                                                                u2minorVersion);
+
+        ClassUtil.checkVersionNumbers(programClass.u4version);
+
+        // Read the constant pool. Note that the first entry is not used.
+        programClass.u2constantPoolCount = dataInput.readUnsignedShort();
+
+        programClass.constantPool = new Constant[programClass.u2constantPoolCount];
+        for (int index = 1; index < programClass.u2constantPoolCount; index++)
+        {
+            Constant constant = createConstant();
+            constant.accept(programClass, this);
+            programClass.constantPool[index] = constant;
+
+            // Long constants and double constants take up two entries in the
+            // constant pool.
+            int tag = constant.getTag();
+            if (tag == ClassConstants.CONSTANT_Long ||
+                tag == ClassConstants.CONSTANT_Double)
+            {
+                programClass.constantPool[++index] = null;
+            }
+        }
+
+        // Read the general class information.
+        programClass.u2accessFlags = dataInput.readUnsignedShort();
+        programClass.u2thisClass   = dataInput.readUnsignedShort();
+        programClass.u2superClass  = dataInput.readUnsignedShort();
+
+        // Read the interfaces.
+        programClass.u2interfacesCount = dataInput.readUnsignedShort();
+
+        programClass.u2interfaces = new int[programClass.u2interfacesCount];
+        for (int index = 0; index < programClass.u2interfacesCount; index++)
+        {
+            programClass.u2interfaces[index] = dataInput.readUnsignedShort();
+        }
+
+        // Read the fields.
+        programClass.u2fieldsCount = dataInput.readUnsignedShort();
+
+        programClass.fields = new ProgramField[programClass.u2fieldsCount];
+        for (int index = 0; index < programClass.u2fieldsCount; index++)
+        {
+            ProgramField programField = new ProgramField();
+            this.visitProgramField(programClass, programField);
+            programClass.fields[index] = programField;
+        }
+
+        // Read the methods.
+        programClass.u2methodsCount = dataInput.readUnsignedShort();
+
+        programClass.methods = new ProgramMethod[programClass.u2methodsCount];
+        for (int index = 0; index < programClass.u2methodsCount; index++)
+        {
+            ProgramMethod programMethod = new ProgramMethod();
+            this.visitProgramMethod(programClass, programMethod);
+            programClass.methods[index] = programMethod;
+        }
+
+        // Read the class attributes.
+        programClass.u2attributesCount = dataInput.readUnsignedShort();
+
+        programClass.attributes = new Attribute[programClass.u2attributesCount];
+        for (int index = 0; index < programClass.u2attributesCount; index++)
+        {
+            Attribute attribute = createAttribute(programClass);
+            attribute.accept(programClass, this);
+            programClass.attributes[index] = attribute;
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Read the general field information.
+        programField.u2accessFlags     = dataInput.readUnsignedShort();
+        programField.u2nameIndex       = dataInput.readUnsignedShort();
+        programField.u2descriptorIndex = dataInput.readUnsignedShort();
+
+        // Read the field attributes.
+        programField.u2attributesCount = dataInput.readUnsignedShort();
+
+        programField.attributes = new Attribute[programField.u2attributesCount];
+        for (int index = 0; index < programField.u2attributesCount; index++)
+        {
+            Attribute attribute = createAttribute(programClass);
+            attribute.accept(programClass, programField, this);
+            programField.attributes[index] = attribute;
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Read the general method information.
+        programMethod.u2accessFlags     = dataInput.readUnsignedShort();
+        programMethod.u2nameIndex       = dataInput.readUnsignedShort();
+        programMethod.u2descriptorIndex = dataInput.readUnsignedShort();
+
+        // Read the method attributes.
+        programMethod.u2attributesCount = dataInput.readUnsignedShort();
+
+        programMethod.attributes = new Attribute[programMethod.u2attributesCount];
+        for (int index = 0; index < programMethod.u2attributesCount; index++)
+        {
+            Attribute attribute = createAttribute(programClass);
+            attribute.accept(programClass, programMethod, this);
+            programMethod.attributes[index] = attribute;
+        }
+    }
+
+
+    public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember)
+    {
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        integerConstant.u4value = dataInput.readInt();
+    }
+
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        longConstant.u8value = dataInput.readLong();
+    }
+
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        floatConstant.f4value = dataInput.readFloat();
+    }
+
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        doubleConstant.f8value = dataInput.readDouble();
+    }
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        stringConstant.u2stringIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        int u2length = dataInput.readUnsignedShort();
+
+        // Read the UTF-8 bytes.
+        byte[] bytes = new byte[u2length];
+        dataInput.readFully(bytes);
+        utf8Constant.setBytes(bytes);
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        refConstant.u2classIndex       = dataInput.readUnsignedShort();
+        refConstant.u2nameAndTypeIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classConstant.u2nameIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        nameAndTypeConstant.u2nameIndex       = dataInput.readUnsignedShort();
+        nameAndTypeConstant.u2descriptorIndex = dataInput.readUnsignedShort();
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        // Read the unknown information.
+        byte[] info = new byte[unknownAttribute.u4attributeLength];
+        dataInput.readFully(info);
+        unknownAttribute.info = info;
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        sourceFileAttribute.u2sourceFileIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        sourceDirAttribute.u2sourceDirIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // Read the inner classes.
+        innerClassesAttribute.u2classesCount = dataInput.readUnsignedShort();
+
+        innerClassesAttribute.classes = new InnerClassesInfo[innerClassesAttribute.u2classesCount];
+        for (int index = 0; index < innerClassesAttribute.u2classesCount; index++)
+        {
+            InnerClassesInfo innerClassesInfo = new InnerClassesInfo();
+            this.visitInnerClassesInfo(clazz, innerClassesInfo);
+            innerClassesAttribute.classes[index] = innerClassesInfo;
+        }
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        enclosingMethodAttribute.u2classIndex       = dataInput.readUnsignedShort();
+        enclosingMethodAttribute.u2nameAndTypeIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        // This attribute does not contain any additional information.
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        // This attribute does not contain any additional information.
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        signatureAttribute.u2signatureIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        constantValueAttribute.u2constantValueIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        // Read the exceptions.
+        exceptionsAttribute.u2exceptionIndexTableLength = dataInput.readUnsignedShort();
+
+        exceptionsAttribute.u2exceptionIndexTable = new int[exceptionsAttribute.u2exceptionIndexTableLength];
+        for (int index = 0; index < exceptionsAttribute.u2exceptionIndexTableLength; index++)
+        {
+            exceptionsAttribute.u2exceptionIndexTable[index] = dataInput.readUnsignedShort();
+        }
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Read the stack size and local variable frame size.
+        codeAttribute.u2maxStack   = dataInput.readUnsignedShort();
+        codeAttribute.u2maxLocals  = dataInput.readUnsignedShort();
+
+        // Read the byte code.
+        codeAttribute.u4codeLength = dataInput.readInt();
+
+        byte[] code = new byte[codeAttribute.u4codeLength];
+        dataInput.readFully(code);
+        codeAttribute.code = code;
+
+        // Read the exceptions.
+        codeAttribute.u2exceptionTableLength = dataInput.readUnsignedShort();
+
+        codeAttribute.exceptionTable = new ExceptionInfo[codeAttribute.u2exceptionTableLength];
+        for (int index = 0; index < codeAttribute.u2exceptionTableLength; index++)
+        {
+            ExceptionInfo exceptionInfo = new ExceptionInfo();
+            this.visitExceptionInfo(clazz, method, codeAttribute, exceptionInfo);
+            codeAttribute.exceptionTable[index] = exceptionInfo;
+        }
+
+        // Read the code attributes.
+        codeAttribute.u2attributesCount = dataInput.readUnsignedShort();
+
+        codeAttribute.attributes = new Attribute[codeAttribute.u2attributesCount];
+        for (int index = 0; index < codeAttribute.u2attributesCount; index++)
+        {
+            Attribute attribute = createAttribute(clazz);
+            attribute.accept(clazz, method, codeAttribute, this);
+            codeAttribute.attributes[index] = attribute;
+        }
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        // Read the stack map frames (only full frames, without tag).
+        stackMapAttribute.u2stackMapFramesCount = dataInput.readUnsignedShort();
+
+        stackMapAttribute.stackMapFrames = new FullFrame[stackMapAttribute.u2stackMapFramesCount];
+        for (int index = 0; index < stackMapAttribute.u2stackMapFramesCount; index++)
+        {
+            FullFrame stackMapFrame = new FullFrame();
+            this.visitFullFrame(clazz, method, codeAttribute, index, stackMapFrame);
+            stackMapAttribute.stackMapFrames[index] = stackMapFrame;
+        }
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        // Read the stack map frames.
+        stackMapTableAttribute.u2stackMapFramesCount = dataInput.readUnsignedShort();
+
+        stackMapTableAttribute.stackMapFrames = new StackMapFrame[stackMapTableAttribute.u2stackMapFramesCount];
+        for (int index = 0; index < stackMapTableAttribute.u2stackMapFramesCount; index++)
+        {
+            StackMapFrame stackMapFrame = createStackMapFrame();
+            stackMapFrame.accept(clazz, method, codeAttribute, 0, this);
+            stackMapTableAttribute.stackMapFrames[index] = stackMapFrame;
+        }
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        // Read the line numbers.
+        lineNumberTableAttribute.u2lineNumberTableLength = dataInput.readUnsignedShort();
+
+        lineNumberTableAttribute.lineNumberTable = new LineNumberInfo[lineNumberTableAttribute.u2lineNumberTableLength];
+        for (int index = 0; index < lineNumberTableAttribute.u2lineNumberTableLength; index++)
+        {
+            LineNumberInfo lineNumberInfo = new LineNumberInfo();
+            this.visitLineNumberInfo(clazz, method, codeAttribute, lineNumberInfo);
+            lineNumberTableAttribute.lineNumberTable[index] = lineNumberInfo;
+        }
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Read the local variables.
+        localVariableTableAttribute.u2localVariableTableLength = dataInput.readUnsignedShort();
+
+        localVariableTableAttribute.localVariableTable = new LocalVariableInfo[localVariableTableAttribute.u2localVariableTableLength];
+        for (int index = 0; index < localVariableTableAttribute.u2localVariableTableLength; index++)
+        {
+            LocalVariableInfo localVariableInfo = new LocalVariableInfo();
+            this.visitLocalVariableInfo(clazz, method, codeAttribute, localVariableInfo);
+            localVariableTableAttribute.localVariableTable[index] = localVariableInfo;
+        }
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Read the local variable types.
+        localVariableTypeTableAttribute.u2localVariableTypeTableLength = dataInput.readUnsignedShort();
+
+        localVariableTypeTableAttribute.localVariableTypeTable = new LocalVariableTypeInfo[localVariableTypeTableAttribute.u2localVariableTypeTableLength];
+        for (int index = 0; index < localVariableTypeTableAttribute.u2localVariableTypeTableLength; index++)
+        {
+            LocalVariableTypeInfo localVariableTypeInfo = new LocalVariableTypeInfo();
+            this.visitLocalVariableTypeInfo(clazz, method, codeAttribute, localVariableTypeInfo);
+            localVariableTypeTableAttribute.localVariableTypeTable[index] = localVariableTypeInfo;
+        }
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Read the annotations.
+        annotationsAttribute.u2annotationsCount = dataInput.readUnsignedShort();
+
+        annotationsAttribute.annotations = new Annotation[annotationsAttribute.u2annotationsCount];
+        for (int index = 0; index < annotationsAttribute.u2annotationsCount; index++)
+        {
+            Annotation annotation = new Annotation();
+            this.visitAnnotation(clazz, annotation);
+            annotationsAttribute.annotations[index] = annotation;
+        }
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Read the parameter annotations.
+        parameterAnnotationsAttribute.u2parametersCount           = dataInput.readUnsignedByte();
+
+        // The java compilers of JDK 1.5, JDK 1.6, and Eclipse all count the
+        // number of parameters of constructors of non-static inner classes
+        // incorrectly. Fix it right here.
+        int parameterStart = 0;
+        if (method.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            int realParametersCount = ClassUtil.internalMethodParameterCount(method.getDescriptor(clazz));
+            parameterStart = realParametersCount - parameterAnnotationsAttribute.u2parametersCount;
+            parameterAnnotationsAttribute.u2parametersCount = realParametersCount;
+        }
+
+        parameterAnnotationsAttribute.u2parameterAnnotationsCount = new int[parameterAnnotationsAttribute.u2parametersCount];
+        parameterAnnotationsAttribute.parameterAnnotations        = new Annotation[parameterAnnotationsAttribute.u2parametersCount][];
+
+        for (int parameterIndex = parameterStart; parameterIndex < parameterAnnotationsAttribute.u2parametersCount; parameterIndex++)
+        {
+            // Read the parameter annotations of the given parameter.
+            int u2annotationsCount = dataInput.readUnsignedShort();
+
+            Annotation[] annotations = new Annotation[u2annotationsCount];
+
+            for (int index = 0; index < u2annotationsCount; index++)
+            {
+                Annotation annotation = new Annotation();
+                this.visitAnnotation(clazz, annotation);
+                annotations[index] = annotation;
+            }
+
+            parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex] = u2annotationsCount;
+            parameterAnnotationsAttribute.parameterAnnotations[parameterIndex]        = annotations;
+        }
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Read the default element value.
+        ElementValue elementValue = createElementValue();
+        elementValue.accept(clazz, null, this);
+        annotationDefaultAttribute.defaultValue = elementValue;
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        innerClassesInfo.u2innerClassIndex       = dataInput.readUnsignedShort();
+        innerClassesInfo.u2outerClassIndex       = dataInput.readUnsignedShort();
+        innerClassesInfo.u2innerNameIndex        = dataInput.readUnsignedShort();
+        innerClassesInfo.u2innerClassAccessFlags = dataInput.readUnsignedShort();
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        exceptionInfo.u2startPC   = dataInput.readUnsignedShort();
+        exceptionInfo.u2endPC     = dataInput.readUnsignedShort();
+        exceptionInfo.u2handlerPC = dataInput.readUnsignedShort();
+        exceptionInfo.u2catchType = dataInput.readUnsignedShort();
+    }
+
+
+    // Implementations for StackMapFrameVisitor.
+
+    public void visitSameZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameZeroFrame sameZeroFrame)
+    {
+        if (sameZeroFrame.getTag() == StackMapFrame.SAME_ZERO_FRAME_EXTENDED)
+        {
+            sameZeroFrame.u2offsetDelta = dataInput.readUnsignedShort();
+        }
+    }
+
+
+    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+    {
+        if (sameOneFrame.getTag() == StackMapFrame.SAME_ONE_FRAME_EXTENDED)
+        {
+            sameOneFrame.u2offsetDelta = dataInput.readUnsignedShort();
+        }
+
+        // Read the verification type of the stack entry.
+        VerificationType verificationType = createVerificationType();
+        verificationType.accept(clazz, method, codeAttribute, offset, this);
+        sameOneFrame.stackItem = verificationType;
+    }
+
+
+    public void visitLessZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LessZeroFrame lessZeroFrame)
+    {
+        lessZeroFrame.u2offsetDelta = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+    {
+        moreZeroFrame.u2offsetDelta = dataInput.readUnsignedShort();
+
+        // Read the verification types of the additional local variables.
+        moreZeroFrame.additionalVariables = new VerificationType[moreZeroFrame.additionalVariablesCount];
+        for (int index = 0; index < moreZeroFrame.additionalVariablesCount; index++)
+        {
+            VerificationType verificationType = createVerificationType();
+            verificationType.accept(clazz, method, codeAttribute, offset, this);
+            moreZeroFrame.additionalVariables[index] = verificationType;
+        }
+    }
+
+
+    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+    {
+        fullFrame.u2offsetDelta = dataInput.readUnsignedShort();
+
+        // Read the verification types of the local variables.
+        fullFrame.variablesCount = dataInput.readUnsignedShort();
+        fullFrame.variables = new VerificationType[fullFrame.variablesCount];
+        for (int index = 0; index < fullFrame.variablesCount; index++)
+        {
+            VerificationType verificationType = createVerificationType();
+            verificationType.variablesAccept(clazz, method, codeAttribute, offset, index, this);
+            fullFrame.variables[index] = verificationType;
+        }
+
+        // Read the verification types of the stack entries.
+        fullFrame.stackCount = dataInput.readUnsignedShort();
+        fullFrame.stack = new VerificationType[fullFrame.stackCount];
+        for (int index = 0; index < fullFrame.stackCount; index++)
+        {
+            VerificationType verificationType = createVerificationType();
+            verificationType.stackAccept(clazz, method, codeAttribute, offset, index, this);
+            fullFrame.stack[index] = verificationType;
+        }
+    }
+
+
+    // Implementations for VerificationTypeVisitor.
+
+    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType)
+    {
+        // Most verification types don't contain any additional information.
+    }
+
+
+    public void visitObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType objectType)
+    {
+        objectType.u2classIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)
+    {
+        uninitializedType.u2newInstructionOffset = dataInput.readUnsignedShort();
+    }
+
+
+    // Implementations for LineNumberInfoVisitor.
+
+    public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
+    {
+        lineNumberInfo.u2startPC    = dataInput.readUnsignedShort();
+        lineNumberInfo.u2lineNumber = dataInput.readUnsignedShort();
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        localVariableInfo.u2startPC         = dataInput.readUnsignedShort();
+        localVariableInfo.u2length          = dataInput.readUnsignedShort();
+        localVariableInfo.u2nameIndex       = dataInput.readUnsignedShort();
+        localVariableInfo.u2descriptorIndex = dataInput.readUnsignedShort();
+        localVariableInfo.u2index           = dataInput.readUnsignedShort();
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        localVariableTypeInfo.u2startPC        = dataInput.readUnsignedShort();
+        localVariableTypeInfo.u2length         = dataInput.readUnsignedShort();
+        localVariableTypeInfo.u2nameIndex      = dataInput.readUnsignedShort();
+        localVariableTypeInfo.u2signatureIndex = dataInput.readUnsignedShort();
+        localVariableTypeInfo.u2index          = dataInput.readUnsignedShort();
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        // Read the annotation type.
+        annotation.u2typeIndex = dataInput.readUnsignedShort();
+
+        // Read the element value pairs.
+        annotation.u2elementValuesCount = dataInput.readUnsignedShort();
+
+        annotation.elementValues = new ElementValue[annotation.u2elementValuesCount];
+        for (int index = 0; index < annotation.u2elementValuesCount; index++)
+        {
+            int u2elementNameIndex = dataInput.readUnsignedShort();
+            ElementValue elementValue = createElementValue();
+            elementValue.u2elementNameIndex = u2elementNameIndex;
+            elementValue.accept(clazz, annotation, this);
+            annotation.elementValues[index] = elementValue;
+        }
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        constantElementValue.u2constantValueIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        enumConstantElementValue.u2typeNameIndex     = dataInput.readUnsignedShort();
+        enumConstantElementValue.u2constantNameIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        classElementValue.u2classInfoIndex = dataInput.readUnsignedShort();
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        // Read the annotation.
+        Annotation annotationValue = new Annotation();
+        this.visitAnnotation(clazz, annotationValue);
+        annotationElementValue.annotationValue = annotationValue;
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        // Read the element values.
+        arrayElementValue.u2elementValuesCount = dataInput.readUnsignedShort();
+
+        arrayElementValue.elementValues = new ElementValue[arrayElementValue.u2elementValuesCount];
+        for (int index = 0; index < arrayElementValue.u2elementValuesCount; index++)
+        {
+            ElementValue elementValue = createElementValue();
+            elementValue.accept(clazz, annotation, this);
+            arrayElementValue.elementValues[index] = elementValue;
+        }
+    }
+
+
+    // Small utility methods.
+
+    private Constant createConstant()
+    {
+        int u1tag = dataInput.readUnsignedByte();
+
+        switch (u1tag)
+        {
+            case ClassConstants.CONSTANT_Utf8:               return new Utf8Constant();
+            case ClassConstants.CONSTANT_Integer:            return new IntegerConstant();
+            case ClassConstants.CONSTANT_Float:              return new FloatConstant();
+            case ClassConstants.CONSTANT_Long:               return new LongConstant();
+            case ClassConstants.CONSTANT_Double:             return new DoubleConstant();
+            case ClassConstants.CONSTANT_String:             return new StringConstant();
+            case ClassConstants.CONSTANT_Fieldref:           return new FieldrefConstant();
+            case ClassConstants.CONSTANT_Methodref:          return new MethodrefConstant();
+            case ClassConstants.CONSTANT_InterfaceMethodref: return new InterfaceMethodrefConstant();
+            case ClassConstants.CONSTANT_Class:              return new ClassConstant();
+            case ClassConstants.CONSTANT_NameAndType:        return new NameAndTypeConstant();
+
+            default: throw new RuntimeException("Unknown constant type ["+u1tag+"] in constant pool");
+        }
+    }
+
+
+    private Attribute createAttribute(Clazz clazz)
+    {
+        int u2attributeNameIndex = dataInput.readUnsignedShort();
+        int u4attributeLength    = dataInput.readInt();
+        String attributeName     = clazz.getString(u2attributeNameIndex);
+        Attribute attribute =
+            attributeName.equals(ClassConstants.ATTR_SourceFile)                           ? (Attribute)new SourceFileAttribute():
+            attributeName.equals(ClassConstants.ATTR_SourceDir)                            ? (Attribute)new SourceDirAttribute():
+            attributeName.equals(ClassConstants.ATTR_InnerClasses)                         ? (Attribute)new InnerClassesAttribute():
+            attributeName.equals(ClassConstants.ATTR_EnclosingMethod)                      ? (Attribute)new EnclosingMethodAttribute():
+            attributeName.equals(ClassConstants.ATTR_Deprecated)                           ? (Attribute)new DeprecatedAttribute():
+            attributeName.equals(ClassConstants.ATTR_Synthetic)                            ? (Attribute)new SyntheticAttribute():
+            attributeName.equals(ClassConstants.ATTR_Signature)                            ? (Attribute)new SignatureAttribute():
+            attributeName.equals(ClassConstants.ATTR_ConstantValue)                        ? (Attribute)new ConstantValueAttribute():
+            attributeName.equals(ClassConstants.ATTR_Exceptions)                           ? (Attribute)new ExceptionsAttribute():
+            attributeName.equals(ClassConstants.ATTR_Code)                                 ? (Attribute)new CodeAttribute():
+            attributeName.equals(ClassConstants.ATTR_StackMap)                             ? (Attribute)new StackMapAttribute():
+            attributeName.equals(ClassConstants.ATTR_StackMapTable)                        ? (Attribute)new StackMapTableAttribute():
+            attributeName.equals(ClassConstants.ATTR_LineNumberTable)                      ? (Attribute)new LineNumberTableAttribute():
+            attributeName.equals(ClassConstants.ATTR_LocalVariableTable)                   ? (Attribute)new LocalVariableTableAttribute():
+            attributeName.equals(ClassConstants.ATTR_LocalVariableTypeTable)               ? (Attribute)new LocalVariableTypeTableAttribute():
+            attributeName.equals(ClassConstants.ATTR_RuntimeVisibleAnnotations)            ? (Attribute)new RuntimeVisibleAnnotationsAttribute():
+            attributeName.equals(ClassConstants.ATTR_RuntimeInvisibleAnnotations)          ? (Attribute)new RuntimeInvisibleAnnotationsAttribute():
+            attributeName.equals(ClassConstants.ATTR_RuntimeVisibleParameterAnnotations)   ? (Attribute)new RuntimeVisibleParameterAnnotationsAttribute():
+            attributeName.equals(ClassConstants.ATTR_RuntimeInvisibleParameterAnnotations) ? (Attribute)new RuntimeInvisibleParameterAnnotationsAttribute():
+            attributeName.equals(ClassConstants.ATTR_AnnotationDefault)                    ? (Attribute)new AnnotationDefaultAttribute():
+                                                                                             (Attribute)new UnknownAttribute(u4attributeLength);
+        attribute.u2attributeNameIndex = u2attributeNameIndex;
+
+        return attribute;
+    }
+
+
+    private StackMapFrame createStackMapFrame()
+    {
+        int u1tag = dataInput.readUnsignedByte();
+
+        return
+            u1tag < StackMapFrame.SAME_ONE_FRAME           ? (StackMapFrame)new SameZeroFrame(u1tag) :
+            u1tag < StackMapFrame.SAME_ONE_FRAME_EXTENDED  ? (StackMapFrame)new SameOneFrame(u1tag)  :
+            u1tag < StackMapFrame.LESS_ZERO_FRAME          ? (StackMapFrame)new SameOneFrame(u1tag)  :
+            u1tag < StackMapFrame.SAME_ZERO_FRAME_EXTENDED ? (StackMapFrame)new LessZeroFrame(u1tag) :
+            u1tag < StackMapFrame.MORE_ZERO_FRAME          ? (StackMapFrame)new SameZeroFrame(u1tag) :
+            u1tag < StackMapFrame.FULL_FRAME               ? (StackMapFrame)new MoreZeroFrame(u1tag) :
+                                                             (StackMapFrame)new FullFrame();
+    }
+
+
+    private VerificationType createVerificationType()
+    {
+        int u1tag = dataInput.readUnsignedByte();
+
+        switch (u1tag)
+        {
+            case VerificationType.INTEGER_TYPE:            return new IntegerType();
+            case VerificationType.FLOAT_TYPE:              return new FloatType();
+            case VerificationType.LONG_TYPE:               return new LongType();
+            case VerificationType.DOUBLE_TYPE:             return new DoubleType();
+            case VerificationType.TOP_TYPE:                return new TopType();
+            case VerificationType.OBJECT_TYPE:             return new ObjectType();
+            case VerificationType.NULL_TYPE:               return new NullType();
+            case VerificationType.UNINITIALIZED_TYPE:      return new UninitializedType();
+            case VerificationType.UNINITIALIZED_THIS_TYPE: return new UninitializedThisType();
+
+            default: throw new RuntimeException("Unknown verification type ["+u1tag+"] in stack map frame");
+        }
+    }
+
+
+    private ElementValue createElementValue()
+    {
+        int u1tag = dataInput.readUnsignedByte();
+
+        switch (u1tag)
+        {
+            case ClassConstants.INTERNAL_TYPE_BOOLEAN:
+            case ClassConstants.INTERNAL_TYPE_BYTE:
+            case ClassConstants.INTERNAL_TYPE_CHAR:
+            case ClassConstants.INTERNAL_TYPE_SHORT:
+            case ClassConstants.INTERNAL_TYPE_INT:
+            case ClassConstants.INTERNAL_TYPE_FLOAT:
+            case ClassConstants.INTERNAL_TYPE_LONG:
+            case ClassConstants.INTERNAL_TYPE_DOUBLE:
+            case ClassConstants.ELEMENT_VALUE_STRING_CONSTANT: return new ConstantElementValue(u1tag);
+
+            case ClassConstants.ELEMENT_VALUE_ENUM_CONSTANT:   return new EnumConstantElementValue();
+            case ClassConstants.ELEMENT_VALUE_CLASS:           return new ClassElementValue();
+            case ClassConstants.ELEMENT_VALUE_ANNOTATION:      return new AnnotationElementValue();
+            case ClassConstants.ELEMENT_VALUE_ARRAY:           return new ArrayElementValue();
+
+            default: throw new IllegalArgumentException("Unknown element value tag ["+u1tag+"]");
+        }
+    }
+}
diff --git a/src/proguard/classfile/io/ProgramClassWriter.java b/src/proguard/classfile/io/ProgramClassWriter.java
new file mode 100644
index 0000000..e56f08a
--- /dev/null
+++ b/src/proguard/classfile/io/ProgramClassWriter.java
@@ -0,0 +1,690 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.io;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.preverification.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+import java.io.*;
+
+/**
+ * This ClassVisitor writes out the ProgramClass objects that it visits to the
+ * given DataOutput object.
+ *
+ * @author Eric Lafortune
+ */
+public class ProgramClassWriter
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             ConstantVisitor,
+             AttributeVisitor
+{
+    private RuntimeDataOutput dataOutput;
+
+    private final ConstantBodyWriter         constantBodyWriter         = new ConstantBodyWriter();
+    private final AttributeBodyWriter        attributeBodyWriter        = new AttributeBodyWriter();
+    private final StackMapFrameBodyWriter    stackMapFrameBodyWriter    = new StackMapFrameBodyWriter();
+    private final VerificationTypeBodyWriter verificationTypeBodyWriter = new VerificationTypeBodyWriter();
+    private final ElementValueBodyWriter     elementValueBodyWriter     = new ElementValueBodyWriter();
+
+
+    /**
+     * Creates a new ProgramClassWriter for reading from the given DataOutput.
+     */
+    public ProgramClassWriter(DataOutput dataOutput)
+    {
+        this.dataOutput = new RuntimeDataOutput(dataOutput);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Write the magic number.
+        dataOutput.writeInt(programClass.u4magic);
+
+        // Write the version numbers.
+        dataOutput.writeShort(ClassUtil.internalMinorClassVersion(programClass.u4version));
+        dataOutput.writeShort(ClassUtil.internalMajorClassVersion(programClass.u4version));
+
+        // Write the constant pool.
+        dataOutput.writeShort(programClass.u2constantPoolCount);
+
+        programClass.constantPoolEntriesAccept(this);
+
+        // Write the general class information.
+        dataOutput.writeShort(programClass.u2accessFlags);
+        dataOutput.writeShort(programClass.u2thisClass);
+        dataOutput.writeShort(programClass.u2superClass);
+
+        // Write the interfaces.
+        dataOutput.writeShort(programClass.u2interfacesCount);
+
+        for (int index = 0; index < programClass.u2interfacesCount; index++)
+        {
+            dataOutput.writeShort(programClass.u2interfaces[index]);
+        }
+
+        // Write the fields.
+        dataOutput.writeShort(programClass.u2fieldsCount);
+
+        programClass.fieldsAccept(this);
+
+        // Write the methods.
+        dataOutput.writeShort(programClass.u2methodsCount);
+
+        programClass.methodsAccept(this);
+
+        // Write the class attributes.
+        dataOutput.writeShort(programClass.u2attributesCount);
+
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Write the general field information.
+        dataOutput.writeShort(programField.u2accessFlags);
+        dataOutput.writeShort(programField.u2nameIndex);
+        dataOutput.writeShort(programField.u2descriptorIndex);
+
+        // Write the field attributes.
+        dataOutput.writeShort(programField.u2attributesCount);
+
+        programField.attributesAccept(programClass, this);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Write the general method information.
+        dataOutput.writeShort(programMethod.u2accessFlags);
+        dataOutput.writeShort(programMethod.u2nameIndex);
+        dataOutput.writeShort(programMethod.u2descriptorIndex);
+
+        // Write the method attributes.
+        dataOutput.writeShort(programMethod.u2attributesCount);
+
+        programMethod.attributesAccept(programClass, this);
+    }
+
+
+    public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember)
+    {
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant)
+    {
+        // Write the tag.
+        dataOutput.writeByte(constant.getTag());
+
+        // Write the actual body.
+        constant.accept(clazz, constantBodyWriter);
+    }
+
+
+    private class ConstantBodyWriter
+    extends       SimplifiedVisitor
+    implements    ConstantVisitor
+    {
+        // Implementations for ConstantVisitor.
+
+        public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+        {
+            dataOutput.writeInt(integerConstant.u4value);
+        }
+
+
+        public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+        {
+            dataOutput.writeLong(longConstant.u8value);
+        }
+
+
+        public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+        {
+            dataOutput.writeFloat(floatConstant.f4value);
+        }
+
+
+        public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+        {
+            dataOutput.writeDouble(doubleConstant.f8value);
+        }
+
+
+        public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+        {
+            dataOutput.writeShort(stringConstant.u2stringIndex);
+        }
+
+
+        public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+        {
+            byte[] bytes = utf8Constant.getBytes();
+
+            dataOutput.writeShort(bytes.length);
+            dataOutput.write(bytes);
+        }
+
+
+        public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+        {
+            dataOutput.writeShort(refConstant.u2classIndex);
+            dataOutput.writeShort(refConstant.u2nameAndTypeIndex);
+        }
+
+
+        public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+        {
+            dataOutput.writeShort(classConstant.u2nameIndex);
+        }
+
+
+        public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+        {
+            dataOutput.writeShort(nameAndTypeConstant.u2nameIndex);
+            dataOutput.writeShort(nameAndTypeConstant.u2descriptorIndex);
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute)
+    {
+        // Write the attribute name index.
+        dataOutput.writeShort(attribute.u2attributeNameIndex);
+
+        // We'll write the attribute body into an array first, so we can
+        // automatically figure out its length.
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+        // Temporarily replace the current data output.
+        RuntimeDataOutput oldDataOutput = dataOutput;
+        dataOutput = new RuntimeDataOutput(new DataOutputStream(byteArrayOutputStream));
+
+        // Write the attribute body into the array. Note that the
+        // accept method with two dummy null arguments never throws
+        // an UnsupportedOperationException.
+        attribute.accept(clazz, null, null, attributeBodyWriter);
+
+        // Restore the original data output.
+        dataOutput = oldDataOutput;
+
+        // Write the attribute length and body.
+        byte[] info = byteArrayOutputStream.toByteArray();
+
+        dataOutput.writeInt(info.length);
+        dataOutput.write(info);
+    }
+
+
+    private class AttributeBodyWriter
+    extends       SimplifiedVisitor
+    implements    AttributeVisitor,
+                  InnerClassesInfoVisitor,
+                  ExceptionInfoVisitor,
+                  StackMapFrameVisitor,
+                  VerificationTypeVisitor,
+                  LineNumberInfoVisitor,
+                  LocalVariableInfoVisitor,
+                  LocalVariableTypeInfoVisitor,
+                  AnnotationVisitor,
+                  ElementValueVisitor
+    {
+        // Implementations for AttributeVisitor.
+
+        public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+        {
+            // Write the unknown information.
+            dataOutput.write(unknownAttribute.info);
+        }
+
+
+        public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+        {
+            dataOutput.writeShort(sourceFileAttribute.u2sourceFileIndex);
+        }
+
+
+        public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+        {
+            dataOutput.writeShort(sourceDirAttribute.u2sourceDirIndex);
+        }
+
+
+        public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+        {
+            // Write the inner classes.
+            dataOutput.writeShort(innerClassesAttribute.u2classesCount);
+
+            innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+        }
+
+
+        public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+        {
+            dataOutput.writeShort(enclosingMethodAttribute.u2classIndex);
+            dataOutput.writeShort(enclosingMethodAttribute.u2nameAndTypeIndex);
+        }
+
+
+        public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+        {
+            // This attribute does not contain any additional information.
+        }
+
+
+        public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+        {
+            // This attribute does not contain any additional information.
+        }
+
+
+        public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+        {
+            dataOutput.writeShort(signatureAttribute.u2signatureIndex);
+        }
+
+
+        public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+        {
+            dataOutput.writeShort(constantValueAttribute.u2constantValueIndex);
+        }
+
+
+        public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+        {
+            // Write the exceptions.
+            dataOutput.writeShort(exceptionsAttribute.u2exceptionIndexTableLength);
+
+            for (int index = 0; index < exceptionsAttribute.u2exceptionIndexTableLength; index++)
+            {
+                dataOutput.writeShort(exceptionsAttribute.u2exceptionIndexTable[index]);
+            }
+        }
+
+
+        public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+        {
+            // Write the stack size and local variable frame size.
+            dataOutput.writeShort(codeAttribute.u2maxStack);
+            dataOutput.writeShort(codeAttribute.u2maxLocals);
+
+            // Write the byte code.
+            dataOutput.writeInt(codeAttribute.u4codeLength);
+
+            dataOutput.write(codeAttribute.code, 0, codeAttribute.u4codeLength);
+
+            // Write the exceptions.
+            dataOutput.writeShort(codeAttribute.u2exceptionTableLength);
+
+            codeAttribute.exceptionsAccept(clazz, method, this);
+
+            // Write the code attributes.
+            dataOutput.writeShort(codeAttribute.u2attributesCount);
+
+            codeAttribute.attributesAccept(clazz, method, ProgramClassWriter.this);
+        }
+
+
+        public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+        {
+            // Write the stack map frames (only full frames, without tag).
+            dataOutput.writeShort(stackMapAttribute.u2stackMapFramesCount);
+
+            stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, stackMapFrameBodyWriter);
+        }
+
+
+        public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+        {
+            // Write the stack map frames.
+            dataOutput.writeShort(stackMapTableAttribute.u2stackMapFramesCount);
+
+            stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+        }
+
+
+        public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+        {
+            // Write the line numbers.
+            dataOutput.writeShort(lineNumberTableAttribute.u2lineNumberTableLength);
+
+            lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
+        }
+
+
+        public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+        {
+            // Write the local variables.
+            dataOutput.writeShort(localVariableTableAttribute.u2localVariableTableLength);
+
+            localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+        }
+
+
+        public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+        {
+            // Write the local variable types.
+            dataOutput.writeShort(localVariableTypeTableAttribute.u2localVariableTypeTableLength);
+
+            localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+        }
+
+
+        public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+        {
+            // Write the annotations.
+            dataOutput.writeShort(annotationsAttribute.u2annotationsCount);
+
+            annotationsAttribute.annotationsAccept(clazz, this);
+        }
+
+
+        public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+        {
+            // Write the parameter annotations.
+            dataOutput.writeByte(parameterAnnotationsAttribute.u2parametersCount);
+
+            for (int parameterIndex = 0; parameterIndex < parameterAnnotationsAttribute.u2parametersCount; parameterIndex++)
+            {
+                // Write the parameter annotations of the given parameter.
+                int          u2annotationsCount = parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex];
+                Annotation[] annotations        = parameterAnnotationsAttribute.parameterAnnotations[parameterIndex];
+
+                dataOutput.writeShort(u2annotationsCount);
+
+                for (int index = 0; index < u2annotationsCount; index++)
+                {
+                    Annotation annotation = annotations[index];
+                    this.visitAnnotation(clazz, annotation);
+                }
+
+            }
+        }
+
+
+        public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+        {
+            // Write the default element value.
+            annotationDefaultAttribute.defaultValue.accept(clazz, null, this);
+        }
+
+
+        // Implementations for InnerClassesInfoVisitor.
+
+        public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+        {
+            dataOutput.writeShort(innerClassesInfo.u2innerClassIndex);
+            dataOutput.writeShort(innerClassesInfo.u2outerClassIndex);
+            dataOutput.writeShort(innerClassesInfo.u2innerNameIndex);
+            dataOutput.writeShort(innerClassesInfo.u2innerClassAccessFlags);
+        }
+
+
+        // Implementations for ExceptionInfoVisitor.
+
+        public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+        {
+            dataOutput.writeShort(exceptionInfo.u2startPC);
+            dataOutput.writeShort(exceptionInfo.u2endPC);
+            dataOutput.writeShort(exceptionInfo.u2handlerPC);
+            dataOutput.writeShort(exceptionInfo.u2catchType);
+        }
+
+
+        // Implementations for StackMapFrameVisitor.
+
+        public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame)
+        {
+            // Write the stack map frame tag.
+            dataOutput.writeByte(stackMapFrame.getTag());
+
+            // Write the actual body.
+            stackMapFrame.accept(clazz, method, codeAttribute, offset, stackMapFrameBodyWriter);
+        }
+
+
+        // Implementations for LineNumberInfoVisitor.
+
+        public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
+        {
+            dataOutput.writeShort(lineNumberInfo.u2startPC);
+            dataOutput.writeShort(lineNumberInfo.u2lineNumber);
+        }
+
+
+        // Implementations for LocalVariableInfoVisitor.
+
+        public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+        {
+            dataOutput.writeShort(localVariableInfo.u2startPC);
+            dataOutput.writeShort(localVariableInfo.u2length);
+            dataOutput.writeShort(localVariableInfo.u2nameIndex);
+            dataOutput.writeShort(localVariableInfo.u2descriptorIndex);
+            dataOutput.writeShort(localVariableInfo.u2index);
+        }
+
+
+        // Implementations for LocalVariableTypeInfoVisitor.
+
+        public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+        {
+            dataOutput.writeShort(localVariableTypeInfo.u2startPC);
+            dataOutput.writeShort(localVariableTypeInfo.u2length);
+            dataOutput.writeShort(localVariableTypeInfo.u2nameIndex);
+            dataOutput.writeShort(localVariableTypeInfo.u2signatureIndex);
+            dataOutput.writeShort(localVariableTypeInfo.u2index);
+        }
+
+
+        // Implementations for AnnotationVisitor.
+
+        public void visitAnnotation(Clazz clazz, Annotation annotation)
+        {
+            // Write the annotation type.
+            dataOutput.writeShort(annotation.u2typeIndex);
+
+            // Write the element value pairs.
+            dataOutput.writeShort(annotation.u2elementValuesCount);
+
+            annotation.elementValuesAccept(clazz, this);
+        }
+
+
+        // Implementations for ElementValueVisitor.
+
+        public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue)
+        {
+            // Write the element name index, if applicable.
+            int u2elementNameIndex = elementValue.u2elementNameIndex;
+            if (u2elementNameIndex != 0)
+            {
+                dataOutput.writeShort(u2elementNameIndex);
+            }
+
+            // Write the tag.
+            dataOutput.writeByte(elementValue.getTag());
+
+            // Write the actual body.
+            elementValue.accept(clazz, annotation, elementValueBodyWriter);
+        }
+    }
+
+
+    private class StackMapFrameBodyWriter
+    extends       SimplifiedVisitor
+    implements    StackMapFrameVisitor,
+                  VerificationTypeVisitor
+    {
+        public void visitSameZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameZeroFrame sameZeroFrame)
+        {
+            if (sameZeroFrame.getTag() == StackMapFrame.SAME_ZERO_FRAME_EXTENDED)
+            {
+                dataOutput.writeShort(sameZeroFrame.u2offsetDelta);
+            }
+        }
+
+
+        public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+        {
+            if (sameOneFrame.getTag() == StackMapFrame.SAME_ONE_FRAME_EXTENDED)
+            {
+                dataOutput.writeShort(sameOneFrame.u2offsetDelta);
+            }
+
+            // Write the verification type of the stack entry.
+            sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
+        }
+
+
+        public void visitLessZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LessZeroFrame lessZeroFrame)
+        {
+            dataOutput.writeShort(lessZeroFrame.u2offsetDelta);
+        }
+
+
+        public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+        {
+            dataOutput.writeShort(moreZeroFrame.u2offsetDelta);
+
+            // Write the verification types of the additional local variables.
+            moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
+        }
+
+
+        public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+        {
+            dataOutput.writeShort(fullFrame.u2offsetDelta);
+
+            // Write the verification types of the local variables.
+            dataOutput.writeShort(fullFrame.variablesCount);
+            fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
+
+            // Write the verification types of the stack entries.
+            dataOutput.writeShort(fullFrame.stackCount);
+            fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
+        }
+
+
+        // Implementations for VerificationTypeVisitor.
+
+        public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType)
+        {
+            // Write the verification type tag.
+            dataOutput.writeByte(verificationType.getTag());
+
+            // Write the actual body.
+            verificationType.accept(clazz, method, codeAttribute, offset, verificationTypeBodyWriter);
+        }
+    }
+
+
+    private class VerificationTypeBodyWriter
+    extends       SimplifiedVisitor
+    implements    VerificationTypeVisitor
+    {
+        // Implementations for VerificationTypeVisitor.
+
+        public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType)
+        {
+            // Most verification types don't contain any additional information.
+        }
+
+
+        public void visitObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType objectType)
+        {
+            dataOutput.writeShort(objectType.u2classIndex);
+        }
+
+
+        public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)
+        {
+            dataOutput.writeShort(uninitializedType.u2newInstructionOffset);
+        }
+    }
+
+
+    private class ElementValueBodyWriter
+    extends       SimplifiedVisitor
+    implements    ElementValueVisitor
+    {
+        // Implementations for ElementValueVisitor.
+
+        public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+        {
+            dataOutput.writeShort(constantElementValue.u2constantValueIndex);
+        }
+
+
+        public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+        {
+            dataOutput.writeShort(enumConstantElementValue.u2typeNameIndex);
+            dataOutput.writeShort(enumConstantElementValue.u2constantNameIndex);
+        }
+
+
+        public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+        {
+            dataOutput.writeShort(classElementValue.u2classInfoIndex);
+        }
+
+
+        public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+        {
+            // Write the annotation.
+            attributeBodyWriter.visitAnnotation(clazz, annotationElementValue.annotationValue);
+        }
+
+
+        public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+        {
+            // Write the element values.
+            dataOutput.writeShort(arrayElementValue.u2elementValuesCount);
+
+            arrayElementValue.elementValuesAccept(clazz, annotation, attributeBodyWriter);
+        }
+    }
+}
diff --git a/src/proguard/classfile/io/RuntimeDataInput.java b/src/proguard/classfile/io/RuntimeDataInput.java
new file mode 100644
index 0000000..104b7c9
--- /dev/null
+++ b/src/proguard/classfile/io/RuntimeDataInput.java
@@ -0,0 +1,223 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.io;
+
+import java.io.*;
+
+/**
+ * This class delegates its method calls to the corresponding DataInput methods,
+ * converting its IOExceptions to RuntimeExceptions.
+ *
+ * @author Eric Lafortune
+ */
+final class RuntimeDataInput
+{
+    private final DataInput dataInput;
+
+
+    public RuntimeDataInput(DataInput dataInput)
+    {
+        this.dataInput = dataInput;
+    }
+
+
+    // Methods delegating to DataInput.
+
+    public boolean readBoolean()
+    {
+        try
+        {
+            return dataInput.readBoolean();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public byte readByte()
+    {
+        try
+        {
+            return dataInput.readByte();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public char readChar()
+    {
+        try
+        {
+            return dataInput.readChar();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public double readDouble()
+    {
+        try
+        {
+            return dataInput.readDouble();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public float readFloat()
+    {
+        try
+        {
+            return dataInput.readFloat();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public void readFully(byte[] b)
+    {
+        try
+        {
+            dataInput.readFully(b);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public void readFully(byte[] b, int off, int len)
+    {
+        try
+        {
+            dataInput.readFully(b, off, len);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public int readInt()
+    {
+        try
+        {
+            return dataInput.readInt();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public String readLine()
+    {
+        try
+        {
+            return dataInput.readLine();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public long readLong()
+    {
+        try
+        {
+            return dataInput.readLong();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public short readShort()
+    {
+        try
+        {
+            return dataInput.readShort();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public int readUnsignedByte()
+    {
+        try
+        {
+            return dataInput.readUnsignedByte();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public int readUnsignedShort()
+    {
+        try
+        {
+            return dataInput.readUnsignedShort();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public String readUTF()
+    {
+        try
+        {
+            return dataInput.readUTF();
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+    public int skipBytes(int n)
+    {
+        try
+        {
+            return dataInput.skipBytes(n);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/proguard/classfile/io/RuntimeDataOutput.java b/src/proguard/classfile/io/RuntimeDataOutput.java
new file mode 100644
index 0000000..ffde3af
--- /dev/null
+++ b/src/proguard/classfile/io/RuntimeDataOutput.java
@@ -0,0 +1,224 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.io;
+
+import java.io.*;
+
+/**
+ * This class delegates its method calls to the corresponding DataOutput methods,
+ * converting its IOExceptions to RuntimeExceptions.
+ *
+ * @author Eric Lafortune
+ */
+final class RuntimeDataOutput
+{
+    private final DataOutput dataOutput;
+
+
+    public RuntimeDataOutput(DataOutput dataOutput)
+    {
+        this.dataOutput = dataOutput;
+    }
+
+
+    // Methods delegating to DataOutput.
+
+    public void write(byte[] b)
+    {
+        try
+        {
+            dataOutput.write(b);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void write(byte[] b, int off, int len)
+    {
+        try
+        {
+            dataOutput.write(b, off, len);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void write(int b)
+    {
+        try
+        {
+            dataOutput.write(b);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeBoolean(boolean v)
+    {
+        try
+        {
+            dataOutput.writeBoolean(v);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeByte(int v)
+    {
+        try
+        {
+            dataOutput.writeByte(v);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeBytes(String s)
+    {
+        try
+        {
+            dataOutput.writeBytes(s);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeChar(int v)
+    {
+        try
+        {
+            dataOutput.writeChar(v);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeChars(String s)
+    {
+        try
+        {
+            dataOutput.writeChars(s);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeDouble(double v)
+    {
+        try
+        {
+            dataOutput.writeDouble(v);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeFloat(float v)
+    {
+        try
+        {
+            dataOutput.writeFloat(v);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeInt(int v)
+    {
+        try
+        {
+            dataOutput.writeInt(v);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeLong(long v)
+    {
+        try
+        {
+            dataOutput.writeLong(v);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeShort(int v)
+    {
+        try
+        {
+            dataOutput.writeShort(v);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
+
+    public void writeUTF(String str)
+    {
+        try
+        {
+            dataOutput.writeUTF(str);
+        }
+        catch (IOException ex)
+        {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+}
diff --git a/src/proguard/classfile/io/package.html b/src/proguard/classfile/io/package.html
new file mode 100644
index 0000000..780b917
--- /dev/null
+++ b/src/proguard/classfile/io/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains classes for reading and writing class files.
+</body>
diff --git a/src/proguard/classfile/package.html b/src/proguard/classfile/package.html
new file mode 100644
index 0000000..fad087c
--- /dev/null
+++ b/src/proguard/classfile/package.html
@@ -0,0 +1,15 @@
+<body>
+This package contains classes to represent the various elements of class files.
+<p>
+A class file is represented by the <code>{@link proguard.classfile.ClassFile
+ClassFile}</code> interface. This interface currently has two alternative
+representations:
+<ul>
+<li><code>{@link ProgramClassFile ProgramClassFile}</code>:
+    a complete representation that can be read, modified, and written back.
+<li><code>{@link LibraryClassFile LibraryClassFile}</code>:
+    an incomplete representation that can be only be read. It is however
+    more compact than <code>ProgramClassFile</code>, and sufficient for
+    analyzing class files from library jars.
+</ul>
+</body>
diff --git a/src/proguard/classfile/util/AccessUtil.java b/src/proguard/classfile/util/AccessUtil.java
new file mode 100644
index 0000000..3ad6961
--- /dev/null
+++ b/src/proguard/classfile/util/AccessUtil.java
@@ -0,0 +1,105 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.ClassConstants;
+
+
+/**
+ * Utility methods for working with access flags. For convenience, this class
+ * defines access levels, in ascending order: <code>PRIVATE</code>,
+ * <code>PACKAGE_VISIBLE</code>, <code>PROTECTED</code>, and <code>PUBLIC</code>.
+ *
+ * @author Eric Lafortune
+ */
+public class AccessUtil
+{
+    public static final int PRIVATE         = 0;
+    public static final int PACKAGE_VISIBLE = 1;
+    public static final int PROTECTED       = 2;
+    public static final int PUBLIC          = 3;
+
+
+    // The mask of access flags.
+    private static final int ACCESS_MASK =
+        ClassConstants.INTERNAL_ACC_PUBLIC  |
+        ClassConstants.INTERNAL_ACC_PRIVATE |
+        ClassConstants.INTERNAL_ACC_PROTECTED;
+
+
+    /**
+     * Returns the corresponding access level of the given access flags.
+     * @param accessFlags the internal access flags.
+     * @return the corresponding access level: <code>PRIVATE</code>,
+     *         <code>PACKAGE_VISIBLE</code>, <code>PROTECTED</code>, or
+     *         <code>PUBLIC</code>.
+     */
+    public static int accessLevel(int accessFlags)
+    {
+        switch (accessFlags & ACCESS_MASK)
+        {
+            case ClassConstants.INTERNAL_ACC_PRIVATE:   return PRIVATE;
+            default:                                    return PACKAGE_VISIBLE;
+            case ClassConstants.INTERNAL_ACC_PROTECTED: return PROTECTED;
+            case ClassConstants.INTERNAL_ACC_PUBLIC:    return PUBLIC;
+        }
+    }
+
+
+    /**
+     * Returns the corresponding access flags of the given access level.
+     * @param accessLevel the access level: <code>PRIVATE</code>,
+     *                    <code>PACKAGE_VISIBLE</code>, <code>PROTECTED</code>,
+     *                    or <code>PUBLIC</code>.
+     * @return the corresponding internal access flags,  the internal access
+     *         flags as a logical bit mask of <code>INTERNAL_ACC_PRIVATE</code>,
+     *         <code>INTERNAL_ACC_PROTECTED</code>, and
+     *         <code>INTERNAL_ACC_PUBLIC</code>.
+     */
+    public static int accessFlags(int accessLevel)
+    {
+        switch (accessLevel)
+        {
+            case PRIVATE:   return ClassConstants.INTERNAL_ACC_PRIVATE;
+            default:        return 0;
+            case PROTECTED: return ClassConstants.INTERNAL_ACC_PROTECTED;
+            case PUBLIC:    return ClassConstants.INTERNAL_ACC_PUBLIC;
+        }
+    }
+
+
+    /**
+     * Replaces the access part of the given access flags.
+     * @param accessFlags the internal access flags.
+     * @param accessFlags the new internal access flags.
+     */
+    public static int replaceAccessFlags(int accessFlags, int newAccessFlags)
+    {
+        // A private class member should not be explicitly final.
+        if (newAccessFlags == ClassConstants.INTERNAL_ACC_PRIVATE)
+        {
+            accessFlags &= ~ClassConstants.INTERNAL_ACC_FINAL;
+        }
+
+        return (accessFlags    & ~ACCESS_MASK) |
+               (newAccessFlags &  ACCESS_MASK);
+    }
+}
diff --git a/src/proguard/classfile/util/ClassReferenceInitializer.java b/src/proguard/classfile/util/ClassReferenceInitializer.java
new file mode 100644
index 0000000..b1f2c27
--- /dev/null
+++ b/src/proguard/classfile/util/ClassReferenceInitializer.java
@@ -0,0 +1,545 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.visitor.*;
+
+
+/**
+ * This ClassVisitor initializes the references of all classes that
+ * it visits.
+ * <p>
+ * All class constant pool entries get direct references to the corresponding
+ * classes. These references make it more convenient to travel up and across
+ * the class hierarchy.
+ * <p>
+ * All field and method reference constant pool entries get direct references
+ * to the corresponding classes, fields, and methods.
+ * <p>
+ * All name and type constant pool entries get a list of direct references to
+ * the classes listed in the type.
+ * <p>
+ * This visitor optionally prints warnings if some items can't be found.
+ * <p>
+ * The class hierarchy must be initialized before using this visitor.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassReferenceInitializer
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             ConstantVisitor,
+             AttributeVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    private final ClassPool      programClassPool;
+    private final ClassPool      libraryClassPool;
+    private final WarningPrinter missingClassWarningPrinter;
+    private final WarningPrinter missingMemberWarningPrinter;
+    private final WarningPrinter dependencyWarningPrinter;
+
+    private final MemberFinder memberFinder = new MemberFinder();
+
+
+    /**
+     * Creates a new ClassReferenceInitializer that initializes the references
+     * of all visited class files, optionally printing warnings if some classes
+     * or class members can't be found or if they are in the program class pool.
+     */
+    public ClassReferenceInitializer(ClassPool      programClassPool,
+                                     ClassPool      libraryClassPool,
+                                     WarningPrinter missingClassWarningPrinter,
+                                     WarningPrinter missingMemberWarningPrinter,
+                                     WarningPrinter dependencyWarningPrinter)
+    {
+        this.programClassPool            = programClassPool;
+        this.libraryClassPool            = libraryClassPool;
+        this.missingClassWarningPrinter  = missingClassWarningPrinter;
+        this.missingMemberWarningPrinter = missingMemberWarningPrinter;
+        this.dependencyWarningPrinter    = dependencyWarningPrinter;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Initialize the constant pool entries.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Initialize all fields and methods.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+
+        // Initialize the attributes.
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Initialize all fields and methods.
+        libraryClass.fieldsAccept(this);
+        libraryClass.methodsAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        programField.referencedClass =
+            findReferencedClass(programClass.getName(),
+                                programField.getDescriptor(programClass));
+
+        // Initialize the attributes.
+        programField.attributesAccept(programClass, this);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        programMethod.referencedClasses =
+            findReferencedClasses(programClass.getName(),
+                                  programMethod.getDescriptor(programClass));
+
+        // Initialize the attributes.
+        programMethod.attributesAccept(programClass, this);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        libraryField.referencedClass =
+            findReferencedClass(libraryClass.getName(),
+                                libraryField.getDescriptor(libraryClass));
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        libraryMethod.referencedClasses =
+            findReferencedClasses(libraryClass.getName(),
+                                  libraryMethod.getDescriptor(libraryClass));
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Fill out the String class.
+        stringConstant.javaLangStringClass =
+            findClass(clazz.getName(), ClassConstants.INTERNAL_NAME_JAVA_LANG_STRING);
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        String className = refConstant.getClassName(clazz);
+
+        // See if we can find the referenced class.
+        // Unresolved references are assumed to refer to library classes
+        // that will not change anyway.
+        Clazz referencedClass = findClass(clazz.getName(), className);
+
+        if (referencedClass != null &&
+            !ClassUtil.isInternalArrayType(className))
+        {
+            String name = refConstant.getName(clazz);
+            String type = refConstant.getType(clazz);
+
+            boolean isFieldRef = refConstant.getTag() == ClassConstants.CONSTANT_Fieldref;
+
+            // See if we can find the referenced class member somewhere in the
+            // hierarchy.
+            refConstant.referencedMember = memberFinder.findMember(clazz,
+                                                                   referencedClass,
+                                                                   name,
+                                                                   type,
+                                                                   isFieldRef);
+            refConstant.referencedClass  = memberFinder.correspondingClass();
+
+            if (refConstant.referencedMember == null)
+            {
+                // We've haven't found the class member anywhere in the hierarchy.
+                missingMemberWarningPrinter.print(clazz.getName(),
+                                                  className,
+                                                  "Warning: " +
+                                                  ClassUtil.externalClassName(clazz.getName()) +
+                                                  ": can't find referenced " +
+                                                  (isFieldRef ?
+                                                      "field '"  + ClassUtil.externalFullFieldDescription(0, name, type) :
+                                                      "method '" + ClassUtil.externalFullMethodDescription(className, 0, name, type)) +
+                                                  "' in class " +
+                                                  ClassUtil.externalClassName(className));
+            }
+        }
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        String className = clazz.getName();
+
+        // Fill out the referenced class.
+        classConstant.referencedClass =
+            findClass(className, classConstant.getName(clazz));
+
+        // Fill out the Class class.
+        classConstant.javaLangClassClass =
+            findClass(className, ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        String className          = clazz.getName();
+        String enclosingClassName = enclosingMethodAttribute.getClassName(clazz);
+
+        // See if we can find the referenced class.
+        Clazz referencedClass = findClass(className, enclosingClassName);
+
+        if (referencedClass == null)
+        {
+            // We couldn't find the enclosing class.
+            missingClassWarningPrinter.print(className,
+                                             enclosingClassName,
+                                             "Warning: " +
+                                             ClassUtil.externalClassName(className) +
+                                             ": can't find enclosing class " +
+                                             ClassUtil.externalClassName(enclosingClassName));
+            return;
+        }
+
+        // Make sure there is actually an enclosed method.
+        if (enclosingMethodAttribute.u2nameAndTypeIndex == 0)
+        {
+            return;
+        }
+
+        String name = enclosingMethodAttribute.getName(clazz);
+        String type = enclosingMethodAttribute.getType(clazz);
+
+        // See if we can find the method in the referenced class.
+        Method referencedMethod = referencedClass.findMethod(name, type);
+
+        if (referencedMethod == null)
+        {
+            // We couldn't find the enclosing method.
+            missingMemberWarningPrinter.print(className,
+                                              enclosingClassName,
+                                              "Warning: " +
+                                              ClassUtil.externalClassName(className) +
+                                              ": can't find enclosing method '" +
+                                              ClassUtil.externalFullMethodDescription(enclosingClassName, 0, name, type) +
+                                              "' in class " +
+                                              ClassUtil.externalClassName(enclosingClassName));
+            return;
+        }
+
+        // Save the references.
+        enclosingMethodAttribute.referencedClass  = referencedClass;
+        enclosingMethodAttribute.referencedMethod = referencedMethod;
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Initialize the nested attributes.
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Initialize the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Initialize the local variable types.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        signatureAttribute.referencedClasses =
+            findReferencedClasses(clazz.getName(),
+                                  clazz.getString(signatureAttribute.u2signatureIndex));
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Initialize the annotations.
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Initialize the annotations.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Initialize the annotation.
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        localVariableInfo.referencedClass =
+            findReferencedClass(clazz.getName(),
+                                clazz.getString(localVariableInfo.u2descriptorIndex));
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        localVariableTypeInfo.referencedClasses =
+            findReferencedClasses(clazz.getName(),
+                                  clazz.getString(localVariableTypeInfo.u2signatureIndex));
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        annotation.referencedClasses =
+            findReferencedClasses(clazz.getName(),
+                                  clazz.getString(annotation.u2typeIndex));
+
+        // Initialize the element values.
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        initializeElementValue(clazz, annotation, constantElementValue);
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        initializeElementValue(clazz, annotation, enumConstantElementValue);
+
+        enumConstantElementValue.referencedClasses =
+            findReferencedClasses(clazz.getName(),
+                                  clazz.getString(enumConstantElementValue.u2typeNameIndex));
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        initializeElementValue(clazz, annotation, classElementValue);
+
+        classElementValue.referencedClasses =
+            findReferencedClasses(clazz.getName(),
+                                  clazz.getString(classElementValue.u2classInfoIndex));
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        initializeElementValue(clazz, annotation, annotationElementValue);
+
+        // Initialize the annotation.
+        annotationElementValue.annotationAccept(clazz, this);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        initializeElementValue(clazz, annotation, arrayElementValue);
+
+        // Initialize the element values.
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+    }
+
+
+    /**
+     * Initializes the referenced method of an element value, if any.
+     */
+    private void initializeElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue)
+    {
+        // See if we have a referenced class.
+        if (annotation                      != null &&
+            annotation.referencedClasses    != null &&
+            elementValue.u2elementNameIndex != 0)
+        {
+            // See if we can find the method in the referenced class
+            // (ignoring the descriptor).
+            String name = clazz.getString(elementValue.u2elementNameIndex);
+
+            Clazz referencedClass = annotation.referencedClasses[0];
+            elementValue.referencedClass  = referencedClass;
+            elementValue.referencedMethod = referencedClass.findMethod(name, null);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the single class referenced by the given descriptor, or
+     * <code>null</code> if there isn't any useful reference.
+     */
+    private Clazz findReferencedClass(String referencingClassName,
+                                      String descriptor)
+    {
+        DescriptorClassEnumeration enumeration =
+            new DescriptorClassEnumeration(descriptor);
+
+        enumeration.nextFluff();
+
+        if (enumeration.hasMoreClassNames())
+        {
+            return findClass(referencingClassName, enumeration.nextClassName());
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Returns an array of classes referenced by the given descriptor, or
+     * <code>null</code> if there aren't any useful references.
+     */
+    private Clazz[] findReferencedClasses(String referencingClassName,
+                                          String descriptor)
+    {
+        DescriptorClassEnumeration enumeration =
+            new DescriptorClassEnumeration(descriptor);
+
+        int classCount = enumeration.classCount();
+        if (classCount > 0)
+        {
+            Clazz[] referencedClasses = new Clazz[classCount];
+
+            boolean foundReferencedClasses = false;
+
+            for (int index = 0; index < classCount; index++)
+            {
+                String fluff = enumeration.nextFluff();
+                String name  = enumeration.nextClassName();
+
+                Clazz referencedClass = findClass(referencingClassName, name);
+
+                if (referencedClass != null)
+                {
+                    referencedClasses[index] = referencedClass;
+                    foundReferencedClasses = true;
+                }
+            }
+
+            if (foundReferencedClasses)
+            {
+                return referencedClasses;
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Returns the class with the given name, either for the program class pool
+     * or from the library class pool, or <code>null</code> if it can't be found.
+     */
+    private Clazz findClass(String referencingClassName, String name)
+    {
+        // Ignore any primitive array types.
+        if (ClassUtil.isInternalArrayType(name) &&
+            !ClassUtil.isInternalClassType(name))
+        {
+            return null;
+        }
+
+        // First look for the class in the program class pool.
+        Clazz clazz = programClassPool.getClass(name);
+
+        // Otherwise look for the class in the library class pool.
+        if (clazz == null)
+        {
+            clazz = libraryClassPool.getClass(name);
+
+            if (clazz == null &&
+                missingClassWarningPrinter != null)
+            {
+                // We didn't find the superclass or interface. Print a warning.
+                missingClassWarningPrinter.print(referencingClassName,
+                                                 name,
+                                                 "Warning: " +
+                                                 ClassUtil.externalClassName(referencingClassName) +
+                                                 ": can't find referenced class " +
+                                                 ClassUtil.externalClassName(name));
+            }
+        }
+        else if (dependencyWarningPrinter != null)
+        {
+            // The superclass or interface was found in the program class pool.
+            // Print a warning.
+            dependencyWarningPrinter.print(referencingClassName,
+                                           name,
+                                           "Warning: library class " +
+                                           ClassUtil.externalClassName(referencingClassName) +
+                                           " depends on program class " +
+                                           ClassUtil.externalClassName(name));
+        }
+
+        return clazz;
+    }
+}
diff --git a/src/proguard/classfile/util/ClassSubHierarchyInitializer.java b/src/proguard/classfile/util/ClassSubHierarchyInitializer.java
new file mode 100644
index 0000000..30fd526
--- /dev/null
+++ b/src/proguard/classfile/util/ClassSubHierarchyInitializer.java
@@ -0,0 +1,77 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor adds all classes that it visits to the list of subclasses
+ * of their superclass. These subclass lists make it more convenient to travel
+ *
+ * @author Eric Lafortune
+ */
+public class ClassSubHierarchyInitializer
+implements   ClassVisitor
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Add this class to the subclasses of its superclass.
+        addSubclass(programClass, programClass.getSuperClass());
+
+        // Add this class to the subclasses of its interfaces.
+        for (int index = 0; index < programClass.u2interfacesCount; index++)
+        {
+            addSubclass(programClass, programClass.getInterface(index));
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Add this class to the subclasses of its superclass,
+        addSubclass(libraryClass, libraryClass.superClass);
+
+        // Add this class to the subclasses of its interfaces.
+        Clazz[] interfaceClasses = libraryClass.interfaceClasses;
+        if (interfaceClasses != null)
+        {
+            for (int index = 0; index < interfaceClasses.length; index++)
+            {
+                // Add this class to the subclasses of the interface class.
+                addSubclass(libraryClass, interfaceClasses[index]);
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    private void addSubclass(Clazz subclass, Clazz clazz)
+    {
+        if (clazz != null)
+        {
+            clazz.addSubClass(subclass);
+        }
+    }
+}
diff --git a/src/proguard/classfile/util/ClassSuperHierarchyInitializer.java b/src/proguard/classfile/util/ClassSuperHierarchyInitializer.java
new file mode 100644
index 0000000..af2a209
--- /dev/null
+++ b/src/proguard/classfile/util/ClassSuperHierarchyInitializer.java
@@ -0,0 +1,168 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor initializes the superclass hierarchy of all classes that
+ * it visits.
+ * <p>
+ * Visited library classes get direct references to their superclasses and
+ * interfaces, replacing the superclass names and interface names. The direct
+ * references are equivalent to the names, but they are more efficient to work
+ * with.
+ * <p>
+ * This visitor optionally prints warnings if some superclasses can't be found
+ * or if they are in the program class pool.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassSuperHierarchyInitializer
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor
+{
+    private final ClassPool      programClassPool;
+    private final ClassPool      libraryClassPool;
+    private final WarningPrinter missingWarningPrinter;
+    private final WarningPrinter dependencyWarningPrinter;
+
+
+    /**
+     * Creates a new ClassSuperHierarchyInitializer that initializes the super
+     * hierarchy of all visited class files, optionally printing warnings if
+     * some classes can't be found or if they are in the program class pool.
+     */
+    public ClassSuperHierarchyInitializer(ClassPool      programClassPool,
+                                          ClassPool      libraryClassPool,
+                                          WarningPrinter missingWarningPrinter,
+                                          WarningPrinter dependencyWarningPrinter)
+    {
+        this.programClassPool         = programClassPool;
+        this.libraryClassPool         = libraryClassPool;
+        this.missingWarningPrinter    = missingWarningPrinter;
+        this.dependencyWarningPrinter = dependencyWarningPrinter;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Link to the super class.
+        programClass.superClassConstantAccept(this);
+
+        // Link to the interfaces.
+        programClass.interfaceConstantsAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        String className = libraryClass.getName();
+
+        // Link to the super class.
+        String superClassName = libraryClass.superClassName;
+        if (superClassName != null)
+        {
+            // Keep a reference to the superclass.
+            libraryClass.superClass = findClass(className, superClassName);
+        }
+
+        // Link to the interfaces.
+        if (libraryClass.interfaceNames != null)
+        {
+            String[] interfaceNames   = libraryClass.interfaceNames;
+            Clazz[]  interfaceClasses = new Clazz[interfaceNames.length];
+
+            for (int index = 0; index < interfaceNames.length; index++)
+            {
+                // Keep a reference to the interface class.
+                interfaceClasses[index] =
+                    findClass(className, interfaceNames[index]);
+            }
+
+            libraryClass.interfaceClasses = interfaceClasses;
+        }
+
+        // Discard the name Strings. From now on, we'll use the object
+        // references.
+        libraryClass.superClassName = null;
+        libraryClass.interfaceNames = null;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classConstant.referencedClass =
+            findClass(clazz.getName(), classConstant.getName(clazz));
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the class with the given name, either for the program class pool
+     * or from the library class pool, or <code>null</code> if it can't be found.
+     */
+    private Clazz findClass(String referencingClassName, String name)
+    {
+        // First look for the class in the program class pool.
+        Clazz clazz = programClassPool.getClass(name);
+
+        // Otherwise look for the class in the library class pool.
+        if (clazz == null)
+        {
+            clazz = libraryClassPool.getClass(name);
+
+            if (clazz == null &&
+                missingWarningPrinter != null)
+            {
+                // We didn't find the superclass or interface. Print a warning.
+                missingWarningPrinter.print(referencingClassName,
+                                            name,
+                                            "Warning: " +
+                                            ClassUtil.externalClassName(referencingClassName) +
+                                            ": can't find superclass or interface " +
+                                            ClassUtil.externalClassName(name));
+            }
+        }
+        else if (dependencyWarningPrinter != null)
+        {
+            // The superclass or interface was found in the program class pool.
+            // Print a warning.
+            dependencyWarningPrinter.print(referencingClassName,
+                                           name,
+                                           "Warning: library class " +
+                                           ClassUtil.externalClassName(referencingClassName) +
+                                           " extends or implements program class " +
+                                           ClassUtil.externalClassName(name));
+        }
+
+        return clazz;
+    }
+}
diff --git a/src/proguard/classfile/util/ClassUtil.java b/src/proguard/classfile/util/ClassUtil.java
new file mode 100644
index 0000000..5f25f01
--- /dev/null
+++ b/src/proguard/classfile/util/ClassUtil.java
@@ -0,0 +1,1154 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.ClassConstants;
+
+import java.util.List;
+
+/**
+ * Utility methods for converting between internal and external representations
+ * of names and descriptions.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassUtil
+{
+    private static final String EMPTY_STRING = "";
+
+
+    /**
+     * Checks whether the given class magic number is correct.
+     * @param magicNumber the magic number.
+     * @throws UnsupportedOperationException when the magic number is incorrect.
+     */
+    public static void checkMagicNumber(int magicNumber) throws UnsupportedOperationException
+    {
+        if (magicNumber != ClassConstants.MAGIC)
+        {
+            throw new UnsupportedOperationException("Invalid magic number ["+Integer.toHexString(magicNumber)+"] in class");
+        }
+    }
+
+
+    /**
+     * Returns the combined class version number.
+     * @param majorVersion the major part of the class version number.
+     * @param minorVersion the minor part of the class version number.
+     * @return the combined class version number.
+     */
+    public static int internalClassVersion(int majorVersion, int minorVersion)
+    {
+        return (majorVersion << 16) | minorVersion;
+    }
+
+
+    /**
+     * Returns the major part of the given class version number.
+     * @param classVersion the combined class version number.
+     * @return the major part of the class version number.
+     */
+    public static int internalMajorClassVersion(int classVersion)
+    {
+        return classVersion >>> 16;
+    }
+
+
+    /**
+     * Returns the internal class version number.
+     * @param classVersion the external class version number.
+     * @return the internal class version number.
+     */
+    public static int internalMinorClassVersion(int classVersion)
+    {
+        return classVersion & 0xffff;
+    }
+
+
+    /**
+     * Returns the internal class version number.
+     * @param classVersion the external class version number.
+     * @return the internal class version number.
+     */
+    public static int internalClassVersion(String classVersion)
+    {
+        return
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_0) ||
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_1) ? ClassConstants.INTERNAL_CLASS_VERSION_1_0 :
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_2) ? ClassConstants.INTERNAL_CLASS_VERSION_1_2 :
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_3) ? ClassConstants.INTERNAL_CLASS_VERSION_1_3 :
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_4) ? ClassConstants.INTERNAL_CLASS_VERSION_1_4 :
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_5_ALIAS) ||
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_5) ? ClassConstants.INTERNAL_CLASS_VERSION_1_5 :
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_6_ALIAS) ||
+            classVersion.equals(ClassConstants.EXTERNAL_CLASS_VERSION_1_6) ? ClassConstants.INTERNAL_CLASS_VERSION_1_6 :
+                                                                             0;
+    }
+
+
+    /**
+     * Returns the minor part of the given class version number.
+     * @param classVersion the combined class version number.
+     * @return the minor part of the class version number.
+     */
+    public static String externalClassVersion(int classVersion)
+    {
+        switch (classVersion)
+        {
+            case ClassConstants.INTERNAL_CLASS_VERSION_1_0: return ClassConstants.EXTERNAL_CLASS_VERSION_1_0;
+            case ClassConstants.INTERNAL_CLASS_VERSION_1_2: return ClassConstants.EXTERNAL_CLASS_VERSION_1_2;
+            case ClassConstants.INTERNAL_CLASS_VERSION_1_3: return ClassConstants.EXTERNAL_CLASS_VERSION_1_3;
+            case ClassConstants.INTERNAL_CLASS_VERSION_1_4: return ClassConstants.EXTERNAL_CLASS_VERSION_1_4;
+            case ClassConstants.INTERNAL_CLASS_VERSION_1_5: return ClassConstants.EXTERNAL_CLASS_VERSION_1_5;
+            case ClassConstants.INTERNAL_CLASS_VERSION_1_6: return ClassConstants.EXTERNAL_CLASS_VERSION_1_6;
+            default:                                        return null;
+        }
+    }
+
+
+    /**
+     * Checks whether the given class version number is supported.
+     * @param classVersion the combined class version number.
+     * @throws UnsupportedOperationException when the version is not supported.
+     */
+    public static void checkVersionNumbers(int classVersion) throws UnsupportedOperationException
+    {
+        if (classVersion < ClassConstants.INTERNAL_CLASS_VERSION_1_0 ||
+            classVersion > ClassConstants.INTERNAL_CLASS_VERSION_1_6)
+        {
+            throw new UnsupportedOperationException("Unsupported version number ["+
+                                                    internalMajorClassVersion(classVersion)+"."+
+                                                    internalMinorClassVersion(classVersion)+"] for class format");
+        }
+    }
+
+
+    /**
+     * Converts an external class name into an internal class name.
+     * @param externalClassName the external class name,
+     *                          e.g. "<code>java.lang.Object</code>"
+     * @return the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     */
+    public static String internalClassName(String externalClassName)
+    {
+        return externalClassName.replace(ClassConstants.EXTERNAL_PACKAGE_SEPARATOR,
+                                         ClassConstants.INTERNAL_PACKAGE_SEPARATOR);
+    }
+
+
+    /**
+     * Converts an internal class description into an external class description.
+     * @param accessFlags       the access flags of the class.
+     * @param internalClassName the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     * @return the external class description,
+     *                          e.g. "<code>public java.lang.Object</code>".
+     */
+    public static String externalFullClassDescription(int    accessFlags,
+                                                      String internalClassName)
+    {
+        return externalClassAccessFlags(accessFlags) +
+               externalClassName(internalClassName);
+    }
+
+
+    /**
+     * Converts an internal class name into an external class name.
+     * @param internalClassName the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     * @return the external class name,
+     *                          e.g. "<code>java.lang.Object</code>".
+     */
+    public static String externalClassName(String internalClassName)
+    {
+        return //internalClassName.startsWith(ClassConstants.INTERNAL_PACKAGE_JAVA_LANG) &&
+               //internalClassName.indexOf(ClassConstants.INTERNAL_PACKAGE_SEPARATOR, ClassConstants.INTERNAL_PACKAGE_JAVA_LANG.length() + 1) < 0 ?
+               //internalClassName.substring(ClassConstants.INTERNAL_PACKAGE_JAVA_LANG.length()) :
+               internalClassName.replace(ClassConstants.INTERNAL_PACKAGE_SEPARATOR,
+                                         ClassConstants.EXTERNAL_PACKAGE_SEPARATOR);
+    }
+
+
+    /**
+     * Converts an internal class name into an external short class name, without
+     * package specification.
+     * @param externalClassName the external class name,
+     *                          e.g. "<code>java.lang.Object</code>"
+     * @return the external short class name,
+     *                          e.g. "<code>Object</code>".
+     */
+    public static String externalShortClassName(String externalClassName)
+    {
+        int index = externalClassName.lastIndexOf(ClassConstants.EXTERNAL_PACKAGE_SEPARATOR);
+        return externalClassName.substring(index+1);
+    }
+
+
+    /**
+     * Returns whether the given internal type is an array type.
+     * @param internalType the internal type,
+     *                     e.g. "<code>[[Ljava/lang/Object;</code>".
+     * @return <code>true</code> if the given type is an array type,
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isInternalArrayType(String internalType)
+    {
+        return internalType.length() > 1 &&
+               internalType.charAt(0) == ClassConstants.INTERNAL_TYPE_ARRAY;
+    }
+
+
+    /**
+     * Returns the number of dimensions of the given internal type.
+     * @param internalType the internal type,
+     *                     e.g. "<code>[[Ljava/lang/Object;</code>".
+     * @return the number of dimensions, e.g. 2.
+     */
+    public static int internalArrayTypeDimensionCount(String internalType)
+    {
+        int dimensions = 0;
+        while (internalType.charAt(dimensions) == ClassConstants.INTERNAL_TYPE_ARRAY)
+        {
+            dimensions++;
+        }
+
+        return dimensions;
+    }
+
+
+    /**
+     * Returns whether the given internal class name is one of the interfaces
+     * that is implemented by all array types. These class names are
+     * "<code>java/lang/Object</code>", "<code>java/lang/Cloneable</code>", and
+     * "<code>java/io/Serializable</code>"
+     * @param internalClassName the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     * @return <code>true</code> if the given type is an array interface name,
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isInternalArrayInterfaceName(String internalClassName)
+    {
+        return ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT.equals(internalClassName)    ||
+               ClassConstants.INTERNAL_NAME_JAVA_LANG_CLONEABLE.equals(internalClassName) ||
+               ClassConstants.INTERNAL_NAME_JAVA_IO_SERIALIZABLE.equals(internalClassName);
+    }
+
+
+    /**
+     * Returns whether the given internal type is a plain primitive type
+     * (not void).
+     * @param internalType the internal type,
+     *                     e.g. "<code>I</code>".
+     * @return <code>true</code> if the given type is a class type,
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isInternalPrimitiveType(char internalType)
+    {
+        return internalType == ClassConstants.INTERNAL_TYPE_BOOLEAN ||
+               internalType == ClassConstants.INTERNAL_TYPE_BYTE    ||
+               internalType == ClassConstants.INTERNAL_TYPE_CHAR    ||
+               internalType == ClassConstants.INTERNAL_TYPE_SHORT   ||
+               internalType == ClassConstants.INTERNAL_TYPE_INT     ||
+               internalType == ClassConstants.INTERNAL_TYPE_FLOAT   ||
+               internalType == ClassConstants.INTERNAL_TYPE_LONG    ||
+               internalType == ClassConstants.INTERNAL_TYPE_DOUBLE;
+    }
+
+
+    /**
+     * Returns whether the given internal type is a primitive Category 2 type.
+     * @param internalType the internal type,
+     *                     e.g. "<code>L</code>".
+     * @return <code>true</code> if the given type is a Category 2 type,
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isInternalCategory2Type(String internalType)
+    {
+        return internalType.length() == 1 &&
+               (internalType.charAt(0) == ClassConstants.INTERNAL_TYPE_LONG ||
+                internalType.charAt(0) == ClassConstants.INTERNAL_TYPE_DOUBLE);
+    }
+
+
+    /**
+     * Returns whether the given internal type is a plain class type
+     * (including an array type of a plain class type).
+     * @param internalType the internal type,
+     *                     e.g. "<code>Ljava/lang/Object;</code>".
+     * @return <code>true</code> if the given type is a class type,
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isInternalClassType(String internalType)
+    {
+        int length = internalType.length();
+        return length > 1 &&
+//             internalType.charAt(0)        == ClassConstants.INTERNAL_TYPE_CLASS_START &&
+               internalType.charAt(length-1) == ClassConstants.INTERNAL_TYPE_CLASS_END;
+    }
+
+
+    /**
+     * Returns the internal type of a given class name.
+     * @param internalClassName the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     * @return the internal type,
+     *                          e.g. "<code>Ljava/lang/Object;</code>".
+     */
+    public static String internalTypeFromClassName(String internalClassName)
+    {
+        return internalArrayTypeFromClassName(internalClassName, 0);
+    }
+
+
+    /**
+     * Returns the internal array type of a given class name with a given number
+     * of dimensions. If the number of dimensions is 0, the class name itself is
+     * returned.
+     * @param internalClassName the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     * @param dimensionCount    the number of array dimensions.
+     * @return the internal array type of the array elements,
+     *                          e.g. "<code>Ljava/lang/Object;</code>".
+     */
+    public static String internalArrayTypeFromClassName(String internalClassName,
+                                                        int    dimensionCount)
+    {
+        StringBuffer buffer = new StringBuffer(internalClassName.length() + dimensionCount + 2);
+
+        for (int dimension = 0; dimension < dimensionCount; dimension++)
+        {
+            buffer.append(ClassConstants.INTERNAL_TYPE_ARRAY);
+        }
+
+        return buffer.append(ClassConstants.INTERNAL_TYPE_CLASS_START)
+                     .append(internalClassName)
+                     .append(ClassConstants.INTERNAL_TYPE_CLASS_END)
+                     .toString();
+    }
+
+
+    /**
+     * Returns the internal element type of a given internal array type.
+     * @param internalArrayType the internal array type,
+     *                          e.g. "<code>[[Ljava/lang/Object;</code>" or
+     *                               "<code>[I</code>".
+     * @return the internal type of the array elements,
+     *                          e.g. "<code>Ljava/lang/Object;</code>" or
+     *                               "<code>I</code>".
+     */
+    public static String internalTypeFromArrayType(String internalArrayType)
+    {
+        int index = internalArrayType.lastIndexOf(ClassConstants.INTERNAL_TYPE_ARRAY);
+        return internalArrayType.substring(index+1);
+    }
+
+
+    /**
+     * Returns the internal class name of a given internal class type
+     * (including an array type). Types involving primitive types are returned
+     * unchanged.
+     * @param internalClassType the internal class type,
+     *                          e.g. "<code>[Ljava/lang/Object;</code>",
+     *                               "<code>Ljava/lang/Object;</code>", or
+     *                               "<code>java/lang/Object</code>".
+     * @return the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     */
+    public static String internalClassNameFromClassType(String internalClassType)
+    {
+        return isInternalClassType(internalClassType) ?
+            internalClassType.substring(internalClassType.indexOf(ClassConstants.INTERNAL_TYPE_CLASS_START)+1,
+                                        internalClassType.length()-1) :
+            internalClassType;
+    }
+
+
+    /**
+     * Returns the internal class name of any given internal descriptor type,
+     * disregarding array prefixes.
+     * @param internalClassType the internal class type,
+     *                          e.g. "<code>Ljava/lang/Object;</code>" or
+     *                               "<code>[[I</code>".
+     * @return the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>" or
+     *                               <code>null</code>.
+     */
+    public static String internalClassNameFromType(String internalClassType)
+    {
+        if (!isInternalClassType(internalClassType))
+        {
+            return null;
+        }
+
+        // Is it an array type?
+        if (isInternalArrayType(internalClassType))
+        {
+            internalClassType = internalTypeFromArrayType(internalClassType);
+        }
+
+        return internalClassNameFromClassType(internalClassType);
+    }
+
+
+    /**
+     * Returns the internal type of the given internal method descriptor.
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(II)Z</code>".
+     * @return the internal return type,
+     *                                 e.g. "<code>Z</code>".
+     */
+    public static String internalMethodReturnType(String internalMethodDescriptor)
+    {
+        int index = internalMethodDescriptor.indexOf(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
+        return internalMethodDescriptor.substring(index + 1);
+    }
+
+
+    /**
+     * Returns the number of parameters of the given internal method descriptor.
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(ID)Z</code>".
+     * @return the number of parameters,
+     *                                 e.g. 2.
+     */
+    public static int internalMethodParameterCount(String internalMethodDescriptor)
+    {
+        InternalTypeEnumeration internalTypeEnumeration =
+            new InternalTypeEnumeration(internalMethodDescriptor);
+
+        int counter = 0;
+        while (internalTypeEnumeration.hasMoreTypes())
+        {
+            internalTypeEnumeration.nextType();
+
+            counter++;
+        }
+
+        return counter;
+    }
+
+
+    /**
+     * Returns the size taken up on the stack by the parameters of the given
+     * internal method descriptor. This accounts for long and double parameters
+     * taking up two entries.
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(ID)Z</code>".
+     * @return the size taken up on the stack,
+     *                                 e.g. 3.
+     */
+    public static int internalMethodParameterSize(String internalMethodDescriptor)
+    {
+        return internalMethodParameterSize(internalMethodDescriptor, true);
+    }
+
+
+    /**
+     * Returns the size taken up on the stack by the parameters of the given
+     * internal method descriptor. This accounts for long and double parameters
+     * taking up two entries, and a non-static method taking up an additional
+     * entry.
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(ID)Z</code>".
+     * @param accessFlags              the access flags of the method,
+     *                                 e.g. 0.
+     * @return the size taken up on the stack,
+     *                                 e.g. 4.
+     */
+    public static int internalMethodParameterSize(String internalMethodDescriptor,
+                                                  int    accessFlags)
+    {
+        return internalMethodParameterSize(internalMethodDescriptor,
+                                           (accessFlags & ClassConstants.INTERNAL_ACC_STATIC) != 0);
+    }
+
+
+    /**
+     * Returns the size taken up on the stack by the parameters of the given
+     * internal method descriptor. This accounts for long and double parameters
+     * taking up two spaces, and a non-static method taking up an additional
+     * entry.
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(ID)Z</code>".
+     * @param isStatic                 specifies whether the method is static,
+     *                                 e.g. false.
+     * @return the size taken up on the stack,
+     *                                 e.g. 4.
+     */
+    public static int internalMethodParameterSize(String  internalMethodDescriptor,
+                                                  boolean isStatic)
+    {
+        InternalTypeEnumeration internalTypeEnumeration =
+            new InternalTypeEnumeration(internalMethodDescriptor);
+
+        int size = isStatic ? 0 : 1;
+        while (internalTypeEnumeration.hasMoreTypes())
+        {
+            String internalType = internalTypeEnumeration.nextType();
+
+            size += internalTypeSize(internalType);
+        }
+
+        return size;
+    }
+
+
+    /**
+     * Returns the size taken up on the stack by the given internal type.
+     * The size is 1, except for long and double types, for which it is 2,
+     * and for the void type, for which 0 is returned.
+     * @param internalType the internal type,
+     *                     e.g. "<code>I</code>".
+     * @return the size taken up on the stack,
+     *                     e.g. 1.
+     */
+    public static int internalTypeSize(String internalType)
+    {
+        if (internalType.length() == 1)
+        {
+            char internalPrimitiveType = internalType.charAt(0);
+            if      (internalPrimitiveType == ClassConstants.INTERNAL_TYPE_LONG ||
+                     internalPrimitiveType == ClassConstants.INTERNAL_TYPE_DOUBLE)
+            {
+                return 2;
+            }
+            else if (internalPrimitiveType == ClassConstants.INTERNAL_TYPE_VOID)
+            {
+                return 0;
+            }
+        }
+
+        return 1;
+    }
+
+
+    /**
+     * Converts an external type into an internal type.
+     * @param externalType the external type,
+     *                     e.g. "<code>java.lang.Object[][]</code>" or
+     *                          "<code>int[]</code>".
+     * @return the internal type,
+     *                     e.g. "<code>[[Ljava/lang/Object;</code>" or
+     *                          "<code>[I</code>".
+     */
+    public static String internalType(String externalType)
+    {
+        // Strip the array part, if any.
+        int dimensionCount = externalArrayTypeDimensionCount(externalType);
+        if (dimensionCount > 0)
+        {
+            externalType = externalType.substring(0, externalType.length() - dimensionCount * ClassConstants.EXTERNAL_TYPE_ARRAY.length());
+        }
+
+        // Analyze the actual type part.
+        char internalTypeChar =
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_VOID   ) ?
+                                ClassConstants.INTERNAL_TYPE_VOID     :
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_BOOLEAN) ?
+                                ClassConstants.INTERNAL_TYPE_BOOLEAN  :
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_BYTE   ) ?
+                                ClassConstants.INTERNAL_TYPE_BYTE     :
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_CHAR   ) ?
+                                ClassConstants.INTERNAL_TYPE_CHAR     :
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_SHORT  ) ?
+                                ClassConstants.INTERNAL_TYPE_SHORT    :
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_INT    ) ?
+                                ClassConstants.INTERNAL_TYPE_INT      :
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_FLOAT  ) ?
+                                ClassConstants.INTERNAL_TYPE_FLOAT    :
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_LONG   ) ?
+                                ClassConstants.INTERNAL_TYPE_LONG     :
+            externalType.equals(ClassConstants.EXTERNAL_TYPE_DOUBLE ) ?
+                                ClassConstants.INTERNAL_TYPE_DOUBLE   :
+            externalType.equals("%"                                 ) ?
+                                '%'                                   :
+                                (char)0;
+
+        String internalType =
+            internalTypeChar != 0 ? String.valueOf(internalTypeChar) :
+                                    ClassConstants.INTERNAL_TYPE_CLASS_START +
+                                    internalClassName(externalType) +
+                                    ClassConstants.INTERNAL_TYPE_CLASS_END;
+
+        // Prepend the array part, if any.
+        for (int count = 0; count < dimensionCount; count++)
+        {
+            internalType = ClassConstants.INTERNAL_TYPE_ARRAY + internalType;
+        }
+
+        return internalType;
+    }
+
+
+    /**
+     * Returns the number of dimensions of the given external type.
+     * @param externalType the external type,
+     *                     e.g. "<code>[[Ljava/lang/Object;</code>".
+     * @return the number of dimensions, e.g. 2.
+     */
+    public static int externalArrayTypeDimensionCount(String externalType)
+    {
+        int dimensions = 0;
+        int length = ClassConstants.EXTERNAL_TYPE_ARRAY.length();
+        int offset = externalType.length() - length;
+        while (externalType.regionMatches(offset,
+                                          ClassConstants.EXTERNAL_TYPE_ARRAY,
+                                          0,
+                                          length))
+        {
+            dimensions++;
+            offset -= length;
+        }
+
+        return dimensions;
+    }
+
+
+    /**
+     * Converts an internal type into an external type.
+     * @param internalType the internal type,
+     *                     e.g. "<code>[[Ljava/lang/Object;</code>" or
+     *                          "<code>[I</code>".
+     * @return the external type,
+     *                     e.g. "<code>java.lang.Object[][]</code>" or
+     *                          "<code>int[]</code>".
+     */
+    public static String externalType(String internalType)
+    {
+        // Strip the array part, if any.
+        int dimensionCount = internalArrayTypeDimensionCount(internalType);
+        if (dimensionCount > 0)
+        {
+            internalType = internalType.substring(dimensionCount);
+        }
+
+        // Analyze the actual type part.
+        char internalTypeChar = internalType.charAt(0);
+
+        String externalType =
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_VOID        ?
+                                ClassConstants.EXTERNAL_TYPE_VOID        :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_BOOLEAN     ?
+                                ClassConstants.EXTERNAL_TYPE_BOOLEAN     :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_BYTE        ?
+                                ClassConstants.EXTERNAL_TYPE_BYTE        :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_CHAR        ?
+                                ClassConstants.EXTERNAL_TYPE_CHAR        :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_SHORT       ?
+                                ClassConstants.EXTERNAL_TYPE_SHORT       :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_INT         ?
+                                ClassConstants.EXTERNAL_TYPE_INT         :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_FLOAT       ?
+                                ClassConstants.EXTERNAL_TYPE_FLOAT       :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_LONG        ?
+                                ClassConstants.EXTERNAL_TYPE_LONG        :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_DOUBLE      ?
+                                ClassConstants.EXTERNAL_TYPE_DOUBLE      :
+            internalTypeChar == '%'                                      ?
+                                "%"                                      :
+            internalTypeChar == ClassConstants.INTERNAL_TYPE_CLASS_START ?
+                                externalClassName(internalType.substring(1, internalType.indexOf(ClassConstants.INTERNAL_TYPE_CLASS_END))) :
+                                null;
+
+        if (externalType == null)
+        {
+            throw new IllegalArgumentException("Unknown type ["+internalType+"]");
+        }
+
+        // Append the array part, if any.
+        for (int count = 0; count < dimensionCount; count++)
+        {
+            externalType += ClassConstants.EXTERNAL_TYPE_ARRAY;
+        }
+
+        return externalType;
+    }
+
+
+    /**
+     * Returns whether the given internal descriptor String represents a method
+     * descriptor.
+     * @param internalDescriptor the internal descriptor String,
+     *                           e.g. "<code>(II)Z</code>".
+     * @return <code>true</code> if the given String is a method descriptor,
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isInternalMethodDescriptor(String internalDescriptor)
+    {
+        return internalDescriptor.charAt(0) == ClassConstants.INTERNAL_METHOD_ARGUMENTS_OPEN;
+    }
+
+
+    /**
+     * Returns whether the given member String represents an external method
+     * name with arguments.
+     * @param externalMemberNameAndArguments the external member String,
+     *                                       e.g. "<code>myField</code>" or
+     *                                       e.g. "<code>myMethod(int,int)</code>".
+     * @return <code>true</code> if the given String refers to a method,
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isExternalMethodNameAndArguments(String externalMemberNameAndArguments)
+    {
+        return externalMemberNameAndArguments.indexOf(ClassConstants.EXTERNAL_METHOD_ARGUMENTS_OPEN) > 0;
+    }
+
+
+    /**
+     * Returns the name part of the given external method name and arguments.
+     * @param externalMethodNameAndArguments the external method name and arguments,
+     *                                       e.g. "<code>myMethod(int,int)</code>".
+     * @return the name part of the String, e.g. "<code>myMethod</code>".
+     */
+    public static String externalMethodName(String externalMethodNameAndArguments)
+    {
+        ExternalTypeEnumeration externalTypeEnumeration =
+            new ExternalTypeEnumeration(externalMethodNameAndArguments);
+
+        return externalTypeEnumeration.methodName();
+    }
+
+
+    /**
+     * Converts the given external method return type and name and arguments to
+     * an internal method descriptor.
+     * @param externalReturnType             the external method return type,
+     *                                       e.g. "<code>boolean</code>".
+     * @param externalMethodNameAndArguments the external method name and arguments,
+     *                                       e.g. "<code>myMethod(int,int)</code>".
+     * @return the internal method descriptor,
+     *                                       e.g. "<code>(II)Z</code>".
+     */
+    public static String internalMethodDescriptor(String externalReturnType,
+                                                  String externalMethodNameAndArguments)
+    {
+        StringBuffer internalMethodDescriptor = new StringBuffer();
+        internalMethodDescriptor.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_OPEN);
+
+        ExternalTypeEnumeration externalTypeEnumeration =
+            new ExternalTypeEnumeration(externalMethodNameAndArguments);
+
+        while (externalTypeEnumeration.hasMoreTypes())
+        {
+            internalMethodDescriptor.append(internalType(externalTypeEnumeration.nextType()));
+        }
+
+        internalMethodDescriptor.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
+        internalMethodDescriptor.append(internalType(externalReturnType));
+
+        return internalMethodDescriptor.toString();
+    }
+
+
+    /**
+     * Converts the given external method return type and List of arguments to
+     * an internal method descriptor.
+     * @param externalReturnType the external method return type,
+     *                                       e.g. "<code>boolean</code>".
+     * @param externalArguments the external method arguments,
+     *                                       e.g. <code>{ "int", "int" }</code>.
+     * @return the internal method descriptor,
+     *                                       e.g. "<code>(II)Z</code>".
+     */
+    public static String internalMethodDescriptor(String externalReturnType,
+                                                  List   externalArguments)
+    {
+        StringBuffer internalMethodDescriptor = new StringBuffer();
+        internalMethodDescriptor.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_OPEN);
+
+        for (int index = 0; index < externalArguments.size(); index++)
+        {
+            internalMethodDescriptor.append(internalType((String)externalArguments.get(index)));
+        }
+
+        internalMethodDescriptor.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
+        internalMethodDescriptor.append(internalType(externalReturnType));
+
+        return internalMethodDescriptor.toString();
+    }
+
+
+    /**
+     * Converts an internal field description into an external full field description.
+     * @param accessFlags             the access flags of the field.
+     * @param fieldName               the field name,
+     *                                e.g. "<code>myField</code>".
+     * @param internalFieldDescriptor the internal field descriptor,
+     *                                e.g. "<code>Z</code>".
+     * @return the external full field description,
+     *                                e.g. "<code>public boolean myField</code>".
+     */
+    public static String externalFullFieldDescription(int    accessFlags,
+                                                      String fieldName,
+                                                      String internalFieldDescriptor)
+    {
+        return externalFieldAccessFlags(accessFlags) +
+               externalType(internalFieldDescriptor) +
+               ' ' +
+               fieldName;
+    }
+
+
+    /**
+     * Converts an internal method description into an external full method description.
+     * @param internalClassName        the internal name of the class of the method,
+     *                                 e.g. "<code>mypackage/MyClass</code>".
+     * @param accessFlags              the access flags of the method.
+     * @param internalMethodName       the internal method name,
+     *                                 e.g. "<code>myMethod</code>" or
+     *                                      "<code>&lt;init&gt;</code>".
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(II)Z</code>".
+     * @return the external full method description,
+     *                                 e.g. "<code>public boolean myMethod(int,int)</code>" or
+     *                                      "<code>public MyClass(int,int)</code>".
+     */
+    public static String externalFullMethodDescription(String internalClassName,
+                                                       int    accessFlags,
+                                                       String internalMethodName,
+                                                       String internalMethodDescriptor)
+    {
+        return externalMethodAccessFlags(accessFlags) +
+               externalMethodReturnTypeAndName(internalClassName,
+                                               internalMethodName,
+                                               internalMethodDescriptor) +
+               ClassConstants.EXTERNAL_METHOD_ARGUMENTS_OPEN +
+               externalMethodArguments(internalMethodDescriptor) +
+               ClassConstants.EXTERNAL_METHOD_ARGUMENTS_CLOSE;
+    }
+
+
+    /**
+     * Converts internal class access flags into an external access description.
+     * @param accessFlags the class access flags.
+     * @return the external class access description,
+     *         e.g. "<code>public final </code>".
+     */
+    public static String externalClassAccessFlags(int accessFlags)
+    {
+        return externalClassAccessFlags(accessFlags, "");
+    }
+
+
+    /**
+     * Converts internal class access flags into an external access description.
+     * @param accessFlags the class access flags.
+     * @param prefix      a prefix that is added to each access modifier.
+     * @return the external class access description,
+     *         e.g. "<code>public final </code>".
+     */
+    public static String externalClassAccessFlags(int accessFlags, String prefix)
+    {
+        if (accessFlags == 0)
+        {
+            return EMPTY_STRING;
+        }
+
+        StringBuffer string = new StringBuffer(50);
+
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PUBLIC).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_FINAL) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_FINAL).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_ANNOTATTION) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_ANNOTATION);
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_INTERFACE) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_INTERFACE).append(' ');
+        }
+        else if ((accessFlags & ClassConstants.INTERNAL_ACC_ENUM) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_ENUM).append(' ');
+        }
+        else if ((accessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_ABSTRACT).append(' ');
+        }
+
+        return string.toString();
+    }
+
+
+    /**
+     * Converts internal field access flags into an external access description.
+     * @param accessFlags the field access flags.
+     * @return the external field access description,
+     *         e.g. "<code>public volatile </code>".
+     */
+    public static String externalFieldAccessFlags(int accessFlags)
+    {
+        return externalFieldAccessFlags(accessFlags, "");
+    }
+
+
+    /**
+     * Converts internal field access flags into an external access description.
+     * @param accessFlags the field access flags.
+     * @param prefix      a prefix that is added to each access modifier.
+     * @return the external field access description,
+     *         e.g. "<code>public volatile </code>".
+     */
+    public static String externalFieldAccessFlags(int accessFlags, String prefix)
+    {
+        if (accessFlags == 0)
+        {
+            return EMPTY_STRING;
+        }
+
+        StringBuffer string = new StringBuffer(50);
+
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PUBLIC).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_PRIVATE) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PRIVATE).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_PROTECTED) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PROTECTED).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_STATIC) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_STATIC).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_FINAL) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_FINAL).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_VOLATILE) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_VOLATILE).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_TRANSIENT) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_TRANSIENT).append(' ');
+        }
+
+        return string.toString();
+    }
+
+
+    /**
+     * Converts internal method access flags into an external access description.
+     * @param accessFlags the method access flags.
+     * @return the external method access description,
+     *                    e.g. "<code>public synchronized </code>".
+     */
+    public static String externalMethodAccessFlags(int accessFlags)
+    {
+        return externalMethodAccessFlags(accessFlags, "");
+    }
+
+
+    /**
+     * Converts internal method access flags into an external access description.
+     * @param accessFlags the method access flags.
+     * @param prefix      a prefix that is added to each access modifier.
+     * @return the external method access description,
+     *                    e.g. "public synchronized ".
+     */
+    public static String externalMethodAccessFlags(int accessFlags, String prefix)
+    {
+        if (accessFlags == 0)
+        {
+            return EMPTY_STRING;
+        }
+
+        StringBuffer string = new StringBuffer(50);
+
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PUBLIC).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_PRIVATE) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PRIVATE).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_PROTECTED) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_PROTECTED).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_STATIC) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_STATIC).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_FINAL) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_FINAL).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_SYNCHRONIZED) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_SYNCHRONIZED).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_NATIVE) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_NATIVE).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_ABSTRACT).append(' ');
+        }
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_STRICT) != 0)
+        {
+            string.append(prefix).append(ClassConstants.EXTERNAL_ACC_STRICT).append(' ');
+        }
+
+        return string.toString();
+    }
+
+
+    /**
+     * Converts an internal method descriptor into an external method return type.
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(II)Z</code>".
+     * @return the external method return type,
+     *                                 e.g. "<code>boolean</code>".
+     */
+    public static String externalMethodReturnType(String internalMethodDescriptor)
+    {
+        return externalType(internalMethodReturnType(internalMethodDescriptor));
+    }
+
+
+    /**
+     * Converts an internal class name, method name, and method descriptor to
+     * an external method return type and name.
+     * @param internalClassName        the internal name of the class of the method,
+     *                                 e.g. "<code>mypackage/MyClass</code>".
+     * @param internalMethodName       the internal method name,
+     *                                 e.g. "<code>myMethod</code>" or
+     *                                      "<code>&lt;init&gt;</code>".
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(II)Z</code>".
+     * @return the external method return type and name,
+     *                                 e.g. "<code>boolean myMethod</code>" or
+     *                                      "<code>MyClass</code>".
+     */
+    private static String externalMethodReturnTypeAndName(String internalClassName,
+                                                          String internalMethodName,
+                                                          String internalMethodDescriptor)
+    {
+        return internalMethodName.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ?
+            externalShortClassName(externalClassName(internalClassName)) :
+            (externalMethodReturnType(internalMethodDescriptor) +
+             ' ' +
+             internalMethodName);
+    }
+
+
+    /**
+     * Converts an internal method descriptor into an external method argument
+     * description.
+     * @param internalMethodDescriptor the internal method descriptor,
+     *                                 e.g. "<code>(II)Z</code>".
+     * @return the external method argument description,
+     *                                 e.g. "<code>int,int</code>".
+     */
+    public static String externalMethodArguments(String internalMethodDescriptor)
+    {
+        StringBuffer externalMethodNameAndArguments = new StringBuffer();
+
+        InternalTypeEnumeration internalTypeEnumeration =
+            new InternalTypeEnumeration(internalMethodDescriptor);
+
+        while (internalTypeEnumeration.hasMoreTypes())
+        {
+            externalMethodNameAndArguments.append(externalType(internalTypeEnumeration.nextType()));
+            if (internalTypeEnumeration.hasMoreTypes())
+            {
+                externalMethodNameAndArguments.append(ClassConstants.EXTERNAL_METHOD_ARGUMENTS_SEPARATOR);
+            }
+        }
+
+        return externalMethodNameAndArguments.toString();
+    }
+
+
+    /**
+     * Returns the internal package name of the given internal class name.
+     * @param internalClassName the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     * @return the internal package name,
+     *                          e.g. "<code>java/lang</code>".
+     */
+    public static String internalPackageName(String internalClassName)
+    {
+        String internalPackagePrefix = internalPackagePrefix(internalClassName);
+        int length = internalPackagePrefix.length();
+        return length > 0 ?
+            internalPackagePrefix.substring(0, length - 1) :
+            "";
+    }
+
+
+    /**
+     * Returns the internal package prefix of the given internal class name.
+     * @param internalClassName the internal class name,
+     *                          e.g. "<code>java/lang/Object</code>".
+     * @return the internal package prefix,
+     *                          e.g. "<code>java/lang/</code>".
+     */
+    public static String internalPackagePrefix(String internalClassName)
+    {
+        return internalClassName.substring(0, internalClassName.lastIndexOf(ClassConstants.INTERNAL_PACKAGE_SEPARATOR,
+                                                                            internalClassName.length() - 2) + 1);
+    }
+
+
+    /**
+     * Returns the external package name of the given external class name.
+     * @param externalClassName the external class name,
+     *                          e.g. "<code>java.lang.Object</code>".
+     * @return the external package name,
+     *                          e.g. "<code>java.lang</code>".
+     */
+    public static String externalPackageName(String externalClassName)
+    {
+        String externalPackagePrefix = externalPackagePrefix(externalClassName);
+        int length = externalPackagePrefix.length();
+        return length > 0 ?
+            externalPackagePrefix.substring(0, length - 1) :
+            "";
+    }
+
+
+    /**
+     * Returns the external package prefix of the given external class name.
+     * @param externalClassName the external class name,
+     *                          e.g. "<code>java.lang.Object</code>".
+     * @return the external package prefix,
+     *                          e.g. "<code>java.lang.</code>".
+     */
+    public static String externalPackagePrefix(String externalClassName)
+    {
+        return externalClassName.substring(0, externalClassName.lastIndexOf(ClassConstants.EXTERNAL_PACKAGE_SEPARATOR,
+                                                                            externalClassName.length() - 2) + 1);
+    }
+}
diff --git a/src/proguard/classfile/util/DescriptorClassEnumeration.java b/src/proguard/classfile/util/DescriptorClassEnumeration.java
new file mode 100644
index 0000000..0bee2d5
--- /dev/null
+++ b/src/proguard/classfile/util/DescriptorClassEnumeration.java
@@ -0,0 +1,236 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.ClassConstants;
+
+import java.util.Stack;
+
+/**
+ * A <code>DescriptorClassEnumeration</code> provides an enumeration of all
+ * classes mentioned in a given descriptor or signature.
+ *
+ * @author Eric Lafortune
+ */
+public class DescriptorClassEnumeration
+{
+    private String  descriptor;
+
+    private int     index;
+    private int     nestingLevel;
+    private boolean isInnerClassName;
+    private String  accumulatedClassName;
+    private Stack   accumulatedClassNames;
+
+
+    /**
+     * Creates a new DescriptorClassEnumeration for the given descriptor.
+     */
+    public DescriptorClassEnumeration(String descriptor)
+    {
+        this.descriptor = descriptor;
+    }
+
+
+    /**
+     * Returns the number of classes contained in the descriptor. This
+     * is the number of class names that the enumeration will return.
+     */
+    public int classCount()
+    {
+        int count = 0;
+
+        nextFluff();
+        while (hasMoreClassNames())
+        {
+            count++;
+
+            nextClassName();
+            nextFluff();
+        }
+
+        index = 0;
+
+        return count;
+    }
+
+
+    /**
+     * Returns whether the enumeration can provide more class names from the
+     * descriptor.
+     */
+    public boolean hasMoreClassNames()
+    {
+        return index < descriptor.length();
+    }
+
+
+    /**
+     * Returns the next fluff (surrounding class names) from the descriptor.
+     */
+    public String nextFluff()
+    {
+        int fluffStartIndex = index;
+
+        // Find the first token marking the start of a class name 'L' or '.'.
+        loop: while (index < descriptor.length())
+        {
+            switch (descriptor.charAt(index++))
+            {
+                case ClassConstants.INTERNAL_TYPE_GENERIC_START:
+                {
+                    nestingLevel++;
+
+                    // Make sure we have a stack.
+                    if (accumulatedClassNames == null)
+                    {
+                        accumulatedClassNames = new Stack();
+                    }
+
+                    // Remember the accumulated class name.
+                    accumulatedClassNames.push(accumulatedClassName);
+
+                    break;
+                }
+                case ClassConstants.INTERNAL_TYPE_GENERIC_END:
+                {
+                    nestingLevel--;
+
+                    // Return to the accumulated class name outside the
+                    // generic block.
+                    accumulatedClassName = (String)accumulatedClassNames.pop();
+
+                    continue loop;
+                }
+                case ClassConstants.INTERNAL_TYPE_GENERIC_BOUND:
+                {
+                    continue loop;
+                }
+                case ClassConstants.INTERNAL_TYPE_CLASS_START:
+                {
+                    // We've found the start of an ordinary class name.
+                    nestingLevel += 2;
+                    isInnerClassName = false;
+                    break loop;
+                }
+                case ClassConstants.INTERNAL_TYPE_CLASS_END:
+                {
+                    nestingLevel -= 2;
+                    break;
+                }
+                case ClassConstants.EXTERNAL_INNER_CLASS_SEPARATOR:
+                {
+                    // We've found the start of an inner class name in a signature.
+                    isInnerClassName = true;
+                    break loop;
+                }
+                case ClassConstants.INTERNAL_TYPE_GENERIC_VARIABLE_START:
+                {
+                    // We've found the start of a type identifier. Skip to the end.
+                    while (descriptor.charAt(index++) != ClassConstants.INTERNAL_TYPE_CLASS_END);
+                    break;
+                }
+            }
+
+            if (nestingLevel == 1 &&
+                descriptor.charAt(index) != ClassConstants.INTERNAL_TYPE_GENERIC_END)
+            {
+                // We're at the start of a type parameter. Skip to the start
+                // of the bounds.
+                while (descriptor.charAt(index++) != ClassConstants.INTERNAL_TYPE_GENERIC_BOUND);
+            }
+        }
+
+        return descriptor.substring(fluffStartIndex, index);
+    }
+
+
+    /**
+     * Returns the next class name from the descriptor.
+     */
+    public String nextClassName()
+    {
+        int classNameStartIndex = index;
+
+        // Find the first token marking the end of a class name '<' or ';'.
+        loop: while (true)
+        {
+            switch (descriptor.charAt(index))
+            {
+                case ClassConstants.INTERNAL_TYPE_GENERIC_START:
+                case ClassConstants.INTERNAL_TYPE_CLASS_END:
+                case ClassConstants.EXTERNAL_INNER_CLASS_SEPARATOR:
+                {
+                    break loop;
+                }
+            }
+
+            index++;
+        }
+
+        String className = descriptor.substring(classNameStartIndex, index);
+
+        // Recompose the inner class name if necessary.
+        accumulatedClassName = isInnerClassName ?
+            accumulatedClassName + ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR + className :
+            className;
+
+        return accumulatedClassName;
+    }
+
+
+    /**
+     * Returns whether the most recently returned class name was a recomposed
+     * inner class name from a signature.
+     */
+    public boolean isInnerClassName()
+    {
+        return isInnerClassName;
+    }
+
+
+    /**
+     * A main method for testing the class name enumeration.
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            for (int index = 0; index < args.length; index++)
+            {
+                String descriptor = args[index];
+
+                System.out.println("Descriptor ["+descriptor+"]");
+                DescriptorClassEnumeration enumeration = new DescriptorClassEnumeration(descriptor);
+                System.out.println("  Fluff: ["+enumeration.nextFluff()+"]");
+                while (enumeration.hasMoreClassNames())
+                {
+                    System.out.println("  Name:  ["+enumeration.nextClassName()+"]");
+                    System.out.println("  Fluff: ["+enumeration.nextFluff()+"]");
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/classfile/util/DynamicClassReferenceInitializer.java b/src/proguard/classfile/util/DynamicClassReferenceInitializer.java
new file mode 100644
index 0000000..09ffdd0
--- /dev/null
+++ b/src/proguard/classfile/util/DynamicClassReferenceInitializer.java
@@ -0,0 +1,478 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.util.StringMatcher;
+
+/**
+ * This InstructionVisitor initializes any constant <code>Class.forName</code> or
+ * <code>.class</code> references of all classes it visits. More specifically,
+ * it fills out the references of string constant pool entries that refer to a
+ * class in the program class pool or in the library class pool.
+ * <p>
+ * It optionally prints notes if on usage of
+ * <code>(SomeClass)Class.forName(variable).newInstance()</code>.
+ * <p>
+ * The class hierarchy must be initialized before using this visitor.
+ *
+ * @see ClassSuperHierarchyInitializer
+ *
+ * @author Eric Lafortune
+ */
+public class DynamicClassReferenceInitializer
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             AttributeVisitor
+{
+    public static final int X = InstructionSequenceMatcher.X;
+    public static final int Y = InstructionSequenceMatcher.Y;
+    public static final int Z = InstructionSequenceMatcher.Z;
+
+    public static final int A = InstructionSequenceMatcher.A;
+    public static final int B = InstructionSequenceMatcher.B;
+    public static final int C = InstructionSequenceMatcher.C;
+    public static final int D = InstructionSequenceMatcher.D;
+
+
+    private final Constant[] CLASS_FOR_NAME_CONSTANTS = new Constant[]
+    {
+        // 0
+        new MethodrefConstant(1, 2, null, null),
+        new ClassConstant(3, null),
+        new NameAndTypeConstant(4, 5),
+        new Utf8Constant(ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_FOR_NAME),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_FOR_NAME),
+
+        // 6
+        new MethodrefConstant(1, 7, null, null),
+        new NameAndTypeConstant(8, 9),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_NEW_INSTANCE),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_NEW_INSTANCE),
+
+        // 10
+        new MethodrefConstant(1, 11, null, null),
+        new NameAndTypeConstant(12, 13),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_GET_COMPONENT_TYPE),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_GET_COMPONENT_TYPE),
+    };
+
+    // Class.forName("SomeClass").
+    private final Instruction[] CONSTANT_CLASS_FOR_NAME_INSTRUCTIONS = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, X),
+        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
+    };
+
+    // (SomeClass)Class.forName(someName).newInstance().
+    private final Instruction[] CLASS_FOR_NAME_CAST_INSTRUCTIONS = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 6),
+        new ConstantInstruction(InstructionConstants.OP_CHECKCAST, X),
+    };
+
+
+//    private Constant[] DOT_CLASS_JAVAC_CONSTANTS = new Constant[]
+//    {
+//        new MethodrefConstant(A, 1, null, null),
+//        new NameAndTypeConstant(2, 3),
+//        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JAVAC),
+//        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC),
+//    };
+
+    private final Constant[] DOT_CLASS_JAVAC_CONSTANTS = new Constant[]
+    {
+        new MethodrefConstant(A, 1, null, null),
+        new NameAndTypeConstant(B, 2),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC),
+    };
+
+    // SomeClass.class = class$("SomeClass") (javac).
+    private final Instruction[] DOT_CLASS_JAVAC_INSTRUCTIONS = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, X),
+        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
+    };
+
+
+//    private Constant[] DOT_CLASS_JIKES_CONSTANTS = new Constant[]
+//    {
+//        new MethodrefConstant(A, 1, null, null),
+//        new NameAndTypeConstant(2, 3),
+//        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JIKES),
+//        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES),
+//    };
+
+    private final Constant[] DOT_CLASS_JIKES_CONSTANTS = new Constant[]
+    {
+        new MethodrefConstant(A, 1, null, null),
+        new NameAndTypeConstant(B, 2),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES),
+    };
+
+    // SomeClass.class = class("SomeClass", false) (jikes).
+    private final Instruction[] DOT_CLASS_JIKES_INSTRUCTIONS = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, X),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
+    };
+
+    // return Class.forName(v0).
+    private final Instruction[] DOT_CLASS_JAVAC_IMPLEMENTATION_INSTRUCTIONS = new Instruction[]
+    {
+        new VariableInstruction(InstructionConstants.OP_ALOAD_0),
+        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
+        new SimpleInstruction(InstructionConstants.OP_ARETURN),
+    };
+
+    // return Class.forName(v0), if (!v1) .getComponentType().
+    private final Instruction[] DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS = new Instruction[]
+    {
+        new VariableInstruction(InstructionConstants.OP_ALOAD_0),
+        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
+        new VariableInstruction(InstructionConstants.OP_ALOAD_1),
+        new BranchInstruction(InstructionConstants.OP_IFNE, +6),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 10),
+        new SimpleInstruction(InstructionConstants.OP_ARETURN),
+    };
+
+    // return Class.forName(v0).getComponentType().
+    private final Instruction[] DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS2 = new Instruction[]
+    {
+        new VariableInstruction(InstructionConstants.OP_ALOAD_0),
+        new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC, 0),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 10),
+        new SimpleInstruction(InstructionConstants.OP_ARETURN),
+    };
+
+
+    private final ClassPool      programClassPool;
+    private final ClassPool      libraryClassPool;
+    private final WarningPrinter missingNotePrinter;
+    private final WarningPrinter dependencyWarningPrinter;
+    private final WarningPrinter notePrinter;
+    private final StringMatcher  noteExceptionMatcher;
+
+
+    private final InstructionSequenceMatcher constantClassForNameMatcher =
+        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
+                                       CONSTANT_CLASS_FOR_NAME_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher classForNameCastMatcher =
+        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
+                                       CLASS_FOR_NAME_CAST_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher dotClassJavacMatcher =
+        new InstructionSequenceMatcher(DOT_CLASS_JAVAC_CONSTANTS,
+                                       DOT_CLASS_JAVAC_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher dotClassJikesMatcher =
+        new InstructionSequenceMatcher(DOT_CLASS_JIKES_CONSTANTS,
+                                       DOT_CLASS_JIKES_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher dotClassJavacImplementationMatcher =
+        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
+                                       DOT_CLASS_JAVAC_IMPLEMENTATION_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher dotClassJikesImplementationMatcher =
+        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
+                                       DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher dotClassJikesImplementationMatcher2 =
+        new InstructionSequenceMatcher(CLASS_FOR_NAME_CONSTANTS,
+                                       DOT_CLASS_JIKES_IMPLEMENTATION_INSTRUCTIONS2);
+
+
+    // A field acting as a return variable for the visitors.
+    private boolean isClassForNameInvocation;
+
+
+    /**
+     * Creates a new DynamicClassReferenceInitializer that optionally prints
+     * warnings and notes, with optional class specifications for which never
+     * to print notes.
+     */
+    public DynamicClassReferenceInitializer(ClassPool      programClassPool,
+                                            ClassPool      libraryClassPool,
+                                            WarningPrinter missingNotePrinter,
+                                            WarningPrinter dependencyWarningPrinter,
+                                            WarningPrinter notePrinter,
+                                            StringMatcher  noteExceptionMatcher)
+    {
+        this.programClassPool         = programClassPool;
+        this.libraryClassPool         = libraryClassPool;
+        this.missingNotePrinter       = missingNotePrinter;
+        this.dependencyWarningPrinter = dependencyWarningPrinter;
+        this.notePrinter              = notePrinter;
+        this.noteExceptionMatcher     = noteExceptionMatcher;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        // Try to match the Class.forName("SomeClass") construct.
+        instruction.accept(clazz, method, codeAttribute, offset,
+                           constantClassForNameMatcher);
+
+        // Did we find a match?
+        if (constantClassForNameMatcher.isMatching())
+        {
+            // Fill out the matched string constant.
+            clazz.constantPoolEntryAccept(constantClassForNameMatcher.matchedConstantIndex(X), this);
+
+            // Don't look for the dynamic construct.
+            classForNameCastMatcher.reset();
+        }
+
+        // Try to match the (SomeClass)Class.forName(someName).newInstance()
+        // construct.
+        instruction.accept(clazz, method, codeAttribute, offset,
+                           classForNameCastMatcher);
+
+        // Did we find a match?
+        if (classForNameCastMatcher.isMatching())
+        {
+            // Print out a note about the construct.
+            clazz.constantPoolEntryAccept(classForNameCastMatcher.matchedConstantIndex(X), this);
+        }
+
+        // Try to match the javac .class construct.
+        instruction.accept(clazz, method, codeAttribute, offset,
+                           dotClassJavacMatcher);
+
+        // Did we find a match?
+        if (dotClassJavacMatcher.isMatching() &&
+            isDotClassMethodref(clazz, dotClassJavacMatcher.matchedConstantIndex(0)))
+        {
+            // Fill out the matched string constant.
+            clazz.constantPoolEntryAccept(dotClassJavacMatcher.matchedConstantIndex(X), this);
+        }
+
+        // Try to match the jikes .class construct.
+        instruction.accept(clazz, method, codeAttribute, offset,
+                           dotClassJikesMatcher);
+
+        // Did we find a match?
+        if (dotClassJikesMatcher.isMatching() &&
+            isDotClassMethodref(clazz, dotClassJikesMatcher.matchedConstantIndex(0)))
+        {
+            // Fill out the matched string constant.
+            clazz.constantPoolEntryAccept(dotClassJikesMatcher.matchedConstantIndex(X), this);
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    /**
+     * Fills out the link to the referenced class.
+     */
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Save a reference to the corresponding class.
+        String externalClassName = stringConstant.getString(clazz);
+        String internalClassName = ClassUtil.internalClassName(externalClassName);
+
+        stringConstant.referencedClass = findClass(clazz.getName(), internalClassName);
+    }
+
+
+    /**
+     * Prints out a note about the class cast to this class, if applicable.
+     */
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Print out a note about the class cast.
+        if (noteExceptionMatcher == null ||
+            !noteExceptionMatcher.matches(classConstant.getName(clazz)))
+        {
+            notePrinter.print(clazz.getName(),
+                              classConstant.getName(clazz),
+                              "Note: " +
+                              ClassUtil.externalClassName(clazz.getName()) +
+                              " calls '(" +
+                              ClassUtil.externalClassName(classConstant.getName(clazz)) +
+                              ")Class.forName(variable).newInstance()'");
+        }
+    }
+
+
+    /**
+     * Checks whether the referenced method is a .class method.
+     */
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        String methodType = methodrefConstant.getType(clazz);
+
+        // Do the method's class and type match?
+        if (methodType.equals(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JAVAC) ||
+            methodType.equals(ClassConstants.INTERNAL_METHOD_TYPE_DOT_CLASS_JIKES))
+        {
+            String methodName = methodrefConstant.getName(clazz);
+
+            // Does the method's name match one of the special names?
+            isClassForNameInvocation =
+                methodName.equals(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JAVAC) ||
+                methodName.equals(ClassConstants.INTERNAL_METHOD_NAME_DOT_CLASS_JIKES);
+
+            if (isClassForNameInvocation)
+            {
+                return;
+            }
+
+            String className  = methodrefConstant.getClassName(clazz);
+
+            // Note that we look for the class by name, since the referenced
+            // class has not been initialized yet.
+            Clazz referencedClass = programClassPool.getClass(className);
+            if (referencedClass != null)
+            {
+                // Check if the code of the referenced method is .class code.
+                // Note that we look for the method by name and type, since the
+                // referenced method has not been initialized yet.
+                referencedClass.methodAccept(methodName,
+                                             methodType,
+                                             new AllAttributeVisitor(this));
+            }
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Check whether this is class$(String), as generated by javac, or
+        // class(String, boolean), as generated by jikes, or an optimized
+        // version.
+        isClassForNameInvocation =
+            isDotClassMethodCode(clazz, method, codeAttribute,
+                                 dotClassJavacImplementationMatcher, 5)  ||
+            isDotClassMethodCode(clazz, method, codeAttribute,
+                                 dotClassJikesImplementationMatcher, 12) ||
+            isDotClassMethodCode(clazz, method, codeAttribute,
+                                 dotClassJikesImplementationMatcher2, 8);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the given method reference corresponds to a .class
+     * method, as generated by javac or by jikes.
+     */
+    private boolean isDotClassMethodref(Clazz clazz, int methodrefConstantIndex)
+    {
+        isClassForNameInvocation = false;
+
+        // Check if the code of the referenced method is .class code.
+        clazz.constantPoolEntryAccept(methodrefConstantIndex, this);
+
+        return isClassForNameInvocation;
+    }
+
+
+    /**
+     * Returns whether the first whether the first instructions of the
+     * given code attribute match with the given instruction matcher.
+     */
+    private boolean isDotClassMethodCode(Clazz                      clazz,
+                                         Method                     method,
+                                         CodeAttribute              codeAttribute,
+                                         InstructionSequenceMatcher codeMatcher,
+                                         int                        codeLength)
+    {
+        // Check the minimum code length.
+        if (codeAttribute.u4codeLength < codeLength)
+        {
+            return false;
+        }
+
+        // Check the actual instructions.
+        codeMatcher.reset();
+        codeAttribute.instructionsAccept(clazz, method, 0, codeLength, codeMatcher);
+        return codeMatcher.isMatching();
+    }
+
+
+    /**
+     * Returns the class with the given name, either for the program class pool
+     * or from the library class pool, or <code>null</code> if it can't be found.
+     */
+    private Clazz findClass(String referencingClassName, String name)
+    {
+        // Ignore any primitive array types.
+        if (ClassUtil.isInternalArrayType(name) &&
+            !ClassUtil.isInternalClassType(name))
+        {
+            return null;
+        }
+
+        // First look for the class in the program class pool.
+        Clazz clazz = programClassPool.getClass(name);
+
+        // Otherwise look for the class in the library class pool.
+        if (clazz == null)
+        {
+            clazz = libraryClassPool.getClass(name);
+
+            if (clazz == null &&
+                missingNotePrinter != null)
+            {
+                // We didn't find the superclass or interface. Print a note.
+                missingNotePrinter.print(referencingClassName,
+                                         name,
+                                         "Note: " +
+                                         ClassUtil.externalClassName(referencingClassName) +
+                                         ": can't find dynamically referenced class " +
+                                         ClassUtil.externalClassName(name));
+            }
+        }
+        else if (dependencyWarningPrinter != null)
+        {
+            // The superclass or interface was found in the program class pool.
+            // Print a warning.
+            dependencyWarningPrinter.print(referencingClassName,
+                                           name,
+                                           "Warning: library class " +
+                                           ClassUtil.externalClassName(referencingClassName) +
+                                           " depends dynamically on program class " +
+                                           ClassUtil.externalClassName(name));
+        }
+
+        return clazz;
+    }
+}
diff --git a/src/proguard/classfile/util/DynamicMemberReferenceInitializer.java b/src/proguard/classfile/util/DynamicMemberReferenceInitializer.java
new file mode 100644
index 0000000..1f57708
--- /dev/null
+++ b/src/proguard/classfile/util/DynamicMemberReferenceInitializer.java
@@ -0,0 +1,604 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.visitor.*;
+import proguard.util.StringMatcher;
+
+/**
+ * This InstructionVisitor initializes any constant
+ * <code>Class.get[Declared]{Field,Method}</code> references of all instructions
+ * it visits. More specifically, it fills out the references of string constant
+ * pool entries that refer to a class member in the program class pool or in the
+ * library class pool.
+ * <p>
+ * It optionally prints notes if on usage of
+ * <code>(SomeClass)Class.forName(variable).newInstance()</code>.
+ * <p>
+ * The class hierarchy and references must be initialized before using this
+ * visitor.
+ *
+ * @see ClassSuperHierarchyInitializer
+ * @see ClassReferenceInitializer
+ *
+ * @author Eric Lafortune
+ */
+public class DynamicMemberReferenceInitializer
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             AttributeVisitor,
+             MemberVisitor
+{
+    public static final int X = InstructionSequenceMatcher.X;
+    public static final int Y = InstructionSequenceMatcher.Y;
+    public static final int Z = InstructionSequenceMatcher.Z;
+
+    public static final int A = InstructionSequenceMatcher.A;
+    public static final int B = InstructionSequenceMatcher.B;
+    public static final int C = InstructionSequenceMatcher.C;
+    public static final int D = InstructionSequenceMatcher.D;
+
+
+    private final Constant[] GET_FIELD_CONSTANTS = new Constant[]
+    {
+        new MethodrefConstant(1, 2, null, null),
+        new ClassConstant(3, null),
+        new NameAndTypeConstant(4, 5),
+        new Utf8Constant(ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_GET_FIELD),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_GET_FIELD),
+    };
+
+    private final Constant[] GET_DECLARED_FIELD_CONSTANTS = new Constant[]
+    {
+        new MethodrefConstant(1, 2, null, null),
+        new ClassConstant(3, null),
+        new NameAndTypeConstant(4, 5),
+        new Utf8Constant(ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_GET_DECLARED_FIELD),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_GET_DECLARED_FIELD),
+    };
+
+    private final Constant[] GET_METHOD_CONSTANTS = new Constant[]
+    {
+        new MethodrefConstant(1, 2, null, null),
+        new ClassConstant(3, null),
+        new NameAndTypeConstant(4, 5),
+        new Utf8Constant(ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_GET_METHOD),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_GET_METHOD),
+    };
+
+    private final Constant[] GET_DECLARED_METHOD_CONSTANTS = new Constant[]
+    {
+        new MethodrefConstant(1, 2, null, null),
+        new ClassConstant(3, null),
+        new NameAndTypeConstant(4, 5),
+        new Utf8Constant(ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_NAME_CLASS_GET_DECLARED_METHOD),
+        new Utf8Constant(ClassConstants.INTERNAL_METHOD_TYPE_CLASS_GET_DECLARED_METHOD),
+    };
+
+    // SomeClass.class.get[Declared]Field("someField").
+    private final Instruction[] CONSTANT_GET_FIELD_INSTRUCTIONS = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, X),
+        new ConstantInstruction(InstructionConstants.OP_LDC, Y),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 0),
+    };
+
+    // SomeClass.class.get[Declared]Method("someMethod", new Class[] {}).
+    private final Instruction[] CONSTANT_GET_METHOD_INSTRUCTIONS0 = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, X),
+        new ConstantInstruction(InstructionConstants.OP_LDC, Y),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+        new ConstantInstruction(InstructionConstants.OP_ANEWARRAY, 1),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 0),
+    };
+
+    // SomeClass.class.get[Declared]Method("someMethod", new Class[] { A.class }).
+    private final Instruction[] CONSTANT_GET_METHOD_INSTRUCTIONS1 = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, X),
+        new ConstantInstruction(InstructionConstants.OP_LDC, Y),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+        new ConstantInstruction(InstructionConstants.OP_ANEWARRAY, 1),
+        new SimpleInstruction(InstructionConstants.OP_DUP),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+        new ConstantInstruction(InstructionConstants.OP_LDC, A),
+        new SimpleInstruction(InstructionConstants.OP_AASTORE),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 0),
+    };
+
+    // SomeClass.class.get[Declared]Method("someMethod", new Class[] { A.class, B.class }).
+    private final Instruction[] CONSTANT_GET_METHOD_INSTRUCTIONS2 = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, X),
+        new ConstantInstruction(InstructionConstants.OP_LDC, Y),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+        new ConstantInstruction(InstructionConstants.OP_ANEWARRAY, 1),
+        new SimpleInstruction(InstructionConstants.OP_DUP),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+        new ConstantInstruction(InstructionConstants.OP_LDC, A),
+        new SimpleInstruction(InstructionConstants.OP_AASTORE),
+        new SimpleInstruction(InstructionConstants.OP_DUP),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+        new ConstantInstruction(InstructionConstants.OP_LDC, B),
+        new SimpleInstruction(InstructionConstants.OP_AASTORE),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 0),
+    };
+
+    // get[Declared]Field("someField").
+    private final Instruction[] GET_FIELD_INSTRUCTIONS = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, Y),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 0),
+    };
+
+    // get[Declared]Method("someMethod", new Class[] {}).
+    private final Instruction[] GET_METHOD_INSTRUCTIONS0 = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, Y),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+        new ConstantInstruction(InstructionConstants.OP_ANEWARRAY, 1),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 0),
+    };
+
+    // get[Declared]Method("someMethod", new Class[] { A.class }).
+    private final Instruction[] GET_METHOD_INSTRUCTIONS1 = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, Y),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+        new ConstantInstruction(InstructionConstants.OP_ANEWARRAY, 1),
+        new SimpleInstruction(InstructionConstants.OP_DUP),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+        new ConstantInstruction(InstructionConstants.OP_LDC, A),
+        new SimpleInstruction(InstructionConstants.OP_AASTORE),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 0),
+    };
+
+    // get[Declared]Method("someMethod", new Class[] { A.class, B.class }).
+    private final Instruction[] GET_METHOD_INSTRUCTIONS2 = new Instruction[]
+    {
+        new ConstantInstruction(InstructionConstants.OP_LDC, Y),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+        new ConstantInstruction(InstructionConstants.OP_ANEWARRAY, 1),
+        new SimpleInstruction(InstructionConstants.OP_DUP),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+        new ConstantInstruction(InstructionConstants.OP_LDC, A),
+        new SimpleInstruction(InstructionConstants.OP_AASTORE),
+        new SimpleInstruction(InstructionConstants.OP_DUP),
+        new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+        new ConstantInstruction(InstructionConstants.OP_LDC, B),
+        new SimpleInstruction(InstructionConstants.OP_AASTORE),
+        new ConstantInstruction(InstructionConstants.OP_INVOKEVIRTUAL, 0),
+    };
+
+
+    private final ClassPool      programClassPool;
+    private final ClassPool      libraryClassPool;
+    private final WarningPrinter notePrinter;
+    private final StringMatcher  noteFieldExceptionMatcher;
+    private final StringMatcher  noteMethodExceptionMatcher;
+
+
+    private final InstructionSequenceMatcher constantGetFieldMatcher =
+        new InstructionSequenceMatcher(GET_FIELD_CONSTANTS,
+                                       CONSTANT_GET_FIELD_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher constantGetDeclaredFieldMatcher =
+        new InstructionSequenceMatcher(GET_DECLARED_FIELD_CONSTANTS,
+                                       CONSTANT_GET_FIELD_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher constantGetMethodMatcher0 =
+        new InstructionSequenceMatcher(GET_METHOD_CONSTANTS,
+                                       CONSTANT_GET_METHOD_INSTRUCTIONS0);
+
+    private final InstructionSequenceMatcher constantGetDeclaredMethodMatcher0 =
+        new InstructionSequenceMatcher(GET_DECLARED_METHOD_CONSTANTS,
+                                       CONSTANT_GET_METHOD_INSTRUCTIONS0);
+
+    private final InstructionSequenceMatcher constantGetMethodMatcher1 =
+        new InstructionSequenceMatcher(GET_METHOD_CONSTANTS,
+                                       CONSTANT_GET_METHOD_INSTRUCTIONS1);
+
+    private final InstructionSequenceMatcher constantGetDeclaredMethodMatcher1 =
+        new InstructionSequenceMatcher(GET_DECLARED_METHOD_CONSTANTS,
+                                       CONSTANT_GET_METHOD_INSTRUCTIONS1);
+
+    private final InstructionSequenceMatcher constantGetMethodMatcher2 =
+        new InstructionSequenceMatcher(GET_METHOD_CONSTANTS,
+                                       CONSTANT_GET_METHOD_INSTRUCTIONS2);
+
+    private final InstructionSequenceMatcher constantGetDeclaredMethodMatcher2 =
+        new InstructionSequenceMatcher(GET_DECLARED_METHOD_CONSTANTS,
+                                       CONSTANT_GET_METHOD_INSTRUCTIONS2);
+
+    private final InstructionSequenceMatcher getFieldMatcher =
+        new InstructionSequenceMatcher(GET_FIELD_CONSTANTS,
+                                       GET_FIELD_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher getDeclaredFieldMatcher =
+        new InstructionSequenceMatcher(GET_DECLARED_FIELD_CONSTANTS,
+                                       GET_FIELD_INSTRUCTIONS);
+
+    private final InstructionSequenceMatcher getMethodMatcher0 =
+        new InstructionSequenceMatcher(GET_METHOD_CONSTANTS,
+                                       GET_METHOD_INSTRUCTIONS0);
+
+    private final InstructionSequenceMatcher getDeclaredMethodMatcher0 =
+        new InstructionSequenceMatcher(GET_DECLARED_METHOD_CONSTANTS,
+                                       GET_METHOD_INSTRUCTIONS0);
+
+    private final InstructionSequenceMatcher getMethodMatcher1 =
+        new InstructionSequenceMatcher(GET_METHOD_CONSTANTS,
+                                       GET_METHOD_INSTRUCTIONS1);
+
+    private final InstructionSequenceMatcher getDeclaredMethodMatcher1 =
+        new InstructionSequenceMatcher(GET_DECLARED_METHOD_CONSTANTS,
+                                       GET_METHOD_INSTRUCTIONS1);
+
+    private final InstructionSequenceMatcher getMethodMatcher2 =
+        new InstructionSequenceMatcher(GET_METHOD_CONSTANTS,
+                                       GET_METHOD_INSTRUCTIONS2);
+
+    private final InstructionSequenceMatcher getDeclaredMethodMatcher2 =
+        new InstructionSequenceMatcher(GET_DECLARED_METHOD_CONSTANTS,
+                                       GET_METHOD_INSTRUCTIONS2);
+
+    private final MemberFinder memberFinder = new MemberFinder();
+
+
+    // Fields acting as parameters for the visitors.
+    private Clazz   referencedClass;
+    private boolean isDeclared;
+    private boolean isField;
+
+
+
+    /**
+     * Creates a new DynamicMemberReferenceInitializer.
+     */
+    public DynamicMemberReferenceInitializer(ClassPool      programClassPool,
+                                             ClassPool      libraryClassPool,
+                                             WarningPrinter notePrinter,
+                                             StringMatcher  noteFieldExceptionMatcher,
+                                             StringMatcher  noteMethodExceptionMatcher)
+    {
+        this.programClassPool           = programClassPool;
+        this.libraryClassPool           = libraryClassPool;
+        this.notePrinter                = notePrinter;
+        this.noteFieldExceptionMatcher  = noteFieldExceptionMatcher;
+        this.noteMethodExceptionMatcher = noteMethodExceptionMatcher;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        // Try to match the SomeClass.class.getField("someField") construct.
+        matchGetMember(clazz, method, codeAttribute, offset, instruction,
+                       constantGetFieldMatcher,
+                       getFieldMatcher, true, false);
+
+        // Try to match the SomeClass.class.getDeclaredField("someField") construct.
+        matchGetMember(clazz, method, codeAttribute, offset, instruction,
+                       constantGetDeclaredFieldMatcher,
+                       getDeclaredFieldMatcher, true, true);
+
+        // Try to match the SomeClass.class.getMethod("someMethod", new Class[]
+        // {}) construct.
+        matchGetMember(clazz, method, codeAttribute, offset, instruction,
+                       constantGetMethodMatcher0,
+                       getMethodMatcher0, false, false);
+
+        // Try to match the SomeClass.class.getDeclaredMethod("someMethod",
+        //  new Class[] {}) construct.
+        matchGetMember(clazz, method, codeAttribute, offset, instruction,
+                       constantGetDeclaredMethodMatcher0,
+                       getDeclaredMethodMatcher0, false, true);
+
+        // Try to match the SomeClass.class.getMethod("someMethod", new Class[]
+        // { A.class }) construct.
+        matchGetMember(clazz, method, codeAttribute, offset, instruction,
+                       constantGetMethodMatcher1,
+                       getMethodMatcher1, false, false);
+
+        // Try to match the SomeClass.class.getDeclaredMethod("someMethod",
+        //  new Class[] { A.class }) construct.
+        matchGetMember(clazz, method, codeAttribute, offset, instruction,
+                       constantGetDeclaredMethodMatcher1,
+                       getDeclaredMethodMatcher1, false, true);
+
+        // Try to match the SomeClass.class.getMethod("someMethod", new Class[]
+        // { A.class, B.class }) construct.
+        matchGetMember(clazz, method, codeAttribute, offset, instruction,
+                       constantGetMethodMatcher2,
+                       getMethodMatcher2, false, false);
+
+        // Try to match the SomeClass.class.getDeclaredMethod("someMethod",
+        //  new Class[] { A.class, B.class }) construct.
+        matchGetMember(clazz, method, codeAttribute, offset, instruction,
+                       constantGetDeclaredMethodMatcher2,
+                       getDeclaredMethodMatcher2, false, true);
+    }
+
+
+    /**
+     * Tries to match the next instruction and fills out the string constant
+     * or prints out a note accordingly.
+     */
+    private void matchGetMember(Clazz                      clazz,
+                                Method                     method,
+                                CodeAttribute              codeAttribute,
+                                int                        offset,
+                                Instruction                instruction,
+                                InstructionSequenceMatcher constantSequenceMatcher,
+                                InstructionSequenceMatcher variableSequenceMatcher,
+                                boolean                    isField,
+                                boolean                    isDeclared)
+    {
+        // Try to match the next instruction in the constant sequence.
+        instruction.accept(clazz, method, codeAttribute, offset,
+                           constantSequenceMatcher);
+
+        // Did we find a match to fill out the string constant?
+        if (constantSequenceMatcher.isMatching())
+        {
+            this.isField    = isField;
+            this.isDeclared = isDeclared;
+
+            // Get the member's class.
+            clazz.constantPoolEntryAccept(constantSequenceMatcher.matchedConstantIndex(X), this);
+
+            // Fill out the matched string constant.
+            clazz.constantPoolEntryAccept(constantSequenceMatcher.matchedConstantIndex(Y), this);
+
+            // Don't look for the dynamic construct.
+            variableSequenceMatcher.reset();
+        }
+
+        // Try to match the next instruction in the variable sequence.
+        instruction.accept(clazz, method, codeAttribute, offset,
+                           variableSequenceMatcher);
+
+        // Did we find a match to print out a note?
+        if (variableSequenceMatcher.isMatching())
+        {
+            // Print out a note about the dynamic invocation.
+            printDynamicInvocationNote(clazz,
+                                       variableSequenceMatcher,
+                                       isField,
+                                       isDeclared);
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    /**
+     * Remembers the referenced class.
+     */
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Remember the referenced class.
+        referencedClass = ClassUtil.isInternalArrayType(classConstant.getName(clazz)) ?
+            null :
+            classConstant.referencedClass;
+    }
+
+
+    /**
+     * Fills out the link to the referenced class member.
+     */
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        if (referencedClass != null)
+        {
+            String name = stringConstant.getString(clazz);
+
+            // See if we can find the referenced class member locally, or
+            // somewhere in the hierarchy.
+            Member referencedMember = isDeclared ? isField ?
+                (Member)referencedClass.findField(name, null) :
+                (Member)referencedClass.findMethod(name, null) :
+                (Member)memberFinder.findMember(clazz,
+                                                referencedClass,
+                                                name,
+                                                null,
+                                                isField);
+            if (referencedMember != null)
+            {
+                stringConstant.referencedMember = referencedMember;
+                stringConstant.referencedClass  = isDeclared ?
+                    referencedClass :
+                    memberFinder.correspondingClass();
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Prints out a note on the matched dynamic invocation, if necessary.
+     */
+    private void printDynamicInvocationNote(Clazz                      clazz,
+                                            InstructionSequenceMatcher noteSequenceMatcher,
+                                            boolean                    isField,
+                                            boolean                    isDeclared)
+    {
+        // Print out a note about the dynamic invocation.
+        if (notePrinter != null &&
+            notePrinter.accepts(clazz.getName()))
+        {
+            // Is the class member name in the list of exceptions?
+            StringMatcher noteExceptionMatcher = isField ?
+                noteFieldExceptionMatcher :
+                noteMethodExceptionMatcher;
+
+            int    memberNameIndex = noteSequenceMatcher.matchedConstantIndex(Y);
+            String memberName      = clazz.getStringString(memberNameIndex);
+
+            if (noteExceptionMatcher == null ||
+                !noteExceptionMatcher.matches(memberName))
+            {
+                // Compose the external member name and partial descriptor.
+                String externalMemberDescription = memberName;
+
+                if (!isField)
+                {
+                    externalMemberDescription += '(';
+                    for (int count = 0; count < 2; count++)
+                    {
+                        int memberArgumentIndex = noteSequenceMatcher.matchedConstantIndex(A + count);
+                        if (memberArgumentIndex > 0)
+                        {
+                            if (count > 0)
+                            {
+                                externalMemberDescription += ',';
+                            }
+                            String className = clazz.getClassName(memberArgumentIndex);
+                            externalMemberDescription += ClassUtil.isInternalArrayType(className) ?
+                                ClassUtil.externalType(className) :
+                                ClassUtil.externalClassName(className);
+                        }
+                    }
+                    externalMemberDescription += ')';
+                }
+
+                // Print out the actual note.
+                notePrinter.print(clazz.getName(),
+                                  "Note: " +
+                                  ClassUtil.externalClassName(clazz.getName()) +
+                                  " accesses a " +
+                                  (isDeclared ? "declared " : "") +
+                                  (isField    ? "field" : "method") +
+                                  " '" +
+                                  externalMemberDescription +
+                                  "' dynamically");
+
+                // Print out notes about potential candidates.
+                ClassVisitor classVisitor;
+
+                if (isField)
+                {
+                    classVisitor =
+                       new AllFieldVisitor(
+                       new MemberNameFilter(memberName, this));
+                }
+                else
+                {
+                    // Compose the partial method descriptor.
+                    String methodDescriptor = "(";
+                    for (int count = 0; count < 2; count++)
+                    {
+                        int memberArgumentIndex = noteSequenceMatcher.matchedConstantIndex(A + count);
+                        if (memberArgumentIndex > 0)
+                        {
+                            if (count > 0)
+                            {
+                                methodDescriptor += ',';
+                            }
+                            String className = clazz.getClassName(memberArgumentIndex);
+                            methodDescriptor += ClassUtil.isInternalArrayType(className) ?
+                                className :
+                                ClassUtil.internalTypeFromClassName(className);
+                        }
+                    }
+                    methodDescriptor += ")L///;";
+
+                    classVisitor =
+                        new AllMethodVisitor(
+                        new MemberNameFilter(memberName,
+                        new MemberDescriptorFilter(methodDescriptor, this)));
+                }
+
+                programClassPool.classesAcceptAlphabetically(classVisitor);
+                libraryClassPool.classesAcceptAlphabetically(classVisitor);
+            }
+        }
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (notePrinter.accepts(programClass.getName()))
+        {
+            System.out.println("      Maybe this is program field '" +
+                               ClassUtil.externalFullClassDescription(0, programClass.getName()) +
+                               " { " +
+                               ClassUtil.externalFullFieldDescription(0, programField.getName(programClass), programField.getDescriptor(programClass)) +
+                               "; }'");
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (notePrinter.accepts(programClass.getName()))
+        {
+            System.out.println("      Maybe this is program method '" +
+                               ClassUtil.externalFullClassDescription(0, programClass.getName()) +
+                               " { " +
+                               ClassUtil.externalFullMethodDescription(null, 0, programMethod.getName(programClass), programMethod.getDescriptor(programClass)) +
+                               "; }'");
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (notePrinter.accepts(libraryClass.getName()))
+        {
+            System.out.println("      Maybe this is library field '" +
+                               ClassUtil.externalFullClassDescription(0, libraryClass.getName()) +
+                               " { " +
+                               ClassUtil.externalFullFieldDescription(0, libraryField.getName(libraryClass), libraryField.getDescriptor(libraryClass)) +
+                               "; }'");
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (notePrinter.accepts(libraryClass.getName()))
+        {
+            System.out.println("      Maybe this is library method '" +
+                               ClassUtil.externalFullClassDescription(0, libraryClass.getName()) +
+                               " { " +
+                               ClassUtil.externalFullMethodDescription(null, 0, libraryMethod.getName(libraryClass), libraryMethod.getDescriptor(libraryClass)) +
+                               "; }'");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/util/ExternalTypeEnumeration.java b/src/proguard/classfile/util/ExternalTypeEnumeration.java
new file mode 100644
index 0000000..6371888
--- /dev/null
+++ b/src/proguard/classfile/util/ExternalTypeEnumeration.java
@@ -0,0 +1,106 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.ClassConstants;
+
+
+/**
+ * An <code>ExternalTypeEnumeration</code> provides an enumeration of all
+ * types listed in a given external descriptor string. The method name can
+ * be retrieved separately.
+ * <p>
+ * A <code>ExternalTypeEnumeration</code> object can be reused for processing
+ * different subsequent descriptors, by means of the <code>setDescriptor</code>
+ * method.
+ *
+ * @author Eric Lafortune
+ */
+public class ExternalTypeEnumeration
+{
+    private String descriptor;
+    private int    index;
+
+
+    public ExternalTypeEnumeration(String descriptor)
+    {
+        setDescriptor(descriptor);
+    }
+
+
+    ExternalTypeEnumeration()
+    {
+    }
+
+
+    void setDescriptor(String descriptor)
+    {
+        this.descriptor = descriptor;
+
+        reset();
+    }
+
+
+    public void reset()
+    {
+        index = descriptor.indexOf(ClassConstants.EXTERNAL_METHOD_ARGUMENTS_OPEN) + 1;
+
+        if (index < 1)
+        {
+            throw new IllegalArgumentException("Missing opening parenthesis in descriptor ["+descriptor+"]");
+        }
+    }
+
+
+    public boolean hasMoreTypes()
+    {
+        return index < descriptor.length() - 1;
+    }
+
+
+    public String nextType()
+    {
+        int startIndex = index;
+
+        // Find the next separating comma.
+        index = descriptor.indexOf(ClassConstants.EXTERNAL_METHOD_ARGUMENTS_SEPARATOR,
+                                   startIndex);
+
+        // Otherwise find the closing parenthesis.
+        if (index < 0)
+        {
+            index = descriptor.indexOf(ClassConstants.EXTERNAL_METHOD_ARGUMENTS_CLOSE,
+                                       startIndex);
+            if (index < 0)
+            {
+                throw new IllegalArgumentException("Missing closing parenthesis in descriptor ["+descriptor+"]");
+            }
+        }
+
+        return descriptor.substring(startIndex, index++).trim();
+    }
+
+
+    public String methodName()
+    {
+        return descriptor.substring(0, descriptor.indexOf(ClassConstants.EXTERNAL_METHOD_ARGUMENTS_OPEN)).trim();
+    }
+}
diff --git a/src/proguard/classfile/util/InstructionSequenceMatcher.java b/src/proguard/classfile/util/InstructionSequenceMatcher.java
new file mode 100644
index 0000000..8a689d5
--- /dev/null
+++ b/src/proguard/classfile/util/InstructionSequenceMatcher.java
@@ -0,0 +1,634 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+
+/**
+ * This InstructionVisitor checks whether a given pattern instruction sequence
+ * occurs in the instructions that are visited. The arguments of the
+ * instruction sequence can be wildcards that are matched.
+ *
+ * @author Eric Lafortune
+ */
+public class InstructionSequenceMatcher
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor
+{
+    /*
+    private static       boolean DEBUG      = false;
+    public  static       boolean DEBUG_MORE = false;
+    /*/
+    private static final boolean DEBUG      = false;
+    private static final boolean DEBUG_MORE = false;
+    //*/
+
+    public static final int X = 0x40000000;
+    public static final int Y = 0x40000001;
+    public static final int Z = 0x40000002;
+
+    public static final int A = 0x40000003;
+    public static final int B = 0x40000004;
+    public static final int C = 0x40000005;
+    public static final int D = 0x40000006;
+
+
+    private final Constant[]    patternConstants;
+    private final Instruction[] patternInstructions;
+
+    private boolean     matching;
+    private boolean     matchingAnyWildCards;
+    private int         patternInstructionIndex;
+    private final int[] matchedInstructionOffsets;
+    private int         matchedArgumentFlags;
+    private final int[] matchedArguments = new int[7];
+    private long        matchedConstantFlags;
+    private final int[] matchedConstantIndices;
+
+    // Fields acting as a parameter and a return value for visitor methods.
+    private Constant patternConstant;
+    private boolean  matchingConstant;
+
+
+    /**
+     * Creates a new InstructionSequenceMatcher.
+     * @param patternConstants        any constants referenced by the pattern
+     *                                instruction.
+     * @param patternInstructions     the pattern instruction sequence.
+     */
+    public InstructionSequenceMatcher(Constant[]    patternConstants,
+                                      Instruction[] patternInstructions)
+    {
+        this.patternConstants    = patternConstants;
+        this.patternInstructions = patternInstructions;
+
+        matchedInstructionOffsets = new int[patternInstructions.length];
+        matchedConstantIndices    = new int[patternConstants.length];
+    }
+
+
+    /**
+     * Starts matching from the first instruction again next time.
+     */
+    public void reset()
+    {
+        patternInstructionIndex = 0;
+        matchedArgumentFlags    = 0;
+        matchedConstantFlags    = 0L;
+    }
+
+
+    public boolean isMatching()
+    {
+        return matching;
+    }
+
+
+    public boolean isMatchingAnyWildcards()
+    {
+        return matchingAnyWildCards;
+    }
+
+
+    public int instructionCount()
+    {
+        return patternInstructions.length;
+    }
+
+
+    public int matchedInstructionOffset(int index)
+    {
+        return matchedInstructionOffsets[index];
+    }
+
+
+    public int matchedArgument(int argument)
+    {
+        int argumentIndex = argument - X;
+        return argumentIndex < 0 ?
+            argument :
+            matchedArguments[argumentIndex];
+    }
+
+
+    public int[] matchedArguments(int[] arguments)
+    {
+        int[] matchedArguments = new int[arguments.length];
+
+        for (int index = 0; index < arguments.length; index++)
+        {
+            matchedArguments[index] = matchedArgument(arguments[index]);
+        }
+
+        return matchedArguments;
+    }
+
+
+    public int matchedConstantIndex(int constantIndex)
+    {
+        int argumentIndex = constantIndex - X;
+        return argumentIndex < 0 ?
+            matchedConstantIndices[constantIndex] :
+            matchedArguments[argumentIndex];
+    }
+
+
+    public int matchedBranchOffset(int offset, int branchOffset)
+    {
+        int argumentIndex = branchOffset - X;
+        return argumentIndex < 0 ?
+            branchOffset :
+            matchedArguments[argumentIndex] - offset;
+    }
+
+
+    public int[] matchedJumpOffsets(int offset, int[] jumpOffsets)
+    {
+        int[] matchedJumpOffsets = new int[jumpOffsets.length];
+
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            matchedJumpOffsets[index] = matchedBranchOffset(offset,
+                                                            jumpOffsets[index]);
+        }
+
+        return matchedJumpOffsets;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        Instruction patternInstruction = patternInstructions[patternInstructionIndex];
+
+        // Check if the instruction matches the next instruction in the sequence.
+        boolean condition =
+            matchingOpcodes(simpleInstruction, patternInstruction) &&
+            matchingArguments(simpleInstruction.constant,
+                              ((SimpleInstruction)patternInstruction).constant);
+
+        // Check if the instruction sequence is matching now.
+        checkMatch(condition,
+                   clazz,
+                   method,
+                   codeAttribute,
+                   offset,
+                   simpleInstruction);
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        Instruction patternInstruction = patternInstructions[patternInstructionIndex];
+
+        // Check if the instruction matches the next instruction in the sequence.
+        boolean condition =
+            matchingOpcodes(variableInstruction, patternInstruction) &&
+            matchingArguments(variableInstruction.variableIndex,
+                              ((VariableInstruction)patternInstruction).variableIndex) &&
+            matchingArguments(variableInstruction.constant,
+                              ((VariableInstruction)patternInstruction).constant);
+
+        // Check if the instruction sequence is matching now.
+        checkMatch(condition,
+                   clazz,
+                   method,
+                   codeAttribute,
+                   offset,
+                   variableInstruction);
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        Instruction patternInstruction = patternInstructions[patternInstructionIndex];
+
+        // Check if the instruction matches the next instruction in the sequence.
+        boolean condition =
+            matchingOpcodes(constantInstruction, patternInstruction) &&
+            matchingConstantIndices(clazz,
+                                    constantInstruction.constantIndex,
+                                    ((ConstantInstruction)patternInstruction).constantIndex) &&
+            matchingArguments(constantInstruction.constant,
+                              ((ConstantInstruction)patternInstruction).constant);
+
+        // Check if the instruction sequence is matching now.
+        checkMatch(condition,
+                   clazz,
+                   method,
+                   codeAttribute,
+                   offset,
+                   constantInstruction);
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        Instruction patternInstruction = patternInstructions[patternInstructionIndex];
+
+        // Check if the instruction matches the next instruction in the from
+        // sequence.
+        boolean condition =
+            matchingOpcodes(branchInstruction, patternInstruction) &&
+            matchingBranchOffsets(offset,
+                                  branchInstruction.branchOffset,
+                                  ((BranchInstruction)patternInstruction).branchOffset);
+
+        // Check if the instruction sequence is matching now.
+        checkMatch(condition,
+                   clazz,
+                   method,
+                   codeAttribute,
+                   offset,
+                   branchInstruction);
+    }
+
+
+    public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction)
+    {
+        Instruction patternInstruction = patternInstructions[patternInstructionIndex];
+
+        // Check if the instruction matches the next instruction in the sequence.
+        boolean condition =
+            matchingOpcodes(tableSwitchInstruction, patternInstruction) &&
+            matchingBranchOffsets(offset,
+                                  tableSwitchInstruction.defaultOffset,
+                                  ((TableSwitchInstruction)patternInstruction).defaultOffset) &&
+            matchingArguments(tableSwitchInstruction.lowCase,
+                              ((TableSwitchInstruction)patternInstruction).lowCase)  &&
+            matchingArguments(tableSwitchInstruction.highCase,
+                              ((TableSwitchInstruction)patternInstruction).highCase) &&
+            matchingJumpOffsets(offset,
+                                tableSwitchInstruction.jumpOffsets,
+                                ((TableSwitchInstruction)patternInstruction).jumpOffsets);
+
+        // Check if the instruction sequence is matching now.
+        checkMatch(condition,
+                   clazz,
+                   method,
+                   codeAttribute,
+                   offset,
+                   tableSwitchInstruction);
+    }
+
+
+    public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction)
+    {
+        Instruction patternInstruction = patternInstructions[patternInstructionIndex];
+
+        // Check if the instruction matches the next instruction in the sequence.
+        boolean condition =
+            matchingOpcodes(lookUpSwitchInstruction, patternInstruction) &&
+            matchingBranchOffsets(offset,
+                                  lookUpSwitchInstruction.defaultOffset,
+                                  ((LookUpSwitchInstruction)patternInstruction).defaultOffset) &&
+            matchingArguments(lookUpSwitchInstruction.cases,
+                              ((LookUpSwitchInstruction)patternInstruction).cases) &&
+            matchingJumpOffsets(offset,
+                                lookUpSwitchInstruction.jumpOffsets,
+                                ((LookUpSwitchInstruction)patternInstruction).jumpOffsets);
+
+        // Check if the instruction sequence is matching now.
+        checkMatch(condition,
+                   clazz,
+                   method,
+                   codeAttribute,
+                   offset,
+                   lookUpSwitchInstruction);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        IntegerConstant integerPatternConstant = (IntegerConstant)patternConstant;
+
+        // Compare the integer values.
+        matchingConstant = integerConstant.getValue() ==
+                           integerPatternConstant.getValue();
+    }
+
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        LongConstant longPatternConstant = (LongConstant)patternConstant;
+
+        // Compare the long values.
+        matchingConstant = longConstant.getValue() ==
+                           longPatternConstant.getValue();
+    }
+
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        FloatConstant floatPatternConstant = (FloatConstant)patternConstant;
+
+        // Compare the float values.
+        matchingConstant = floatConstant.getValue() ==
+                           floatPatternConstant.getValue();
+    }
+
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        DoubleConstant doublePatternConstant = (DoubleConstant)patternConstant;
+
+        // Compare the double values.
+        matchingConstant = doubleConstant.getValue() ==
+                           doublePatternConstant.getValue();
+    }
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        StringConstant stringPatternConstant = (StringConstant)patternConstant;
+
+        // Check the UTF-8 constant.
+        matchingConstant =
+            matchingConstantIndices(clazz,
+                                    stringConstant.u2stringIndex,
+                                    stringPatternConstant.u2stringIndex);
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        Utf8Constant utf8PatternConstant = (Utf8Constant)patternConstant;
+
+        // Compare the actual strings.
+        matchingConstant = utf8Constant.getString().equals(
+                           utf8PatternConstant.getString());
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        RefConstant refPatternConstant = (RefConstant)patternConstant;
+
+        // Check the class and the name and type.
+        matchingConstant =
+            matchingConstantIndices(clazz,
+                                    refConstant.getClassIndex(),
+                                    refPatternConstant.getClassIndex()) &&
+            matchingConstantIndices(clazz,
+                                    refConstant.getNameAndTypeIndex(),
+                                    refPatternConstant.getNameAndTypeIndex());
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        ClassConstant classPatternConstant = (ClassConstant)patternConstant;
+
+        // Check the class name.
+        matchingConstant =
+            matchingConstantIndices(clazz,
+                                    classConstant.u2nameIndex,
+                                    classPatternConstant.u2nameIndex);
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        NameAndTypeConstant typePatternConstant = (NameAndTypeConstant)patternConstant;
+
+        // Check the name and the descriptor.
+        matchingConstant =
+            matchingConstantIndices(clazz,
+                                    nameAndTypeConstant.u2nameIndex,
+                                    typePatternConstant.u2nameIndex) &&
+            matchingConstantIndices(clazz,
+                                    nameAndTypeConstant.u2descriptorIndex,
+                                    typePatternConstant.u2descriptorIndex);
+    }
+
+
+    // Small utility methods.
+
+    private boolean matchingOpcodes(Instruction instruction1,
+                                    Instruction instruction2)
+    {
+        // Check the opcode.
+        return instruction1.opcode            == instruction2.opcode ||
+               instruction1.canonicalOpcode() == instruction2.opcode;
+    }
+
+
+    private boolean matchingArguments(int argument1,
+                                      int argument2)
+    {
+        int argumentIndex = argument2 - X;
+        if (argumentIndex < 0)
+        {
+            // Check the literal argument.
+            return argument1 == argument2;
+        }
+        else if ((matchedArgumentFlags & (1 << argumentIndex)) == 0)
+        {
+            // Store a wildcard argument.
+            matchedArguments[argumentIndex] = argument1;
+            matchedArgumentFlags |= 1 << argumentIndex;
+
+            return true;
+        }
+        else
+        {
+            // Check the previously stored wildcard argument.
+            return matchedArguments[argumentIndex] == argument1;
+        }
+    }
+
+
+    private boolean matchingArguments(int[] arguments1,
+                                      int[] arguments2)
+    {
+        if (arguments1.length != arguments2.length)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < arguments1.length; index++)
+        {
+            if (!matchingArguments(arguments1[index], arguments2[index]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    private boolean matchingConstantIndices(Clazz clazz,
+                                            int   constantIndex1,
+                                            int   constantIndex2)
+    {
+        if (constantIndex2 >= X)
+        {
+            // Check the constant index.
+            return matchingArguments(constantIndex1, constantIndex2);
+        }
+        else if ((matchedConstantFlags & (1L << constantIndex2)) == 0)
+        {
+            // Check the actual constant.
+            matchingConstant = false;
+            patternConstant  = patternConstants[constantIndex2];
+
+            if (clazz.getTag(constantIndex1) == patternConstant.getTag())
+            {
+                clazz.constantPoolEntryAccept(constantIndex1, this);
+
+                if (matchingConstant)
+                {
+                    // Store the constant index.
+                    matchedConstantIndices[constantIndex2] = constantIndex1;
+                    matchedConstantFlags |= 1L << constantIndex2;
+                }
+            }
+
+            return matchingConstant;
+        }
+        else
+        {
+            // Check a previously stored constant index.
+            return matchedConstantIndices[constantIndex2] == constantIndex1;
+        }
+    }
+
+
+    private boolean matchingBranchOffsets(int offset,
+                                          int branchOffset1,
+                                          int branchOffset2)
+    {
+        int argumentIndex = branchOffset2 - X;
+        if (argumentIndex < 0)
+        {
+            // Check the literal argument.
+            return branchOffset1 == branchOffset2;
+        }
+        else if ((matchedArgumentFlags & (1 << argumentIndex)) == 0)
+        {
+            // Store a wildcard argument.
+            matchedArguments[argumentIndex] = offset + branchOffset1;
+            matchedArgumentFlags |= 1 << argumentIndex;
+
+            return true;
+        }
+        else
+        {
+            // Check the previously stored wildcard argument.
+            return matchedArguments[argumentIndex] == offset + branchOffset1;
+        }
+    }
+
+
+    private boolean matchingJumpOffsets(int   offset,
+                                        int[] jumpOffsets1,
+                                        int[] jumpOffsets2)
+    {
+        if (jumpOffsets1.length != jumpOffsets2.length)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < jumpOffsets1.length; index++)
+        {
+            if (!matchingBranchOffsets(offset,
+                                       jumpOffsets1[index],
+                                       jumpOffsets2[index]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    private void checkMatch(boolean       condition,
+                            Clazz         clazz,
+                            Method        method,
+                            CodeAttribute codeAttribute,
+                            int           offset,
+                            Instruction   instruction)
+    {
+        if (DEBUG_MORE)
+        {
+            System.out.println("InstructionSequenceMatcher: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]: "+patternInstructions[patternInstructionIndex].toString(patternInstructionIndex)+(condition?"\t== ":"\t   ")+instruction.toString(offset));
+        }
+
+        // Did the instruction match?
+        if (condition)
+        {
+            // Remember the offset of the matching instruction.
+            matchedInstructionOffsets[patternInstructionIndex] = offset;
+
+            // Try to match the next instruction next time.
+            patternInstructionIndex++;
+
+            // Did we match all instructions in the sequence?
+            matching = patternInstructionIndex == patternInstructions.length;
+
+            // Did we match any wildcards along the way?
+            matchingAnyWildCards = matchedArgumentFlags != 0;
+
+            if (matching)
+            {
+                if (DEBUG)
+                {
+                    System.out.println("InstructionSequenceMatcher: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+                    for (int index = 0; index < patternInstructionIndex; index++)
+                    {
+                        System.out.println("    "+InstructionFactory.create(codeAttribute.code, matchedInstructionOffsets[index]).toString(matchedInstructionOffsets[index]));
+                    }
+                }
+
+                // Start matching from the first instruction again next time.
+                reset();
+            }
+        }
+        else
+        {
+            // The instruction didn't match.
+            matching = false;
+
+            // Is this a failed second instruction?
+            boolean retry = patternInstructionIndex == 1;
+
+            // Start matching from the first instruction next time.
+            reset();
+
+            // Retry a failed second instruction as a first instruction.
+            if (retry)
+            {
+                instruction.accept(clazz, method, codeAttribute, offset, this);
+            }
+        }
+    }
+}
diff --git a/src/proguard/classfile/util/InternalTypeEnumeration.java b/src/proguard/classfile/util/InternalTypeEnumeration.java
new file mode 100644
index 0000000..76f7e84
--- /dev/null
+++ b/src/proguard/classfile/util/InternalTypeEnumeration.java
@@ -0,0 +1,204 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.ClassConstants;
+
+
+/**
+ * An <code>InternalTypeEnumeration</code> provides an enumeration of all
+ * parameter types listed in a given internal method descriptor or signature.
+ * The signature can also be a class signature. The return type of a method
+ * descriptor can retrieved separately.
+ *
+ * @author Eric Lafortune
+ */
+public class InternalTypeEnumeration
+{
+    private String descriptor;
+    private int    firstIndex;
+    private int    lastIndex;
+    private int    index;
+
+
+    /**
+     * Creates a new InternalTypeEnumeration for the given method descriptor.
+     */
+    public InternalTypeEnumeration(String descriptor)
+    {
+        this.descriptor = descriptor;
+        this.firstIndex = descriptor.indexOf(ClassConstants.INTERNAL_METHOD_ARGUMENTS_OPEN);
+        this.lastIndex  = descriptor.indexOf(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
+        this.index      = firstIndex + 1;
+
+        if (lastIndex < 0)
+        {
+            lastIndex = descriptor.length();
+        }
+    }
+
+
+    /**
+     * Returns the formal type parameters from the descriptor, assuming it's a
+     * method descriptor.
+     */
+    public String formalTypeParameters()
+    {
+        return descriptor.substring(0, firstIndex);
+    }
+
+
+    /**
+     * Returns whether the enumeration can provide more types from the method
+     * descriptor.
+     */
+    public boolean hasMoreTypes()
+    {
+        return index < lastIndex;
+    }
+
+
+    /**
+     * Returns the next type from the method descriptor.
+     */
+    public String nextType()
+    {
+        int startIndex = index;
+
+        skipArray();
+
+        char c = descriptor.charAt(index++);
+        switch (c)
+        {
+            case ClassConstants.INTERNAL_TYPE_CLASS_START:
+            case ClassConstants.INTERNAL_TYPE_GENERIC_VARIABLE_START:
+            {
+                skipClass();
+                break;
+            }
+            case ClassConstants.INTERNAL_TYPE_GENERIC_START:
+            {
+                skipGeneric();
+                break;
+            }
+        }
+
+        return descriptor.substring(startIndex, index);
+    }
+
+
+    /**
+     * Returns the return type from the descriptor, assuming it's a method
+     * descriptor.
+     */
+    public String returnType()
+    {
+        return descriptor.substring(lastIndex + 1);
+    }
+
+
+    // Small utility methods.
+
+    private void skipArray()
+    {
+        while (descriptor.charAt(index) == ClassConstants.INTERNAL_TYPE_ARRAY)
+        {
+            index++;
+        }
+    }
+
+
+    private void skipClass()
+    {
+        while (true)
+        {
+            char c = descriptor.charAt(index++);
+            switch (c)
+            {
+                case ClassConstants.INTERNAL_TYPE_GENERIC_START:
+                    skipGeneric();
+                    break;
+
+                case ClassConstants.INTERNAL_TYPE_CLASS_END:
+                    return;
+            }
+        }
+    }
+
+
+    private void skipGeneric()
+    {
+        int nestingLevel = 1;
+
+        do
+        {
+            char c = descriptor.charAt(index++);
+            switch (c)
+            {
+                case ClassConstants.INTERNAL_TYPE_GENERIC_START:
+                    nestingLevel++;
+                    break;
+
+                case ClassConstants.INTERNAL_TYPE_GENERIC_END:
+                    nestingLevel--;
+                    break;
+            }
+        }
+        while (nestingLevel > 0);
+    }
+
+
+    /**
+     * A main method for testing the type enumeration.
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            for (int index = 0; index < args.length; index++)
+            {
+                String descriptor = args[index];
+
+                System.out.println("Descriptor ["+descriptor+"]");
+                InternalTypeEnumeration enumeration = new InternalTypeEnumeration(descriptor);
+
+                if (enumeration.firstIndex >= 0)
+                {
+                    System.out.println("  Formal type parameters ["+enumeration.formalTypeParameters()+"]");
+                }
+
+                while (enumeration.hasMoreTypes())
+                {
+                    System.out.println("  Type ["+enumeration.nextType()+"]");
+                }
+
+                if (enumeration.lastIndex < descriptor.length())
+                {
+                    System.out.println("  Return type ["+enumeration.returnType()+"]");
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/classfile/util/MemberFinder.java b/src/proguard/classfile/util/MemberFinder.java
new file mode 100644
index 0000000..0fdeec0
--- /dev/null
+++ b/src/proguard/classfile/util/MemberFinder.java
@@ -0,0 +1,197 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This class provides methods to find class members in a given class or in its
+ * hierarchy.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberFinder
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private static class MemberFoundException extends RuntimeException {}
+    private static final MemberFoundException MEMBER_FOUND = new MemberFoundException();
+
+    private Clazz  clazz;
+    private Member member;
+
+
+    /**
+     * Finds the field with the given name and descriptor in the given
+     * class or its hierarchy.
+     */
+    public Field findField(Clazz  referencingClass,
+                           Clazz  clazz,
+                           String name,
+                           String descriptor)
+    {
+        return (Field)findMember(referencingClass, clazz, name, descriptor, true);
+    }
+
+
+    /**
+     * Finds the method with the given name and descriptor in the given
+     * class or its hierarchy.
+     */
+    public Method findMethod(Clazz  referencingClass,
+                             Clazz  clazz,
+                             String name,
+                             String descriptor)
+    {
+        return (Method)findMember(referencingClass, clazz, name, descriptor, false);
+    }
+
+
+    /**
+     * Finds the class member with the given name and descriptor in the given
+     * class or its hierarchy.
+     */
+    public Member findMember(Clazz   referencingClass,
+                             Clazz   clazz,
+                             String  name,
+                             String  descriptor,
+                             boolean isField)
+    {
+        // Organize a search in the hierarchy of superclasses and interfaces.
+        // The class member may be in a different class, if the code was
+        // compiled with "-target 1.2" or higher (the default in JDK 1.4).
+        try
+        {
+            this.clazz  = null;
+            this.member = null;
+            clazz.hierarchyAccept(true, true, true, false, isField ?
+                (ClassVisitor)new NamedFieldVisitor(name, descriptor,
+                              new MemberClassAccessFilter(referencingClass, this)) :
+                (ClassVisitor)new NamedMethodVisitor(name, descriptor,
+                              new MemberClassAccessFilter(referencingClass, this)));
+        }
+        catch (MemberFoundException ex)
+        {
+            // We've found the member we were looking for.
+        }
+
+        return member;
+    }
+
+
+    /**
+     * Returns the corresponding class of the most recently found class
+     * member.
+     */
+    public Clazz correspondingClass()
+    {
+        return clazz;
+    }
+
+
+    /**
+     * Returns whether the given method is overridden anywhere down the class
+     * hierarchy.
+     */
+    public boolean isOverriden(Clazz  clazz,
+                               Method method)
+    {
+        String name       = method.getName(clazz);
+        String descriptor = method.getDescriptor(clazz);
+
+        // Go looking for the method down the class hierarchy.
+        try
+        {
+            this.clazz  = null;
+            this.member = null;
+
+            clazz.hierarchyAccept(false, false, false, true,
+                new NamedMethodVisitor(name, descriptor,
+                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, this)));
+        }
+        catch (MemberFoundException ex)
+        {
+            // We've found an overriding method.
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns whether the given field is shadowed anywhere down the class
+     * hierarchy.
+     */
+    public boolean isShadowed(Clazz clazz,
+                              Field field)
+    {
+        String name       = field.getName(clazz);
+        String descriptor = field.getDescriptor(clazz);
+
+        // Go looking for the field down the class hierarchy.
+        try
+        {
+            this.clazz  = null;
+            this.member = null;
+            clazz.hierarchyAccept(false, false, false, true,
+                new NamedFieldVisitor(name, descriptor,
+                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE, this)));
+        }
+        catch (MemberFoundException ex)
+        {
+            // We've found a shadowing field.
+            return true;
+        }
+
+        return false;
+    }
+
+
+//    // Implementations for ClassVisitor.
+//
+//    private void visitAnyClass(Clazz clazz)
+//    {
+//        if (member == null)
+//        {
+//            member = isField ?
+//                (Member)clazz.findField(name, descriptor) :
+//                (Member)clazz.findMethod(name, descriptor);
+//
+//            if (member != null)
+//            {
+//                this.clazz = clazz;
+//            }
+//        }
+//    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        this.clazz  = clazz;
+        this.member = member;
+
+        throw MEMBER_FOUND;
+    }
+}
diff --git a/src/proguard/classfile/util/MethodLinker.java b/src/proguard/classfile/util/MethodLinker.java
new file mode 100644
index 0000000..5f2ea6f
--- /dev/null
+++ b/src/proguard/classfile/util/MethodLinker.java
@@ -0,0 +1,165 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.*;
+
+import java.util.*;
+
+/**
+ * This ClassVisitor links all corresponding non-private methods in the class
+ * hierarchies of all visited classes. Visited classes are typically all class
+ * files that are not being subclassed. Chains of links that have been created
+ * in previous invocations are merged with new chains of links, in order to
+ * create a consistent set of chains.
+ * <p>
+ * As a MemberVisitor, it links all corresponding class members that it visits,
+ * including fields and private class members.
+ * <p>
+ * Class initialization methods and constructors are always ignored.
+ *
+ * @author Eric Lafortune
+ */
+public class MethodLinker
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor
+{
+    // An object that is reset and reused every time.
+    // The map: [class member name+' '+descriptor - class member info]
+    private final Map memberMap = new HashMap();
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitAnyClass(Clazz clazz)
+    {
+        // Collect all non-private members in this class hierarchy.
+        clazz.hierarchyAccept(true, true, true, false,
+            new AllMethodVisitor(
+            new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+            this)));
+
+        // Clean up for the next class hierarchy.
+        memberMap.clear();
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        // Get the class member's name and descriptor.
+        String name       = member.getName(clazz);
+        String descriptor = member.getDescriptor(clazz);
+
+        // Special cases: <clinit> and <init> are always kept unchanged.
+        // We can ignore them here.
+        if (name.equals(ClassConstants.INTERNAL_METHOD_NAME_CLINIT) ||
+            name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            return;
+        }
+
+        // See if we've already come across a method with the same name and
+        // descriptor.
+        String key = name + ' ' + descriptor;
+        Member otherMember = (Member)memberMap.get(key);
+
+        if (otherMember == null)
+        {
+            // Get the last method in the chain.
+            Member thisLastMember = lastMember(member);
+
+            // Store the new class method in the map.
+            memberMap.put(key, thisLastMember);
+        }
+        else
+        {
+            // Link both members.
+            link(member, otherMember);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Links the two given class members.
+     */
+    private static void link(Member member1, Member member2)
+    {
+        // Get the last methods in the both chains.
+        Member lastMember1 = lastMember(member1);
+        Member lastMember2 = lastMember(member2);
+
+        // Check if both link chains aren't already ending in the same element.
+        if (!lastMember1.equals(lastMember2))
+        {
+            // Merge the two chains, with the library members last.
+            if (lastMember2 instanceof LibraryMember)
+            {
+                lastMember1.setVisitorInfo(lastMember2);
+            }
+            else
+            {
+                lastMember2.setVisitorInfo(lastMember1);
+            }
+        }
+    }
+
+
+    /**
+     * Finds the last class member in the linked list of related class members.
+     * @param member the given class member.
+     * @return the last class member in the linked list.
+     */
+    public static Member lastMember(Member member)
+    {
+        Member lastMember = member;
+        while (lastMember.getVisitorInfo() != null &&
+               lastMember.getVisitorInfo() instanceof Member)
+        {
+            lastMember = (Member)lastMember.getVisitorInfo();
+        }
+
+        return lastMember;
+    }
+
+
+    /**
+     * Finds the last visitor accepter in the linked list of visitors.
+     * @param visitorAccepter the given method.
+     * @return the last method in the linked list.
+     */
+    public static VisitorAccepter lastVisitorAccepter(VisitorAccepter visitorAccepter)
+    {
+        VisitorAccepter lastVisitorAccepter = visitorAccepter;
+        while (lastVisitorAccepter.getVisitorInfo() != null &&
+               lastVisitorAccepter.getVisitorInfo() instanceof VisitorAccepter)
+        {
+            lastVisitorAccepter = (VisitorAccepter)lastVisitorAccepter.getVisitorInfo();
+        }
+
+        return lastVisitorAccepter;
+    }
+}
diff --git a/src/proguard/classfile/util/SimplifiedVisitor.java b/src/proguard/classfile/util/SimplifiedVisitor.java
new file mode 100644
index 0000000..87b7fe4
--- /dev/null
+++ b/src/proguard/classfile/util/SimplifiedVisitor.java
@@ -0,0 +1,810 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.instruction.*;
+
+/**
+ * This abstract utility class allows to implement various visitor interfaces
+ * with simplified methods. The provided methods delegate to other versions
+ * with fewer arguments or more general arguments.
+ *
+ * @author Eric Lafortune
+ * @noinspection AbstractClassWithoutAbstractMethods
+ */
+public abstract class SimplifiedVisitor
+{
+    // Simplifications for ClassVisitor.
+
+    /**
+     * Visits any type of class member of the given class.
+     */
+    public void visitAnyClass(Clazz Clazz)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        visitAnyClass(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        visitAnyClass(libraryClass);
+    }
+
+
+    // Simplifications for MemberVisitor.
+
+    /**
+     * Visits any type of class member of the given class.
+     */
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    /**
+     * Visits any type of class member of the given program class.
+     */
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        visitAnyMember(programClass, programMember);
+    }
+
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        visitProgramMember(programClass, programField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        visitProgramMember(programClass, programMethod);
+    }
+
+
+    /**
+     * Visits any type of class member of the given library class.
+     */
+    public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember)
+    {
+        visitAnyMember(libraryClass, libraryMember);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        visitLibraryMember(libraryClass, libraryField);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        visitLibraryMember(libraryClass, libraryMethod);
+    }
+
+
+    // Simplifications for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        visitAnyConstant(clazz, integerConstant);
+    }
+
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        visitAnyConstant(clazz, longConstant);
+    }
+
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        visitAnyConstant(clazz, floatConstant);
+    }
+
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        visitAnyConstant(clazz, doubleConstant);
+    }
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        visitAnyConstant(clazz, stringConstant);
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        visitAnyConstant(clazz, utf8Constant);
+    }
+
+
+    /**
+     * Visits any type of RefConstant of the given class.
+     */
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        visitAnyConstant(clazz, refConstant);
+    }
+
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        visitAnyRefConstant(clazz, fieldrefConstant);
+    }
+
+
+    /**
+     * Visits any type of method RefConstant of the given class.
+     */
+    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        visitAnyRefConstant(clazz, refConstant);
+    }
+
+
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant)
+    {
+        visitAnyMethodrefConstant(clazz, interfaceMethodrefConstant);
+    }
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        visitAnyMethodrefConstant(clazz, methodrefConstant);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        visitAnyConstant(clazz, classConstant);
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        visitAnyConstant(clazz, nameAndTypeConstant);
+    }
+
+
+    // Simplifications for AttributeVisitor.
+
+    /**
+     * Visit any type of attribute.
+     */
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        visitAnyAttribute(clazz, unknownAttribute);
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        visitAnyAttribute(clazz, sourceFileAttribute);
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        visitAnyAttribute(clazz, sourceDirAttribute);
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        visitAnyAttribute(clazz, innerClassesAttribute);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        visitAnyAttribute(clazz, enclosingMethodAttribute);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        visitAnyAttribute(clazz, deprecatedAttribute);
+    }
+
+
+    /**
+     * Visits the given DeprecatedAttribute of any type of class member.
+     */
+    public void visitDeprecatedAttribute(Clazz clazz, Member member, DeprecatedAttribute deprecatedAttribute)
+    {
+        visitDeprecatedAttribute(clazz, deprecatedAttribute);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Field field, DeprecatedAttribute deprecatedAttribute)
+    {
+        visitDeprecatedAttribute(clazz, (Member)field, deprecatedAttribute);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Method method, DeprecatedAttribute deprecatedAttribute)
+    {
+        visitDeprecatedAttribute(clazz, (Member)method, deprecatedAttribute);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        visitAnyAttribute(clazz, syntheticAttribute);
+    }
+
+
+    /**
+     * Visits the given SyntheticAttribute of any type of class member.
+     */
+    public void visitSyntheticAttribute(Clazz clazz, Member member, SyntheticAttribute syntheticAttribute)
+    {
+        visitSyntheticAttribute(clazz, syntheticAttribute);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Field field, SyntheticAttribute syntheticAttribute)
+    {
+        visitSyntheticAttribute(clazz, (Member)field, syntheticAttribute);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Method method, SyntheticAttribute syntheticAttribute)
+    {
+        visitSyntheticAttribute(clazz, (Member)method, syntheticAttribute);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        visitAnyAttribute(clazz, signatureAttribute);
+    }
+
+
+    /**
+     * Visits the given SignatureAttribute of any type of class member.
+     */
+    public void visitSignatureAttribute(Clazz clazz, Member member, SignatureAttribute signatureAttribute)
+    {
+        visitSignatureAttribute(clazz, signatureAttribute);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute signatureAttribute)
+    {
+        visitSignatureAttribute(clazz, (Member)field, signatureAttribute);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute signatureAttribute)
+    {
+        visitSignatureAttribute(clazz, (Member)method, signatureAttribute);
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        visitAnyAttribute(clazz, constantValueAttribute);
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        visitAnyAttribute(clazz, exceptionsAttribute);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        visitAnyAttribute(clazz, codeAttribute);
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        visitAnyAttribute(clazz, stackMapAttribute);
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        visitAnyAttribute(clazz, stackMapTableAttribute);
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        visitAnyAttribute(clazz, lineNumberTableAttribute);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        visitAnyAttribute(clazz, localVariableTableAttribute);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        visitAnyAttribute(clazz, localVariableTypeTableAttribute);
+    }
+
+
+    /**
+     * Visits any type of AnnotationsAttribute of a class.
+     */
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        visitAnyAttribute(clazz, annotationsAttribute);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        visitAnyAnnotationsAttribute(clazz, runtimeVisibleAnnotationsAttribute);
+    }
+
+
+    /**
+     * Visits the given RuntimeVisibleAnnotationsAttribute of any type of class member.
+     */
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Member member, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        visitRuntimeVisibleAnnotationsAttribute(clazz, runtimeVisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        visitRuntimeVisibleAnnotationsAttribute(clazz, (Member)field, runtimeVisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        visitRuntimeVisibleAnnotationsAttribute(clazz, (Member)method, runtimeVisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        visitAnyAnnotationsAttribute(clazz, runtimeInvisibleAnnotationsAttribute);
+    }
+
+
+    /**
+     * Visits the given RuntimeInvisibleAnnotationsAttribute of any type of class member.
+     */
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Member member, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        visitRuntimeInvisibleAnnotationsAttribute(clazz, runtimeInvisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        visitRuntimeInvisibleAnnotationsAttribute(clazz, (Member)field, runtimeInvisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        visitRuntimeInvisibleAnnotationsAttribute(clazz, (Member)method, runtimeInvisibleAnnotationsAttribute);
+    }
+
+
+    /**
+     * Visits any type of ParameterAnnotationsAttribute.
+     */
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        visitAnyAttribute(clazz, parameterAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute)
+    {
+        visitAnyParameterAnnotationsAttribute(clazz, method, runtimeVisibleParameterAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute)
+    {
+        visitAnyParameterAnnotationsAttribute(clazz, method, runtimeInvisibleParameterAnnotationsAttribute);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        visitAnyAttribute(clazz, annotationDefaultAttribute);
+    }
+
+
+    // Simplifications for InstructionVisitor.
+
+    /**
+     * Visits any type of Instruction.
+     */
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        visitAnyInstruction(clazz, method, codeAttribute, offset, simpleInstruction);
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        visitAnyInstruction(clazz, method, codeAttribute, offset, variableInstruction);
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        visitAnyInstruction(clazz, method, codeAttribute, offset, constantInstruction);
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        visitAnyInstruction(clazz, method, codeAttribute, offset, branchInstruction);
+    }
+
+
+    /**
+     * Visits either type of SwitchInstruction.
+     */
+    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
+    {
+        visitAnyInstruction(clazz, method, codeAttribute, offset, switchInstruction);
+    }
+
+
+    public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction)
+    {
+        visitAnySwitchInstruction(clazz, method, codeAttribute, offset, tableSwitchInstruction);
+    }
+
+
+    public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction)
+    {
+        visitAnySwitchInstruction(clazz, method, codeAttribute, offset, lookUpSwitchInstruction);
+    }
+
+
+    // Simplifications for StackMapFrameVisitor.
+
+    /**
+     * Visits any type of VerificationType.
+     */
+    public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    public void visitSameZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameZeroFrame sameZeroFrame)
+    {
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameZeroFrame);
+    }
+
+
+    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+    {
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, sameOneFrame);
+    }
+
+
+    public void visitLessZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LessZeroFrame lessZeroFrame)
+    {
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, lessZeroFrame);
+    }
+
+
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+    {
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, moreZeroFrame);
+    }
+
+
+    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+    {
+        visitAnyStackMapFrame(clazz, method, codeAttribute, offset, fullFrame);
+    }
+
+
+    // Simplifications for VerificationTypeVisitor.
+
+    /**
+     * Visits any type of VerificationType.
+     */
+    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    public void visitIntegerType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, IntegerType integerType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, integerType);
+    }
+
+
+    public void visitFloatType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FloatType floatType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, floatType);
+    }
+
+
+    public void visitLongType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LongType longType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, longType);
+    }
+
+
+    public void visitDoubleType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, DoubleType doubleType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, doubleType);
+    }
+
+
+    public void visitTopType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TopType topType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, topType);
+    }
+
+
+    public void visitObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType objectType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, objectType);
+    }
+
+
+    public void visitNullType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, NullType nullType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, nullType);
+    }
+
+
+    public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, uninitializedType);
+    }
+
+
+    public void visitUninitializedThisType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedThisType uninitializedThisType)
+    {
+        visitAnyVerificationType(clazz, method, codeAttribute, offset, uninitializedThisType);
+    }
+
+
+    public void visitStackIntegerType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, IntegerType integerType)
+    {
+        visitIntegerType(clazz, method, codeAttribute, offset, integerType);
+    }
+
+
+    public void visitStackFloatType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, FloatType floatType)
+    {
+        visitFloatType(clazz, method, codeAttribute, offset, floatType);
+    }
+
+
+    public void visitStackLongType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, LongType longType)
+    {
+        visitLongType(clazz, method, codeAttribute, offset, longType);
+    }
+
+
+    public void visitStackDoubleType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, DoubleType doubleType)
+    {
+        visitDoubleType(clazz, method, codeAttribute, offset, doubleType);
+    }
+
+
+    public void visitStackTopType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, TopType topType)
+    {
+        visitTopType(clazz, method, codeAttribute, offset, topType);
+    }
+
+
+    public void visitStackObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, ObjectType objectType)
+    {
+        visitObjectType(clazz, method, codeAttribute, offset, objectType);
+    }
+
+
+    public void visitStackNullType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, NullType nullType)
+    {
+        visitNullType(clazz, method, codeAttribute, offset, nullType);
+    }
+
+
+    public void visitStackUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, UninitializedType uninitializedType)
+    {
+        visitUninitializedType(clazz, method, codeAttribute, offset, uninitializedType);
+    }
+
+
+    public void visitStackUninitializedThisType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, UninitializedThisType uninitializedThisType)
+    {
+        visitUninitializedThisType(clazz, method, codeAttribute, offset, uninitializedThisType);
+    }
+
+
+
+    public void visitVariablesIntegerType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, IntegerType integerType)
+    {
+        visitIntegerType(clazz, method, codeAttribute, offset, integerType);
+    }
+
+
+    public void visitVariablesFloatType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, FloatType floatType)
+    {
+        visitFloatType(clazz, method, codeAttribute, offset, floatType);
+    }
+
+
+    public void visitVariablesLongType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, LongType longType)
+    {
+        visitLongType(clazz, method, codeAttribute, offset, longType);
+    }
+
+
+    public void visitVariablesDoubleType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, DoubleType doubleType)
+    {
+        visitDoubleType(clazz, method, codeAttribute, offset, doubleType);
+    }
+
+
+    public void visitVariablesTopType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, TopType topType)
+    {
+        visitTopType(clazz, method, codeAttribute, offset, topType);
+    }
+
+
+    public void visitVariablesObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, ObjectType objectType)
+    {
+        visitObjectType(clazz, method, codeAttribute, offset, objectType);
+    }
+
+
+    public void visitVariablesNullType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, NullType nullType)
+    {
+        visitNullType(clazz, method, codeAttribute, offset, nullType);
+    }
+
+
+    public void visitVariablesUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, UninitializedType uninitializedType)
+    {
+        visitUninitializedType(clazz, method, codeAttribute, offset, uninitializedType);
+    }
+
+
+    public void visitVariablesUninitializedThisType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int index, UninitializedThisType uninitializedThisType)
+    {
+        visitUninitializedThisType(clazz, method, codeAttribute, offset, uninitializedThisType);
+    }
+
+
+    // Simplifications for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    /**
+     * Visits the given Annotation of any type of class member.
+     */
+    public void visitAnnotation(Clazz clazz, Member member, Annotation annotation)
+    {
+        visitAnnotation(clazz, annotation);
+    }
+
+
+    public void visitAnnotation(Clazz clazz, Field field, Annotation annotation)
+    {
+        visitAnnotation(clazz, (Member)field, annotation);
+    }
+
+
+    public void visitAnnotation(Clazz clazz, Method method, Annotation annotation)
+    {
+        visitAnnotation(clazz, (Member)method, annotation);
+    }
+
+
+    public void visitAnnotation(Clazz clazz, Method method, int parameterIndex, Annotation annotation)
+    {
+        visitAnnotation(clazz, method, annotation);
+    }
+
+
+    // Simplifications for ElementValueVisitor.
+
+    public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue)
+    {
+        throw new UnsupportedOperationException("Method must be overridden in ["+this.getClass().getName()+"] if ever called");
+    }
+
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        visitAnyElementValue(clazz, annotation, constantElementValue);
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        visitAnyElementValue(clazz, annotation, enumConstantElementValue);
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        visitAnyElementValue(clazz, annotation, classElementValue);
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        visitAnyElementValue(clazz, annotation, annotationElementValue);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        visitAnyElementValue(clazz, annotation, arrayElementValue);
+    }
+}
diff --git a/src/proguard/classfile/util/StringReferenceInitializer.java b/src/proguard/classfile/util/StringReferenceInitializer.java
new file mode 100644
index 0000000..3884a04
--- /dev/null
+++ b/src/proguard/classfile/util/StringReferenceInitializer.java
@@ -0,0 +1,89 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+
+/**
+ * This ConstantVisitor initializes any class references of all string constants
+ * it visits. More specifically, it fills out the references of string constant
+ * pool entries that happen to refer to a class in the program class pool or in
+ * the library class pool.
+ *
+ * @author Eric Lafortune
+ */
+public class StringReferenceInitializer
+extends      SimplifiedVisitor
+implements   ConstantVisitor
+{
+    private final ClassPool programClassPool;
+    private final ClassPool libraryClassPool;
+
+
+    /**
+     * Creates a new StringReferenceInitializer.
+     */
+    public StringReferenceInitializer(ClassPool programClassPool,
+                                      ClassPool libraryClassPool)
+    {
+        this.programClassPool = programClassPool;
+        this.libraryClassPool = libraryClassPool;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        if (stringConstant.referencedClass == null)
+        {
+            // See if we can find the referenced class.
+            stringConstant.referencedClass =
+                findClass(ClassUtil.internalClassName(stringConstant.getString(clazz)));
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the class with the given name, either for the program class pool
+     * or from the library class pool, or <code>null</code> if it can't be found.
+     */
+    private Clazz findClass(String name)
+    {
+        // First look for the class in the program class pool.
+        Clazz clazz = programClassPool.getClass(name);
+
+        // Otherwise look for the class in the library class pool.
+        if (clazz == null)
+        {
+            clazz = libraryClassPool.getClass(name);
+        }
+
+        return clazz;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/util/StringSharer.java b/src/proguard/classfile/util/StringSharer.java
new file mode 100644
index 0000000..56de7c5
--- /dev/null
+++ b/src/proguard/classfile/util/StringSharer.java
@@ -0,0 +1,155 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor shares strings in the class files that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class StringSharer
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             AttributeVisitor
+{
+    // A fields acting as an argument for the visitor methods.
+    private String name;
+    private String type;
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Replace name strings in the constant pool by shared strings.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Replace attribute name strings in the constant pool by internalized
+        // strings.
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Replace the super class name string by the shared name string.
+        Clazz superClass = libraryClass.superClass;
+        if (superClass != null)
+        {
+            libraryClass.superClassName = superClass.getName();
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitAnyStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        Member referencedMember = stringConstant.referencedMember;
+        if (referencedMember != null)
+        {
+            Clazz referencedClass = stringConstant.referencedClass;
+
+            // Put the actual class member's name in the class pool.
+            name = referencedMember.getName(referencedClass);
+            clazz.constantPoolEntryAccept(stringConstant.u2stringIndex, this);
+        }
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        Member referencedMember = refConstant.referencedMember;
+        if (referencedMember != null)
+        {
+            Clazz referencedClass = refConstant.referencedClass;
+
+            // Put the actual class member's name and type strings in the class
+            // pool.
+            name = referencedMember.getName(referencedClass);
+            type = referencedMember.getDescriptor(referencedClass);
+            clazz.constantPoolEntryAccept(refConstant.u2nameAndTypeIndex, this);
+        }
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        if (name != null)
+        {
+            // Put the actual class member's name and type strings in the class
+            // pool.
+            clazz.constantPoolEntryAccept(nameAndTypeConstant.u2nameIndex, this);
+            name = type;
+            clazz.constantPoolEntryAccept(nameAndTypeConstant.u2descriptorIndex, this);
+        }
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        Clazz referencedClass = classConstant.referencedClass;
+        if (referencedClass != null)
+        {
+            // Put the actual class's name string in the class pool.
+            name = referencedClass.getName();
+            clazz.constantPoolEntryAccept(classConstant.u2nameIndex, this);
+        }
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        // Do we have a new string to put into this constant?
+        if (name != null)
+        {
+            // Replace the string, if it's actually the same.
+            if (name.equals(utf8Constant.getString()))
+            {
+                utf8Constant.setString(name);
+            }
+
+            name = null;
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute)
+    {
+        // Put the internalized attribute's name string in the class pool.
+        name = attribute.getAttributeName(clazz).intern();
+        clazz.constantPoolEntryAccept(attribute.u2attributeNameIndex, this);
+    }
+}
diff --git a/src/proguard/classfile/util/WarningPrinter.java b/src/proguard/classfile/util/WarningPrinter.java
new file mode 100644
index 0000000..87d8978
--- /dev/null
+++ b/src/proguard/classfile/util/WarningPrinter.java
@@ -0,0 +1,136 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.util;
+
+import proguard.util.*;
+
+import java.io.PrintStream;
+import java.util.List;
+
+/**
+ * This class prints out and counts warnings.
+ *
+ * @author Eric Lafortune
+ */
+public class WarningPrinter
+{
+    private final PrintStream   printStream;
+    private final StringMatcher classFilter;
+    private int                 warningCount;
+
+
+    /**
+     * Creates a new WarningPrinter that prints to the System.err print stream.
+     */
+    public WarningPrinter()
+    {
+        this(System.err);
+    }
+
+
+    /**
+     * Creates a new WarningPrinter that prints to the given print stream.
+     */
+    public WarningPrinter(PrintStream printStream)
+    {
+        this.printStream = printStream;
+        this.classFilter = null;
+    }
+
+
+    /**
+     * Creates a new WarningPrinter that prints to the given print stream,
+     * except if the names of any involved classes matches the given filter.
+     */
+    public WarningPrinter(PrintStream printStream, List classFilter)
+    {
+        this.printStream = printStream;
+        this.classFilter = classFilter == null ? null :
+            new ListParser(new ClassNameParser()).parse(classFilter);
+    }
+
+
+    /**
+     * Prints out the given warning and increments the warning count, if
+     * the given class name passes the class name filter.
+     */
+    public void print(String className, String warning)
+    {
+        if (accepts(className))
+        {
+            print(warning);
+        }
+    }
+
+
+    /**
+     * Returns whether the given class name passes the class name filter.
+     */
+    public boolean accepts(String className)
+    {
+        return classFilter == null ||
+            !classFilter.matches(className);
+    }
+
+
+    /**
+     * Prints out the given warning and increments the warning count, if
+     * the given class names pass the class name filter.
+     */
+    public void print(String className1, String className2, String warning)
+    {
+        if (accepts(className1, className2))
+        {
+            print(warning);
+        }
+    }
+
+
+    /**
+     * Returns whether the given class names pass the class name filter.
+     */
+    public boolean accepts(String className1, String className2)
+    {
+        return classFilter == null ||
+            !(classFilter.matches(className1) ||
+              classFilter.matches(className2));
+    }
+
+
+    /**
+     * Prints out the given warning and increments the warning count.
+     */
+    private void print(String warning)
+    {
+        printStream.println(warning);
+
+        warningCount++;
+    }
+
+
+    /**
+     * Returns the number of warnings printed so far.
+     */
+    public int getWarningCount()
+    {
+        return warningCount;
+    }
+}
diff --git a/src/proguard/classfile/util/package.html b/src/proguard/classfile/util/package.html
new file mode 100644
index 0000000..b1b881e
--- /dev/null
+++ b/src/proguard/classfile/util/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains utility classes for processing class files.
+</body>
diff --git a/src/proguard/classfile/visitor/AllClassVisitor.java b/src/proguard/classfile/visitor/AllClassVisitor.java
new file mode 100644
index 0000000..06aca2c
--- /dev/null
+++ b/src/proguard/classfile/visitor/AllClassVisitor.java
@@ -0,0 +1,47 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.ClassPool;
+
+
+/**
+ * This ClassPoolVisitor lets a given ClassVisitor visit all Clazz
+ * objects of the class pools it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllClassVisitor implements ClassPoolVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    public AllClassVisitor(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    public void visitClassPool(ClassPool classPool)
+    {
+        classPool.classesAccept(classVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/AllFieldVisitor.java b/src/proguard/classfile/visitor/AllFieldVisitor.java
new file mode 100644
index 0000000..8bff7d4
--- /dev/null
+++ b/src/proguard/classfile/visitor/AllFieldVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This ClassVisitor lets a given MemberVisitor visit all FieldMember
+ * objects of the classes      it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllFieldVisitor implements ClassVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    public AllFieldVisitor(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.fieldsAccept(memberVisitor);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.fieldsAccept(memberVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/AllMemberVisitor.java b/src/proguard/classfile/visitor/AllMemberVisitor.java
new file mode 100644
index 0000000..448470e
--- /dev/null
+++ b/src/proguard/classfile/visitor/AllMemberVisitor.java
@@ -0,0 +1,57 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This ClassVisitor lets a given MemberVisitor visit all Member
+ * objects of the classes      it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllMemberVisitor implements ClassVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    public AllMemberVisitor(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.fieldsAccept(memberVisitor);
+        programClass.methodsAccept(memberVisitor);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.fieldsAccept(memberVisitor);
+        libraryClass.methodsAccept(memberVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/AllMethodVisitor.java b/src/proguard/classfile/visitor/AllMethodVisitor.java
new file mode 100644
index 0000000..75b919d
--- /dev/null
+++ b/src/proguard/classfile/visitor/AllMethodVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This ClassVisitor lets a given MemberVisitor visit all MethodMember
+ * objects of the classes      it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AllMethodVisitor implements ClassVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    public AllMethodVisitor(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.methodsAccept(memberVisitor);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.methodsAccept(memberVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/BottomClassFilter.java b/src/proguard/classfile/visitor/BottomClassFilter.java
new file mode 100644
index 0000000..8f5bdd1
--- /dev/null
+++ b/src/proguard/classfile/visitor/BottomClassFilter.java
@@ -0,0 +1,69 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to another given
+ * <code>ClassVisitor</code>, but only when visiting classes that don't
+ * have any subclasses.
+ *
+ * @author Eric Lafortune
+ */
+public class BottomClassFilter implements ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ProgramClassFilter.
+     * @param classVisitor     the <code>ClassVisitor</code> to which visits
+     *                         will be delegated.
+     */
+    public BottomClassFilter(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Is this a bottom class in the class hierarchy?
+        if (programClass.subClasses == null)
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Is this a bottom class in the class hierarchy?
+        if (libraryClass.subClasses == null)
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassAccessFilter.java b/src/proguard/classfile/visitor/ClassAccessFilter.java
new file mode 100644
index 0000000..a8815b6
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassAccessFilter.java
@@ -0,0 +1,88 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>ClassVisitor</code>     delegates its visits to another given
+ * <code>ClassVisitor</code>, but only when the visited class
+ * has the proper access flags.
+ *
+ * @see ClassConstants
+ *
+ * @author Eric Lafortune
+ */
+public class ClassAccessFilter implements ClassVisitor
+{
+    private final int          requiredSetAccessFlags;
+    private final int          requiredUnsetAccessFlags;
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ClassAccessFilter.
+     * @param requiredSetAccessFlags   the class access flags that should be
+     *                                 set.
+     * @param requiredUnsetAccessFlags the class access flags that should be
+     *                                 unset.
+     * @param classVisitor             the <code>ClassVisitor</code> to
+     *                                 which visits will be delegated.
+     */
+    public ClassAccessFilter(int          requiredSetAccessFlags,
+                             int          requiredUnsetAccessFlags,
+                             ClassVisitor classVisitor)
+    {
+        this.requiredSetAccessFlags   = requiredSetAccessFlags;
+        this.requiredUnsetAccessFlags = requiredUnsetAccessFlags;
+        this.classVisitor             = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (accepted(programClass.getAccessFlags()))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (accepted(libraryClass.getAccessFlags()))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean accepted(int accessFlags)
+    {
+        return (requiredSetAccessFlags   & ~accessFlags) == 0 &&
+               (requiredUnsetAccessFlags &  accessFlags) == 0;
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassCleaner.java b/src/proguard/classfile/visitor/ClassCleaner.java
new file mode 100644
index 0000000..36165ef
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassCleaner.java
@@ -0,0 +1,275 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.preverification.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.Constant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This <code>ClassVisitor</code> removes all visitor information of the
+ * classes it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassCleaner
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             MemberVisitor,
+             AttributeVisitor,
+             ExceptionInfoVisitor,
+             InnerClassesInfoVisitor,
+             StackMapFrameVisitor,
+             VerificationTypeVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        clean(programClass);
+
+        programClass.constantPoolEntriesAccept(this);
+
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        clean(libraryClass);
+
+        libraryClass.fieldsAccept(this);
+        libraryClass.methodsAccept(this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant)
+    {
+        clean(constant);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        clean(programMember);
+
+        programMember.attributesAccept(programClass, this);
+    }
+
+
+    public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember)
+    {
+        clean(libraryMember);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute)
+    {
+        clean(attribute);
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        clean(innerClassesAttribute);
+
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        clean(exceptionsAttribute);
+
+        exceptionsAttribute.exceptionEntriesAccept((ProgramClass)clazz, this);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        clean(codeAttribute);
+
+        codeAttribute.exceptionsAccept(clazz, method, this);
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        clean(stackMapAttribute);
+
+        stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        clean(stackMapTableAttribute);
+
+        stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        clean(annotationsAttribute);
+
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        clean(parameterAnnotationsAttribute);
+
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        clean(annotationDefaultAttribute);
+
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        clean(innerClassesInfo);
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        clean(exceptionInfo);
+    }
+
+
+    // Implementations for StackMapFrameVisitor.
+
+    public void visitSameZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameZeroFrame sameZeroFrame)
+    {
+        clean(sameZeroFrame);
+    }
+
+
+    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+    {
+        clean(sameOneFrame);
+
+        sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitLessZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LessZeroFrame lessZeroFrame)
+    {
+        clean(lessZeroFrame);
+    }
+
+
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+    {
+        clean(moreZeroFrame);
+
+        moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+    {
+        clean(fullFrame);
+
+        fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
+        fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for VerificationTypeVisitor.
+
+    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType)
+    {
+        clean(verificationType);
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        clean(annotation);
+
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue)
+    {
+        clean(elementValue);
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        clean(annotationElementValue);
+
+        annotationElementValue.annotationAccept(clazz, this);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        clean(arrayElementValue);
+    }
+
+
+    // Small utility methods.
+
+    private void clean(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(null);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassCollector.java b/src/proguard/classfile/visitor/ClassCollector.java
new file mode 100644
index 0000000..a69fe76
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassCollector.java
@@ -0,0 +1,58 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.util.SimplifiedVisitor;
+
+import java.util.Set;
+
+/**
+ * This <code>ClassVisitor</code> collects the classes that it visits in the
+ * given collection.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassCollector
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private final Set set;
+
+
+    /**
+     * Creates a new ClassCollector.
+     * @param set the <code>Set</code> in which all class names will be
+     *            collected.
+     */
+    public ClassCollector(Set set)
+    {
+        this.set = set;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitAnyClass(Clazz clazz)
+    {
+        set.add(clazz);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassCounter.java b/src/proguard/classfile/visitor/ClassCounter.java
new file mode 100644
index 0000000..c58c090
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassCounter.java
@@ -0,0 +1,56 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This ClassVisitor counts the number of classes that has been visited.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassCounter implements ClassVisitor
+{
+    private int count;
+
+
+    /**
+     * Returns the number of classes that has been visited so far.
+     */
+    public int getCount()
+    {
+        return count;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        count++;
+    }
+
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        count++;
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassForNameClassVisitor.java b/src/proguard/classfile/visitor/ClassForNameClassVisitor.java
new file mode 100644
index 0000000..ee028f8
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassForNameClassVisitor.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+
+/**
+ * This ConstantVisitor lets a given <code>ClassVisitor</code> visit all
+ * constant classes involved in any <code>Class.forName</code> constructs that
+ * it visits.
+ *
+ * @see DotClassClassVisitor
+ *
+ * @author Eric Lafortune
+ */
+public class ClassForNameClassVisitor
+extends      SimplifiedVisitor
+implements   ConstantVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ClassHierarchyTraveler.
+     * @param classVisitor the <code>ClassVisitor</code> to which visits will
+     *                     be delegated.
+     */
+    public ClassForNameClassVisitor(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Visit the referenced class from the Class.forName construct, if any.
+        stringConstant.referencedClassAccept(classVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassHierarchyTraveler.java b/src/proguard/classfile/visitor/ClassHierarchyTraveler.java
new file mode 100644
index 0000000..2e1755e
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassHierarchyTraveler.java
@@ -0,0 +1,91 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>ClassVisitor</code> lets a given <code>ClassVisitor</code>
+ * optionally travel to the visited class, its superclass, its interfaces, and
+ * its subclasses.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassHierarchyTraveler implements ClassVisitor
+{
+    private final boolean visitThisClass;
+    private final boolean visitSuperClass;
+    private final boolean visitInterfaces;
+    private final boolean visitSubclasses;
+
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ClassHierarchyTraveler.
+     * @param visitThisClass  specifies whether to visit the originally visited
+     *                        classes.
+     * @param visitSuperClass specifies whether to visit the super classes of
+     *                        the visited classes.
+     * @param visitInterfaces specifies whether to visit the interfaces of
+     *                        the visited classes.
+     * @param visitSubclasses specifies whether to visit the subclasses of
+     *                        the visited classes.
+     * @param classVisitor    the <code>ClassVisitor</code> to
+     *                        which visits will be delegated.
+     */
+    public ClassHierarchyTraveler(boolean      visitThisClass,
+                                  boolean      visitSuperClass,
+                                  boolean      visitInterfaces,
+                                  boolean      visitSubclasses,
+                                  ClassVisitor classVisitor)
+    {
+        this.visitThisClass  = visitThisClass;
+        this.visitSuperClass = visitSuperClass;
+        this.visitInterfaces = visitInterfaces;
+        this.visitSubclasses = visitSubclasses;
+
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.hierarchyAccept(visitThisClass,
+                                     visitSuperClass,
+                                     visitInterfaces,
+                                     visitSubclasses,
+                                     classVisitor);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.hierarchyAccept(visitThisClass,
+                                     visitSuperClass,
+                                     visitInterfaces,
+                                     visitSubclasses,
+                                     classVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassNameFilter.java b/src/proguard/classfile/visitor/ClassNameFilter.java
new file mode 100644
index 0000000..c016a34
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassNameFilter.java
@@ -0,0 +1,112 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.util.*;
+
+import java.util.List;
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to another given
+ * <code>ClassVisitor</code>, but only when the visited class has a name that
+ * matches a given regular expression.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassNameFilter implements ClassVisitor
+{
+    private final StringMatcher regularExpressionMatcher;
+    private final ClassVisitor  classVisitor;
+
+
+    /**
+     * Creates a new ClassNameFilter.
+     * @param regularExpression the regular expression against which class names
+     *                          will be matched.
+     * @param classVisitor      the <code>ClassVisitor</code> to which visits
+     *                          will be delegated.
+     */
+    public ClassNameFilter(String       regularExpression,
+                           ClassVisitor classVisitor)
+    {
+        this(new ListParser(new ClassNameParser()).parse(regularExpression),
+             classVisitor);
+    }
+
+
+    /**
+     * Creates a new ClassNameFilter.
+     * @param regularExpression the regular expression against which class names
+     *                          will be matched.
+     * @param classVisitor      the <code>ClassVisitor</code> to which visits
+     *                          will be delegated.
+     */
+    public ClassNameFilter(List         regularExpression,
+                           ClassVisitor classVisitor)
+    {
+        this(new ListParser(new ClassNameParser()).parse(regularExpression),
+             classVisitor);
+    }
+
+
+    /**
+     * Creates a new ClassNameFilter.
+     * @param regularExpressionMatcher the regular expression against which
+     *                                 class names will be matched.
+     * @param classVisitor             the <code>ClassVisitor</code> to which
+     *                                 visits will be delegated.
+     */
+    public ClassNameFilter(StringMatcher regularExpressionMatcher,
+                           ClassVisitor  classVisitor)
+    {
+        this.regularExpressionMatcher = regularExpressionMatcher;
+        this.classVisitor             = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (accepted(programClass.getName()))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (accepted(libraryClass.getName()))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean accepted(String name)
+    {
+        return regularExpressionMatcher.matches(name);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassPoolFiller.java b/src/proguard/classfile/visitor/ClassPoolFiller.java
new file mode 100644
index 0000000..e1773de
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassPoolFiller.java
@@ -0,0 +1,55 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+
+
+/**
+ * This ClassVisitor collects all the classes it visits in a given
+ * class pool.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassPoolFiller
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private final ClassPool classPool;
+
+
+    /**
+     * Creates a new ClassPoolFiller.
+     */
+    public ClassPoolFiller(ClassPool classPool)
+    {
+        this.classPool = classPool;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitAnyClass(Clazz clazz)
+    {
+        classPool.addClass(clazz);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassPoolVisitor.java b/src/proguard/classfile/visitor/ClassPoolVisitor.java
new file mode 100644
index 0000000..0b659dc
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassPoolVisitor.java
@@ -0,0 +1,37 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.ClassPool;
+
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>ClassPool</code> objects. Note that there is only a single
+ * implementation of <code>ClassPool</code>, such that this interface
+ * is not strictly necessary as a visitor.
+ *
+ * @author Eric Lafortune
+ */
+public interface ClassPoolVisitor
+{
+    public void visitClassPool(ClassPool classPool);
+}
diff --git a/src/proguard/classfile/visitor/ClassPresenceFilter.java b/src/proguard/classfile/visitor/ClassPresenceFilter.java
new file mode 100644
index 0000000..429c340
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassPresenceFilter.java
@@ -0,0 +1,93 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to one of two
+ * <code>ClassVisitor</code> instances, depending on whether the name of
+ * the visited class file is present in a given <code>ClassPool</code> or not.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassPresenceFilter implements ClassVisitor
+{
+    private final ClassPool    classPool;
+    private final ClassVisitor presentClassVisitor;
+    private final ClassVisitor missingClassVisitor;
+
+
+    /**
+     * Creates a new ClassPresenceFilter.
+     * @param classPool           the <code>ClassPool</code> in which the
+     *                            presence will be tested.
+     * @param presentClassVisitor the <code>ClassVisitor</code> to which visits
+     *                            of present class files will be delegated.
+     * @param missingClassVisitor the <code>ClassVisitor</code> to which visits
+     *                            of missing class files will be delegated.
+     */
+    public ClassPresenceFilter(ClassPool    classPool,
+                               ClassVisitor presentClassVisitor,
+                               ClassVisitor missingClassVisitor)
+    {
+        this.classPool           = classPool;
+        this.presentClassVisitor = presentClassVisitor;
+        this.missingClassVisitor = missingClassVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        ClassVisitor classFileVisitor = classFileVisitor(programClass);
+
+        if (classFileVisitor != null)
+        {
+            classFileVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        ClassVisitor classFileVisitor = classFileVisitor(libraryClass);
+
+        if (classFileVisitor != null)
+        {
+            classFileVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the appropriate <code>ClassVisitor</code>.
+     */
+    private ClassVisitor classFileVisitor(Clazz clazz)
+    {
+        return classPool.getClass(clazz.getName()) != null ?
+            presentClassVisitor :
+            missingClassVisitor;
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassPrinter.java b/src/proguard/classfile/visitor/ClassPrinter.java
new file mode 100644
index 0000000..1da7d16
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassPrinter.java
@@ -0,0 +1,954 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.preverification.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.*;
+
+import java.io.PrintStream;
+
+
+/**
+ * This <code>ClassVisitor</code> prints out the complete internal
+ * structure of the classes it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassPrinter
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             MemberVisitor,
+             AttributeVisitor,
+             ExceptionInfoVisitor,
+             InnerClassesInfoVisitor,
+             StackMapFrameVisitor,
+             VerificationTypeVisitor,
+             LineNumberInfoVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor,
+             InstructionVisitor
+{
+    private static final String INDENTATION = "  ";
+
+    private final PrintStream ps;
+    private int         indentation;
+
+
+    /**
+     * Creates a new ClassPrinter that prints to <code>System.out</code>.
+     */
+    public ClassPrinter()
+    {
+        this(System.out);
+    }
+
+
+    /**
+     * Creates a new ClassPrinter that prints to the given
+     * <code>PrintStream</code>.
+     */
+    public ClassPrinter(PrintStream printStream)
+    {
+        ps = printStream;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        println("_____________________________________________________________________");
+        println(visitorInfo(programClass) + " " +
+                "Program class: " + programClass.getName());
+        indent();
+        println("Superclass:    " + programClass.getSuperName());
+        println("Major version: 0x" + Integer.toHexString(ClassUtil.internalMajorClassVersion(programClass.u4version)));
+        println("Minor version: 0x" + Integer.toHexString(ClassUtil.internalMinorClassVersion(programClass.u4version)));
+        println("Access flags:  0x" + Integer.toHexString(programClass.u2accessFlags));
+        println("  = " +
+                ((programClass.u2accessFlags & ClassConstants.INTERNAL_ACC_ANNOTATTION) != 0 ? "@ " : "") +
+                ClassUtil.externalClassAccessFlags(programClass.u2accessFlags) +
+                ((programClass.u2accessFlags & ClassConstants.INTERNAL_ACC_ENUM)      != 0 ? "enum " :
+                 (programClass.u2accessFlags & ClassConstants.INTERNAL_ACC_INTERFACE) == 0 ? "class " :
+                                                                                             "") +
+                ClassUtil.externalClassName(programClass.getName()) +
+                (programClass.u2superClass == 0 ? "" : " extends " +
+                ClassUtil.externalClassName(programClass.getSuperName())));
+        outdent();
+        println();
+
+        println("Interfaces (count = " + programClass.u2interfacesCount + "):");
+        indent();
+        programClass.interfaceConstantsAccept(this);
+        outdent();
+        println();
+
+        println("Constant Pool (count = " + programClass.u2constantPoolCount + "):");
+        indent();
+        programClass.constantPoolEntriesAccept(this);
+        outdent();
+        println();
+
+        println("Fields (count = " + programClass.u2fieldsCount + "):");
+        indent();
+        programClass.fieldsAccept(this);
+        outdent();
+        println();
+
+        println("Methods (count = " + programClass.u2methodsCount + "):");
+        indent();
+        programClass.methodsAccept(this);
+        outdent();
+        println();
+
+        println("Class file attributes (count = " + programClass.u2attributesCount + "):");
+        indent();
+        programClass.attributesAccept(this);
+        outdent();
+        println();
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        println("_____________________________________________________________________");
+        println(visitorInfo(libraryClass) + " " +
+                "Library class: " + libraryClass.getName());
+        indent();
+        println("Superclass:    " + libraryClass.getSuperName());
+        println("Access flags:  0x" + Integer.toHexString(libraryClass.u2accessFlags));
+        println("  = " +
+                ((libraryClass.u2accessFlags & ClassConstants.INTERNAL_ACC_ANNOTATTION) != 0 ? "@ " : "") +
+                ClassUtil.externalClassAccessFlags(libraryClass.u2accessFlags) +
+                ((libraryClass.u2accessFlags & ClassConstants.INTERNAL_ACC_ENUM)      != 0 ? "enum " :
+                 (libraryClass.u2accessFlags & ClassConstants.INTERNAL_ACC_INTERFACE) == 0 ? "class " :
+                                                                                             "") +
+                ClassUtil.externalClassName(libraryClass.getName()) +
+                (libraryClass.getSuperName() == null ? "" : " extends "  +
+                ClassUtil.externalClassName(libraryClass.getSuperName())));
+        outdent();
+        println();
+
+        println("Interfaces (count = " + libraryClass.interfaceClasses.length + "):");
+        for (int index = 0; index < libraryClass.interfaceClasses.length; index++)
+        {
+            Clazz interfaceClass = libraryClass.interfaceClasses[index];
+            if (interfaceClass != null)
+            {
+                println("  + " + interfaceClass.getName());
+            }
+        }
+
+        println("Fields (count = " + libraryClass.fields.length + "):");
+        libraryClass.fieldsAccept(this);
+
+        println("Methods (count = " + libraryClass.methods.length + "):");
+        libraryClass.methodsAccept(this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        println(visitorInfo(integerConstant) + " Integer [" +
+                integerConstant.getValue() + "]");
+    }
+
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        println(visitorInfo(longConstant) + " Long [" +
+                longConstant.getValue() + "]");
+    }
+
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        println(visitorInfo(floatConstant) + " Float [" +
+                floatConstant.getValue() + "]");
+    }
+
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        println(visitorInfo(doubleConstant) + " Double [" +
+                doubleConstant.getValue() + "]");
+    }
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        println(visitorInfo(stringConstant) + " String [" +
+                clazz.getString(stringConstant.u2stringIndex) + "]");
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        println(visitorInfo(utf8Constant) + " Utf8 [" +
+                utf8Constant.getString() + "]");
+    }
+
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        println(visitorInfo(fieldrefConstant) + " Fieldref [" +
+                clazz.getClassName(fieldrefConstant.u2classIndex)  + "." +
+                clazz.getName(fieldrefConstant.u2nameAndTypeIndex) + " " +
+                clazz.getType(fieldrefConstant.u2nameAndTypeIndex) + "]");
+    }
+
+
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant)
+    {
+        println(visitorInfo(interfaceMethodrefConstant) + " InterfaceMethodref [" +
+                clazz.getClassName(interfaceMethodrefConstant.u2classIndex)  + "." +
+                clazz.getName(interfaceMethodrefConstant.u2nameAndTypeIndex) + " " +
+                clazz.getType(interfaceMethodrefConstant.u2nameAndTypeIndex) + "]");
+    }
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        println(visitorInfo(methodrefConstant) + " Methodref [" +
+                clazz.getClassName(methodrefConstant.u2classIndex)  + "." +
+                clazz.getName(methodrefConstant.u2nameAndTypeIndex) + " " +
+                clazz.getType(methodrefConstant.u2nameAndTypeIndex) + "]");
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        println(visitorInfo(classConstant) + " Class [" +
+                clazz.getString(classConstant.u2nameIndex) + "]");
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        println(visitorInfo(nameAndTypeConstant) + " NameAndType [" +
+                clazz.getString(nameAndTypeConstant.u2nameIndex) + " " +
+                clazz.getString(nameAndTypeConstant.u2descriptorIndex) + "]");
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        println(visitorInfo(programField) + " " +
+                "Field:        " +
+                programField.getName(programClass) + " " +
+                programField.getDescriptor(programClass));
+
+        indent();
+        println("Access flags: 0x" + Integer.toHexString(programField.u2accessFlags));
+        println("  = " +
+                ClassUtil.externalFullFieldDescription(programField.u2accessFlags,
+                                                       programField.getName(programClass),
+                                                       programField.getDescriptor(programClass)));
+
+        visitMember(programClass, programField);
+        outdent();
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        println(visitorInfo(programMethod) + " " +
+                "Method:       " +
+                programMethod.getName(programClass) +
+                programMethod.getDescriptor(programClass));
+
+        indent();
+        println("Access flags: 0x" + Integer.toHexString(programMethod.u2accessFlags));
+        println("  = " +
+                ClassUtil.externalFullMethodDescription(programClass.getName(),
+                                                        programMethod.u2accessFlags,
+                                                        programMethod.getName(programClass),
+                                                        programMethod.getDescriptor(programClass)));
+
+        visitMember(programClass, programMethod);
+        outdent();
+    }
+
+
+    private void visitMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        if (programMember.u2attributesCount > 0)
+        {
+            println("Class member attributes (count = " + programMember.u2attributesCount + "):");
+            programMember.attributesAccept(programClass, this);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        println(visitorInfo(libraryField) + " " +
+                "Field:        " +
+                libraryField.getName(libraryClass) + " " +
+                libraryField.getDescriptor(libraryClass));
+
+        indent();
+        println("Access flags: 0x" + Integer.toHexString(libraryField.u2accessFlags));
+        println("  = " +
+                ClassUtil.externalFullFieldDescription(libraryField.u2accessFlags,
+                                                       libraryField.getName(libraryClass),
+                                                       libraryField.getDescriptor(libraryClass)));
+        outdent();
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        println(visitorInfo(libraryMethod) + " " +
+                "Method:       " +
+                libraryMethod.getName(libraryClass) + " " +
+                libraryMethod.getDescriptor(libraryClass));
+
+        indent();
+        println("Access flags: 0x" + Integer.toHexString(libraryMethod.u2accessFlags));
+        println("  = " +
+                ClassUtil.externalFullMethodDescription(libraryClass.getName(),
+                                                        libraryMethod.u2accessFlags,
+                                                        libraryMethod.getName(libraryClass),
+                                                        libraryMethod.getDescriptor(libraryClass)));
+        outdent();
+    }
+
+
+    // Implementations for AttributeVisitor.
+    // Note that attributes are typically only referenced once, so we don't
+    // test if they are marked already.
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        println(visitorInfo(unknownAttribute) +
+                " Unknown attribute (" + clazz.getString(unknownAttribute.u2attributeNameIndex) + ")");
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        println(visitorInfo(sourceFileAttribute) +
+                " Source file attribute:");
+
+        indent();
+        clazz.constantPoolEntryAccept(sourceFileAttribute.u2sourceFileIndex, this);
+        outdent();
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        println(visitorInfo(sourceDirAttribute) +
+                " Source dir attribute:");
+
+        indent();
+        clazz.constantPoolEntryAccept(sourceDirAttribute.u2sourceDirIndex, this);
+        outdent();
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        println(visitorInfo(innerClassesAttribute) +
+                " Inner classes attribute (count = " + innerClassesAttribute.u2classesCount + ")");
+
+        indent();
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+        outdent();
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        println(visitorInfo(enclosingMethodAttribute) +
+                " Enclosing method attribute:");
+
+        indent();
+        clazz.constantPoolEntryAccept(enclosingMethodAttribute.u2classIndex, this);
+
+        if (enclosingMethodAttribute.u2nameAndTypeIndex != 0)
+        {
+            clazz.constantPoolEntryAccept(enclosingMethodAttribute.u2nameAndTypeIndex, this);
+        }
+        outdent();
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        println(visitorInfo(deprecatedAttribute) +
+                " Deprecated attribute");
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        println(visitorInfo(syntheticAttribute) +
+                " Synthetic attribute");
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        println(visitorInfo(signatureAttribute) +
+                " Signature attribute:");
+
+        indent();
+        clazz.constantPoolEntryAccept(signatureAttribute.u2signatureIndex, this);
+        outdent();
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        println(visitorInfo(constantValueAttribute) +
+                " Constant value attribute:");
+
+        clazz.constantPoolEntryAccept(constantValueAttribute.u2constantValueIndex, this);
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        println(visitorInfo(exceptionsAttribute) +
+                " Exceptions attribute (count = " + exceptionsAttribute.u2exceptionIndexTableLength + ")");
+
+        indent();
+        exceptionsAttribute.exceptionEntriesAccept((ProgramClass)clazz, this);
+        outdent();
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        println(visitorInfo(codeAttribute) +
+                " Code attribute instructions (code length = "+ codeAttribute.u4codeLength +
+                ", locals = "+ codeAttribute.u2maxLocals +
+                ", stack = "+ codeAttribute.u2maxStack + "):");
+
+        indent();
+
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        println("Code attribute exceptions (count = " +
+                codeAttribute.u2exceptionTableLength + "):");
+
+        codeAttribute.exceptionsAccept(clazz, method, this);
+
+        println("Code attribute attributes (attribute count = " +
+                codeAttribute.u2attributesCount + "):");
+
+        codeAttribute.attributesAccept(clazz, method, this);
+
+        outdent();
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        println(visitorInfo(codeAttribute) +
+                " Stack map attribute (count = "+
+                stackMapAttribute.u2stackMapFramesCount + "):");
+
+        indent();
+        stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+        outdent();
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        println(visitorInfo(codeAttribute) +
+                " Stack map table attribute (count = "+
+                stackMapTableAttribute.u2stackMapFramesCount + "):");
+
+        indent();
+        stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+        outdent();
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        println(visitorInfo(lineNumberTableAttribute) +
+                " Line number table attribute (count = " +
+                lineNumberTableAttribute.u2lineNumberTableLength + ")");
+
+        indent();
+        lineNumberTableAttribute.lineNumbersAccept(clazz, method, codeAttribute, this);
+        outdent();
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        println(visitorInfo(localVariableTableAttribute) +
+                " Local variable table attribute (count = " +
+                localVariableTableAttribute.u2localVariableTableLength + ")");
+
+        indent();
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+        outdent();
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        println(visitorInfo(localVariableTypeTableAttribute) +
+                " Local variable type table attribute (count = "+
+                localVariableTypeTableAttribute.u2localVariableTypeTableLength + ")");
+
+        indent();
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+        outdent();
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        println(visitorInfo(runtimeVisibleAnnotationsAttribute) +
+                " Runtime visible annotations attribute:");
+
+        indent();
+        runtimeVisibleAnnotationsAttribute.annotationsAccept(clazz, this);
+        outdent();
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        println(visitorInfo(runtimeInvisibleAnnotationsAttribute) +
+                " Runtime invisible annotations attribute:");
+
+        indent();
+        runtimeInvisibleAnnotationsAttribute.annotationsAccept(clazz, this);
+        outdent();
+    }
+
+
+    public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute)
+    {
+        println(visitorInfo(runtimeVisibleParameterAnnotationsAttribute) +
+                " Runtime visible parameter annotations attribute (parameter count = " + runtimeVisibleParameterAnnotationsAttribute.u2parametersCount + "):");
+
+        indent();
+        runtimeVisibleParameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+        outdent();
+    }
+
+
+    public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute)
+    {
+        println(visitorInfo(runtimeInvisibleParameterAnnotationsAttribute) +
+                " Runtime invisible parameter annotations attribute (parameter count = " + runtimeInvisibleParameterAnnotationsAttribute.u2parametersCount + "):");
+
+        indent();
+        runtimeInvisibleParameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+        outdent();
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        println(visitorInfo(annotationDefaultAttribute) +
+                " Annotation default attribute:");
+
+        indent();
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+        outdent();
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        println(visitorInfo(innerClassesInfo) +
+                " InnerClassesInfo:");
+
+        indent();
+        innerClassesInfo.innerClassConstantAccept(clazz, this);
+        innerClassesInfo.outerClassConstantAccept(clazz, this);
+        innerClassesInfo.innerNameConstantAccept(clazz, this);
+        outdent();
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        println(instruction.toString(offset));
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        println(constantInstruction.toString(offset));
+
+        indent();
+        clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+        outdent();
+    }
+
+
+    public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction)
+    {
+        println(tableSwitchInstruction.toString(offset));
+
+        indent();
+
+        int[] jumpOffsets = tableSwitchInstruction.jumpOffsets;
+
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            int jumpOffset = jumpOffsets[index];
+            println(Integer.toString(tableSwitchInstruction.lowCase + index)  + ": offset = " + jumpOffset + ", target = " + (offset + jumpOffset));
+        }
+
+        int defaultOffset = tableSwitchInstruction.defaultOffset;
+        println("default: offset = " + defaultOffset + ", target = "+ (offset + defaultOffset));
+
+        outdent();
+    }
+
+
+    public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction)
+    {
+        println(lookUpSwitchInstruction.toString(offset));
+
+        indent();
+
+        int[] cases       = lookUpSwitchInstruction.cases;
+        int[] jumpOffsets = lookUpSwitchInstruction.jumpOffsets;
+
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            int jumpOffset = jumpOffsets[index];
+            println(Integer.toString(cases[index])  + ": offset = " + jumpOffset + ", target = " + (offset + jumpOffset));
+        }
+
+        int defaultOffset = lookUpSwitchInstruction.defaultOffset;
+        println("default: offset = " + defaultOffset + ", target = "+ (offset + defaultOffset));
+
+        outdent();
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        println(visitorInfo(exceptionInfo) +
+                " ExceptionInfo (" +
+                exceptionInfo.u2startPC + " -> " +
+                exceptionInfo.u2endPC + ": " +
+                exceptionInfo.u2handlerPC + "):");
+
+        if (exceptionInfo.u2catchType != 0)
+        {
+            clazz.constantPoolEntryAccept(exceptionInfo.u2catchType, this);
+        }
+    }
+
+
+    // Implementations for StackMapFrameVisitor.
+
+    public void visitSameZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameZeroFrame sameZeroFrame)
+    {
+        println(visitorInfo(sameZeroFrame) +
+                " [" + offset  + "]" +
+                " Var: ..., Stack: (empty)");
+    }
+
+
+    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+    {
+        print(visitorInfo(sameOneFrame) +
+              " [" + offset  + "]" +
+              " Var: ..., Stack: ");
+
+        sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
+
+        println();
+    }
+
+
+    public void visitLessZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LessZeroFrame lessZeroFrame)
+    {
+        println(visitorInfo(lessZeroFrame) +
+                " [" + offset  + "]" +
+                " Var: -" + lessZeroFrame.choppedVariablesCount +
+                ", Stack: (empty)");
+    }
+
+
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+    {
+        print(visitorInfo(moreZeroFrame) +
+              " [" + offset  + "]" +
+              " Var: ...");
+
+        moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
+
+        ps.println(", Stack: (empty)");
+    }
+
+
+    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+    {
+        print(visitorInfo(fullFrame) +
+              " [" + offset  + "]" +
+              " Var: ");
+
+        fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
+
+        ps.print(", Stack: ");
+
+        fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
+
+        println();
+    }
+
+
+    // Implementations for VerificationTypeVisitor.
+
+    public void visitIntegerType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, IntegerType integerType)
+    {
+        ps.print("[i]");
+    }
+
+
+    public void visitFloatType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FloatType floatType)
+    {
+        ps.print("[f]");
+    }
+
+
+    public void visitLongType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LongType longType)
+    {
+        ps.print("[l]");
+    }
+
+
+    public void visitDoubleType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, DoubleType doubleType)
+    {
+        ps.print("[d]");
+    }
+
+
+    public void visitTopType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TopType topType)
+    {
+        ps.print("[T]");
+    }
+
+
+    public void visitObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType objectType)
+    {
+        ps.print("[a:" + clazz.getClassName(objectType.u2classIndex) + "]");
+    }
+
+
+    public void visitNullType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, NullType nullType)
+    {
+        ps.print("[n]");
+    }
+
+
+    public void visitUninitializedType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedType uninitializedType)
+    {
+        ps.print("[u:" + uninitializedType.u2newInstructionOffset + "]");
+    }
+
+
+    public void visitUninitializedThisType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, UninitializedThisType uninitializedThisType)
+    {
+        ps.print("[u:this]");
+    }
+
+
+    // Implementations for LineNumberInfoVisitor.
+
+    public void visitLineNumberInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberInfo lineNumberInfo)
+    {
+        println("[" + lineNumberInfo.u2startPC + "] -> line " +
+                lineNumberInfo.u2lineNumber);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        println("#" + localVariableInfo.u2index + ": " +
+                localVariableInfo.u2startPC + " -> " +
+                (localVariableInfo.u2startPC + localVariableInfo.u2length) + " [" +
+                clazz.getString(localVariableInfo.u2descriptorIndex) + " " +
+                clazz.getString(localVariableInfo.u2nameIndex) + "]");
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        println("#" + localVariableTypeInfo.u2index + ": " +
+                localVariableTypeInfo.u2startPC + " -> " +
+                (localVariableTypeInfo.u2startPC + localVariableTypeInfo.u2length) + " [" +
+                clazz.getString(localVariableTypeInfo.u2signatureIndex) + " " +
+                clazz.getString(localVariableTypeInfo.u2nameIndex) + "]");
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        println(visitorInfo(annotation) +
+                " Annotation [" + clazz.getString(annotation.u2typeIndex) + "]:");
+
+        indent();
+        annotation.elementValuesAccept(clazz, this);
+        outdent();
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        println(visitorInfo(constantElementValue) +
+                " Constant element value [" +
+                (constantElementValue.u2elementNameIndex == 0 ? "(default)" :
+                clazz.getString(constantElementValue.u2elementNameIndex)) + " '" +
+                constantElementValue.u1tag + "']");
+
+        indent();
+        clazz.constantPoolEntryAccept(constantElementValue.u2constantValueIndex, this);
+        outdent();
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        println(visitorInfo(enumConstantElementValue) +
+                " Enum constant element value [" +
+                (enumConstantElementValue.u2elementNameIndex == 0 ? "(default)" :
+                clazz.getString(enumConstantElementValue.u2elementNameIndex)) + ", " +
+                clazz.getString(enumConstantElementValue.u2typeNameIndex)  + ", " +
+                clazz.getString(enumConstantElementValue.u2constantNameIndex) + "]");
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        println(visitorInfo(classElementValue) +
+                " Class element value [" +
+                (classElementValue.u2elementNameIndex == 0 ? "(default)" :
+                clazz.getString(classElementValue.u2elementNameIndex)) + ", " +
+                clazz.getString(classElementValue.u2classInfoIndex) + "]");
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        println(visitorInfo(annotationElementValue) +
+                " Annotation element value [" +
+                (annotationElementValue.u2elementNameIndex == 0 ? "(default)" :
+                clazz.getString(annotationElementValue.u2elementNameIndex)) + "]:");
+
+        indent();
+        annotationElementValue.annotationAccept(clazz, this);
+        outdent();
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        println(visitorInfo(arrayElementValue) +
+                " Array element value [" +
+                (arrayElementValue.u2elementNameIndex == 0 ? "(default)" :
+                clazz.getString(arrayElementValue.u2elementNameIndex)) + "]:");
+
+        indent();
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+        outdent();
+    }
+
+
+    // Small utility methods.
+
+    private void indent()
+    {
+        indentation++;
+    }
+
+    private void outdent()
+    {
+        indentation--;
+    }
+
+    private void println(String string)
+    {
+        print(string);
+        println();
+
+    }
+
+    private void print(String string)
+    {
+        for (int index = 0; index < indentation; index++)
+        {
+            ps.print(INDENTATION);
+        }
+
+        ps.print(string);
+    }
+
+    private void println()
+    {
+        ps.println();
+    }
+
+
+    private String visitorInfo(VisitorAccepter visitorAccepter)
+    {
+        return visitorAccepter.getVisitorInfo() == null ? "-" : "+";
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassVersionFilter.java b/src/proguard/classfile/visitor/ClassVersionFilter.java
new file mode 100644
index 0000000..578cabf
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassVersionFilter.java
@@ -0,0 +1,72 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to program classes to
+ * another given <code>ClassVisitor</code>, but only when the class version
+ * number of the visited program class lies in a given range.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassVersionFilter implements ClassVisitor
+{
+    private final int          minimumClassVersion;
+    private final int          maximumClassVersion;
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ClassVersionFilter.
+     * @param minimumClassVersion the minimum class version number.
+     * @param maximumClassVersion the maximum class version number.
+     * @param classVisitor        the <code>ClassVisitor</code> to which visits
+     *                            will be delegated.
+     */
+    public ClassVersionFilter(int          minimumClassVersion,
+                              int          maximumClassVersion,
+                              ClassVisitor classVisitor)
+    {
+        this.minimumClassVersion = minimumClassVersion;
+        this.maximumClassVersion = maximumClassVersion;
+        this.classVisitor        = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (programClass.u4version >= minimumClassVersion &&
+            programClass.u4version <= maximumClassVersion)
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Library classes don't have version numbers.
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassVersionSetter.java b/src/proguard/classfile/visitor/ClassVersionSetter.java
new file mode 100644
index 0000000..34dfbc1
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassVersionSetter.java
@@ -0,0 +1,83 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+import java.util.Set;
+
+/**
+ * This <code>ClassVisitor</code> sets the version number of the program classes
+ * that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassVersionSetter implements ClassVisitor
+{
+    private final int classVersion;
+
+    private final Set newerClassVersions;
+
+
+    /**
+     * Creates a new ClassVersionSetter.
+     * @param classVersion the class version number.
+     */
+    public ClassVersionSetter(int classVersion)
+    {
+        this(classVersion, null);
+    }
+
+
+    /**
+     * Creates a new ClassVersionSetter that also stores any newer class version
+     * numbers that it encounters while visiting program classes.
+     * @param classVersion       the class version number.
+     * @param newerClassVersions the <code>Set</code> in which newer class
+     *                           version numbers can be collected.
+     */
+    public ClassVersionSetter(int classVersion,
+                              Set newerClassVersions)
+    {
+        this.classVersion       = classVersion;
+        this.newerClassVersions = newerClassVersions;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (programClass.u4version > classVersion &&
+            newerClassVersions != null)
+        {
+            newerClassVersions.add(new Integer(programClass.u4version));
+        }
+
+        programClass.u4version = classVersion;
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Library classes don't have version numbers.
+    }
+}
diff --git a/src/proguard/classfile/visitor/ClassVisitor.java b/src/proguard/classfile/visitor/ClassVisitor.java
new file mode 100644
index 0000000..fdba2df
--- /dev/null
+++ b/src/proguard/classfile/visitor/ClassVisitor.java
@@ -0,0 +1,36 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>Clazz</code> objects.
+ *
+ * @author Eric Lafortune
+ */
+public interface ClassVisitor
+{
+    public void visitProgramClass(ProgramClass programClass);
+    public void visitLibraryClass(LibraryClass libraryClass);
+}
diff --git a/src/proguard/classfile/visitor/ConcreteClassDownTraveler.java b/src/proguard/classfile/visitor/ConcreteClassDownTraveler.java
new file mode 100644
index 0000000..ec3fe68
--- /dev/null
+++ b/src/proguard/classfile/visitor/ConcreteClassDownTraveler.java
@@ -0,0 +1,100 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>ClassVisitor</code> lets a given <code>ClassVisitor</code>
+ * travel to the first concrete subclasses down in its hierarchy of abstract
+ * classes and concrete classes.
+ *
+ * @author Eric Lafortune
+ */
+public class ConcreteClassDownTraveler
+implements   ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ConcreteClassDownTraveler.
+     * @param classVisitor     the <code>ClassVisitor</code> to
+     *                         which visits will be delegated.
+     */
+    public ConcreteClassDownTraveler(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Is this an abstract class or an interface?
+        if ((programClass.getAccessFlags() &
+             (ClassConstants.INTERNAL_ACC_INTERFACE |
+              ClassConstants.INTERNAL_ACC_ABSTRACT)) != 0)
+        {
+            // Travel down the hierarchy.
+            Clazz[] subClasses = programClass.subClasses;
+            if (subClasses != null)
+            {
+                for (int index = 0; index < subClasses.length; index++)
+                {
+                    subClasses[index].accept(this);
+                }
+            }
+        }
+        else
+        {
+            // Visit the class. Don't descend any further.
+            programClass.accept(classVisitor);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Is this an abstract class or interface?
+        if ((libraryClass.getAccessFlags() &
+             (ClassConstants.INTERNAL_ACC_INTERFACE |
+              ClassConstants.INTERNAL_ACC_ABSTRACT)) != 0)
+        {
+            // Travel down the hierarchy.
+            Clazz[] subClasses = libraryClass.subClasses;
+            if (subClasses != null)
+            {
+                for (int index = 0; index < subClasses.length; index++)
+                {
+                    subClasses[index].accept(this);
+                }
+            }
+        }
+        else
+        {
+            // Visit the class. Don't descend any further.
+            libraryClass.accept(classVisitor);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/DotClassClassVisitor.java b/src/proguard/classfile/visitor/DotClassClassVisitor.java
new file mode 100644
index 0000000..263dbd5
--- /dev/null
+++ b/src/proguard/classfile/visitor/DotClassClassVisitor.java
@@ -0,0 +1,91 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+
+/**
+ * This InstructionVisitor lets a given <code>ClassVisitor</code> visit all
+ * classes involved in any <code>.class</code> constructs that it visits.
+ * <p>
+ * Note that before JDK 1.5, <code>.class</code> constructs are actually
+ * compiled differently, using <code>Class.forName</code> constructs.
+ *
+ * @see ClassForNameClassVisitor
+ *
+ * @author Eric Lafortune
+ */
+public class DotClassClassVisitor
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ClassHierarchyTraveler.
+     * @param classVisitor the <code>ClassVisitor</code> to which visits will
+     *                     be delegated.
+     */
+    public DotClassClassVisitor(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        byte opcode = constantInstruction.opcode;
+
+        // Could this instruction be a .class construct?
+        if (opcode == InstructionConstants.OP_LDC ||
+            opcode == InstructionConstants.OP_LDC_W)
+        {
+            clazz.constantPoolEntryAccept(constantInstruction.constantIndex,
+                                          this);
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Visit the referenced class from the .class construct.
+        classConstant.referencedClassAccept(classVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ExceptClassFilter.java b/src/proguard/classfile/visitor/ExceptClassFilter.java
new file mode 100644
index 0000000..924485e
--- /dev/null
+++ b/src/proguard/classfile/visitor/ExceptClassFilter.java
@@ -0,0 +1,69 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to another given
+ * <code>ClassVisitor</code>, except for one given class.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptClassFilter implements ClassVisitor
+{
+    private final Clazz        exceptClass;
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ClassNameFilter.
+     * @param exceptClass  the class that will not be visited.
+     * @param classVisitor the <code>ClassVisitor</code> to which visits will
+     *                     be delegated.
+     */
+    public ExceptClassFilter(Clazz        exceptClass,
+                             ClassVisitor classVisitor)
+    {
+        this.exceptClass  = exceptClass;
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (!programClass.equals(exceptClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (!libraryClass.equals(exceptClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/ExceptClassesFilter.java b/src/proguard/classfile/visitor/ExceptClassesFilter.java
new file mode 100644
index 0000000..7380c40
--- /dev/null
+++ b/src/proguard/classfile/visitor/ExceptClassesFilter.java
@@ -0,0 +1,90 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to another given
+ * <code>ClassVisitor</code>, except for classes are in a given list.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptClassesFilter implements ClassVisitor
+{
+    private final Clazz[]      exceptClasses;
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ExceptClassesFilter.
+     * @param exceptClasses the classes that will not be visited.
+     * @param classVisitor  the <code>ClassVisitor</code> to which visits will
+     *                      be delegated.
+     */
+    public ExceptClassesFilter(Clazz[]      exceptClasses,
+                               ClassVisitor classVisitor)
+    {
+        this.exceptClasses = exceptClasses;
+        this.classVisitor  = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (!present(programClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (!present(libraryClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean present(Clazz clazz)
+    {
+        if (exceptClasses == null)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < exceptClasses.length; index++)
+        {
+            if (exceptClasses[index].equals(clazz))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/ExceptionCounter.java b/src/proguard/classfile/visitor/ExceptionCounter.java
new file mode 100644
index 0000000..c324129
--- /dev/null
+++ b/src/proguard/classfile/visitor/ExceptionCounter.java
@@ -0,0 +1,52 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.ExceptionInfoVisitor;
+
+/**
+ * This ExceptionInfoVisitor counts the number of exceptions that has been visited.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionCounter implements ExceptionInfoVisitor
+{
+    private int count;
+
+
+    /**
+     * Returns the number of exceptions that has been visited so far.
+     */
+    public int getCount()
+    {
+        return count;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        count++;
+    }
+}
diff --git a/src/proguard/classfile/visitor/ExceptionExcludedOffsetFilter.java b/src/proguard/classfile/visitor/ExceptionExcludedOffsetFilter.java
new file mode 100644
index 0000000..3911e39
--- /dev/null
+++ b/src/proguard/classfile/visitor/ExceptionExcludedOffsetFilter.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.ExceptionInfoVisitor;
+
+/**
+ * This <code>ExceptionInfoVisitor</code> delegates its visits to another given
+ * <code>ExceptionInfoVisitor</code>, but only when the visited exception
+ * does not cover the instruction at the given offset.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionExcludedOffsetFilter
+implements   ExceptionInfoVisitor
+{
+    private final int                  instructionOffset;
+    private final ExceptionInfoVisitor exceptionInfoVisitor;
+
+
+    /**
+     * Creates a new ExceptionExcludedOffsetFilter.
+     * @param instructionOffset    the instruction offset.
+     * @param exceptionInfoVisitor the ExceptionInfoVisitor to which visits
+     *                             will be delegated.
+     */
+    public ExceptionExcludedOffsetFilter(int                  instructionOffset,
+                                         ExceptionInfoVisitor exceptionInfoVisitor)
+    {
+        this.instructionOffset    = instructionOffset;
+        this.exceptionInfoVisitor = exceptionInfoVisitor;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        if (!exceptionInfo.isApplicable(instructionOffset))
+        {
+            exceptionInfoVisitor.visitExceptionInfo(clazz, method, codeAttribute, exceptionInfo);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/ExceptionHandlerConstantVisitor.java b/src/proguard/classfile/visitor/ExceptionHandlerConstantVisitor.java
new file mode 100644
index 0000000..e0fdec3
--- /dev/null
+++ b/src/proguard/classfile/visitor/ExceptionHandlerConstantVisitor.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.ExceptionInfoVisitor;
+
+/**
+ * This <code>ExceptionInfoVisitor</code> lets a given
+ * <code>ConstantVisitor</code> visit all catch class constants of exceptions
+ * that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionHandlerConstantVisitor
+implements   ExceptionInfoVisitor
+{
+    private final ConstantVisitor constantVisitor;
+
+
+    /**
+     * Creates a new ExceptionHandlerConstantVisitor.
+     * @param constantVisitor the ConstantVisitor that will visit the catch
+     *                        class constants.
+     */
+    public ExceptionHandlerConstantVisitor(ConstantVisitor constantVisitor)
+    {
+        this.constantVisitor = constantVisitor;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        int catchType = exceptionInfo.u2catchType;
+        if (catchType != 0)
+        {
+            clazz.constantPoolEntryAccept(catchType, constantVisitor);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/ExceptionHandlerFilter.java b/src/proguard/classfile/visitor/ExceptionHandlerFilter.java
new file mode 100644
index 0000000..a90fb56
--- /dev/null
+++ b/src/proguard/classfile/visitor/ExceptionHandlerFilter.java
@@ -0,0 +1,70 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.ExceptionInfoVisitor;
+
+/**
+ * This <code>ExceptionInfoVisitor</code> delegates its visits to another given
+ * <code>ExceptionInfoVisitor</code>, but only when the visited exception
+ * targets an instruction in the given range of offsets.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionHandlerFilter
+implements   ExceptionInfoVisitor
+{
+    private final int                  startOffset;
+    private final int                  endOffset;
+    private final ExceptionInfoVisitor exceptionInfoVisitor;
+
+
+    /**
+     * Creates a new ExceptionHandlerFilter.
+     * @param startOffset          the start of the instruction offset range.
+     * @param endOffset            the end of the instruction offset range.
+     * @param exceptionInfoVisitor the ExceptionInfoVisitor to which visits
+     *                             will be delegated.
+     */
+    public ExceptionHandlerFilter(int                  startOffset,
+                                  int                  endOffset,
+                                  ExceptionInfoVisitor exceptionInfoVisitor)
+    {
+        this.startOffset          = startOffset;
+        this.endOffset            = endOffset;
+        this.exceptionInfoVisitor = exceptionInfoVisitor;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        int handlerPC = exceptionInfo.u2handlerPC;
+        if (handlerPC >= startOffset &&
+            handlerPC <  endOffset)
+        {
+            exceptionInfoVisitor.visitExceptionInfo(clazz, method, codeAttribute, exceptionInfo);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/ExceptionOffsetFilter.java b/src/proguard/classfile/visitor/ExceptionOffsetFilter.java
new file mode 100644
index 0000000..e2a4fc9
--- /dev/null
+++ b/src/proguard/classfile/visitor/ExceptionOffsetFilter.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.ExceptionInfoVisitor;
+
+/**
+ * This <code>ExceptionInfoVisitor</code> delegates its visits to another given
+ * <code>ExceptionInfoVisitor</code>, but only when the visited exception
+ * covers the instruction at the given offset.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionOffsetFilter
+implements   ExceptionInfoVisitor
+{
+    private final int                  instructionOffset;
+    private final ExceptionInfoVisitor exceptionInfoVisitor;
+
+
+    /**
+     * Creates a new ExceptionOffsetFilter.
+     * @param instructionOffset    the instruction offset.
+     * @param exceptionInfoVisitor the ExceptionInfoVisitor to which visits
+     *                             will be delegated.
+     */
+    public ExceptionOffsetFilter(int                  instructionOffset,
+                                 ExceptionInfoVisitor exceptionInfoVisitor)
+    {
+        this.instructionOffset    = instructionOffset;
+        this.exceptionInfoVisitor = exceptionInfoVisitor;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        if (exceptionInfo.isApplicable(instructionOffset))
+        {
+            exceptionInfoVisitor.visitExceptionInfo(clazz, method, codeAttribute, exceptionInfo);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/ExceptionRangeFilter.java b/src/proguard/classfile/visitor/ExceptionRangeFilter.java
new file mode 100644
index 0000000..c541b1f
--- /dev/null
+++ b/src/proguard/classfile/visitor/ExceptionRangeFilter.java
@@ -0,0 +1,68 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.ExceptionInfoVisitor;
+
+/**
+ * This <code>ExceptionInfoVisitor</code> delegates its visits to another given
+ * <code>ExceptionInfoVisitor</code>, but only when the visited exception
+ * overlaps with the given instruction range.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionRangeFilter
+implements   ExceptionInfoVisitor
+{
+    private final int                  startOffset;
+    private final int                  endOffset;
+    private final ExceptionInfoVisitor exceptionInfoVisitor;
+
+
+    /**
+     * Creates a new ExceptionRangeFilter.
+     * @param startOffset          the start offset of the instruction range.
+     * @param endOffset            the end offset of the instruction range.
+     * @param exceptionInfoVisitor the ExceptionInfoVisitor to which visits
+     *                             will be delegated.
+     */
+    public ExceptionRangeFilter(int                  startOffset,
+                                int                  endOffset,
+                                ExceptionInfoVisitor exceptionInfoVisitor)
+    {
+        this.startOffset          = startOffset;
+        this.endOffset            = endOffset;
+        this.exceptionInfoVisitor = exceptionInfoVisitor;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        if (exceptionInfo.isApplicable(startOffset, endOffset))
+        {
+            exceptionInfoVisitor.visitExceptionInfo(clazz, method, codeAttribute, exceptionInfo);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/ImplementedClassConstantFilter.java b/src/proguard/classfile/visitor/ImplementedClassConstantFilter.java
new file mode 100644
index 0000000..6fe2e7d
--- /dev/null
+++ b/src/proguard/classfile/visitor/ImplementedClassConstantFilter.java
@@ -0,0 +1,69 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This <code>ConstantVisitor</code> delegates its visits to class constants
+ * to another given <code>ConstantVisitor</code>, except for classes that
+ * extend or implement a given class. This exception includes the class itself.
+ *
+ * @author Eric Lafortune
+ */
+public class ImplementedClassConstantFilter
+extends      SimplifiedVisitor
+implements   ConstantVisitor
+{
+    private final Clazz           implementedClass;
+    private final ConstantVisitor constantVisitor;
+
+
+    /**
+     * Creates a new ImplementedClassConstantFilter.
+     * @param implementedClass the class whose implementations will not be
+     *                         visited.
+     * @param constantVisitor  the <code>ConstantVisitor</code> to which visits
+     *                         will be delegated.
+     */
+    public ImplementedClassConstantFilter(Clazz           implementedClass,
+                                          ConstantVisitor constantVisitor)
+    {
+        this.implementedClass = implementedClass;
+        this.constantVisitor  = constantVisitor;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        Clazz referencedClass = classConstant.referencedClass;
+        if (referencedClass == null ||
+            !referencedClass.extendsOrImplements(implementedClass))
+        {
+            constantVisitor.visitClassConstant(clazz, classConstant);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/ImplementedClassFilter.java b/src/proguard/classfile/visitor/ImplementedClassFilter.java
new file mode 100644
index 0000000..955a74e
--- /dev/null
+++ b/src/proguard/classfile/visitor/ImplementedClassFilter.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to another given
+ * <code>ClassVisitor</code>, except for classes that extend or implement
+ * a given class.
+ *
+ * @author Eric Lafortune
+ */
+public class ImplementedClassFilter implements ClassVisitor
+{
+    private final Clazz        implementedClass;
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ImplementedClassFilter.
+     * @param implementedClass the class whose implementations will not be
+     *                         visited.
+     * @param classVisitor     the <code>ClassVisitor</code> to which visits will
+     *                         be delegated.
+     */
+    public ImplementedClassFilter(Clazz        implementedClass,
+                                  ClassVisitor classVisitor)
+    {
+        this.implementedClass = implementedClass;
+        this.classVisitor     = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (!programClass.extendsOrImplements(implementedClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (!libraryClass.extendsOrImplements(implementedClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/ImplementingClassConstantFilter.java b/src/proguard/classfile/visitor/ImplementingClassConstantFilter.java
new file mode 100644
index 0000000..9e9cea3
--- /dev/null
+++ b/src/proguard/classfile/visitor/ImplementingClassConstantFilter.java
@@ -0,0 +1,70 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This <code>ConstantVisitor</code> delegates its visits to class constants
+ * to another given <code>ConstantVisitor</code>, except for classes that
+ * are extended or implemented by a given class. This exception includes the
+ * class itself.
+ *
+ * @author Eric Lafortune
+ */
+public class ImplementingClassConstantFilter
+extends      SimplifiedVisitor
+implements   ConstantVisitor
+{
+    private final Clazz           implementingClass;
+    private final ConstantVisitor constantVisitor;
+
+
+    /**
+     * Creates a new ImplementingClassConstantFilter.
+     * @param implementingClass the class whose superclasses and interfaces will
+     *                          not be visited.
+     * @param constantVisitor   the <code>ConstantVisitor</code> to which visits
+     *                          will be delegated.
+     */
+    public ImplementingClassConstantFilter(Clazz           implementingClass,
+                                           ConstantVisitor constantVisitor)
+    {
+        this.implementingClass = implementingClass;
+        this.constantVisitor   = constantVisitor;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        Clazz referencedClass = classConstant.referencedClass;
+        if (referencedClass == null ||
+            !implementingClass.extendsOrImplements(referencedClass))
+        {
+            constantVisitor.visitClassConstant(clazz, classConstant);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/LibraryClassFilter.java b/src/proguard/classfile/visitor/LibraryClassFilter.java
new file mode 100644
index 0000000..0e40f2f
--- /dev/null
+++ b/src/proguard/classfile/visitor/LibraryClassFilter.java
@@ -0,0 +1,60 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to another given
+ * <code>ClassVisitor</code>, but only when visiting library classes.
+ *
+ * @author Eric Lafortune
+ */
+public class LibraryClassFilter implements ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new LibraryClassFilter.
+     * @param classVisitor     the <code>ClassVisitor</code> to which visits
+     *                         will be delegated.
+     */
+    public LibraryClassFilter(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Don't delegate visits to program classes.
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        classVisitor.visitLibraryClass(libraryClass);
+    }
+}
diff --git a/src/proguard/classfile/visitor/LibraryMemberFilter.java b/src/proguard/classfile/visitor/LibraryMemberFilter.java
new file mode 100644
index 0000000..0ee80e5
--- /dev/null
+++ b/src/proguard/classfile/visitor/LibraryMemberFilter.java
@@ -0,0 +1,73 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to another given
+ * <code>MemberVisitor</code>, but only when visiting members of library
+ * classes.
+ *
+ * @author Eric Lafortune
+ */
+public class LibraryMemberFilter implements MemberVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new ProgramMemberFilter.
+     * @param memberVisitor     the <code>MemberVisitor</code> to which
+     *                          visits will be delegated.
+     */
+    public LibraryMemberFilter(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Don't delegate visits to program members.
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Don't delegate visits to program members.
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        memberVisitor.visitLibraryField(libraryClass, libraryField);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+    }
+}
diff --git a/src/proguard/classfile/visitor/MemberAccessFilter.java b/src/proguard/classfile/visitor/MemberAccessFilter.java
new file mode 100644
index 0000000..6fd32e3
--- /dev/null
+++ b/src/proguard/classfile/visitor/MemberAccessFilter.java
@@ -0,0 +1,122 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to another given
+ * <code>MemberVisitor</code>, but only when the visited member has the proper
+ * access flags.
+ * <p>
+ * If conflicting access flags (public/private/protected) are specified,
+ * having one of them set will be considered sufficient.
+ *
+ * @see ClassConstants
+ *
+ * @author Eric Lafortune
+ */
+public class MemberAccessFilter
+implements   MemberVisitor
+{
+    // A mask of conflicting access flags. These are interpreted in a special
+    // way if more of them are required at the same time. In that case, one
+    // of them being set is sufficient.
+    private static final int ACCESS_MASK =
+        ClassConstants.INTERNAL_ACC_PUBLIC  |
+        ClassConstants.INTERNAL_ACC_PRIVATE |
+        ClassConstants.INTERNAL_ACC_PROTECTED;
+
+    private final int           requiredSetAccessFlags;
+    private final int           requiredUnsetAccessFlags;
+    private final int           requiredOneSetAccessFlags;
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new MemberAccessFilter.
+     * @param requiredSetAccessFlags   the class access flags that should be
+     *                                 set.
+     * @param requiredUnsetAccessFlags the class access flags that should be
+     *                                 unset.
+     * @param memberVisitor            the <code>MemberVisitor</code> to
+     *                                 which visits will be delegated.
+     */
+    public MemberAccessFilter(int           requiredSetAccessFlags,
+                              int           requiredUnsetAccessFlags,
+                              MemberVisitor memberVisitor)
+    {
+        this.requiredSetAccessFlags    = requiredSetAccessFlags & ~ACCESS_MASK;
+        this.requiredUnsetAccessFlags  = requiredUnsetAccessFlags;
+        this.requiredOneSetAccessFlags = requiredSetAccessFlags &  ACCESS_MASK;
+        this.memberVisitor             = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (accepted(programField.getAccessFlags()))
+        {
+            memberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (accepted(programMethod.getAccessFlags()))
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (accepted(libraryField.getAccessFlags()))
+        {
+            memberVisitor.visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (accepted(libraryMethod.getAccessFlags()))
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean accepted(int accessFlags)
+    {
+        return (requiredSetAccessFlags    & ~accessFlags) == 0 &&
+               (requiredUnsetAccessFlags  &  accessFlags) == 0 &&
+               (requiredOneSetAccessFlags == 0                 ||
+               (requiredOneSetAccessFlags &  accessFlags) != 0);
+    }
+}
diff --git a/src/proguard/classfile/visitor/MemberClassAccessFilter.java b/src/proguard/classfile/visitor/MemberClassAccessFilter.java
new file mode 100644
index 0000000..85272ff
--- /dev/null
+++ b/src/proguard/classfile/visitor/MemberClassAccessFilter.java
@@ -0,0 +1,106 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to another given
+ * <code>MemberVisitor</code>, but only when the visited member is accessible
+ * from the given referencing class.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberClassAccessFilter
+implements   MemberVisitor
+{
+    private final Clazz         referencingClass;
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new MemberAccessFilter.
+     * @param referencingClass the class that is accessing the member.
+     * @param memberVisitor    the <code>MemberVisitor</code> to which visits
+     *                         will be delegated.
+     */
+    public MemberClassAccessFilter(Clazz         referencingClass,
+                                   MemberVisitor memberVisitor)
+    {
+        this.referencingClass = referencingClass;
+        this.memberVisitor    = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (accepted(programClass, programField.getAccessFlags()))
+        {
+            memberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (accepted(programClass, programMethod.getAccessFlags()))
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (accepted(libraryClass, libraryField.getAccessFlags()))
+        {
+            memberVisitor.visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (accepted(libraryClass, libraryMethod.getAccessFlags()))
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean accepted(Clazz clazz, int memberAccessFlags)
+    {
+        int accessLevel = AccessUtil.accessLevel(memberAccessFlags);
+
+        return
+            (accessLevel >= AccessUtil.PUBLIC                                                              ) ||
+            (accessLevel >= AccessUtil.PRIVATE         && referencingClass.equals(clazz)                   ) ||
+            (accessLevel >= AccessUtil.PACKAGE_VISIBLE && (ClassUtil.internalPackageName(referencingClass.getName()).equals(
+                                                           ClassUtil.internalPackageName(clazz.getName())))) ||
+            (accessLevel >= AccessUtil.PROTECTED       && (referencingClass.extends_(clazz)                  ||
+                                                           referencingClass.extendsOrImplements(clazz))            );
+    }
+}
diff --git a/src/proguard/classfile/visitor/MemberCollector.java b/src/proguard/classfile/visitor/MemberCollector.java
new file mode 100644
index 0000000..ec68b2d
--- /dev/null
+++ b/src/proguard/classfile/visitor/MemberCollector.java
@@ -0,0 +1,59 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+
+import java.util.Set;
+
+/**
+ * This MemberVisitor collects the concatenated name/descriptor strings of
+ * class members that have been visited.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberCollector
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final Set set;
+
+
+    /**
+     * Creates a new MemberCollector.
+     * @param set the <code>Set</code> in which all method names/descriptor
+     *            strings will be collected.
+     */
+    public MemberCollector(Set set)
+    {
+        this.set = set;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        set.add(member.getName(clazz) + member.getDescriptor(clazz));
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/MemberCounter.java b/src/proguard/classfile/visitor/MemberCounter.java
new file mode 100644
index 0000000..c2da72e
--- /dev/null
+++ b/src/proguard/classfile/visitor/MemberCounter.java
@@ -0,0 +1,72 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This MemberVisitor counts the number of class members that have been visited.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberCounter implements MemberVisitor
+{
+    private int count;
+
+
+    /**
+     * Returns the number of class members that has been visited so far.
+     */
+    public int getCount()
+    {
+        return count;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitLibraryField(LibraryClass libraryClass,
+                                  LibraryField libraryField)
+    {
+        count++;
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass,
+                                   LibraryMethod libraryMethod)
+    {
+        count++;
+    }
+
+
+    public void visitProgramField(ProgramClass programClass,
+                                  ProgramField programField)
+    {
+        count++;
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass,
+                                   ProgramMethod programMethod)
+    {
+        count++;
+    }
+}
diff --git a/src/proguard/classfile/visitor/MemberDescriptorFilter.java b/src/proguard/classfile/visitor/MemberDescriptorFilter.java
new file mode 100644
index 0000000..bd69304
--- /dev/null
+++ b/src/proguard/classfile/visitor/MemberDescriptorFilter.java
@@ -0,0 +1,113 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.util.*;
+
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to another given
+ * <code>MemberVisitor</code>, but only when the visited member
+ * has a descriptor that matches a given regular expression.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberDescriptorFilter implements MemberVisitor
+{
+    private final StringMatcher regularExpressionMatcher;
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new MemberDescriptorFilter.
+     * @param regularExpression the regular expression against which member
+     *                          descriptors will be matched.
+     * @param memberVisitor     the <code>MemberVisitor</code> to which visits
+     *                          will be delegated.
+     */
+    public MemberDescriptorFilter(String        regularExpression,
+                                  MemberVisitor memberVisitor)
+    {
+        this(new ClassNameParser().parse(regularExpression), memberVisitor);
+    }
+
+
+    /**
+     * Creates a new MemberDescriptorFilter.
+     * @param regularExpressionMatcher the regular expression against which
+     *                                 member descriptors will be matched.
+     * @param memberVisitor            the <code>MemberVisitor</code> to which
+     *                                 visits will be delegated.
+     */
+    public MemberDescriptorFilter(StringMatcher regularExpressionMatcher,
+                                  MemberVisitor memberVisitor)
+    {
+        this.regularExpressionMatcher = regularExpressionMatcher;
+        this.memberVisitor            = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (accepted(programField.getDescriptor(programClass)))
+        {
+            memberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (accepted(programMethod.getDescriptor(programClass)))
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (accepted(libraryField.getDescriptor(libraryClass)))
+        {
+            memberVisitor.visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (accepted(libraryMethod.getDescriptor(libraryClass)))
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean accepted(String name)
+    {
+        return regularExpressionMatcher.matches(name);
+    }
+}
diff --git a/src/proguard/classfile/visitor/MemberNameFilter.java b/src/proguard/classfile/visitor/MemberNameFilter.java
new file mode 100644
index 0000000..0fe450e
--- /dev/null
+++ b/src/proguard/classfile/visitor/MemberNameFilter.java
@@ -0,0 +1,113 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.util.*;
+
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to another given
+ * <code>MemberVisitor</code>, but only when the visited member
+ * has a name that matches a given regular expression.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberNameFilter implements MemberVisitor
+{
+    private final StringMatcher regularExpressionMatcher;
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new MemberNameFilter.
+     * @param regularExpression the regular expression against which member
+     *                          names will be matched.
+     * @param memberVisitor     the <code>MemberVisitor</code> to which visits
+     *                          will be delegated.
+     */
+    public MemberNameFilter(String        regularExpression,
+                            MemberVisitor memberVisitor)
+    {
+        this(new NameParser().parse(regularExpression), memberVisitor);
+    }
+
+
+    /**
+     * Creates a new MemberNameFilter.
+     * @param regularExpressionMatcher the regular expression against which
+     *                                 member names will be matched.
+     * @param memberVisitor            the <code>MemberVisitor</code> to which
+     *                                 visits will be delegated.
+     */
+    public MemberNameFilter(StringMatcher regularExpressionMatcher,
+                            MemberVisitor memberVisitor)
+    {
+        this.regularExpressionMatcher = regularExpressionMatcher;
+        this.memberVisitor            = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (accepted(programField.getName(programClass)))
+        {
+            memberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (accepted(programMethod.getName(programClass)))
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (accepted(libraryField.getName(libraryClass)))
+        {
+            memberVisitor.visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (accepted(libraryMethod.getName(libraryClass)))
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean accepted(String name)
+    {
+        return regularExpressionMatcher.matches(name);
+    }
+}
diff --git a/src/proguard/classfile/visitor/MemberToClassVisitor.java b/src/proguard/classfile/visitor/MemberToClassVisitor.java
new file mode 100644
index 0000000..a405cfc
--- /dev/null
+++ b/src/proguard/classfile/visitor/MemberToClassVisitor.java
@@ -0,0 +1,90 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This MemberVisitor delegates all visits to a given ClassVisitor.
+ * The latter visits the class of each visited class member, although
+ * never twice in a row.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberToClassVisitor implements MemberVisitor
+{
+    private final ClassVisitor classVisitor;
+
+    private Clazz lastVisitedClass;
+
+
+    public MemberToClassVisitor(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (!programClass.equals(lastVisitedClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+
+            lastVisitedClass = programClass;
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (!programClass.equals(lastVisitedClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+
+            lastVisitedClass = programClass;
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (!libraryClass.equals(lastVisitedClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+
+            lastVisitedClass = libraryClass;
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (!libraryClass.equals(lastVisitedClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+
+            lastVisitedClass = libraryClass;
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/MemberVisitor.java b/src/proguard/classfile/visitor/MemberVisitor.java
new file mode 100644
index 0000000..01fdf71
--- /dev/null
+++ b/src/proguard/classfile/visitor/MemberVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This interface specifies the methods for a visitor of
+ * <code>ProgramMember</code> objects and <code>LibraryMember</code>
+ * objects.
+ *
+ * @author Eric Lafortune
+ */
+public interface MemberVisitor
+{
+    public void visitProgramField( ProgramClass programClass, ProgramField  programField);
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod);
+
+    public void visitLibraryField( LibraryClass libraryClass, LibraryField  libraryField);
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod);
+}
diff --git a/src/proguard/classfile/visitor/MethodImplementationFilter.java b/src/proguard/classfile/visitor/MethodImplementationFilter.java
new file mode 100644
index 0000000..57d923a
--- /dev/null
+++ b/src/proguard/classfile/visitor/MethodImplementationFilter.java
@@ -0,0 +1,70 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to methods to
+ * another given <code>MemberVisitor</code>, but only when the visited
+ * method may have implementations.
+ *
+ * @see Clazz#mayHaveImplementations(Method)
+ * @author Eric Lafortune
+ */
+public class MethodImplementationFilter
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new MethodImplementationFilter.
+     * @param memberVisitor     the <code>MemberVisitor</code> to which
+     *                          visits will be delegated.
+     */
+    public MethodImplementationFilter(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (programClass.mayHaveImplementations(programMethod))
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (libraryClass.mayHaveImplementations(libraryMethod))
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/MethodImplementationTraveler.java b/src/proguard/classfile/visitor/MethodImplementationTraveler.java
new file mode 100644
index 0000000..dc0ea36
--- /dev/null
+++ b/src/proguard/classfile/visitor/MethodImplementationTraveler.java
@@ -0,0 +1,128 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This <code>MemberVisitor</code> lets a given <code>MemberVisitor</code>
+ * travel to all concrete and abstract implementations of the visited methods
+ * in their class hierarchies.
+ *
+ * @author Eric Lafortune
+ */
+public class MethodImplementationTraveler
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final boolean       visitThisMethod;
+    private final boolean       visitSuperMethods;
+    private final boolean       visitInterfaceMethods;
+    private final boolean       visitOverridingMethods;
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new MethodImplementationTraveler.
+     * @param visitThisMethod        specifies whether to visit the originally
+     *                               visited methods.
+     * @param visitSuperMethods      specifies whether to visit the method in
+     *                               the super classes.
+     * @param visitInterfaceMethods  specifies whether to visit the method in
+     *                               the interface classes.
+     * @param visitOverridingMethods specifies whether to visit the method in
+     *                               the subclasses.
+     * @param memberVisitor          the <code>MemberVisitor</code> to which
+     *                               visits will be delegated.
+     */
+    public MethodImplementationTraveler(boolean       visitThisMethod,
+                                        boolean       visitSuperMethods,
+                                        boolean       visitInterfaceMethods,
+                                        boolean       visitOverridingMethods,
+                                        MemberVisitor memberVisitor)
+    {
+        this.visitThisMethod        = visitThisMethod;
+        this.visitSuperMethods      = visitSuperMethods;
+        this.visitInterfaceMethods  = visitInterfaceMethods;
+        this.visitOverridingMethods = visitOverridingMethods;
+        this.memberVisitor          = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (visitThisMethod)
+        {
+            programMethod.accept(programClass, memberVisitor);
+        }
+
+        if (!isSpecial(programClass, programMethod))
+        {
+            programClass.hierarchyAccept(false,
+                                         visitSuperMethods,
+                                         visitInterfaceMethods,
+                                         visitOverridingMethods,
+                                         new NamedMethodVisitor(programMethod.getName(programClass),
+                                                                programMethod.getDescriptor(programClass),
+                                         new MemberAccessFilter(0,
+                                                                ClassConstants.INTERNAL_ACC_PRIVATE |
+                                                                ClassConstants.INTERNAL_ACC_STATIC,
+                                         memberVisitor)));
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (visitThisMethod)
+        {
+            libraryMethod.accept(libraryClass, memberVisitor);
+        }
+
+        if (!isSpecial(libraryClass, libraryMethod))
+        {
+            libraryClass.hierarchyAccept(false,
+                                         visitSuperMethods,
+                                         visitInterfaceMethods,
+                                         visitOverridingMethods,
+                                         new NamedMethodVisitor(libraryMethod.getName(libraryClass),
+                                                                libraryMethod.getDescriptor(libraryClass),
+                                         new MemberAccessFilter(0,
+                                                                ClassConstants.INTERNAL_ACC_PRIVATE |
+                                                                ClassConstants.INTERNAL_ACC_STATIC,
+                                         memberVisitor)));
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean isSpecial(Clazz clazz, Method method)
+    {
+        return (method.getAccessFlags() &
+                (ClassConstants.INTERNAL_ACC_PRIVATE |
+                 ClassConstants.INTERNAL_ACC_STATIC)) != 0 ||
+               method.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT);
+    }
+}
diff --git a/src/proguard/classfile/visitor/MultiClassPoolVisitor.java b/src/proguard/classfile/visitor/MultiClassPoolVisitor.java
new file mode 100644
index 0000000..044d55a
--- /dev/null
+++ b/src/proguard/classfile/visitor/MultiClassPoolVisitor.java
@@ -0,0 +1,88 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.ClassPool;
+
+
+/**
+ * This ClassPoolVisitor delegates all visits to each ClassPoolVisitor
+ * in a given list.
+ *
+ * @author Eric Lafortune
+ */
+public class MultiClassPoolVisitor implements ClassPoolVisitor
+{
+    private static final int ARRAY_SIZE_INCREMENT = 5;
+
+    private ClassPoolVisitor[] classPoolVisitors;
+    private int                classPoolVisitorCount;
+
+
+    public MultiClassPoolVisitor()
+    {
+    }
+
+
+    public MultiClassPoolVisitor(ClassPoolVisitor[] classPoolVisitors)
+    {
+        this.classPoolVisitors     = classPoolVisitors;
+        this.classPoolVisitorCount = classPoolVisitors.length;
+    }
+
+
+    public void addClassPoolVisitor(ClassPoolVisitor classPoolVisitor)
+    {
+        ensureArraySize();
+
+        classPoolVisitors[classPoolVisitorCount++] = classPoolVisitor;
+    }
+
+
+    private void ensureArraySize()
+    {
+        if (classPoolVisitors == null)
+        {
+            classPoolVisitors = new ClassPoolVisitor[ARRAY_SIZE_INCREMENT];
+        }
+        else if (classPoolVisitors.length == classPoolVisitorCount)
+        {
+            ClassPoolVisitor[] newClassPoolVisitors =
+                new ClassPoolVisitor[classPoolVisitorCount +
+                                     ARRAY_SIZE_INCREMENT];
+            System.arraycopy(classPoolVisitors, 0,
+                             newClassPoolVisitors, 0,
+                             classPoolVisitorCount);
+            classPoolVisitors = newClassPoolVisitors;
+        }
+    }
+
+
+    // Implementations for ClassPoolVisitor.
+
+    public void visitClassPool(ClassPool classPool)
+    {
+        for (int index = 0; index < classPoolVisitorCount; index++)
+        {
+            classPoolVisitors[index].visitClassPool(classPool);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/MultiClassVisitor.java b/src/proguard/classfile/visitor/MultiClassVisitor.java
new file mode 100644
index 0000000..d34d91e
--- /dev/null
+++ b/src/proguard/classfile/visitor/MultiClassVisitor.java
@@ -0,0 +1,97 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This ClassVisitor delegates all visits to each ClassVisitor
+ * in a given list.
+ *
+ * @author Eric Lafortune
+ */
+public class MultiClassVisitor implements ClassVisitor
+{
+    private static final int ARRAY_SIZE_INCREMENT = 5;
+
+    private ClassVisitor[] classVisitors;
+    private int            classVisitorCount;
+
+
+    public MultiClassVisitor()
+    {
+    }
+
+
+    public MultiClassVisitor(ClassVisitor[] classVisitors)
+    {
+        this.classVisitors     = classVisitors;
+        this.classVisitorCount = classVisitors.length;
+    }
+
+
+    public void addClassVisitor(ClassVisitor classVisitor)
+    {
+        ensureArraySize();
+
+        classVisitors[classVisitorCount++] = classVisitor;
+    }
+
+
+    private void ensureArraySize()
+    {
+        if (classVisitors == null)
+        {
+            classVisitors = new ClassVisitor[ARRAY_SIZE_INCREMENT];
+        }
+        else if (classVisitors.length == classVisitorCount)
+        {
+            ClassVisitor[] newClassVisitors =
+                new ClassVisitor[classVisitorCount +
+                                     ARRAY_SIZE_INCREMENT];
+            System.arraycopy(classVisitors, 0,
+                             newClassVisitors, 0,
+                             classVisitorCount);
+            classVisitors = newClassVisitors;
+        }
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        for (int index = 0; index < classVisitorCount; index++)
+        {
+            classVisitors[index].visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        for (int index = 0; index < classVisitorCount; index++)
+        {
+            classVisitors[index].visitLibraryClass(libraryClass);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/MultiMemberVisitor.java b/src/proguard/classfile/visitor/MultiMemberVisitor.java
new file mode 100644
index 0000000..cc4629c
--- /dev/null
+++ b/src/proguard/classfile/visitor/MultiMemberVisitor.java
@@ -0,0 +1,113 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This MemberVisitor delegates all visits to each MemberVisitor
+ * in a given list.
+ *
+ * @author Eric Lafortune
+ */
+public class MultiMemberVisitor implements MemberVisitor
+{
+    private static final int ARRAY_SIZE_INCREMENT = 5;
+
+    private MemberVisitor[] memberVisitors;
+    private int             memberVisitorCount;
+
+
+    public MultiMemberVisitor()
+    {
+    }
+
+
+    public MultiMemberVisitor(MemberVisitor[] memberVisitors)
+    {
+        this.memberVisitors     = memberVisitors;
+        this.memberVisitorCount = memberVisitors.length;
+    }
+
+
+    public void addMemberVisitor(MemberVisitor memberVisitor)
+    {
+        ensureArraySize();
+
+        memberVisitors[memberVisitorCount++] = memberVisitor;
+    }
+
+
+    private void ensureArraySize()
+    {
+        if (memberVisitors == null)
+        {
+            memberVisitors = new MemberVisitor[ARRAY_SIZE_INCREMENT];
+        }
+        else if (memberVisitors.length == memberVisitorCount)
+        {
+            MemberVisitor[] newMemberVisitors =
+                new MemberVisitor[memberVisitorCount +
+                                         ARRAY_SIZE_INCREMENT];
+            System.arraycopy(memberVisitors, 0,
+                             newMemberVisitors, 0,
+                             memberVisitorCount);
+            memberVisitors = newMemberVisitors;
+        }
+    }
+
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        for (int index = 0; index < memberVisitorCount; index++)
+        {
+            memberVisitors[index].visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        for (int index = 0; index < memberVisitorCount; index++)
+        {
+            memberVisitors[index].visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        for (int index = 0; index < memberVisitorCount; index++)
+        {
+            memberVisitors[index].visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        for (int index = 0; index < memberVisitorCount; index++)
+        {
+            memberVisitors[index].visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/NamedClassVisitor.java b/src/proguard/classfile/visitor/NamedClassVisitor.java
new file mode 100644
index 0000000..a14d04a
--- /dev/null
+++ b/src/proguard/classfile/visitor/NamedClassVisitor.java
@@ -0,0 +1,49 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.ClassPool;
+
+
+/**
+ * This class visits Clazz objects with the given name.
+ *
+ * @author Eric Lafortune
+ */
+public class NamedClassVisitor implements ClassPoolVisitor
+{
+    private final ClassVisitor classVisitor;
+    private final String       name;
+
+
+    public NamedClassVisitor(ClassVisitor classVisitor,
+                             String       name)
+    {
+        this.classVisitor = classVisitor;
+        this.name         = name;
+    }
+
+
+    public void visitClassPool(ClassPool classPool)
+    {
+        classPool.classAccept(name, classVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/NamedFieldVisitor.java b/src/proguard/classfile/visitor/NamedFieldVisitor.java
new file mode 100644
index 0000000..76b66c6
--- /dev/null
+++ b/src/proguard/classfile/visitor/NamedFieldVisitor.java
@@ -0,0 +1,61 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This class visits ProgramMember objects referring to fields, identified by
+ * a name and descriptor pair.
+ *
+ * @author Eric Lafortune
+ */
+public class NamedFieldVisitor implements ClassVisitor
+{
+    private final String        name;
+    private final String        descriptor;
+    private final MemberVisitor memberVisitor;
+
+
+    public NamedFieldVisitor(String        name,
+                             String        descriptor,
+                             MemberVisitor memberVisitor)
+    {
+        this.name          = name;
+        this.descriptor    = descriptor;
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.fieldAccept(name, descriptor, memberVisitor);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.fieldAccept(name, descriptor, memberVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/NamedMethodVisitor.java b/src/proguard/classfile/visitor/NamedMethodVisitor.java
new file mode 100644
index 0000000..d4611c1
--- /dev/null
+++ b/src/proguard/classfile/visitor/NamedMethodVisitor.java
@@ -0,0 +1,61 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This class visits ProgramMember objects referring to methods, identified by
+ * a name and descriptor pair.
+ *
+ * @author Eric Lafortune
+ */
+public class NamedMethodVisitor implements ClassVisitor
+{
+    private final String        name;
+    private final String        descriptor;
+    private final MemberVisitor memberVisitor;
+
+
+    public NamedMethodVisitor(String        name,
+                              String        descriptor,
+                              MemberVisitor memberVisitor)
+    {
+        this.name          = name;
+        this.descriptor    = descriptor;
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.methodAccept(name, descriptor, memberVisitor);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.methodAccept(name, descriptor, memberVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ProgramClassFilter.java b/src/proguard/classfile/visitor/ProgramClassFilter.java
new file mode 100644
index 0000000..fba3b21
--- /dev/null
+++ b/src/proguard/classfile/visitor/ProgramClassFilter.java
@@ -0,0 +1,60 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to another given
+ * <code>ClassVisitor</code>, but only when visiting program classes.
+ *
+ * @author Eric Lafortune
+ */
+public class ProgramClassFilter implements ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ProgramClassFilter.
+     * @param classVisitor     the <code>ClassVisitor</code> to which visits
+     *                         will be delegated.
+     */
+    public ProgramClassFilter(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        classVisitor.visitProgramClass(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Don't delegate visits to library classes.
+    }
+}
diff --git a/src/proguard/classfile/visitor/ProgramMemberFilter.java b/src/proguard/classfile/visitor/ProgramMemberFilter.java
new file mode 100644
index 0000000..048a1e6
--- /dev/null
+++ b/src/proguard/classfile/visitor/ProgramMemberFilter.java
@@ -0,0 +1,73 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to another given
+ * <code>MemberVisitor</code>, but only when visiting members of program
+ * classes.
+ *
+ * @author Eric Lafortune
+ */
+public class ProgramMemberFilter implements MemberVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new ProgramMemberFilter.
+     * @param memberVisitor     the <code>MemberVisitor</code> to which
+     *                          visits will be delegated.
+     */
+    public ProgramMemberFilter(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        memberVisitor.visitProgramField(programClass, programField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        memberVisitor.visitProgramMethod(programClass, programMethod);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        // Don't delegate visits to library members.
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        // Don't delegate visits to library members.
+    }
+}
diff --git a/src/proguard/classfile/visitor/ReferencedClassVisitor.java b/src/proguard/classfile/visitor/ReferencedClassVisitor.java
new file mode 100644
index 0000000..986c3f9
--- /dev/null
+++ b/src/proguard/classfile/visitor/ReferencedClassVisitor.java
@@ -0,0 +1,248 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This ClassVisitor, MemberVisitor, ConstantVisitor, AttributeVisitor, etc.
+ * lets a given ClassVisitor visit all the referenced classes of the elements
+ * that it visits. Only downstream elements are considered (in order to avoid
+ * loops and repeated visits).
+ *
+ * @author Eric Lafortune
+ */
+public class ReferencedClassVisitor
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             ConstantVisitor,
+             AttributeVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    public ReferencedClassVisitor(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Visit the constant pool entries.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Visit the fields and methods.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+
+        // Visit the attributes.
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Visit the superclass and interfaces.
+        libraryClass.superClassAccept(classVisitor);
+        libraryClass.interfacesAccept(classVisitor);
+
+        // Visit the fields and methods.
+        libraryClass.fieldsAccept(this);
+        libraryClass.methodsAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        // Let the visitor visit the classes referenced in the descriptor string.
+        programMember.referencedClassesAccept(classVisitor);
+
+        // Visit the attributes.
+        programMember.attributesAccept(programClass, this);
+    }
+
+
+    public void visitLibraryMember(LibraryClass programClass, LibraryMember libraryMember)
+    {
+        // Let the visitor visit the classes referenced in the descriptor string.
+        libraryMember.referencedClassesAccept(classVisitor);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Let the visitor visit the class referenced in the string constant.
+        stringConstant.referencedClassAccept(classVisitor);
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        // Let the visitor visit the class referenced in the reference constant.
+        refConstant.referencedClassAccept(classVisitor);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Let the visitor visit the class referenced in the class constant.
+        classConstant.referencedClassAccept(classVisitor);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        // Let the visitor visit the class of the enclosing method.
+        enclosingMethodAttribute.referencedClassAccept(classVisitor);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Visit the attributes of the code attribute.
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Visit the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Visit the local variable types.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        // Let the visitor visit the classes referenced in the signature string.
+        signatureAttribute.referencedClassesAccept(classVisitor);
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Visit the annotations.
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Visit the parameter annotations.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Visit the default element value.
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        // Let the visitor visit the class referenced in the local variable.
+        localVariableInfo.referencedClassAccept(classVisitor);
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        // Let the visitor visit the classes referenced in the local variable type.
+        localVariableTypeInfo.referencedClassesAccept(classVisitor);
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        // Let the visitor visit the classes referenced in the annotation.
+        annotation.referencedClassesAccept(classVisitor);
+
+        // Visit the element values.
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) {}
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        // Let the visitor visit the classes referenced in the constant element value.
+        enumConstantElementValue.referencedClassesAccept(classVisitor);
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        // Let the visitor visit the classes referenced in the class element value.
+        classElementValue.referencedClassesAccept(classVisitor);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        // Visit the element values.
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+    }
+}
diff --git a/src/proguard/classfile/visitor/ReferencedMemberVisitor.java b/src/proguard/classfile/visitor/ReferencedMemberVisitor.java
new file mode 100644
index 0000000..c4d34b8
--- /dev/null
+++ b/src/proguard/classfile/visitor/ReferencedMemberVisitor.java
@@ -0,0 +1,73 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.ElementValueVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This ConstantVisitor and ElementValueVisitor lets a given MemberVisitor
+ * visit all the referenced class members of the elements that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class ReferencedMemberVisitor
+extends      SimplifiedVisitor
+implements   ConstantVisitor,
+             ElementValueVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    public ReferencedMemberVisitor(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        stringConstant.referencedMemberAccept(memberVisitor);
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        refConstant.referencedMemberAccept(memberVisitor);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue)
+    {
+        elementValue.referencedMethodAccept(memberVisitor);
+    }
+}
diff --git a/src/proguard/classfile/visitor/SimilarMemberVisitor.java b/src/proguard/classfile/visitor/SimilarMemberVisitor.java
new file mode 100644
index 0000000..6dc06af
--- /dev/null
+++ b/src/proguard/classfile/visitor/SimilarMemberVisitor.java
@@ -0,0 +1,125 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This <code>MemberVisitor</code> lets a given <code>MemberVisitor</code>
+ * visit all members that have the same name and type as the visited methods
+ * in the class hierarchy of a given target class.
+ *
+ * @author Eric Lafortune
+ */
+public class SimilarMemberVisitor
+implements   MemberVisitor
+{
+    private final Clazz         targetClass;
+    private final boolean       visitThisMember;
+    private final boolean       visitSuperMembers;
+    private final boolean       visitInterfaceMembers;
+    private final boolean       visitOverridingMembers;
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new SimilarMemberVisitor.
+     * @param targetClass            the class in whose hierarchy to look for
+     *                               the visited class members.
+     * @param visitThisMember        specifies whether to visit the class
+     *                               members in the target class itself.
+     * @param visitSuperMembers      specifies whether to visit the class
+     *                               members in the super classes of the target
+     *                               class.
+     * @param visitInterfaceMembers  specifies whether to visit the class
+     *                               members in the interface classes of the
+     *                               target class.
+     * @param visitOverridingMembers specifies whether to visit the class
+     *                               members in the subclasses of the target
+     *                               class.
+     * @param memberVisitor          the <code>MemberVisitor</code> to which
+     *                               visits will be delegated.
+     */
+    public SimilarMemberVisitor(Clazz         targetClass,
+                                boolean       visitThisMember,
+                                boolean       visitSuperMembers,
+                                boolean       visitInterfaceMembers,
+                                boolean       visitOverridingMembers,
+                                MemberVisitor memberVisitor)
+    {
+        this.targetClass            = targetClass;
+        this.visitThisMember        = visitThisMember;
+        this.visitSuperMembers      = visitSuperMembers;
+        this.visitInterfaceMembers  = visitInterfaceMembers;
+        this.visitOverridingMembers = visitOverridingMembers;
+        this.memberVisitor          = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        targetClass.hierarchyAccept(visitThisMember,
+                                    visitSuperMembers,
+                                    visitInterfaceMembers,
+                                    visitOverridingMembers,
+                                    new NamedFieldVisitor(programField.getName(programClass),
+                                                          programField.getDescriptor(programClass),
+                                                          memberVisitor));
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        targetClass.hierarchyAccept(visitThisMember,
+                                    visitSuperMembers,
+                                    visitInterfaceMembers,
+                                    visitOverridingMembers,
+                                    new NamedFieldVisitor(libraryField.getName(libraryClass),
+                                                          libraryField.getDescriptor(libraryClass),
+                                                          memberVisitor));
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        targetClass.hierarchyAccept(visitThisMember,
+                                    visitSuperMembers,
+                                    visitInterfaceMembers,
+                                    visitOverridingMembers,
+                                    new NamedMethodVisitor(programMethod.getName(programClass),
+                                                           programMethod.getDescriptor(programClass),
+                                                           memberVisitor));
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        targetClass.hierarchyAccept(visitThisMember,
+                                    visitSuperMembers,
+                                    visitInterfaceMembers,
+                                    visitOverridingMembers,
+                                    new NamedMethodVisitor(libraryMethod.getName(libraryClass),
+                                                           libraryMethod.getDescriptor(libraryClass),
+                                                           memberVisitor));
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/SimpleClassPrinter.java b/src/proguard/classfile/visitor/SimpleClassPrinter.java
new file mode 100644
index 0000000..a661110
--- /dev/null
+++ b/src/proguard/classfile/visitor/SimpleClassPrinter.java
@@ -0,0 +1,167 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+import proguard.classfile.util.ClassUtil;
+
+import java.io.PrintStream;
+
+
+/**
+ * This <code>ClassVisitor</code> and <code>MemberVisitor</code>
+ * prints out the class names of the classes it visits, and the full class
+ * member descriptions of the class members it visits. The names are printed
+ * in a readable, Java-like format. The access modifiers can be included or not.
+ *
+ * @author Eric Lafortune
+ */
+public class SimpleClassPrinter
+implements   ClassVisitor,
+             MemberVisitor
+{
+    private final boolean     printAccessModifiers;
+    private final PrintStream ps;
+
+
+    /**
+     * Creates a new SimpleClassPrinter that prints to
+     * <code>System.out</code>, including the access modifiers.
+     */
+    public SimpleClassPrinter()
+    {
+        this(true);
+    }
+
+    /**
+     * Creates a new SimpleClassPrinter that prints to
+     * <code>System.out</code>, with or without the access modifiers.
+     */
+    public SimpleClassPrinter(boolean printAccessModifiers)
+    {
+        this(printAccessModifiers, System.out);
+    }
+
+    /**
+     * Creates a new SimpleClassPrinter that prints to the given
+     * <code>PrintStream</code>, with or without the access modifiers.
+     */
+    public SimpleClassPrinter(boolean     printAccessModifiers,
+                              PrintStream printStream)
+    {
+        this.printAccessModifiers = printAccessModifiers;
+        this.ps                   = printStream;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        ps.println(ClassUtil.externalFullClassDescription(
+                       printAccessModifiers ?
+                           programClass.getAccessFlags() :
+                           0,
+                       programClass.getName()));
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        ps.println(ClassUtil.externalFullClassDescription(
+                       printAccessModifiers ?
+                           libraryClass.getAccessFlags() :
+                           0,
+                       libraryClass.getName()));
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        ps.println(ClassUtil.externalFullClassDescription(
+                       printAccessModifiers ?
+                           programClass.getAccessFlags() :
+                           0,
+                       programClass.getName()) +
+                   ": " +
+                   ClassUtil.externalFullFieldDescription(
+                       printAccessModifiers ?
+                           programField.getAccessFlags() :
+                           0,
+                       programField.getName(programClass),
+                       programField.getDescriptor(programClass)));
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        ps.println(ClassUtil.externalFullClassDescription(
+                       printAccessModifiers ?
+                           programClass.getAccessFlags() :
+                           0,
+                       programClass.getName()) +
+                   ": " +
+                   ClassUtil.externalFullMethodDescription(
+                       programClass.getName(),
+                       printAccessModifiers ?
+                           programMethod.getAccessFlags() :
+                           0,
+                       programMethod.getName(programClass),
+                       programMethod.getDescriptor(programClass)));
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        ps.println(ClassUtil.externalFullClassDescription(
+                       printAccessModifiers ?
+                           libraryClass.getAccessFlags() :
+                           0,
+                       libraryClass.getName()) +
+                   ": " +
+                   ClassUtil.externalFullFieldDescription(
+                       printAccessModifiers ?
+                           libraryField.getAccessFlags() :
+                           0,
+                       libraryField.getName(libraryClass),
+                       libraryField.getDescriptor(libraryClass)));
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        ps.println(ClassUtil.externalFullClassDescription(
+                       printAccessModifiers ?
+                           libraryClass.getAccessFlags() :
+                           0,
+                       libraryClass.getName()) +
+                   ": " +
+                   ClassUtil.externalFullMethodDescription(
+                       libraryClass.getName(),
+                       printAccessModifiers ?
+                           libraryMethod.getAccessFlags() :
+                           0,
+                       libraryMethod.getName(libraryClass),
+                       libraryMethod.getDescriptor(libraryClass)));
+    }
+}
diff --git a/src/proguard/classfile/visitor/SubclassFilter.java b/src/proguard/classfile/visitor/SubclassFilter.java
new file mode 100644
index 0000000..69ea1a1
--- /dev/null
+++ b/src/proguard/classfile/visitor/SubclassFilter.java
@@ -0,0 +1,91 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+/**
+ * This <code>ClassVisitor</code> delegates its visits to another given
+ * <code>ClassVisitor</code>, except for classes that have a given class as
+ * direct subclass.
+ *
+ * @author Eric Lafortune
+ */
+public class SubclassFilter implements ClassVisitor
+{
+    private final Clazz        subclass;
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new SubclassFilter.
+     * @param subclass     the class whose superclasses will not be visited.
+     * @param classVisitor the <code>ClassVisitor</code> to which visits will
+     *                     be delegated.
+     */
+    public SubclassFilter(Clazz        subclass,
+                          ClassVisitor classVisitor)
+    {
+        this.subclass     = subclass;
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (!present(programClass.subClasses))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (!present(libraryClass.subClasses))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean present(Clazz[] subclasses)
+    {
+        if (subclasses == null)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < subclasses.length; index++)
+        {
+            if (subclasses[index].equals(subclass))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/SubclassTraveler.java b/src/proguard/classfile/visitor/SubclassTraveler.java
new file mode 100644
index 0000000..4170341
--- /dev/null
+++ b/src/proguard/classfile/visitor/SubclassTraveler.java
@@ -0,0 +1,60 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This <code>ClassVisitor</code> lets a given <code>ClassVisitor</code>
+ * travel to direct subclasses of the visited class.
+ *
+ * @author Eric Lafortune
+ */
+public class SubclassTraveler implements ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new ClassHierarchyTraveler.
+     * @param classVisitor    the <code>ClassVisitor</code> to
+     *                        which visits will be delegated.
+     */
+    public SubclassTraveler(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.subclassesAccept(classVisitor);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.subclassesAccept(classVisitor);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/classfile/visitor/VariableClassVisitor.java b/src/proguard/classfile/visitor/VariableClassVisitor.java
new file mode 100644
index 0000000..2f575c4
--- /dev/null
+++ b/src/proguard/classfile/visitor/VariableClassVisitor.java
@@ -0,0 +1,78 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This ClassVisitor delegates all method calls to a ClassVisitor
+ * that can be changed at any time.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableClassVisitor implements ClassVisitor
+{
+    private ClassVisitor classVisitor;
+
+
+    public VariableClassVisitor()
+    {
+        this(null);
+    }
+
+
+    public VariableClassVisitor(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    public void setClassVisitor(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+    public ClassVisitor getClassVisitor()
+    {
+        return classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (classVisitor != null)
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (classVisitor != null)
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/VariableMemberVisitor.java b/src/proguard/classfile/visitor/VariableMemberVisitor.java
new file mode 100644
index 0000000..c58cff3
--- /dev/null
+++ b/src/proguard/classfile/visitor/VariableMemberVisitor.java
@@ -0,0 +1,96 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.classfile.visitor;
+
+import proguard.classfile.*;
+
+
+/**
+ * This MemberVisitor     delegates all method calls to a MemberVisitor
+ * that can be changed at any time.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableMemberVisitor implements MemberVisitor
+{
+    private MemberVisitor memberVisitor;
+
+
+    public VariableMemberVisitor()
+    {
+        this(null);
+    }
+
+
+    public VariableMemberVisitor(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    public void setMemberVisitor(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+    public MemberVisitor getMemberVisitor()
+    {
+        return memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (memberVisitor != null)
+        {
+            memberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (memberVisitor != null)
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (memberVisitor != null)
+        {
+            memberVisitor.visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (memberVisitor != null)
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+}
diff --git a/src/proguard/classfile/visitor/package.html b/src/proguard/classfile/visitor/package.html
new file mode 100644
index 0000000..d3be40c
--- /dev/null
+++ b/src/proguard/classfile/visitor/package.html
@@ -0,0 +1,40 @@
+<body>
+This package contains interfaces and classes for processing class files from
+the <code>{@link proguard.classfile proguard.classfile}</code> package using
+the <i>visitor pattern</i>. Cfr., for instance, "Design Patterns, Elements of
+Reusable OO Software", by Gamma, Helm, Johnson, and Vlissider.
+<p>
+Why the visitor pattern? Class files frequently contain lists of elements of
+various mixed types: class items, constant pool entries, attributes,...
+These lists and types are largely fixed; they won't change much in future
+releases of the Java class file specifications. On the other hand, the kinds
+of operations that we may wish to perform on the class files may change and
+expand. We want to separate the objects and the operations performed upon them.
+This is a good place to use the visitor pattern.
+<p>
+Visitor interfaces avoid having to do series of <code>instanceof</code> tests
+on the elements of a list, followed by type casts and the proper operations.
+Every list element is a visitor accepter. When its <code>accept</code> method
+is called by a visitor, it calls its corresponding <code>visitX</code> method
+in the visitor, passing itself as an argument. This technique is called
+double-dispatch.
+<p>
+As already mentioned, the main advantage is avoiding lots of
+<code>instanceof</code> tests and type casts. Also, implementing a visitor
+interface ensures you're handling all possible visitor accepter types. Each
+type has its own method, which you simply have to implement.
+<p>
+A disadvantage is that the visitor methods always get the same names, specified
+by the visitor interface. These names aren't descriptive at all, making code
+harder to read. It's the visitor classes that describe the operations now.
+<p>
+Also, the visitor methods always have the same parameters and return values, as
+specified by the visitor interfaces. Passing additional parameters is done by
+means of extra fields in the visitor, which is somewhat of a kludge.
+<p>
+Because objects (the visitor accepters) and the operations performed upon them
+(the visitors) are now separated, it becomes harder to associate some state
+with the objects. For convenience, we always provide an extra <i>visitor
+info</i> field in visitor accepters, in which visitors can put any temporary
+information they want.
+</body>
diff --git a/src/proguard/evaluation/BasicBranchUnit.java b/src/proguard/evaluation/BasicBranchUnit.java
new file mode 100644
index 0000000..3a2db76
--- /dev/null
+++ b/src/proguard/evaluation/BasicBranchUnit.java
@@ -0,0 +1,126 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.evaluation.value.InstructionOffsetValue;
+
+/**
+ * This BranchUnit remembers the branch unit commands that are invoked on it.
+ * I doesn't consider conditions when branching.
+ *
+ * @author Eric Lafortune
+ */
+public class BasicBranchUnit
+implements   BranchUnit
+{
+    private boolean                wasCalled;
+    private InstructionOffsetValue traceBranchTargets;
+
+
+    /**
+     * Resets the flag that tells whether any of the branch unit commands was
+     * called.
+     */
+    public void resetCalled()
+    {
+        wasCalled = false;
+    }
+
+    /**
+     * Sets the flag that tells whether any of the branch unit commands was
+     * called.
+     */
+    protected void setCalled()
+    {
+        wasCalled = true;
+    }
+
+    /**
+     * Returns whether any of the branch unit commands was called.
+     */
+    public boolean wasCalled()
+    {
+        return wasCalled;
+    }
+
+
+    /**
+     * Sets the initial branch targets, which will be updated as the branch
+     * methods of the branch unit are called.
+     */
+    public void setTraceBranchTargets(InstructionOffsetValue branchTargets)
+    {
+        this.traceBranchTargets = branchTargets;
+    }
+
+    public InstructionOffsetValue getTraceBranchTargets()
+    {
+        return traceBranchTargets;
+    }
+
+
+    // Implementations for BranchUnit.
+
+    public void branch(Clazz         clazz,
+                       CodeAttribute codeAttribute,
+                       int           offset,
+                       int           branchTarget)
+    {
+        // Override the branch targets.
+        traceBranchTargets = new InstructionOffsetValue(branchTarget);
+
+        wasCalled = true;
+    }
+
+
+    public void branchConditionally(Clazz         clazz,
+                                    CodeAttribute codeAttribute,
+                                    int           offset,
+                                    int           branchTarget,
+                                    int           conditional)
+    {
+        // Accumulate the branch targets.
+        traceBranchTargets =
+            traceBranchTargets.generalize(new InstructionOffsetValue(branchTarget)).instructionOffsetValue();
+
+        wasCalled = true;
+    }
+
+
+    public void returnFromMethod()
+    {
+        // Stop processing this block.
+        traceBranchTargets = InstructionOffsetValue.EMPTY_VALUE;
+
+        wasCalled = true;
+    }
+
+
+    public void throwException()
+    {
+        // Stop processing this block.
+        traceBranchTargets = InstructionOffsetValue.EMPTY_VALUE;
+
+        wasCalled = true;
+    }
+}
diff --git a/src/proguard/evaluation/BasicInvocationUnit.java b/src/proguard/evaluation/BasicInvocationUnit.java
new file mode 100644
index 0000000..bccd866
--- /dev/null
+++ b/src/proguard/evaluation/BasicInvocationUnit.java
@@ -0,0 +1,380 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.evaluation.value.*;
+
+/**
+ * This InvocationUnit sets up the variables for entering a method,
+ * and it updates the stack for the invocation of a class member,
+ * using simple values.
+ *
+ * @author Eric Lafortune
+ */
+public class BasicInvocationUnit
+extends      SimplifiedVisitor
+implements   InvocationUnit,
+             ConstantVisitor,
+             MemberVisitor
+{
+    protected final ValueFactory valueFactory;
+
+    // Fields acting as parameters between the visitor methods.
+    private boolean isStatic;
+    private boolean isLoad;
+    private Stack   stack;
+    private Clazz   returnTypeClass;
+
+
+    /**
+     * Creates a new BasicInvocationUnit with the given value factory.
+     */
+    public BasicInvocationUnit(ValueFactory valueFactory)
+    {
+        this.valueFactory = valueFactory;
+    }
+
+
+    // Implementations for InvocationUnit.
+
+    public void enterMethod(Clazz clazz, Method method, Variables variables)
+    {
+        String descriptor = method.getDescriptor(clazz);
+
+        // Initialize the parameters.
+        boolean isStatic =
+            (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0;
+
+        // Count the number of parameters, taking into account their categories.
+        int parameterSize = ClassUtil.internalMethodParameterSize(descriptor, isStatic);
+
+        // Reuse the existing parameters object, ensuring the right size.
+        variables.reset(parameterSize);
+
+        // Go over the parameters again.
+        InternalTypeEnumeration internalTypeEnumeration =
+            new InternalTypeEnumeration(descriptor);
+
+        int parameterIndex = 0;
+        int variableIndex  = 0;
+
+        // Put the 'this' reference in variable 0.
+        if (!isStatic)
+        {
+            // Get the reference value.
+            Value value = getMethodParameterValue(clazz,
+                                                  method,
+                                                  parameterIndex++,
+                                                  ClassUtil.internalTypeFromClassName(clazz.getName()),
+                                                  clazz);
+
+            // Store the value in variable 0.
+            variables.store(variableIndex++, value);
+        }
+
+        Clazz[] referencedClasses = ((ProgramMethod)method).referencedClasses;
+        int referencedClassIndex = 0;
+
+        // Set up the variables corresponding to the parameter types and values.
+        while (internalTypeEnumeration.hasMoreTypes())
+        {
+            String type = internalTypeEnumeration.nextType();
+
+            Clazz referencedClass = referencedClasses != null &&
+                                    ClassUtil.isInternalClassType(type) ?
+                referencedClasses[referencedClassIndex++] :
+                null;
+
+            // Get the parameter value.
+            Value value = getMethodParameterValue(clazz,
+                                                  method,
+                                                  parameterIndex++,
+                                                  type,
+                                                  referencedClass);
+
+            // Store the value in the corresponding variable.
+            variables.store(variableIndex++, value);
+
+            // Increment the variable index again for Category 2 values.
+            if (value.isCategory2())
+            {
+                variableIndex++;
+            }
+        }
+    }
+
+
+    public void exitMethod(Clazz clazz, Method method, Value returnValue)
+    {
+        setMethodReturnValue(clazz, method, returnValue);
+    }
+
+
+    public void invokeMember(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction, Stack stack)
+    {
+        int constantIndex = constantInstruction.constantIndex;
+
+        switch (constantInstruction.opcode)
+        {
+            case InstructionConstants.OP_GETSTATIC:
+                isStatic = true;
+                isLoad   = true;
+                break;
+
+            case InstructionConstants.OP_PUTSTATIC:
+                isStatic = true;
+                isLoad   = false;
+                break;
+
+            case InstructionConstants.OP_GETFIELD:
+                isStatic = false;
+                isLoad   = true;
+                break;
+
+            case InstructionConstants.OP_PUTFIELD:
+                isStatic = false;
+                isLoad   = false;
+                break;
+
+            case InstructionConstants.OP_INVOKESTATIC:
+                isStatic = true;
+                break;
+
+            case InstructionConstants.OP_INVOKEVIRTUAL:
+            case InstructionConstants.OP_INVOKESPECIAL:
+            case InstructionConstants.OP_INVOKEINTERFACE:
+                isStatic = false;
+                break;
+        }
+
+        // Pop the parameters and push the return value.
+        this.stack = stack;
+        clazz.constantPoolEntryAccept(constantIndex, this);
+        this.stack = null;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        // Pop the field value, if applicable.
+        if (!isLoad)
+        {
+            setFieldValue(clazz, fieldrefConstant, stack.pop());
+        }
+
+        // Pop the reference value, if applicable.
+        if (!isStatic)
+        {
+            setFieldClassValue(clazz, fieldrefConstant, stack.apop());
+        }
+
+        // Push the field value, if applicable.
+        if (isLoad)
+        {
+            String type = fieldrefConstant.getType(clazz);
+
+            stack.push(getFieldValue(clazz, fieldrefConstant, type));
+        }
+    }
+
+
+    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant methodrefConstant)
+    {
+        String type = methodrefConstant.getType(clazz);
+
+        // Count the number of parameters.
+        int parameterCount = ClassUtil.internalMethodParameterCount(type);
+        if (!isStatic)
+        {
+            parameterCount++;
+        }
+
+        // Pop the parameters and the class reference, in reverse order.
+        for (int parameterIndex = parameterCount-1; parameterIndex >= 0; parameterIndex--)
+        {
+            setMethodParameterValue(clazz, methodrefConstant, parameterIndex, stack.pop());
+        }
+
+        // Push the return value, if applicable.
+        String returnType = ClassUtil.internalMethodReturnType(type);
+        if (returnType.charAt(0) != ClassConstants.INTERNAL_TYPE_VOID)
+        {
+            stack.push(getMethodReturnValue(clazz, methodrefConstant, returnType));
+        }
+    }
+
+
+    /**
+     * Sets the class through which the specified field is accessed.
+     */
+    protected void setFieldClassValue(Clazz          clazz,
+                                      RefConstant    refConstant,
+                                      ReferenceValue value)
+    {
+        // We don't care about the new value.
+    }
+
+
+    /**
+     * Returns the class though which the specified field is accessed.
+     */
+    protected Value getFieldClassValue(Clazz       clazz,
+                                       RefConstant refConstant,
+                                       String      type)
+    {
+        // Try to figure out the class of the return type.
+        returnTypeClass = null;
+        refConstant.referencedMemberAccept(this);
+
+        return valueFactory.createValue(type,
+                                        returnTypeClass,
+                                        true);
+    }
+
+
+    /**
+     * Sets the value of the specified field.
+     */
+    protected void setFieldValue(Clazz       clazz,
+                                 RefConstant refConstant,
+                                 Value       value)
+    {
+        // We don't care about the new field value.
+    }
+
+
+    /**
+     * Returns the value of the specified field.
+     */
+    protected Value getFieldValue(Clazz       clazz,
+                                  RefConstant refConstant,
+                                  String      type)
+    {
+        // Try to figure out the class of the return type.
+        returnTypeClass = null;
+        refConstant.referencedMemberAccept(this);
+
+        return valueFactory.createValue(type,
+                                        returnTypeClass,
+                                        true);
+    }
+
+
+    /**
+     * Sets the value of the specified method parameter.
+     */
+    protected void setMethodParameterValue(Clazz       clazz,
+                                           RefConstant refConstant,
+                                           int         parameterIndex,
+                                           Value       value)
+    {
+        // We don't care about the parameter value.
+    }
+
+
+    /**
+     * Returns the value of the specified method parameter.
+     */
+    protected Value getMethodParameterValue(Clazz  clazz,
+                                            Method method,
+                                            int    parameterIndex,
+                                            String type,
+                                            Clazz  referencedClass)
+    {
+        return valueFactory.createValue(type, referencedClass, true);
+    }
+
+
+    /**
+     * Sets the return value of the specified method.
+     */
+    protected void setMethodReturnValue(Clazz  clazz,
+                                        Method method,
+                                        Value  value)
+    {
+        // We don't care about the return value.
+    }
+
+
+    /**
+     * Returns the return value of the specified method.
+     */
+    protected Value getMethodReturnValue(Clazz       clazz,
+                                         RefConstant refConstant,
+                                         String      type)
+    {
+        // Try to figure out the class of the return type.
+        returnTypeClass = null;
+        refConstant.referencedMemberAccept(this);
+
+        return valueFactory.createValue(type,
+                                        returnTypeClass,
+                                        true);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        returnTypeClass = programField.referencedClass;
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        Clazz[] referencedClasses = programMethod.referencedClasses;
+        if (referencedClasses != null)
+        {
+            returnTypeClass = referencedClasses[referencedClasses.length - 1];
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass programClass, LibraryField programField)
+    {
+        returnTypeClass = programField.referencedClass;
+    }
+
+
+    public void visitLibraryMethod(LibraryClass programClass, LibraryMethod programMethod)
+    {
+        Clazz[] referencedClasses = programMethod.referencedClasses;
+        if (referencedClasses != null)
+        {
+            returnTypeClass = referencedClasses[referencedClasses.length - 1];
+        }
+    }
+
+
+//    public void visitLibraryMember(LibraryClass libraryClass, LibraryMember libraryMember)
+//    {
+//    }
+}
diff --git a/src/proguard/evaluation/BranchUnit.java b/src/proguard/evaluation/BranchUnit.java
new file mode 100644
index 0000000..b709807
--- /dev/null
+++ b/src/proguard/evaluation/BranchUnit.java
@@ -0,0 +1,63 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.CodeAttribute;
+
+/**
+ * This InstructionVisitor evaluates the instructions that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public interface BranchUnit
+{
+    /**
+     * Sets the new instruction offset.
+     */
+    public void branch(Clazz         clazz,
+                       CodeAttribute codeAttribute,
+                       int           offset,
+                       int           branchTarget);
+
+
+    /**
+     * Sets the new instruction offset, depending on the certainty of the
+     * conditional branch.
+     */
+    public void branchConditionally(Clazz         clazz,
+                                    CodeAttribute codeAttribute,
+                                    int           offset,
+                                    int           branchTarget,
+                                    int           conditional);
+
+
+    /**
+     * Returns from the method with the given value.
+     */
+    public void returnFromMethod();
+
+
+    /**
+     * Handles the throwing of an exception.
+     */
+    public void throwException();
+}
diff --git a/src/proguard/evaluation/InvocationUnit.java b/src/proguard/evaluation/InvocationUnit.java
new file mode 100644
index 0000000..cb4d3c5
--- /dev/null
+++ b/src/proguard/evaluation/InvocationUnit.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.ConstantInstruction;
+import proguard.evaluation.value.Value;
+
+/**
+ * This interface sets up the variables for entering a method,
+ * and it updates the stack for the invocation of a class member.
+ *
+ * @author Eric Lafortune
+ */
+public interface InvocationUnit
+{
+    /**
+     * Sets up the given variables for entering the given method.
+     */
+    public void enterMethod(Clazz     clazz,
+                            Method    method,
+                            Variables variables);
+
+
+    /**
+     * Exits the given method with the given return value.
+     */
+    public void exitMethod(Clazz  clazz,
+                           Method method,
+                           Value  returnValue);
+
+
+    /**
+     * Updates the given stack corresponding to the execution of the given
+     * field or method reference instruction.
+     */
+    public void invokeMember(Clazz               clazz,
+                             Method              method,
+                             CodeAttribute       codeAttribute,
+                             int                 offset,
+                             ConstantInstruction constantInstruction,
+                             Stack               stack);
+}
diff --git a/src/proguard/evaluation/Processor.java b/src/proguard/evaluation/Processor.java
new file mode 100644
index 0000000..74afd0b
--- /dev/null
+++ b/src/proguard/evaluation/Processor.java
@@ -0,0 +1,979 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.evaluation.value.*;
+
+/**
+ * This InstructionVisitor executes the instructions that it visits on a given
+ * local variable frame and stack.
+ *
+ * @author Eric Lafortune
+ */
+public class Processor
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor
+{
+    private final Variables      variables;
+    private final Stack          stack;
+    private final ValueFactory   valueFactory;
+    private final BranchUnit     branchUnit;
+    private final InvocationUnit invocationUnit;
+
+    // Fields acting as parameters for the ConstantVisitor methods.
+    private boolean handleClassConstantAsClassValue;
+    private Value   cpValue;
+
+
+    /**
+     * Creates a new processor that operates on the given environment.
+     * @param variables      the local variable frame.
+     * @param stack          the local stack.
+     * @param branchUnit     the class that can affect the program counter.
+     * @param invocationUnit the class that can access other program members.
+     */
+    public Processor(Variables      variables,
+                     Stack          stack,
+                     ValueFactory   valueFactory,
+                     BranchUnit     branchUnit,
+                     InvocationUnit invocationUnit)
+    {
+        this.variables      = variables;
+        this.stack          = stack;
+        this.valueFactory   = valueFactory;
+        this.branchUnit     = branchUnit;
+        this.invocationUnit = invocationUnit;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        switch (simpleInstruction.opcode)
+        {
+            case InstructionConstants.OP_NOP:
+                break;
+
+            case InstructionConstants.OP_ACONST_NULL:
+                stack.push(valueFactory.createReferenceValueNull());
+                break;
+
+            case InstructionConstants.OP_ICONST_M1:
+            case InstructionConstants.OP_ICONST_0:
+            case InstructionConstants.OP_ICONST_1:
+            case InstructionConstants.OP_ICONST_2:
+            case InstructionConstants.OP_ICONST_3:
+            case InstructionConstants.OP_ICONST_4:
+            case InstructionConstants.OP_ICONST_5:
+            case InstructionConstants.OP_BIPUSH:
+            case InstructionConstants.OP_SIPUSH:
+                stack.push(valueFactory.createIntegerValue(simpleInstruction.constant));
+                break;
+
+            case InstructionConstants.OP_LCONST_0:
+            case InstructionConstants.OP_LCONST_1:
+                stack.push(valueFactory.createLongValue(simpleInstruction.constant));
+                break;
+
+            case InstructionConstants.OP_FCONST_0:
+            case InstructionConstants.OP_FCONST_1:
+            case InstructionConstants.OP_FCONST_2:
+                stack.push(valueFactory.createFloatValue((float)simpleInstruction.constant));
+                break;
+
+            case InstructionConstants.OP_DCONST_0:
+            case InstructionConstants.OP_DCONST_1:
+                stack.push(valueFactory.createDoubleValue((double)simpleInstruction.constant));
+                break;
+
+            case InstructionConstants.OP_IALOAD:
+            case InstructionConstants.OP_BALOAD:
+            case InstructionConstants.OP_CALOAD:
+            case InstructionConstants.OP_SALOAD:
+                stack.ipop();
+                stack.apop();
+                stack.push(valueFactory.createIntegerValue());
+                break;
+
+            case InstructionConstants.OP_LALOAD:
+                stack.ipop();
+                stack.apop();
+                stack.push(valueFactory.createLongValue());
+                break;
+
+            case InstructionConstants.OP_FALOAD:
+                stack.ipop();
+                stack.apop();
+                stack.push(valueFactory.createFloatValue());
+                break;
+
+            case InstructionConstants.OP_DALOAD:
+                stack.ipop();
+                stack.apop();
+                stack.push(valueFactory.createDoubleValue());
+                break;
+
+            case InstructionConstants.OP_AALOAD:
+            {
+                IntegerValue   arrayIndex     = stack.ipop();
+                ReferenceValue arrayReference = stack.apop();
+                stack.push(arrayReference.arrayLoad(arrayIndex, valueFactory));
+                break;
+            }
+
+            case InstructionConstants.OP_IASTORE:
+            case InstructionConstants.OP_BASTORE:
+            case InstructionConstants.OP_CASTORE:
+            case InstructionConstants.OP_SASTORE:
+                stack.ipop();
+                stack.ipop();
+                stack.apop();
+                break;
+
+            case InstructionConstants.OP_LASTORE:
+                stack.lpop();
+                stack.ipop();
+                stack.apop();
+                break;
+
+            case InstructionConstants.OP_FASTORE:
+                stack.fpop();
+                stack.ipop();
+                stack.apop();
+                break;
+
+            case InstructionConstants.OP_DASTORE:
+                stack.dpop();
+                stack.ipop();
+                stack.apop();
+                break;
+
+            case InstructionConstants.OP_AASTORE:
+                stack.apop();
+                stack.ipop();
+                stack.apop();
+                break;
+
+            case InstructionConstants.OP_POP:
+                stack.pop1();
+                break;
+
+            case InstructionConstants.OP_POP2:
+                stack.pop2();
+                break;
+
+            case InstructionConstants.OP_DUP:
+                stack.dup();
+                break;
+
+            case InstructionConstants.OP_DUP_X1:
+                stack.dup_x1();
+                break;
+
+            case InstructionConstants.OP_DUP_X2:
+                stack.dup_x2();
+                break;
+
+            case InstructionConstants.OP_DUP2:
+                stack.dup2();
+                break;
+
+            case InstructionConstants.OP_DUP2_X1:
+                stack.dup2_x1();
+                break;
+
+            case InstructionConstants.OP_DUP2_X2:
+                stack.dup2_x2();
+                break;
+
+            case InstructionConstants.OP_SWAP:
+                stack.swap();
+                break;
+
+            case InstructionConstants.OP_IADD:
+                stack.push(stack.ipop().add(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LADD:
+                stack.push(stack.lpop().add(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_FADD:
+                stack.push(stack.fpop().add(stack.fpop()));
+                break;
+
+            case InstructionConstants.OP_DADD:
+                stack.push(stack.dpop().add(stack.dpop()));
+                break;
+
+            case InstructionConstants.OP_ISUB:
+                stack.push(stack.ipop().subtractFrom(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LSUB:
+                stack.push(stack.lpop().subtractFrom(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_FSUB:
+                stack.push(stack.fpop().subtractFrom(stack.fpop()));
+                break;
+
+            case InstructionConstants.OP_DSUB:
+                stack.push(stack.dpop().subtractFrom(stack.dpop()));
+                break;
+
+            case InstructionConstants.OP_IMUL:
+                stack.push(stack.ipop().multiply(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LMUL:
+                stack.push(stack.lpop().multiply(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_FMUL:
+                stack.push(stack.fpop().multiply(stack.fpop()));
+                break;
+
+            case InstructionConstants.OP_DMUL:
+                stack.push(stack.dpop().multiply(stack.dpop()));
+                break;
+
+            case InstructionConstants.OP_IDIV:
+                try
+                {
+                    stack.push(stack.ipop().divideOf(stack.ipop()));
+                }
+                catch (ArithmeticException ex)
+                {
+                    stack.push(valueFactory.createIntegerValue());
+                    // TODO: Forward ArithmeticExceptions.
+                    //stack.clear();
+                    //stack.push(valueFactory.createReference(false));
+                    //branchUnit.throwException();
+                }
+                break;
+
+            case InstructionConstants.OP_LDIV:
+                try
+                {
+                    stack.push(stack.lpop().divideOf(stack.lpop()));
+                }
+                catch (ArithmeticException ex)
+                {
+                    stack.push(valueFactory.createLongValue());
+                    // TODO: Forward ArithmeticExceptions.
+                    //stack.clear();
+                    //stack.push(valueFactory.createReference(false));
+                    //branchUnit.throwException();
+                }
+                break;
+
+            case InstructionConstants.OP_FDIV:
+                stack.push(stack.fpop().divideOf(stack.fpop()));
+                break;
+
+            case InstructionConstants.OP_DDIV:
+                stack.push(stack.dpop().divideOf(stack.dpop()));
+                break;
+
+            case InstructionConstants.OP_IREM:
+                try
+                {
+                    stack.push(stack.ipop().remainderOf(stack.ipop()));
+                }
+                catch (ArithmeticException ex)
+                {
+                    stack.push(valueFactory.createIntegerValue());
+                    // TODO: Forward ArithmeticExceptions.
+                    //stack.clear();
+                    //stack.push(valueFactory.createReference(false));
+                    //branchUnit.throwException();
+                }
+                break;
+
+            case InstructionConstants.OP_LREM:
+                try
+                {
+                    stack.push(stack.lpop().remainderOf(stack.lpop()));
+                }
+                catch (ArithmeticException ex)
+                {
+                    stack.push(valueFactory.createLongValue());
+                    // TODO: Forward ArithmeticExceptions.
+                    //stack.clear();
+                    //stack.push(valueFactory.createReference(false));
+                    //branchUnit.throwException();
+                }
+                break;
+
+            case InstructionConstants.OP_FREM:
+                stack.push(stack.fpop().remainderOf(stack.fpop()));
+                break;
+
+            case InstructionConstants.OP_DREM:
+                stack.push(stack.dpop().remainderOf(stack.dpop()));
+                break;
+
+            case InstructionConstants.OP_INEG:
+                stack.push(stack.ipop().negate());
+                break;
+
+            case InstructionConstants.OP_LNEG:
+                stack.push(stack.lpop().negate());
+                break;
+
+            case InstructionConstants.OP_FNEG:
+                stack.push(stack.fpop().negate());
+                break;
+
+            case InstructionConstants.OP_DNEG:
+                stack.push(stack.dpop().negate());
+                break;
+
+            case InstructionConstants.OP_ISHL:
+                stack.push(stack.ipop().shiftLeftOf(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LSHL:
+                stack.push(stack.ipop().shiftLeftOf(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_ISHR:
+                stack.push(stack.ipop().shiftRightOf(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LSHR:
+                stack.push(stack.ipop().shiftRightOf(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_IUSHR:
+                stack.push(stack.ipop().unsignedShiftRightOf(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LUSHR:
+                stack.push(stack.ipop().unsignedShiftRightOf(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_IAND:
+                stack.push(stack.ipop().and(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LAND:
+                stack.push(stack.lpop().and(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_IOR:
+                stack.push(stack.ipop().or(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LOR:
+                stack.push(stack.lpop().or(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_IXOR:
+                stack.push(stack.ipop().xor(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_LXOR:
+                stack.push(stack.lpop().xor(stack.lpop()));
+                break;
+
+            case InstructionConstants.OP_I2L:
+                stack.push(stack.ipop().convertToLong());
+                break;
+
+            case InstructionConstants.OP_I2F:
+                stack.push(stack.ipop().convertToFloat());
+                break;
+
+            case InstructionConstants.OP_I2D:
+                stack.push(stack.ipop().convertToDouble());
+                break;
+
+            case InstructionConstants.OP_L2I:
+                stack.push(stack.lpop().convertToInteger());
+                break;
+
+            case InstructionConstants.OP_L2F:
+                stack.push(stack.lpop().convertToFloat());
+                break;
+
+            case InstructionConstants.OP_L2D:
+                stack.push(stack.lpop().convertToDouble());
+                break;
+
+            case InstructionConstants.OP_F2I:
+                stack.push(stack.fpop().convertToInteger());
+                break;
+
+            case InstructionConstants.OP_F2L:
+                stack.push(stack.fpop().convertToLong());
+                break;
+
+            case InstructionConstants.OP_F2D:
+                stack.push(stack.fpop().convertToDouble());
+                break;
+
+            case InstructionConstants.OP_D2I:
+                stack.push(stack.dpop().convertToInteger());
+                break;
+
+            case InstructionConstants.OP_D2L:
+                stack.push(stack.dpop().convertToLong());
+                break;
+
+            case InstructionConstants.OP_D2F:
+                stack.push(stack.dpop().convertToFloat());
+                break;
+
+            case InstructionConstants.OP_I2B:
+                stack.push(stack.ipop().convertToByte());
+                break;
+
+            case InstructionConstants.OP_I2C:
+                stack.push(stack.ipop().convertToCharacter());
+                break;
+
+            case InstructionConstants.OP_I2S:
+                stack.push(stack.ipop().convertToShort());
+                break;
+
+            case InstructionConstants.OP_LCMP:
+//                stack.push(stack.lpop().compareReverse(stack.lpop()));
+
+                LongValue longValue1 = stack.lpop();
+                LongValue longValue2 = stack.lpop();
+                stack.push(longValue2.compare(longValue1));
+                break;
+
+            case InstructionConstants.OP_FCMPL:
+                FloatValue floatValue1 = stack.fpop();
+                FloatValue floatValue2 = stack.fpop();
+                stack.push(floatValue2.compare(floatValue1));
+                break;
+
+            case InstructionConstants.OP_FCMPG:
+                stack.push(stack.fpop().compareReverse(stack.fpop()));
+                break;
+
+            case InstructionConstants.OP_DCMPL:
+                DoubleValue doubleValue1 = stack.dpop();
+                DoubleValue doubleValue2 = stack.dpop();
+                stack.push(doubleValue2.compare(doubleValue1));
+                break;
+
+            case InstructionConstants.OP_DCMPG:
+                stack.push(stack.dpop().compareReverse(stack.dpop()));
+                break;
+
+            case InstructionConstants.OP_IRETURN:
+                invocationUnit.exitMethod(clazz, method, stack.ipop());
+                branchUnit.returnFromMethod();
+                break;
+
+            case InstructionConstants.OP_LRETURN:
+                invocationUnit.exitMethod(clazz, method, stack.lpop());
+                branchUnit.returnFromMethod();
+                break;
+
+            case InstructionConstants.OP_FRETURN:
+                invocationUnit.exitMethod(clazz, method, stack.fpop());
+                branchUnit.returnFromMethod();
+                break;
+
+            case InstructionConstants.OP_DRETURN:
+                invocationUnit.exitMethod(clazz, method, stack.dpop());
+                branchUnit.returnFromMethod();
+                break;
+
+            case InstructionConstants.OP_ARETURN:
+                invocationUnit.exitMethod(clazz, method, stack.apop());
+                branchUnit.returnFromMethod();
+                break;
+
+            case InstructionConstants.OP_RETURN:
+                branchUnit.returnFromMethod();
+                break;
+
+            case InstructionConstants.OP_NEWARRAY:
+                IntegerValue arrayLength = stack.ipop();
+                stack.push(valueFactory.createArrayReferenceValue(String.valueOf(InstructionUtil.internalTypeFromArrayType((byte)simpleInstruction.constant)),
+                                                                  null,
+                                                                  arrayLength));
+                break;
+
+            case InstructionConstants.OP_ARRAYLENGTH:
+                stack.apop();
+                stack.push(valueFactory.createIntegerValue());
+                break;
+
+            case InstructionConstants.OP_ATHROW:
+                ReferenceValue exceptionReferenceValue = stack.apop();
+                stack.clear();
+                stack.push(exceptionReferenceValue);
+                branchUnit.throwException();
+                break;
+
+            case InstructionConstants.OP_MONITORENTER:
+            case InstructionConstants.OP_MONITOREXIT:
+                stack.apop();
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown simple instruction ["+simpleInstruction.opcode+"]");
+        }
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        int constantIndex = constantInstruction.constantIndex;
+
+        switch (constantInstruction.opcode)
+        {
+            case InstructionConstants.OP_LDC:
+            case InstructionConstants.OP_LDC_W:
+            case InstructionConstants.OP_LDC2_W:
+                stack.push(cpValue(clazz, constantIndex, true));
+                break;
+
+            case InstructionConstants.OP_GETSTATIC:
+            case InstructionConstants.OP_PUTSTATIC:
+            case InstructionConstants.OP_GETFIELD:
+            case InstructionConstants.OP_PUTFIELD:
+            case InstructionConstants.OP_INVOKEVIRTUAL:
+            case InstructionConstants.OP_INVOKESPECIAL:
+            case InstructionConstants.OP_INVOKESTATIC:
+            case InstructionConstants.OP_INVOKEINTERFACE:
+                invocationUnit.invokeMember(clazz, method, codeAttribute, offset, constantInstruction, stack);
+                break;
+
+            case InstructionConstants.OP_NEW:
+                stack.push(cpValue(clazz, constantIndex).referenceValue());
+                break;
+
+            case InstructionConstants.OP_ANEWARRAY:
+            {
+                ReferenceValue referenceValue = cpValue(clazz, constantIndex).referenceValue();
+
+                stack.push(valueFactory.createArrayReferenceValue(referenceValue.internalType(),
+                                                                  referenceValue.getReferencedClass(),
+                                                                  stack.ipop()));
+                break;
+            }
+
+            case InstructionConstants.OP_CHECKCAST:
+                // TODO: Check cast.
+                ReferenceValue castValue = stack.apop();
+                ReferenceValue castResultValue =
+                    castValue.isNull() == Value.ALWAYS ? castValue :
+                    castValue.isNull() == Value.NEVER  ? cpValue(clazz, constantIndex).referenceValue() :
+                                                         cpValue(clazz, constantIndex).referenceValue().generalize(valueFactory.createReferenceValueNull());
+                stack.push(castResultValue);
+                break;
+
+            case InstructionConstants.OP_INSTANCEOF:
+            {
+                ReferenceValue referenceValue = cpValue(clazz, constantIndex).referenceValue();
+
+                int instanceOf = stack.apop().instanceOf(referenceValue.getType(),
+                                                         referenceValue.getReferencedClass());
+
+                stack.push(instanceOf == Value.NEVER  ? valueFactory.createIntegerValue(0) :
+                           instanceOf == Value.ALWAYS ? valueFactory.createIntegerValue(1) :
+                                                        valueFactory.createIntegerValue());
+                break;
+            }
+
+            case InstructionConstants.OP_MULTIANEWARRAY:
+            {
+                int dimensionCount = constantInstruction.constant;
+                for (int dimension = 0; dimension < dimensionCount; dimension++)
+                {
+                    // TODO: Use array lengths.
+                    IntegerValue arrayLength = stack.ipop();
+                }
+
+                stack.push(cpValue(clazz, constantIndex).referenceValue());
+                break;
+            }
+
+            default:
+                throw new IllegalArgumentException("Unknown constant pool instruction ["+constantInstruction.opcode+"]");
+        }
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        int variableIndex = variableInstruction.variableIndex;
+
+        switch (variableInstruction.opcode)
+        {
+            case InstructionConstants.OP_ILOAD:
+            case InstructionConstants.OP_ILOAD_0:
+            case InstructionConstants.OP_ILOAD_1:
+            case InstructionConstants.OP_ILOAD_2:
+            case InstructionConstants.OP_ILOAD_3:
+                stack.push(variables.iload(variableIndex));
+                break;
+
+            case InstructionConstants.OP_LLOAD:
+            case InstructionConstants.OP_LLOAD_0:
+            case InstructionConstants.OP_LLOAD_1:
+            case InstructionConstants.OP_LLOAD_2:
+            case InstructionConstants.OP_LLOAD_3:
+                stack.push(variables.lload(variableIndex));
+                break;
+
+            case InstructionConstants.OP_FLOAD:
+            case InstructionConstants.OP_FLOAD_0:
+            case InstructionConstants.OP_FLOAD_1:
+            case InstructionConstants.OP_FLOAD_2:
+            case InstructionConstants.OP_FLOAD_3:
+                stack.push(variables.fload(variableIndex));
+                break;
+
+            case InstructionConstants.OP_DLOAD:
+            case InstructionConstants.OP_DLOAD_0:
+            case InstructionConstants.OP_DLOAD_1:
+            case InstructionConstants.OP_DLOAD_2:
+            case InstructionConstants.OP_DLOAD_3:
+                stack.push(variables.dload(variableIndex));
+                break;
+
+            case InstructionConstants.OP_ALOAD:
+            case InstructionConstants.OP_ALOAD_0:
+            case InstructionConstants.OP_ALOAD_1:
+            case InstructionConstants.OP_ALOAD_2:
+            case InstructionConstants.OP_ALOAD_3:
+                stack.push(variables.aload(variableIndex));
+                break;
+
+            case InstructionConstants.OP_ISTORE:
+            case InstructionConstants.OP_ISTORE_0:
+            case InstructionConstants.OP_ISTORE_1:
+            case InstructionConstants.OP_ISTORE_2:
+            case InstructionConstants.OP_ISTORE_3:
+                variables.store(variableIndex, stack.ipop());
+                break;
+
+            case InstructionConstants.OP_LSTORE:
+            case InstructionConstants.OP_LSTORE_0:
+            case InstructionConstants.OP_LSTORE_1:
+            case InstructionConstants.OP_LSTORE_2:
+            case InstructionConstants.OP_LSTORE_3:
+                variables.store(variableIndex, stack.lpop());
+                break;
+
+            case InstructionConstants.OP_FSTORE:
+            case InstructionConstants.OP_FSTORE_0:
+            case InstructionConstants.OP_FSTORE_1:
+            case InstructionConstants.OP_FSTORE_2:
+            case InstructionConstants.OP_FSTORE_3:
+                variables.store(variableIndex, stack.fpop());
+                break;
+
+            case InstructionConstants.OP_DSTORE:
+            case InstructionConstants.OP_DSTORE_0:
+            case InstructionConstants.OP_DSTORE_1:
+            case InstructionConstants.OP_DSTORE_2:
+            case InstructionConstants.OP_DSTORE_3:
+                variables.store(variableIndex, stack.dpop());
+                break;
+
+            case InstructionConstants.OP_ASTORE:
+            case InstructionConstants.OP_ASTORE_0:
+            case InstructionConstants.OP_ASTORE_1:
+            case InstructionConstants.OP_ASTORE_2:
+            case InstructionConstants.OP_ASTORE_3:
+                // The operand on the stack can be a reference or a return
+                // address, so we'll relax the pop operation.
+                //variables.store(variableIndex, stack.apop());
+                variables.store(variableIndex, stack.pop());
+                break;
+
+            case InstructionConstants.OP_IINC:
+                variables.store(variableIndex,
+                                variables.iload(variableIndex).add(
+                                valueFactory.createIntegerValue(variableInstruction.constant)));
+                break;
+
+            case InstructionConstants.OP_RET:
+                // The return address should be in the last offset of the
+                // given instruction offset variable (even though there may
+                // be other offsets).
+                InstructionOffsetValue instructionOffsetValue = variables.oload(variableIndex);
+                branchUnit.branch(clazz,
+                                  codeAttribute,
+                                  offset,
+                                  instructionOffsetValue.instructionOffset(instructionOffsetValue.instructionOffsetCount()-1));
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown variable instruction ["+variableInstruction.opcode+"]");
+        }
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        int branchTarget = offset + branchInstruction.branchOffset;
+
+        switch (branchInstruction.opcode)
+        {
+            case InstructionConstants.OP_IFEQ:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().equal(valueFactory.createIntegerValue(0)));
+                break;
+
+            case InstructionConstants.OP_IFNE:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().notEqual(valueFactory.createIntegerValue(0)));
+                break;
+
+            case InstructionConstants.OP_IFLT:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().lessThan(valueFactory.createIntegerValue(0)));
+                break;
+
+            case InstructionConstants.OP_IFGE:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().greaterThanOrEqual(valueFactory.createIntegerValue(0)));
+                break;
+
+            case InstructionConstants.OP_IFGT:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().greaterThan(valueFactory.createIntegerValue(0)));
+                break;
+
+            case InstructionConstants.OP_IFLE:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().lessThanOrEqual(valueFactory.createIntegerValue(0)));
+                break;
+
+
+            case InstructionConstants.OP_IFICMPEQ:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().equal(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_IFICMPNE:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().notEqual(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_IFICMPLT:
+                // Note that the stack entries are popped in reverse order.
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().greaterThan(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_IFICMPGE:
+                // Note that the stack entries are popped in reverse order.
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().lessThanOrEqual(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_IFICMPGT:
+                // Note that the stack entries are popped in reverse order.
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().lessThan(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_IFICMPLE:
+                // Note that the stack entries are popped in reverse order.
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.ipop().greaterThanOrEqual(stack.ipop()));
+                break;
+
+            case InstructionConstants.OP_IFACMPEQ:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.apop().equal(stack.apop()));
+                break;
+
+            case InstructionConstants.OP_IFACMPNE:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.apop().notEqual(stack.apop()));
+                break;
+
+            case InstructionConstants.OP_GOTO:
+            case InstructionConstants.OP_GOTO_W:
+                branchUnit.branch(clazz, codeAttribute, offset, branchTarget);
+                break;
+
+
+            case InstructionConstants.OP_JSR:
+            case InstructionConstants.OP_JSR_W:
+                stack.push(new InstructionOffsetValue(offset +
+                                                      branchInstruction.length(offset)));
+                branchUnit.branch(clazz, codeAttribute, offset, branchTarget);
+                break;
+
+            case InstructionConstants.OP_IFNULL:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.apop().isNull());
+                break;
+
+            case InstructionConstants.OP_IFNONNULL:
+                branchUnit.branchConditionally(clazz, codeAttribute, offset, branchTarget,
+                    stack.apop().isNotNull());
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown branch instruction ["+branchInstruction.opcode+"]");
+        }
+    }
+
+
+    public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction)
+    {
+        IntegerValue indexValue = stack.ipop();
+
+        // If there is no definite branch in any of the cases below,
+        // branch to the default offset.
+        branchUnit.branch(clazz, codeAttribute,
+                          offset,
+                          offset + tableSwitchInstruction.defaultOffset);
+
+        for (int index = 0; index < tableSwitchInstruction.jumpOffsets.length; index++)
+        {
+            int conditional = indexValue.equal(valueFactory.createIntegerValue(
+                tableSwitchInstruction.lowCase + index));
+            branchUnit.branchConditionally(clazz, codeAttribute,
+                                           offset,
+                                           offset + tableSwitchInstruction.jumpOffsets[index],
+                                           conditional);
+
+            // If this branch is always taken, we can skip the rest.
+            if (conditional == Value.ALWAYS)
+            {
+                break;
+            }
+        }
+    }
+
+
+    public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction)
+    {
+        IntegerValue indexValue = stack.ipop();
+
+        // If there is no definite branch in any of the cases below,
+        // branch to the default offset.
+        branchUnit.branch(clazz, codeAttribute,
+                          offset,
+                          offset + lookUpSwitchInstruction.defaultOffset);
+
+        for (int index = 0; index < lookUpSwitchInstruction.jumpOffsets.length; index++)
+        {
+            int conditional = indexValue.equal(valueFactory.createIntegerValue(
+                lookUpSwitchInstruction.cases[index]));
+            branchUnit.branchConditionally(clazz, codeAttribute,
+                                           offset,
+                                           offset + lookUpSwitchInstruction.jumpOffsets[index],
+                                           conditional);
+
+            // If this branch is always taken, we can skip the rest.
+            if (conditional == Value.ALWAYS)
+            {
+                break;
+            }
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        cpValue = valueFactory.createIntegerValue(integerConstant.getValue());
+    }
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        cpValue = valueFactory.createLongValue(longConstant.getValue());
+    }
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        cpValue = valueFactory.createFloatValue(floatConstant.getValue());
+    }
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        cpValue = valueFactory.createDoubleValue(doubleConstant.getValue());
+    }
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        cpValue = valueFactory.createReferenceValue(ClassConstants.INTERNAL_NAME_JAVA_LANG_STRING,
+                                                    stringConstant.javaLangStringClass,
+                                                    false);
+    }
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        cpValue = handleClassConstantAsClassValue ?
+            valueFactory.createReferenceValue(ClassConstants.INTERNAL_NAME_JAVA_LANG_CLASS,
+                                              classConstant.javaLangClassClass,
+                                              false) :
+            valueFactory.createReferenceValue(classConstant.getName(clazz),
+                                              classConstant.referencedClass,
+                                              false);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the Value of the constant pool element at the given index.
+     */
+    private Value cpValue(Clazz clazz,
+                          int   constantIndex)
+    {
+        return cpValue(clazz, constantIndex, false);
+    }
+
+
+    /**
+     * Returns the Value of the constant pool element at the given index.
+     */
+    private Value cpValue(Clazz   clazz,
+                          int     constantIndex,
+                          boolean handleClassConstantAsClassValue)
+    {
+        this.handleClassConstantAsClassValue = handleClassConstantAsClassValue;
+
+        // Visit the constant pool entry to get its return value.
+        clazz.constantPoolEntryAccept(constantIndex, this);
+
+        return cpValue;
+    }
+}
diff --git a/src/proguard/evaluation/Stack.java b/src/proguard/evaluation/Stack.java
new file mode 100644
index 0000000..2808e62
--- /dev/null
+++ b/src/proguard/evaluation/Stack.java
@@ -0,0 +1,561 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.evaluation.value.*;
+
+/**
+ * This class represents an operand stack that contains <code>Value</code>
+ * objects.
+ *
+ * @author Eric Lafortune
+ */
+public class Stack
+{
+    private static final TopValue TOP_VALUE = new TopValue();
+
+
+    protected Value[] values;
+    protected int     currentSize;
+    protected int     actualMaxSize;
+
+
+    /**
+     * Creates a new Stack with a given maximum size, accounting for the double
+     * space required by Category 2 values.
+     */
+    public Stack(int maxSize)
+    {
+        values = new Value[maxSize];
+    }
+
+
+    /**
+     * Creates a Stack that is a copy of the given Stack.
+     */
+    public Stack(Stack stack)
+    {
+        // Create the values array.
+        this(stack.values.length);
+
+        // Copy the stack contents.
+        copy(stack);
+    }
+
+
+    /**
+     * Returns the actual maximum stack size that was required for all stack
+     * operations, accounting for the double space required by Category 2 values.
+     */
+    public int getActualMaxSize()
+    {
+        return actualMaxSize;
+    }
+
+
+    /**
+     * Resets this Stack, so that it can be reused.
+     */
+    public void reset(int maxSize)
+    {
+        // Is the values array large enough?
+        if (maxSize > values.length)
+        {
+            // Create a new one.
+            values = new Value[maxSize];
+        }
+
+        // Clear the sizes.
+        clear();
+
+        actualMaxSize = 0;
+    }
+
+
+    /**
+     * Copies the values of the given Stack into this Stack.
+     */
+    public void copy(Stack other)
+    {
+        // Is the values array large enough?
+        if (other.values.length > values.length)
+        {
+            // Create a new one.
+            values = new Value[other.values.length];
+        }
+
+        // Copy the stack contents.
+        System.arraycopy(other.values, 0, this.values, 0, other.currentSize);
+
+        // Copy the sizes.
+        currentSize   = other.currentSize;
+        actualMaxSize = other.actualMaxSize;
+    }
+
+
+    /**
+     * Generalizes the values of this Stack with the values of the given Stack.
+     * The stacks must have the same current sizes.
+     * @return whether the generalization has made any difference.
+     */
+    public boolean generalize(Stack other)
+    {
+        if (this.currentSize != other.currentSize)
+        {
+            throw new IllegalArgumentException("Stacks have different current sizes ["+this.currentSize+"] and ["+other.currentSize+"]");
+        }
+
+        boolean changed = false;
+
+        // Generalize the stack values.
+        for (int index = 0; index < currentSize; index++)
+        {
+            Value thisValue  = this.values[index];
+
+            if (thisValue != null)
+            {
+                Value newValue = null;
+
+                Value otherValue = other.values[index];
+
+                if (otherValue != null)
+                {
+                    newValue = thisValue.generalize(otherValue);
+                }
+
+                changed = changed || !thisValue.equals(newValue);
+
+                values[index] = newValue;
+            }
+        }
+
+        // Check if the other stack extends beyond this one.
+        if (this.actualMaxSize < other.actualMaxSize)
+        {
+            this.actualMaxSize = other.actualMaxSize;
+        }
+
+        return changed;
+    }
+
+
+    /**
+     * Clears the stack.
+     */
+    public void clear()
+    {
+        // Clear the stack contents.
+        for (int index = 0; index < currentSize; index++)
+        {
+            values[index] = null;
+        }
+
+        currentSize = 0;
+    }
+
+
+    /**
+     * Returns the number of elements currently on the stack, accounting for the
+     * double space required by Category 2 values.
+     */
+    public int size()
+    {
+        return currentSize;
+    }
+
+
+    /**
+     * Gets the specified Value from the stack, without disturbing it.
+     * @param index the index of the stack element, counting from the bottom
+     *              of the stack.
+     * @return the value at the specified position.
+     */
+    public Value getBottom(int index)
+    {
+        return values[index];
+    }
+
+
+    /**
+     * Sets the specified Value on the stack, without disturbing it.
+     * @param index the index of the stack element, counting from the bottom
+     *              of the stack.
+     * @param value the value to set.
+     */
+    public void setBottom(int index, Value value)
+    {
+        values[index] = value;
+    }
+
+
+    /**
+     * Gets the specified Value from the stack, without disturbing it.
+     * @param index the index of the stack element, counting from the top
+     *              of the stack.
+     * @return the value at the specified position.
+     */
+    public Value getTop(int index)
+    {
+        return values[currentSize - index - 1];
+    }
+
+
+    /**
+     * Sets the specified Value on the stack, without disturbing it.
+     * @param index the index of the stack element, counting from the top
+     *              of the stack.
+     * @param value the value to set.
+     */
+    public void setTop(int index, Value value)
+    {
+        values[currentSize - index - 1] = value;
+    }
+
+
+    /**
+     * Removes the specified Value from the stack.
+     * @param index the index of the stack element, counting from the top
+     *              of the stack.
+     */
+    public void removeTop(int index)
+    {
+        System.arraycopy(values, currentSize - index,
+                         values, currentSize - index - 1,
+                         index);
+        currentSize--;
+    }
+
+
+    /**
+     * Pushes the given Value onto the stack.
+     */
+    public void push(Value value)
+    {
+        // Account for the extra space required by Category 2 values.
+        if (value.isCategory2())
+        {
+            values[currentSize++] = TOP_VALUE;
+        }
+
+        // Push the value.
+        values[currentSize++] = value;
+
+        // Update the maximum actual size;
+        if (actualMaxSize < currentSize)
+        {
+            actualMaxSize = currentSize;
+        }
+    }
+
+
+    /**
+     * Pops the top Value from the stack.
+     */
+    public Value pop()
+    {
+        Value value = values[--currentSize];
+
+        values[currentSize] = null;
+
+        // Account for the extra space required by Category 2 values.
+        if (value.isCategory2())
+        {
+            values[--currentSize] = null;
+        }
+
+        return value;
+    }
+
+
+    // Pop methods that provide convenient casts to the expected value types.
+
+    /**
+     * Pops the top IntegerValue from the stack.
+     */
+    public IntegerValue ipop()
+    {
+        return pop().integerValue();
+    }
+
+
+    /**
+     * Pops the top LongValue from the stack.
+     */
+    public LongValue lpop()
+    {
+        return pop().longValue();
+    }
+
+
+    /**
+     * Pops the top FloatValue from the stack.
+     */
+    public FloatValue fpop()
+    {
+        return pop().floatValue();
+    }
+
+
+    /**
+     * Pops the top DoubleValue from the stack.
+     */
+    public DoubleValue dpop()
+    {
+        return pop().doubleValue();
+    }
+
+
+    /**
+     * Pops the top ReferenceValue from the stack.
+     */
+    public ReferenceValue apop()
+    {
+        return pop().referenceValue();
+    }
+
+
+    /**
+     * Pops the top InstructionOffsetValue from the stack.
+     */
+    public InstructionOffsetValue opop()
+    {
+        return pop().instructionOffsetValue();
+    }
+
+
+    /**
+     * Pops the top category 1 value from the stack.
+     */
+    public void pop1()
+    {
+        values[--currentSize] = null;
+    }
+
+
+    /**
+     * Pops the top category 2 value from the stack (or alternatively, two
+     * Category 1 stack elements).
+     */
+    public void pop2()
+    {
+        values[--currentSize] = null;
+        values[--currentSize] = null;
+    }
+
+
+    /**
+     * Duplicates the top Category 1 value.
+     */
+    public void dup()
+    {
+        values[currentSize] = values[currentSize - 1].category1Value();
+
+        currentSize++;
+
+        // Update the maximum actual size;
+        if (actualMaxSize < currentSize)
+        {
+            actualMaxSize = currentSize;
+        }
+    }
+
+
+    /**
+     * Duplicates the top Category 1 value, one Category 1 element down the
+     * stack.
+     */
+    public void dup_x1()
+    {
+        values[currentSize]     = values[currentSize - 1].category1Value();
+        values[currentSize - 1] = values[currentSize - 2].category1Value();
+        values[currentSize - 2] = values[currentSize    ];
+
+        currentSize++;
+
+        // Update the maximum actual size;
+        if (actualMaxSize < currentSize)
+        {
+            actualMaxSize = currentSize;
+        }
+    }
+
+
+    /**
+     * Duplicates the top Category 1 value, two Category 1 elements (or one
+     * Category 2 element) down the stack.
+     */
+    public void dup_x2()
+    {
+        values[currentSize]     = values[currentSize - 1].category1Value();
+        values[currentSize - 1] = values[currentSize - 2];
+        values[currentSize - 2] = values[currentSize - 3];
+        values[currentSize - 3] = values[currentSize    ];
+
+        currentSize++;
+
+        // Update the maximum actual size;
+        if (actualMaxSize < currentSize)
+        {
+            actualMaxSize = currentSize;
+        }
+    }
+
+    /**
+     * Duplicates the top Category 2 value (or alternatively, the equivalent
+     * Category 1 stack elements).
+     */
+    public void dup2()
+    {
+        values[currentSize    ] = values[currentSize - 2];
+        values[currentSize + 1] = values[currentSize - 1];
+
+        currentSize += 2;
+
+        // Update the maximum actual size;
+        if (actualMaxSize < currentSize)
+        {
+            actualMaxSize = currentSize;
+        }
+    }
+
+
+    /**
+     * Duplicates the top Category 2 value, one Category 1 element down the
+     * stack (or alternatively, the equivalent Category 1 stack values).
+     */
+    public void dup2_x1()
+    {
+        values[currentSize + 1] = values[currentSize - 1];
+        values[currentSize    ] = values[currentSize - 2];
+        values[currentSize - 1] = values[currentSize - 3];
+        values[currentSize - 2] = values[currentSize + 1];
+        values[currentSize - 3] = values[currentSize    ];
+
+        currentSize += 2;
+
+        // Update the maximum actual size;
+        if (actualMaxSize < currentSize)
+        {
+            actualMaxSize = currentSize;
+        }
+    }
+
+
+    /**
+     * Duplicates the top Category 2 value, one Category 2 stack element down
+     * the stack (or alternatively, the equivalent Category 1 stack values).
+     */
+    public void dup2_x2()
+    {
+        values[currentSize + 1] = values[currentSize - 1];
+        values[currentSize    ] = values[currentSize - 2];
+        values[currentSize - 1] = values[currentSize - 3];
+        values[currentSize - 2] = values[currentSize - 4];
+        values[currentSize - 3] = values[currentSize + 1];
+        values[currentSize - 4] = values[currentSize    ];
+
+        currentSize += 2;
+
+        // Update the maximum actual size;
+        if (actualMaxSize < currentSize)
+        {
+            actualMaxSize = currentSize;
+        }
+    }
+
+
+    /**
+     * Swaps the top two Category 1 values.
+     */
+    public void swap()
+    {
+        Value value1 = values[currentSize - 1].category1Value();
+        Value value2 = values[currentSize - 2].category1Value();
+
+        values[currentSize - 1] = value2;
+        values[currentSize - 2] = value1;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        Stack other = (Stack)object;
+
+        if (this.currentSize != other.currentSize)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < currentSize; index++)
+        {
+            Value thisValue  = this.values[index];
+            Value otherValue = other.values[index];
+            if (thisValue == null ? otherValue != null :
+                                    !thisValue.equals(otherValue))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    public int hashCode()
+    {
+        int hashCode = currentSize;
+
+        for (int index = 0; index < currentSize; index++)
+        {
+            Value value = values[index];
+            if (value != null)
+            {
+                hashCode ^= value.hashCode();
+            }
+        }
+
+        return hashCode;
+    }
+
+
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        for (int index = 0; index < currentSize; index++)
+        {
+            Value value = values[index];
+            buffer = buffer.append('[')
+                           .append(value == null ? "empty" : value.toString())
+                           .append(']');
+        }
+
+        return buffer.toString();
+    }
+}
diff --git a/src/proguard/evaluation/TracedStack.java b/src/proguard/evaluation/TracedStack.java
new file mode 100644
index 0000000..c24f783
--- /dev/null
+++ b/src/proguard/evaluation/TracedStack.java
@@ -0,0 +1,342 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.evaluation.value.Value;
+
+/**
+ * This Stack saves additional information with stack elements, to keep track
+ * of their origins and destinations.
+ * <p>
+ * The stack stores a given producer Value along with each Value it stores.
+ * It then generalizes a given collected Value with the producer Value
+ * of each Value it loads. The producer Value and the initial collected Value
+ * can be set; the generalized collected Value can be retrieved.
+ *
+ * @author Eric Lafortune
+ */
+public class TracedStack extends Stack
+{
+    private Value producerValue;
+    private Stack producerStack;
+
+
+    /**
+     * Creates a new TracedStack with a given maximum size.
+     */
+    public TracedStack(int maxSize)
+    {
+        super(maxSize);
+
+        producerStack = new Stack(maxSize);
+    }
+
+
+    /**
+     * Creates a new TracedStack that is a copy of the given TracedStack.
+     */
+    public TracedStack(TracedStack tracedStack)
+    {
+        super(tracedStack);
+
+        producerStack = new Stack(tracedStack.producerStack);
+    }
+
+
+    /**
+     * Sets the Value that will be stored along with all push and pop
+     * instructions.
+     */
+    public void setProducerValue(Value producerValue)
+    {
+        this.producerValue = producerValue;
+    }
+
+
+    /**
+     * Gets the specified producer Value from the stack, without disturbing it.
+     * @param index the index of the stack element, counting from the bottom
+     *              of the stack.
+     * @return the producer value at the specified position.
+     */
+    public Value getBottomProducerValue(int index)
+    {
+        return producerStack.getBottom(index);
+    }
+
+
+    /**
+     * Sets the specified producer Value on the stack, without disturbing it.
+     * @param index the index of the stack element, counting from the bottom
+     *              of the stack.
+     * @param value the producer value to set.
+     */
+    public void setBottomProducerValue(int index, Value value)
+    {
+        producerStack.setBottom(index, value);
+    }
+
+
+    /**
+     * Gets the specified producer Value from the stack, without disturbing it.
+     * @param index the index of the stack element, counting from the top
+     *              of the stack.
+     * @return the producer value at the specified position.
+     */
+    public Value getTopProducerValue(int index)
+    {
+        return producerStack.getTop(index);
+    }
+
+
+    /**
+     * Sets the specified producer Value on the stack, without disturbing it.
+     * @param index the index of the stack element, counting from the top
+     *              of the stack.
+     * @param value the producer value to set.
+     */
+    public void setTopProducerValue(int index, Value value)
+    {
+        producerStack.setTop(index, value);
+    }
+
+
+    // Implementations for Stack.
+
+    public void reset(int size)
+    {
+        super.reset(size);
+
+        producerStack.reset(size);
+    }
+
+    public void copy(TracedStack other)
+    {
+        super.copy(other);
+
+        producerStack.copy(other.producerStack);
+    }
+
+    public boolean generalize(TracedStack other)
+    {
+        return
+            super.generalize(other) |
+            producerStack.generalize(other.producerStack);
+    }
+
+    public void clear()
+    {
+        super.clear();
+
+        producerStack.clear();
+    }
+
+    public void removeTop(int index)
+    {
+        super.removeTop(index);
+
+        producerStack.removeTop(index);
+    }
+
+    public void push(Value value)
+    {
+        super.push(value);
+
+        producerPush();
+
+        // Account for the extra space required by Category 2 values.
+        if (value.isCategory2())
+        {
+            producerPush();
+        }
+    }
+
+    public Value pop()
+    {
+        Value value = super.pop();
+
+        producerPop();
+
+        // Account for the extra space required by Category 2 values.
+        if (value.isCategory2())
+        {
+            producerPop();
+        }
+
+        return value;
+    }
+
+    public void pop1()
+    {
+        super.pop1();
+
+        producerPop();
+    }
+
+    public void pop2()
+    {
+        super.pop2();
+
+        producerPop();
+        producerPop();
+    }
+
+    public void dup()
+    {
+        super.dup();
+
+        producerPop();
+        producerPush();
+        producerPush();
+    }
+
+    public void dup_x1()
+    {
+        super.dup_x1();
+
+        producerPop();
+        producerPop();
+        producerPush();
+        producerPush();
+        producerPush();
+    }
+
+    public void dup_x2()
+    {
+        super.dup_x2();
+
+        producerPop();
+        producerPop();
+        producerPop();
+        producerPush();
+        producerPush();
+        producerPush();
+        producerPush();
+    }
+
+    public void dup2()
+    {
+        super.dup2();
+
+        producerPop();
+        producerPop();
+        producerPush();
+        producerPush();
+        producerPush();
+        producerPush();
+    }
+
+    public void dup2_x1()
+    {
+        super.dup2_x1();
+
+        producerPop();
+        producerPop();
+        producerPop();
+        producerPush();
+        producerPush();
+        producerPush();
+        producerPush();
+        producerPush();
+    }
+
+    public void dup2_x2()
+    {
+        super.dup2_x2();
+
+        producerPop();
+        producerPop();
+        producerPop();
+        producerPop();
+        producerPush();
+        producerPush();
+        producerPush();
+        producerPush();
+        producerPush();
+        producerPush();
+    }
+
+    public void swap()
+    {
+        super.swap();
+
+        producerPop();
+        producerPop();
+        producerPush();
+        producerPush();
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        TracedStack other = (TracedStack)object;
+
+        return super.equals(object) &&
+               this.producerStack.equals(other.producerStack);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               producerStack.hashCode();
+    }
+
+
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        for (int index = 0; index < this.size(); index++)
+        {
+            Value value         = this.values[index];
+            Value producerValue = producerStack.getBottom(index);
+            buffer = buffer.append('[')
+                           .append(producerValue == null ? "empty:" : producerValue.toString())
+                           .append(value         == null ? "empty"  : value.toString())
+                           .append(']');
+        }
+
+        return buffer.toString();
+    }
+
+
+    // Small utility methods.
+
+    private void producerPush()
+    {
+        producerStack.push(producerValue);
+    }
+
+
+    private void producerPop()
+    {
+        producerStack.pop();
+    }
+}
diff --git a/src/proguard/evaluation/TracedVariables.java b/src/proguard/evaluation/TracedVariables.java
new file mode 100644
index 0000000..1ae6ba6
--- /dev/null
+++ b/src/proguard/evaluation/TracedVariables.java
@@ -0,0 +1,229 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.evaluation.value.Value;
+
+/**
+ * This Variables class saves additional information with variables, to keep
+ * track of their origins.
+ * <p>
+ * The Variables class stores a given producer Value along with each Value it
+ * stores. It then generalizes a given collected Value with the producer Value
+ * of each Value it loads. The producer Value and the initial collected Value
+ * can be set; the generalized collected Value can be retrieved.
+ * <p>
+ * In addition, an initialization index can be reset and retrieved, pointing
+ * to the most recent variable that has been initialized by a store operation.
+ *
+ * @author Eric Lafortune
+ */
+public class TracedVariables extends Variables
+{
+    public static final int NONE = -1;
+
+
+    private Value     producerValue;
+    private Variables producerVariables;
+    private int       initializationIndex;
+
+
+    /**
+     * Creates a new TracedVariables with a given size.
+     */
+    public TracedVariables(int size)
+    {
+        super(size);
+
+        producerVariables = new Variables(size);
+    }
+
+
+    /**
+     * Creates a new TracedVariables that is a copy of the given TracedVariables.
+     */
+    public TracedVariables(TracedVariables tracedVariables)
+    {
+        super(tracedVariables);
+
+        producerVariables = new Variables(tracedVariables.producerVariables);
+    }
+
+
+    /**
+     * Sets the Value that will be stored along with all store instructions.
+     */
+    public void setProducerValue(Value producerValue)
+    {
+        this.producerValue = producerValue;
+    }
+
+
+    /**
+     * Resets the initialization index.
+     */
+    public void resetInitialization()
+    {
+        initializationIndex = NONE;
+    }
+
+
+    /**
+     * Returns the most recent initialization index since it has been reset.
+     */
+    public int getInitializationIndex()
+    {
+        return initializationIndex;
+    }
+
+
+    /**
+     * Gets the producer Value for the specified variable, without disturbing it.
+     * @param index the variable index.
+     * @return the producer value of the given variable.
+     */
+    public Value getProducerValue(int index)
+    {
+        return producerVariables.getValue(index);
+    }
+
+
+    /**
+     * Sets the given producer Value for the specified variable, without
+     * disturbing it.
+     * @param index the variable index.
+     * @param value the producer value to set.
+     */
+    public void setProducerValue(int index, Value value)
+    {
+        producerVariables.store(index, value);
+    }
+
+
+    // Implementations for Variables.
+
+    public void reset(int size)
+    {
+        super.reset(size);
+
+        producerVariables.reset(size);
+    }
+
+    public void initialize(TracedVariables other)
+    {
+        super.initialize(other);
+
+        producerVariables.initialize(other.producerVariables);
+    }
+
+    public boolean generalize(TracedVariables other,
+                              boolean         clearConflictingOtherVariables)
+    {
+        boolean variablesChanged = super.generalize(other, clearConflictingOtherVariables);
+        boolean producersChanged = producerVariables.generalize(other.producerVariables, clearConflictingOtherVariables);
+        /* consumerVariables.generalize(other.consumerVariables)*/
+
+        // Clear any traces if a variable has become null.
+        if (variablesChanged)
+        {
+            for (int index = 0; index < size; index++)
+            {
+                if (values[index] == null)
+                {
+                    producerVariables.values[index] = null;
+
+                    if (clearConflictingOtherVariables)
+                    {
+                        other.producerVariables.values[index] = null;
+                    }
+                }
+            }
+        }
+
+        return variablesChanged || producersChanged;
+    }
+
+
+    public void store(int index, Value value)
+    {
+        // Is this store operation an initialization of the variable?
+        Value previousValue = super.load(index);
+        if (previousValue == null ||
+            previousValue.computationalType() != value.computationalType())
+        {
+            initializationIndex = index;
+        }
+
+        // Store the value itself in the variable.
+        super.store(index, value);
+
+        // Store the producer value in its producer variable.
+        producerVariables.store(index, producerValue);
+
+        // Account for the extra space required by Category 2 values.
+        if (value.isCategory2())
+        {
+            producerVariables.store(index+1, producerValue);
+        }
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        TracedVariables other = (TracedVariables)object;
+
+        return super.equals(object) &&
+               this.producerVariables.equals(other.producerVariables);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               producerVariables.hashCode();
+    }
+
+
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        for (int index = 0; index < this.size(); index++)
+        {
+            Value value         = this.values[index];
+            Value producerValue = producerVariables.getValue(index);
+            buffer = buffer.append('[')
+                           .append(producerValue == null ? "empty:" : producerValue.toString())
+                           .append(value         == null ? "empty"  : value.toString())
+                           .append(']');
+        }
+
+        return buffer.toString();
+    }
+}
diff --git a/src/proguard/evaluation/Variables.java b/src/proguard/evaluation/Variables.java
new file mode 100644
index 0000000..4496aaa
--- /dev/null
+++ b/src/proguard/evaluation/Variables.java
@@ -0,0 +1,348 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation;
+
+import proguard.evaluation.value.*;
+
+/**
+ * This class represents a local variable frame that contains <code>Value</code>
+ * objects. Values are generalizations of all values that have been stored in
+ * the respective variables.
+ *
+ * @author Eric Lafortune
+ */
+public class Variables
+{
+    private static final TopValue TOP_VALUE = new TopValue();
+
+
+    protected Value[] values;
+    protected int     size;
+
+
+    /**
+     * Creates a new Variables object with a given maximum number of variables.
+     */
+    public Variables(int size)
+    {
+        this.values = new Value[size];
+        this.size   = size;
+    }
+
+
+    /**
+     * Creates a Variables object that is a copy of the given Variables object.
+     */
+    public Variables(Variables variables)
+    {
+        // Create the values array.
+        this(variables.size);
+
+        // Copy the values.
+        initialize(variables);
+    }
+
+
+    /**
+     * Resets this Variables object, so that it can be reused.
+     */
+    public void reset(int size)
+    {
+        // Is the values array large enough?
+        if (size > values.length)
+        {
+            // Create a new one.
+            values = new Value[size];
+        }
+        else
+        {
+            // Clear the variables.
+            for (int index = 0; index < values.length; index++)
+            {
+                values[index] = null;
+            }
+        }
+
+        this.size = size;
+    }
+
+
+    /**
+     * Initializes the values of this Variables object with the values of the
+     * given Variables object. The other object may have fewer values, in which
+     * case the remaining values are left unchanged.
+     */
+    public void initialize(Variables other)
+    {
+        if (this.size < other.size)
+        {
+            throw new IllegalArgumentException("Variable frame is too small ["+this.size+"] compared to other frame ["+other.size+"]");
+        }
+
+        // Copy the values.
+        System.arraycopy(other.values, 0, this.values, 0, other.size);
+    }
+
+
+    /**
+     * Generalizes the values of this Variables object with the values of the
+     * given Variables object.
+     * @param clearConflictingOtherVariables specifies whether the other
+     *                                       variables should be cleared too,
+     *                                       in case of conflicts.
+     * @return whether the generalization has made any difference.
+     */
+    public boolean generalize(Variables other,
+                              boolean   clearConflictingOtherVariables)
+    {
+        if (this.size != other.size)
+        {
+            throw new IllegalArgumentException("Variable frames have different sizes ["+this.size+"] and ["+other.size+"]");
+        }
+
+        boolean changed = false;
+
+        for (int index = 0; index < size; index++)
+        {
+            Value thisValue  = this.values[index];
+            Value otherValue = other.values[index];
+
+            // Occasionally, two values of different types might be present
+            // in the same variable in a variable frame (corresponding to
+            // two local variables that share the same index), at some point
+            // outside of their scopes. Don't generalize the variable then,
+            // but let it clear instead.
+            if (thisValue  != null &&
+                otherValue != null &&
+                thisValue.computationalType() == otherValue.computationalType())
+            {
+                Value newValue = thisValue.generalize(otherValue);
+
+                changed = changed || !thisValue.equals(newValue);
+
+                this.values[index] = newValue;
+            }
+            else
+            {
+                changed = changed || thisValue != null;
+
+                this.values[index] = null;
+
+                if (clearConflictingOtherVariables)
+                {
+                    other.values[index] = null;
+                }
+            }
+        }
+
+        return changed;
+    }
+
+
+    /**
+     * Returns the number of variables.
+     */
+    public int size()
+    {
+        return size;
+    }
+
+
+    /**
+     * Gets the Value of the variable with the given index, without disturbing it.
+     */
+    public Value getValue(int index)
+    {
+        if (index < 0 ||
+            index >= size)
+        {
+            throw new IndexOutOfBoundsException("Variable index ["+index+"] out of bounds ["+size+"]");
+        }
+
+        return values[index];
+    }
+
+
+    /**
+     * Stores the given Value at the given variable index.
+     */
+    public void store(int index, Value value)
+    {
+        if (index < 0 ||
+            index >= size)
+        {
+            throw new IndexOutOfBoundsException("Variable index ["+index+"] out of bounds ["+size+"]");
+        }
+
+        // Store the value.
+        values[index] = value;
+
+        // Account for the extra space required by Category 2 values.
+        if (value.isCategory2())
+        {
+            values[index + 1] = TOP_VALUE;
+        }
+    }
+
+
+    /**
+     * Loads the Value from the variable with the given index.
+     */
+    public Value load(int index)
+    {
+        if (index < 0 ||
+            index >= size)
+        {
+            throw new IndexOutOfBoundsException("Variable index ["+index+"] out of bounds ["+size+"]");
+        }
+
+        return values[index];
+    }
+
+
+    // Load methods that provide convenient casts to the expected value types.
+
+    /**
+     * Loads the IntegerValue from the variable with the given index.
+     */
+    public IntegerValue iload(int index)
+    {
+        return load(index).integerValue();
+    }
+
+
+    /**
+     * Loads the LongValue from the variable with the given index.
+     */
+    public LongValue lload(int index)
+    {
+        return load(index).longValue();
+    }
+
+
+    /**
+     * Loads the FloatValue from the variable with the given index.
+     */
+    public FloatValue fload(int index)
+    {
+        return load(index).floatValue();
+    }
+
+
+    /**
+     * Loads the DoubleValue from the variable with the given index.
+     */
+    public DoubleValue dload(int index)
+    {
+        return load(index).doubleValue();
+    }
+
+
+    /**
+     * Loads the ReferenceValue from the variable with the given index.
+     */
+    public ReferenceValue aload(int index)
+    {
+        return load(index).referenceValue();
+    }
+
+
+    /**
+     * Loads the InstructionOffsetValue from the variable with the given index.
+     */
+    public InstructionOffsetValue oload(int index)
+    {
+        return load(index).instructionOffsetValue();
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        Variables other = (Variables)object;
+
+        if (this.size != other.size)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < size; index++)
+        {
+            Value thisValue  = this.values[index];
+            Value otherValue = other.values[index];
+
+            // Occasionally, two values of different types might be
+            // present in the same variable in a variable frame
+            // (corresponding to two local variables that share the
+            // same index), at some point outside of their scopes.
+            // We'll ignore these.
+            if (thisValue  != null &&
+                otherValue != null &&
+                thisValue.computationalType() == otherValue.computationalType() &&
+                !thisValue.equals(otherValue))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    public int hashCode()
+    {
+        int hashCode = size;
+
+        for (int index = 0; index < size; index++)
+        {
+            Value value = values[index];
+            if (value != null)
+            {
+                hashCode ^= value.hashCode();
+            }
+        }
+
+        return hashCode;
+    }
+
+
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        for (int index = 0; index < size; index++)
+        {
+            Value value = values[index];
+            buffer = buffer.append('[')
+                           .append(value == null ? "empty" : value.toString())
+                           .append(']');
+        }
+
+        return buffer.toString();
+    }
+}
diff --git a/src/proguard/evaluation/value/Category1Value.java b/src/proguard/evaluation/value/Category1Value.java
new file mode 100644
index 0000000..b8c5db2
--- /dev/null
+++ b/src/proguard/evaluation/value/Category1Value.java
@@ -0,0 +1,41 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This abstract class represents a partially evaluated Category 1 value.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class Category1Value extends Value
+{
+    // Implementations for Value.
+
+    public final Category1Value category1Value()
+    {
+        return this;
+    }
+
+    public final boolean isCategory2()
+    {
+        return false;
+    }
+}
diff --git a/src/proguard/evaluation/value/Category2Value.java b/src/proguard/evaluation/value/Category2Value.java
new file mode 100644
index 0000000..65916c7
--- /dev/null
+++ b/src/proguard/evaluation/value/Category2Value.java
@@ -0,0 +1,41 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This abstract class represents a partially evaluated Category 2 value.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class Category2Value extends Value
+{
+    // Implementations for Value.
+
+    public final Category2Value category2Value()
+    {
+        return this;
+    }
+
+    public final boolean isCategory2()
+    {
+        return true;
+    }
+}
diff --git a/src/proguard/evaluation/value/ComparisonValue.java b/src/proguard/evaluation/value/ComparisonValue.java
new file mode 100644
index 0000000..e4e2e02
--- /dev/null
+++ b/src/proguard/evaluation/value/ComparisonValue.java
@@ -0,0 +1,69 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents the result of a comparisons of two scalar
+ * values.
+ *
+ * @author Eric Lafortune
+ */
+final class ComparisonValue extends SpecificIntegerValue
+{
+    private final Value value1;
+    private final Value value2;
+
+
+    /**
+     * Creates a new comparison integer value of the two given scalar values.
+     */
+    public ComparisonValue(Value integerValue1,
+                           Value integerValue2)
+    {
+        this.value1 = integerValue1;
+        this.value2 = integerValue2;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.value1.equals(((ComparisonValue)object).value1) &&
+               this.value2.equals(((ComparisonValue)object).value2);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               value1.hashCode() ^
+               value2.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "("+value1+"~"+ value2 +")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/CompositeDoubleValue.java b/src/proguard/evaluation/value/CompositeDoubleValue.java
new file mode 100644
index 0000000..a6d48ef
--- /dev/null
+++ b/src/proguard/evaluation/value/CompositeDoubleValue.java
@@ -0,0 +1,81 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This DoubleValue represents the result of a binary operation on two double
+ * values.
+ *
+ * @author Eric Lafortune
+ */
+final class CompositeDoubleValue extends SpecificDoubleValue
+{
+    public static final byte ADD       = '+';
+    public static final byte SUBTRACT  = '-';
+    public static final byte MULTIPLY  = '*';
+    public static final byte DIVIDE    = '/';
+    public static final byte REMAINDER = '%';
+
+
+    private final DoubleValue doubleValue1;
+    private final byte        operation;
+    private final DoubleValue doubleValue2;
+
+
+    /**
+     * Creates a new composite double value of the two given double values
+     * and the given operation.
+     */
+    public CompositeDoubleValue(DoubleValue doubleValue1,
+                                byte        operation,
+                                DoubleValue doubleValue2)
+    {
+        this.doubleValue1 = doubleValue1;
+        this.operation    = operation;
+        this.doubleValue2 = doubleValue2;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.doubleValue1.equals(((CompositeDoubleValue)object).doubleValue1) &&
+               this.operation        == ((CompositeDoubleValue)object).operation     &&
+               this.doubleValue2.equals(((CompositeDoubleValue)object).doubleValue2);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               doubleValue1.hashCode() ^
+               doubleValue2.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "("+doubleValue1+((char)operation)+doubleValue2+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/CompositeFloatValue.java b/src/proguard/evaluation/value/CompositeFloatValue.java
new file mode 100644
index 0000000..4df890a
--- /dev/null
+++ b/src/proguard/evaluation/value/CompositeFloatValue.java
@@ -0,0 +1,81 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This FloatValue represents the result of a binary operation on two float
+ * values.
+ *
+ * @author Eric Lafortune
+ */
+final class CompositeFloatValue extends SpecificFloatValue
+{
+    public static final byte ADD       = '+';
+    public static final byte SUBTRACT  = '-';
+    public static final byte MULTIPLY  = '*';
+    public static final byte DIVIDE    = '/';
+    public static final byte REMAINDER = '%';
+
+
+    private final FloatValue floatValue1;
+    private final byte        operation;
+    private final FloatValue floatValue2;
+
+
+    /**
+     * Creates a new composite float value of the two given float values
+     * and the given operation.
+     */
+    public CompositeFloatValue(FloatValue floatValue1,
+                                byte        operation,
+                                FloatValue floatValue2)
+    {
+        this.floatValue1 = floatValue1;
+        this.operation    = operation;
+        this.floatValue2 = floatValue2;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.floatValue1.equals(((CompositeFloatValue)object).floatValue1) &&
+               this.operation       == ((CompositeFloatValue)object).operation    &&
+               this.floatValue2.equals(((CompositeFloatValue)object).floatValue2);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               floatValue1.hashCode() ^
+               floatValue2.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "("+floatValue1+((char)operation)+floatValue2+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/CompositeIntegerValue.java b/src/proguard/evaluation/value/CompositeIntegerValue.java
new file mode 100644
index 0000000..6d4f59c
--- /dev/null
+++ b/src/proguard/evaluation/value/CompositeIntegerValue.java
@@ -0,0 +1,87 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents the result of a binary operation on two integer
+ * values.
+ *
+ * @author Eric Lafortune
+ */
+final class CompositeIntegerValue extends SpecificIntegerValue
+{
+    public static final byte ADD                  = '+';
+    public static final byte SUBTRACT             = '-';
+    public static final byte MULTIPLY             = '*';
+    public static final byte DIVIDE               = '/';
+    public static final byte REMAINDER            = '%';
+    public static final byte SHIFT_LEFT           = '<';
+    public static final byte SHIFT_RIGHT          = '>';
+    public static final byte UNSIGNED_SHIFT_RIGHT = '}';
+    public static final byte AND                  = '&';
+    public static final byte OR                   = '|';
+    public static final byte XOR                  = '^';
+
+
+    private final IntegerValue integerValue1;
+    private final byte         operation;
+    private final IntegerValue integerValue2;
+
+
+    /**
+     * Creates a new composite integer value of the two given integer values
+     * and the given operation.
+     */
+    public CompositeIntegerValue(IntegerValue integerValue1,
+                                 byte         operation,
+                                 IntegerValue integerValue2)
+    {
+        this.integerValue1 = integerValue1;
+        this.operation     = operation;
+        this.integerValue2 = integerValue2;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.integerValue1.equals(((CompositeIntegerValue)object).integerValue1) &&
+               this.operation         == ((CompositeIntegerValue)object).operation      &&
+               this.integerValue2.equals(((CompositeIntegerValue)object).integerValue2);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               integerValue1.hashCode() ^
+               integerValue2.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "("+integerValue1+((char)operation)+integerValue2+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/CompositeLongValue.java b/src/proguard/evaluation/value/CompositeLongValue.java
new file mode 100644
index 0000000..7f63211
--- /dev/null
+++ b/src/proguard/evaluation/value/CompositeLongValue.java
@@ -0,0 +1,87 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This LongValue represents the result of a binary operation on two long
+ * values.
+ *
+ * @author Eric Lafortune
+ */
+final class CompositeLongValue extends SpecificLongValue
+{
+    public static final byte ADD                  = '+';
+    public static final byte SUBTRACT             = '-';
+    public static final byte MULTIPLY             = '*';
+    public static final byte DIVIDE               = '/';
+    public static final byte REMAINDER            = '%';
+    public static final byte SHIFT_LEFT           = '<';
+    public static final byte SHIFT_RIGHT          = '>';
+    public static final byte UNSIGNED_SHIFT_RIGHT = '}';
+    public static final byte AND                  = '&';
+    public static final byte OR                   = '|';
+    public static final byte XOR                  = '^';
+
+
+    private final LongValue longValue1;
+    private final byte      operation;
+    private final Value     longValue2;
+
+
+    /**
+     * Creates a new composite long value of the two given long values
+     * and the given operation.
+     */
+    public CompositeLongValue(LongValue longValue1,
+                              byte      operation,
+                              Value     longValue2)
+    {
+        this.longValue1 = longValue1;
+        this.operation  = operation;
+        this.longValue2 = longValue2;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.longValue1.equals(((CompositeLongValue)object).longValue1) &&
+               this.operation      == ((CompositeLongValue)object).operation   &&
+               this.longValue2.equals(((CompositeLongValue)object).longValue2);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               longValue1.hashCode() ^
+               longValue2.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "("+longValue1+((char)operation)+longValue2+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ConvertedByteValue.java b/src/proguard/evaluation/value/ConvertedByteValue.java
new file mode 100644
index 0000000..7ab5d0a
--- /dev/null
+++ b/src/proguard/evaluation/value/ConvertedByteValue.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents a byte value that is converted from an
+ * integer value.
+ *
+ * @author Eric Lafortune
+ */
+final class ConvertedByteValue extends SpecificIntegerValue
+{
+    private final IntegerValue value;
+
+
+    /**
+     * Creates a new converted byte value of the given integer value.
+     */
+    public ConvertedByteValue(IntegerValue value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.value.equals(((ConvertedByteValue)object).value);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               value.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "(byte)("+value+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ConvertedCharacterValue.java b/src/proguard/evaluation/value/ConvertedCharacterValue.java
new file mode 100644
index 0000000..76568d1
--- /dev/null
+++ b/src/proguard/evaluation/value/ConvertedCharacterValue.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents a character value that is converted from an
+ * integer value.
+ *
+ * @author Eric Lafortune
+ */
+final class ConvertedCharacterValue extends SpecificIntegerValue
+{
+    private final IntegerValue value;
+
+
+    /**
+     * Creates a new converted character value of the given integer value.
+     */
+    public ConvertedCharacterValue(IntegerValue value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.value.equals(((ConvertedCharacterValue)object).value);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               value.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "(char)("+value+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ConvertedDoubleValue.java b/src/proguard/evaluation/value/ConvertedDoubleValue.java
new file mode 100644
index 0000000..a6afe34
--- /dev/null
+++ b/src/proguard/evaluation/value/ConvertedDoubleValue.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This DoubleValue represents a double value that is converted from another
+ * scalar value.
+ *
+ * @author Eric Lafortune
+ */
+final class ConvertedDoubleValue extends SpecificDoubleValue
+{
+    private final Value value;
+
+
+    /**
+     * Creates a new converted double value of the given value.
+     */
+    public ConvertedDoubleValue(Value value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.value.equals(((ConvertedDoubleValue)object).value);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               value.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "(double)("+value+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ConvertedFloatValue.java b/src/proguard/evaluation/value/ConvertedFloatValue.java
new file mode 100644
index 0000000..33683b7
--- /dev/null
+++ b/src/proguard/evaluation/value/ConvertedFloatValue.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This FloatValue represents a float value that is converted from another
+ * scalar value.
+ *
+ * @author Eric Lafortune
+ */
+final class ConvertedFloatValue extends SpecificFloatValue
+{
+    private final Value value;
+
+
+    /**
+     * Creates a new converted float value of the given value.
+     */
+    public ConvertedFloatValue(Value value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.value.equals(((ConvertedFloatValue)object).value);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               value.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "(float)("+value+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ConvertedIntegerValue.java b/src/proguard/evaluation/value/ConvertedIntegerValue.java
new file mode 100644
index 0000000..aecb533
--- /dev/null
+++ b/src/proguard/evaluation/value/ConvertedIntegerValue.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents a integer value that is converted from another
+ * scalar value.
+ *
+ * @author Eric Lafortune
+ */
+final class ConvertedIntegerValue extends SpecificIntegerValue
+{
+    private final Value value;
+
+
+    /**
+     * Creates a new converted integer value of the given value.
+     */
+    public ConvertedIntegerValue(Value value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.value.equals(((ConvertedIntegerValue)object).value);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               value.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "(int)("+value+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ConvertedLongValue.java b/src/proguard/evaluation/value/ConvertedLongValue.java
new file mode 100644
index 0000000..ec97008
--- /dev/null
+++ b/src/proguard/evaluation/value/ConvertedLongValue.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This LongValue represents a long value that is converted from another
+ * scalar value.
+ *
+ * @author Eric Lafortune
+ */
+final class ConvertedLongValue extends SpecificLongValue
+{
+    private final Value value;
+
+
+    /**
+     * Creates a new converted long value of the given value.
+     */
+    public ConvertedLongValue(Value value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.value.equals(((ConvertedLongValue)object).value);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               value.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "(long)("+value+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ConvertedShortValue.java b/src/proguard/evaluation/value/ConvertedShortValue.java
new file mode 100644
index 0000000..ab79b49
--- /dev/null
+++ b/src/proguard/evaluation/value/ConvertedShortValue.java
@@ -0,0 +1,64 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents a short value that is converted from an
+ * integer value.
+ *
+ * @author Eric Lafortune
+ */
+final class ConvertedShortValue extends SpecificIntegerValue
+{
+    private final IntegerValue value;
+
+
+    /**
+     * Creates a new converted short value of the given integer value.
+     */
+    public ConvertedShortValue(IntegerValue value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.value.equals(((ConvertedShortValue)object).value);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               value.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "(short)("+value+")";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/DoubleValue.java b/src/proguard/evaluation/value/DoubleValue.java
new file mode 100644
index 0000000..e39ee5c
--- /dev/null
+++ b/src/proguard/evaluation/value/DoubleValue.java
@@ -0,0 +1,364 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.ClassConstants;
+
+/**
+ * This class represents a partially evaluated double value.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class DoubleValue extends Category2Value
+{
+    /**
+     * Returns the specific double value, if applicable.
+     */
+    public double value()
+    {
+        return 0.0;
+    }
+
+
+    // Basic unary methods.
+
+    /**
+     * Returns the negated value of this DoubleValue.
+     */
+    public abstract DoubleValue negate();
+
+    /**
+     * Converts this DoubleValue to an IntegerValue.
+     */
+    public abstract IntegerValue convertToInteger();
+
+    /**
+     * Converts this DoubleValue to a LongValue.
+     */
+    public abstract LongValue convertToLong();
+
+    /**
+     * Converts this DoubleValue to a FloatValue.
+     */
+    public abstract FloatValue convertToFloat();
+
+
+    // Basic binary methods.
+
+    /**
+     * Returns the generalization of this DoubleValue and the given other
+     * DoubleValue.
+     */
+    public abstract DoubleValue generalize(DoubleValue other);
+
+
+    /**
+     * Returns the sum of this DoubleValue and the given DoubleValue.
+     */
+    public abstract DoubleValue add(DoubleValue other);
+
+    /**
+     * Returns the difference of this DoubleValue and the given DoubleValue.
+     */
+    public abstract DoubleValue subtract(DoubleValue other);
+
+    /**
+     * Returns the difference of the given DoubleValue and this DoubleValue.
+     */
+    public abstract DoubleValue subtractFrom(DoubleValue other);
+
+    /**
+     * Returns the product of this DoubleValue and the given DoubleValue.
+     */
+    public abstract DoubleValue multiply(DoubleValue other);
+
+    /**
+     * Returns the quotient of this DoubleValue and the given DoubleValue.
+     */
+    public abstract DoubleValue divide(DoubleValue other);
+
+    /**
+     * Returns the quotient of the given DoubleValue and this DoubleValue.
+     */
+    public abstract DoubleValue divideOf(DoubleValue other);
+
+    /**
+     * Returns the remainder of this DoubleValue divided by the given DoubleValue.
+     */
+    public abstract DoubleValue remainder(DoubleValue other);
+
+    /**
+     * Returns the remainder of the given DoubleValue divided by this DoubleValue.
+     */
+    public abstract DoubleValue remainderOf(DoubleValue other);
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this DoubleValue is
+     * less than, equal to, or greater than the given DoubleValue, respectively.
+     */
+    public abstract IntegerValue compare(DoubleValue other);
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this DoubleValue is
+     * less than, equal to, or greater than the given DoubleValue, respectively.
+     */
+    public final IntegerValue compareReverse(DoubleValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Similar binary methods, but this time with more specific arguments.
+
+    /**
+     * Returns the generalization of this DoubleValue and the given other
+     * SpecificDoubleValue.
+     */
+    public DoubleValue generalize(SpecificDoubleValue other)
+    {
+        return generalize((DoubleValue)other);
+    }
+
+
+    /**
+     * Returns the sum of this DoubleValue and the given SpecificDoubleValue.
+     */
+    public DoubleValue add(SpecificDoubleValue other)
+    {
+        return add((DoubleValue)other);
+    }
+
+    /**
+     * Returns the difference of this DoubleValue and the given SpecificDoubleValue.
+     */
+    public DoubleValue subtract(SpecificDoubleValue other)
+    {
+        return subtract((DoubleValue)other);
+    }
+
+    /**
+     * Returns the difference of the given SpecificDoubleValue and this DoubleValue.
+     */
+    public DoubleValue subtractFrom(SpecificDoubleValue other)
+    {
+        return subtractFrom((DoubleValue)other);
+    }
+
+    /**
+     * Returns the product of this DoubleValue and the given SpecificDoubleValue.
+     */
+    public DoubleValue multiply(SpecificDoubleValue other)
+    {
+        return multiply((DoubleValue)other);
+    }
+
+    /**
+     * Returns the quotient of this DoubleValue and the given SpecificDoubleValue.
+     */
+    public DoubleValue divide(SpecificDoubleValue other)
+    {
+        return divide((DoubleValue)other);
+    }
+
+    /**
+     * Returns the quotient of the given SpecificDoubleValue and this
+     * DoubleValue.
+     */
+    public DoubleValue divideOf(SpecificDoubleValue other)
+    {
+        return divideOf((DoubleValue)other);
+    }
+
+    /**
+     * Returns the remainder of this DoubleValue divided by the given
+     * SpecificDoubleValue.
+     */
+    public DoubleValue remainder(SpecificDoubleValue other)
+    {
+        return remainder((DoubleValue)other);
+    }
+
+    /**
+     * Returns the remainder of the given SpecificDoubleValue and this
+     * DoubleValue.
+     */
+    public DoubleValue remainderOf(SpecificDoubleValue other)
+    {
+        return remainderOf((DoubleValue)other);
+    }
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this DoubleValue is
+     * less than, equal to, or greater than the given SpecificDoubleValue,
+     * respectively.
+     */
+    public IntegerValue compare(SpecificDoubleValue other)
+    {
+        return compare((DoubleValue)other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this DoubleValue is
+     * less than, equal to, or greater than the given SpecificDoubleValue,
+     * respectively.
+     */
+    public final IntegerValue compareReverse(SpecificDoubleValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Similar binary methods, but this time with particular arguments.
+
+    /**
+     * Returns the generalization of this DoubleValue and the given other
+     * ParticularDoubleValue.
+     */
+    public DoubleValue generalize(ParticularDoubleValue other)
+    {
+        return generalize((SpecificDoubleValue)other);
+    }
+
+
+    /**
+     * Returns the sum of this DoubleValue and the given ParticularDoubleValue.
+     */
+    public DoubleValue add(ParticularDoubleValue other)
+    {
+        return add((SpecificDoubleValue)other);
+    }
+
+    /**
+     * Returns the difference of this DoubleValue and the given ParticularDoubleValue.
+     */
+    public DoubleValue subtract(ParticularDoubleValue other)
+    {
+        return subtract((SpecificDoubleValue)other);
+    }
+
+    /**
+     * Returns the difference of the given ParticularDoubleValue and this DoubleValue.
+     */
+    public DoubleValue subtractFrom(ParticularDoubleValue other)
+    {
+        return subtractFrom((SpecificDoubleValue)other);
+    }
+
+    /**
+     * Returns the product of this DoubleValue and the given ParticularDoubleValue.
+     */
+    public DoubleValue multiply(ParticularDoubleValue other)
+    {
+        return multiply((SpecificDoubleValue)other);
+    }
+
+    /**
+     * Returns the quotient of this DoubleValue and the given ParticularDoubleValue.
+     */
+    public DoubleValue divide(ParticularDoubleValue other)
+    {
+        return divide((SpecificDoubleValue)other);
+    }
+
+    /**
+     * Returns the quotient of the given ParticularDoubleValue and this
+     * DoubleValue.
+     */
+    public DoubleValue divideOf(ParticularDoubleValue other)
+    {
+        return divideOf((SpecificDoubleValue)other);
+    }
+
+    /**
+     * Returns the remainder of this DoubleValue divided by the given
+     * ParticularDoubleValue.
+     */
+    public DoubleValue remainder(ParticularDoubleValue other)
+    {
+        return remainder((SpecificDoubleValue)other);
+    }
+
+    /**
+     * Returns the remainder of the given ParticularDoubleValue and this
+     * DoubleValue.
+     */
+    public DoubleValue remainderOf(ParticularDoubleValue other)
+    {
+        return remainderOf((SpecificDoubleValue)other);
+    }
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this DoubleValue is
+     * less than, equal to, or greater than the given ParticularDoubleValue,
+     * respectively.
+     */
+    public IntegerValue compare(ParticularDoubleValue other)
+    {
+        return compare((SpecificDoubleValue)other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this DoubleValue is
+     * less than, equal to, or greater than the given ParticularDoubleValue,
+     * respectively.
+     */
+    public final IntegerValue compareReverse(ParticularDoubleValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Implementations for Value.
+
+    public final DoubleValue doubleValue()
+    {
+        return this;
+    }
+
+    public Value refresh()
+    {
+        return this;
+    }
+
+    public final Value generalize(Value other)
+    {
+        return this.generalize(other.doubleValue());
+    }
+
+    public final int computationalType()
+    {
+        return TYPE_DOUBLE;
+    }
+
+    public final String internalType()
+    {
+        return String.valueOf(ClassConstants.INTERNAL_TYPE_DOUBLE);
+    }
+}
diff --git a/src/proguard/evaluation/value/FloatValue.java b/src/proguard/evaluation/value/FloatValue.java
new file mode 100644
index 0000000..b30e395
--- /dev/null
+++ b/src/proguard/evaluation/value/FloatValue.java
@@ -0,0 +1,359 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.ClassConstants;
+
+/**
+ * This class represents a partially evaluated float value.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class FloatValue extends Category1Value
+{
+    /**
+     * Returns the specific float value, if applicable.
+     */
+    public float value()
+    {
+        return 0f;
+    }
+
+
+    // Basic unary methods.
+
+    /**
+     * Returns the negated value of this FloatValue.
+     */
+    public abstract FloatValue negate();
+
+    /**
+     * Converts this FloatValue to an IntegerValue.
+     */
+    public abstract IntegerValue convertToInteger();
+
+    /**
+     * Converts this FloatValue to a LongValue.
+     */
+    public abstract LongValue convertToLong();
+
+    /**
+     * Converts this FloatValue to a DoubleValue.
+     */
+    public abstract DoubleValue convertToDouble();
+
+
+    // Basic binary methods.
+
+    /**
+     * Returns the generalization of this FloatValue and the given other
+     * FloatValue.
+     */
+    public abstract FloatValue generalize(FloatValue other);
+
+
+    /**
+     * Returns the sum of this FloatValue and the given FloatValue.
+     */
+    public abstract FloatValue add(FloatValue other);
+
+    /**
+     * Returns the difference of this FloatValue and the given FloatValue.
+     */
+    public abstract FloatValue subtract(FloatValue other);
+
+    /**
+     * Returns the difference of the given FloatValue and this FloatValue.
+     */
+    public abstract FloatValue subtractFrom(FloatValue other);
+
+    /**
+     * Returns the product of this FloatValue and the given FloatValue.
+     */
+    public abstract FloatValue multiply(FloatValue other);
+
+    /**
+     * Returns the quotient of this FloatValue and the given FloatValue.
+     */
+    public abstract FloatValue divide(FloatValue other);
+
+    /**
+     * Returns the quotient of the given FloatValue and this FloatValue.
+     */
+    public abstract FloatValue divideOf(FloatValue other);
+
+    /**
+     * Returns the remainder of this FloatValue divided by the given FloatValue.
+     */
+    public abstract FloatValue remainder(FloatValue other);
+
+    /**
+     * Returns the remainder of the given FloatValue divided by this FloatValue.
+     */
+    public abstract FloatValue remainderOf(FloatValue other);
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this FloatValue is
+     * less than, equal to, or greater than the given FloatValue, respectively.
+     */
+    public abstract IntegerValue compare(FloatValue other);
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this FloatValue is
+     * less than, equal to, or greater than the given FloatValue, respectively.
+     */
+    public final IntegerValue compareReverse(FloatValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Similar binary methods, but this time with more specific arguments.
+
+    /**
+     * Returns the generalization of this FloatValue and the given other
+     * SpecificFloatValue.
+     */
+    public FloatValue generalize(SpecificFloatValue other)
+    {
+        return generalize((FloatValue)other);
+    }
+
+
+    /**
+     * Returns the sum of this FloatValue and the given SpecificFloatValue.
+     */
+    public FloatValue add(SpecificFloatValue other)
+    {
+        return add((FloatValue)other);
+    }
+
+    /**
+     * Returns the difference of this FloatValue and the given SpecificFloatValue.
+     */
+    public FloatValue subtract(SpecificFloatValue other)
+    {
+        return subtract((FloatValue)other);
+    }
+
+    /**
+     * Returns the difference of the given SpecificFloatValue and this FloatValue.
+     */
+    public FloatValue subtractFrom(SpecificFloatValue other)
+    {
+        return subtractFrom((FloatValue)other);
+    }
+
+    /**
+     * Returns the product of this FloatValue and the given SpecificFloatValue.
+     */
+    public FloatValue multiply(SpecificFloatValue other)
+    {
+        return multiply((FloatValue)other);
+    }
+
+    /**
+     * Returns the quotient of this FloatValue and the given SpecificFloatValue.
+     */
+    public FloatValue divide(SpecificFloatValue other)
+    {
+        return divide((FloatValue)other);
+    }
+
+    /**
+     * Returns the quotient of the given SpecificFloatValue and this
+     * FloatValue.
+     */
+    public FloatValue divideOf(SpecificFloatValue other)
+    {
+        return divideOf((FloatValue)other);
+    }
+
+    /**
+     * Returns the remainder of this FloatValue divided by the given
+     * SpecificFloatValue.
+     */
+    public FloatValue remainder(SpecificFloatValue other)
+    {
+        return remainder((FloatValue)other);
+    }
+
+    /**
+     * Returns the remainder of the given SpecificFloatValue and this
+     * FloatValue.
+     */
+    public FloatValue remainderOf(SpecificFloatValue other)
+    {
+        return remainderOf((FloatValue)other);
+    }
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this FloatValue is
+     * less than, equal to, or greater than the given SpecificFloatValue,
+     * respectively.
+     */
+    public IntegerValue compare(SpecificFloatValue other)
+    {
+        return compare((FloatValue)other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this FloatValue is
+     * less than, equal to, or greater than the given SpecificFloatValue,
+     * respectively.
+     */
+    public final IntegerValue compareReverse(SpecificFloatValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Similar binary methods, but this time with particular arguments.
+
+    /**
+     * Returns the generalization of this FloatValue and the given other
+     * ParticularFloatValue.
+     */
+    public FloatValue generalize(ParticularFloatValue other)
+    {
+        return generalize((SpecificFloatValue)other);
+    }
+
+
+    /**
+     * Returns the sum of this FloatValue and the given ParticularFloatValue.
+     */
+    public FloatValue add(ParticularFloatValue other)
+    {
+        return add((SpecificFloatValue)other);
+    }
+
+    /**
+     * Returns the difference of this FloatValue and the given ParticularFloatValue.
+     */
+    public FloatValue subtract(ParticularFloatValue other)
+    {
+        return subtract((SpecificFloatValue)other);
+    }
+
+    /**
+     * Returns the difference of the given ParticularFloatValue and this FloatValue.
+     */
+    public FloatValue subtractFrom(ParticularFloatValue other)
+    {
+        return subtractFrom((SpecificFloatValue)other);
+    }
+
+    /**
+     * Returns the product of this FloatValue and the given ParticularFloatValue.
+     */
+    public FloatValue multiply(ParticularFloatValue other)
+    {
+        return multiply((SpecificFloatValue)other);
+    }
+
+    /**
+     * Returns the quotient of this FloatValue and the given ParticularFloatValue.
+     */
+    public FloatValue divide(ParticularFloatValue other)
+    {
+        return divide((SpecificFloatValue)other);
+    }
+
+    /**
+     * Returns the quotient of the given ParticularFloatValue and this
+     * FloatValue.
+     */
+    public FloatValue divideOf(ParticularFloatValue other)
+    {
+        return divideOf((SpecificFloatValue)other);
+    }
+
+    /**
+     * Returns the remainder of this FloatValue divided by the given
+     * ParticularFloatValue.
+     */
+    public FloatValue remainder(ParticularFloatValue other)
+    {
+        return remainder((SpecificFloatValue)other);
+    }
+
+    /**
+     * Returns the remainder of the given ParticularFloatValue and this
+     * FloatValue.
+     */
+    public FloatValue remainderOf(ParticularFloatValue other)
+    {
+        return remainderOf((SpecificFloatValue)other);
+    }
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this FloatValue is
+     * less than, equal to, or greater than the given ParticularFloatValue,
+     * respectively.
+     */
+    public IntegerValue compare(ParticularFloatValue other)
+    {
+        return compare((SpecificFloatValue)other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this FloatValue is
+     * less than, equal to, or greater than the given ParticularFloatValue,
+     * respectively.
+     */
+    public final IntegerValue compareReverse(ParticularFloatValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Implementations for Value.
+
+    public final FloatValue floatValue()
+    {
+        return this;
+    }
+
+    public final Value generalize(Value other)
+    {
+        return this.generalize(other.floatValue());
+    }
+
+    public final int computationalType()
+    {
+        return TYPE_FLOAT;
+    }
+
+    public final String internalType()
+    {
+        return String.valueOf(ClassConstants.INTERNAL_TYPE_FLOAT);
+    }
+}
diff --git a/src/proguard/evaluation/value/IdentifiedDoubleValue.java b/src/proguard/evaluation/value/IdentifiedDoubleValue.java
new file mode 100644
index 0000000..4009c6e
--- /dev/null
+++ b/src/proguard/evaluation/value/IdentifiedDoubleValue.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This DoubleValue represents a double value that is identified by a unique ID.
+ *
+ * @author Eric Lafortune
+ */
+final class IdentifiedDoubleValue extends SpecificDoubleValue
+{
+    private final ValueFactory valuefactory;
+    private final int          id;
+
+
+    /**
+     * Creates a new double value with the given ID.
+     */
+    public IdentifiedDoubleValue(ValueFactory valuefactory, int id)
+    {
+        this.valuefactory = valuefactory;
+        this.id           = id;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.valuefactory.equals(((IdentifiedDoubleValue)object).valuefactory) &&
+               this.id == ((IdentifiedDoubleValue)object).id;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               valuefactory.hashCode() ^
+               id;
+    }
+
+
+    public String toString()
+    {
+        return "d"+id;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/IdentifiedFloatValue.java b/src/proguard/evaluation/value/IdentifiedFloatValue.java
new file mode 100644
index 0000000..87bed64
--- /dev/null
+++ b/src/proguard/evaluation/value/IdentifiedFloatValue.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This FloatValue represents a float value that is identified by a unique ID.
+ *
+ * @author Eric Lafortune
+ */
+final class IdentifiedFloatValue extends SpecificFloatValue
+{
+    private final ValueFactory valuefactory;
+    private final int          id;
+
+
+    /**
+     * Creates a new float value with the given ID.
+     */
+    public IdentifiedFloatValue(ValueFactory valuefactory, int id)
+    {
+        this.valuefactory = valuefactory;
+        this.id           = id;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.valuefactory.equals(((IdentifiedFloatValue)object).valuefactory) &&
+               this.id == ((IdentifiedFloatValue)object).id;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               valuefactory.hashCode() ^
+               id;
+    }
+
+
+    public String toString()
+    {
+        return "f"+id;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/IdentifiedIntegerValue.java b/src/proguard/evaluation/value/IdentifiedIntegerValue.java
new file mode 100644
index 0000000..66e88ee
--- /dev/null
+++ b/src/proguard/evaluation/value/IdentifiedIntegerValue.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents a integer value that is identified by a unique ID.
+ *
+ * @author Eric Lafortune
+ */
+final class IdentifiedIntegerValue extends SpecificIntegerValue
+{
+    private final ValueFactory valuefactory;
+    private final int          id;
+
+
+    /**
+     * Creates a new integer value with the given ID.
+     */
+    public IdentifiedIntegerValue(ValueFactory valuefactory, int id)
+    {
+        this.valuefactory = valuefactory;
+        this.id           = id;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.valuefactory.equals(((IdentifiedIntegerValue)object).valuefactory) &&
+               this.id == ((IdentifiedIntegerValue)object).id;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               valuefactory.hashCode() ^
+               id;
+    }
+
+
+    public String toString()
+    {
+        return "i"+id;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/IdentifiedLongValue.java b/src/proguard/evaluation/value/IdentifiedLongValue.java
new file mode 100644
index 0000000..eea1816
--- /dev/null
+++ b/src/proguard/evaluation/value/IdentifiedLongValue.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This LongValue represents a long value that is identified by a unique ID.
+ *
+ * @author Eric Lafortune
+ */
+final class IdentifiedLongValue extends SpecificLongValue
+{
+    private final ValueFactory valuefactory;
+    private final int          id;
+
+
+    /**
+     * Creates a new long value with the given ID.
+     */
+    public IdentifiedLongValue(ValueFactory valuefactory, int id)
+    {
+        this.valuefactory = valuefactory;
+        this.id           = id;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.valuefactory.equals(((IdentifiedLongValue)object).valuefactory) &&
+               this.id == ((IdentifiedLongValue)object).id;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               valuefactory.hashCode() ^
+               id;
+    }
+
+
+    public String toString()
+    {
+        return "l"+id;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/IdentifiedReferenceValue.java b/src/proguard/evaluation/value/IdentifiedReferenceValue.java
new file mode 100644
index 0000000..cb5efc1
--- /dev/null
+++ b/src/proguard/evaluation/value/IdentifiedReferenceValue.java
@@ -0,0 +1,91 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.Clazz;
+
+/**
+ * This LongValue represents a reference value that is identified by a unique ID.
+ *
+ * @author Eric Lafortune
+ */
+final class IdentifiedReferenceValue extends ReferenceValue
+{
+    private final ValueFactory valuefactory;
+    private final int          id;
+
+
+    /**
+     * Creates a new long value with the given ID.
+     */
+    public IdentifiedReferenceValue(String       type,
+                                    Clazz        referencedClass,
+                                    boolean      mayBeNull,
+                                    ValueFactory valuefactory,
+                                    int          id)
+    {
+        super(type, referencedClass, mayBeNull);
+
+        this.valuefactory = valuefactory;
+        this.id           = id;
+    }
+
+
+    // Implementations for ReferenceValue.
+
+    public int equal(ReferenceValue other)
+    {
+        return this.equals(other) ? ALWAYS : MAYBE;
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isSpecific()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.valuefactory.equals(((IdentifiedReferenceValue)object).valuefactory) &&
+               this.id == ((IdentifiedReferenceValue)object).id;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               valuefactory.hashCode() ^
+               id;
+    }
+
+
+    public String toString()
+    {
+        return super.toString()+'#'+id;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/IdentifiedValueFactory.java b/src/proguard/evaluation/value/IdentifiedValueFactory.java
new file mode 100644
index 0000000..2b14e72
--- /dev/null
+++ b/src/proguard/evaluation/value/IdentifiedValueFactory.java
@@ -0,0 +1,75 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.*;
+import proguard.classfile.util.ClassUtil;
+
+/**
+ * This class provides methods to create and reuse IntegerValue objects.
+ *
+ * @author Eric Lafortune
+ */
+public class IdentifiedValueFactory
+extends      SpecificValueFactory
+{
+    private int integerID;
+    private int longID;
+    private int floatID;
+    private int doubleID;
+    private int referenceID;
+
+
+    // Implementations for ValueFactory.
+
+    public IntegerValue createIntegerValue()
+    {
+        return new IdentifiedIntegerValue(this, integerID++);
+    }
+
+
+    public LongValue createLongValue()
+    {
+        return new IdentifiedLongValue(this, longID++);
+    }
+
+
+    public FloatValue createFloatValue()
+    {
+        return new IdentifiedFloatValue(this, floatID++);
+    }
+
+
+    public DoubleValue createDoubleValue()
+    {
+        return new IdentifiedDoubleValue(this, doubleID++);
+    }
+
+
+    public ReferenceValue createReferenceValue(String  type,
+                                               Clazz   referencedClass,
+                                               boolean mayBeNull)
+    {
+        return type == null ?
+            REFERENCE_VALUE_NULL :
+            new IdentifiedReferenceValue(type, referencedClass, mayBeNull, this, referenceID++);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/InstructionOffsetValue.java b/src/proguard/evaluation/value/InstructionOffsetValue.java
new file mode 100644
index 0000000..10b5a6f
--- /dev/null
+++ b/src/proguard/evaluation/value/InstructionOffsetValue.java
@@ -0,0 +1,307 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.ClassConstants;
+
+/**
+ * This class represents a partially evaluated instruction offset. It can
+ * contain 0 or more specific instruction offsets.
+ *
+ * @author Eric Lafortune
+ */
+public class InstructionOffsetValue extends Category1Value
+{
+    public static final InstructionOffsetValue EMPTY_VALUE = new InstructionOffsetValue();
+
+
+    private int[] values;
+
+
+    private InstructionOffsetValue()
+    {
+    }
+
+
+    public InstructionOffsetValue(int value)
+    {
+        this.values = new int[] { value };
+    }
+
+
+    public InstructionOffsetValue(int[] values)
+    {
+        this.values = values;
+    }
+
+
+    public int instructionOffsetCount()
+    {
+        return values == null ? 0 : values.length;
+    }
+
+
+    public int instructionOffset(int index)
+    {
+        return values[index];
+    }
+
+
+    /**
+     * Returns whether the given value is present in this list of instruction
+     * offsets.
+     */
+    public boolean contains(int value)
+    {
+        if (values != null)
+        {
+            for (int index = 0; index < values.length; index++)
+            {
+                if (values[index] == value)
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns the minimum value from this list of instruction offsets.
+     * Returns <code>Integer.MAX_VALUE</code> if the list is empty.
+     */
+    public int minimumValue()
+    {
+        int minimumValue = Integer.MAX_VALUE;
+
+        if (values != null)
+        {
+            for (int index = 0; index < values.length; index++)
+            {
+                int value = values[index];
+
+                if (minimumValue > value)
+                {
+                    minimumValue = value;
+                }
+            }
+        }
+
+        return minimumValue;
+    }
+
+
+    /**
+     * Returns the maximum value from this list of instruction offsets.
+     * Returns <code>Integer.MIN_VALUE</code> if the list is empty.
+     */
+    public int maximumValue()
+    {
+        int maximumValue = Integer.MIN_VALUE;
+
+        if (values != null)
+        {
+            for (int index = 0; index < values.length; index++)
+            {
+                int value = values[index];
+
+                if (maximumValue < value)
+                {
+                    maximumValue = value;
+                }
+            }
+        }
+
+        return maximumValue;
+    }
+
+
+    /**
+     * Returns the generalization of this InstructionOffsetValue and the given
+     * other InstructionOffsetValue. The values of the other InstructionOffsetValue
+     * are guaranteed to remain at the end of the list, in the same order.
+     */
+    public final Value generalize(InstructionOffsetValue other)
+    {
+        // If the values array of either is null, return the other one.
+        if (this.values == null)
+        {
+            return other;
+        }
+
+        if (other.values == null)
+        {
+            return this;
+        }
+
+        // Compute the length of the union of the arrays.
+        int newLength = this.values.length;
+        for (int index = 0; index < other.values.length; index++)
+        {
+            if (!this.contains(other.values[index]))
+            {
+                newLength++;
+            }
+        }
+
+        // If the length of the union array is equal to the length of the values
+        // array of either, return it.
+        if (newLength == other.values.length)
+        {
+            return other;
+        }
+
+        // The ordering of the this array may not be right, so we can't just
+        // use it.
+        //if (newLength == this.values.length)
+        //{
+        //    return this;
+        //}
+
+        // Create the union array.
+        int[] newValues = new int[newLength];
+
+        int newIndex = 0;
+
+        // Copy the values that are different from the other array.
+        for (int index = 0; index < this.values.length; index++)
+        {
+            if (!other.contains(this.values[index]))
+            {
+                newValues[newIndex++] = this.values[index];
+            }
+        }
+
+        // Copy the values from the other array.
+        for (int index = 0; index < other.values.length; index++)
+        {
+            newValues[newIndex++] = other.values[index];
+        }
+
+        return new InstructionOffsetValue(newValues);
+    }
+
+
+    // Implementations for Value.
+
+    public final InstructionOffsetValue instructionOffsetValue()
+    {
+        return this;
+    }
+
+    public boolean isSpecific()
+    {
+        return true;
+    }
+
+    public boolean isParticular()
+    {
+        return true;
+    }
+
+    public final Value generalize(Value other)
+    {
+        return this.generalize(other.instructionOffsetValue());
+    }
+
+    public final int computationalType()
+    {
+        return TYPE_INSTRUCTION_OFFSET;
+    }
+
+    public final String internalType()
+    {
+        return String.valueOf(ClassConstants.INTERNAL_TYPE_INT);
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        InstructionOffsetValue other = (InstructionOffsetValue)object;
+        if (this.values == other.values)
+        {
+            return true;
+        }
+
+        if (this.values  == null ||
+            other.values == null ||
+            this.values.length != other.values.length)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < other.values.length; index++)
+        {
+            if (!this.contains(other.values[index]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    public int hashCode()
+    {
+        int hashCode = this.getClass().hashCode();
+
+        if (values != null)
+        {
+            for (int index = 0; index < values.length; index++)
+            {
+                hashCode ^= values[index];
+            }
+        }
+
+        return hashCode;
+    }
+
+
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        if (values != null)
+        {
+            for (int index = 0; index < values.length; index++)
+            {
+                if (index > 0)
+                {
+                    buffer.append(',');
+                }
+                buffer.append(values[index]);
+            }
+        }
+
+        return buffer.append(':').toString();
+    }
+}
diff --git a/src/proguard/evaluation/value/IntegerValue.java b/src/proguard/evaluation/value/IntegerValue.java
new file mode 100644
index 0000000..508c5f5
--- /dev/null
+++ b/src/proguard/evaluation/value/IntegerValue.java
@@ -0,0 +1,1002 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.ClassConstants;
+
+/**
+ * This class represents a partially evaluated integer value.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class IntegerValue extends Category1Value
+{
+    /**
+     * Returns the specific integer value, if applicable.
+     */
+    public int value()
+    {
+        return 0;
+    }
+
+
+    // Basic unary methods.
+
+    /**
+     * Returns the negated value of this IntegerValue.
+     */
+    public abstract IntegerValue negate();
+
+    /**
+     * Converts this IntegerValue to a byte IntegerValue.
+     */
+    public abstract IntegerValue convertToByte();
+
+    /**
+     * Converts this IntegerValue to a character IntegerValue.
+     */
+    public abstract IntegerValue convertToCharacter();
+
+    /**
+     * Converts this IntegerValue to a short IntegerValue.
+     */
+    public abstract IntegerValue convertToShort();
+
+    /**
+     * Converts this IntegerValue to a LongValue.
+     */
+    public abstract LongValue convertToLong();
+
+    /**
+     * Converts this IntegerValue to a FloatValue.
+     */
+    public abstract FloatValue convertToFloat();
+
+    /**
+     * Converts this IntegerValue to a DoubleValue.
+     */
+    public abstract DoubleValue convertToDouble();
+
+
+    // Basic binary methods.
+
+    /**
+     * Returns the generalization of this IntegerValue and the given other
+     * IntegerValue.
+     */
+    public abstract IntegerValue generalize(IntegerValue other);
+
+    /**
+     * Returns the sum of this IntegerValue and the given IntegerValue.
+     */
+    public abstract IntegerValue add(IntegerValue other);
+
+    /**
+     * Returns the difference of this IntegerValue and the given IntegerValue.
+     */
+    public abstract IntegerValue subtract(IntegerValue other);
+
+    /**
+     * Returns the difference of the given IntegerValue and this IntegerValue.
+     */
+    public abstract IntegerValue subtractFrom(IntegerValue other);
+
+    /**
+     * Returns the product of this IntegerValue and the given IntegerValue.
+     */
+    public abstract IntegerValue multiply(IntegerValue other)
+    throws ArithmeticException;
+
+    /**
+     * Returns the quotient of this IntegerValue and the given IntegerValue.
+     */
+    public abstract IntegerValue divide(IntegerValue other)
+    throws ArithmeticException;
+
+    /**
+     * Returns the quotient of the given IntegerValue and this IntegerValue.
+     */
+    public abstract IntegerValue divideOf(IntegerValue other)
+    throws ArithmeticException;
+
+    /**
+     * Returns the remainder of this IntegerValue divided by the given
+     * IntegerValue.
+     */
+    public abstract IntegerValue remainder(IntegerValue other)
+    throws ArithmeticException;
+
+    /**
+     * Returns the remainder of the given IntegerValue divided by this
+     * IntegerValue.
+     */
+    public abstract IntegerValue remainderOf(IntegerValue other)
+    throws ArithmeticException;
+
+    /**
+     * Returns this IntegerValue, shifted left by the given IntegerValue.
+     */
+    public abstract IntegerValue shiftLeft(IntegerValue other);
+
+    /**
+     * Returns this IntegerValue, shifted right by the given IntegerValue.
+     */
+    public abstract IntegerValue shiftRight(IntegerValue other);
+
+    /**
+     * Returns this unsigned IntegerValue, shifted left by the given
+     * IntegerValue.
+     */
+    public abstract IntegerValue unsignedShiftRight(IntegerValue other);
+
+    /**
+     * Returns the given IntegerValue, shifted left by this IntegerValue.
+     */
+    public abstract IntegerValue shiftLeftOf(IntegerValue other);
+
+    /**
+     * Returns the given IntegerValue, shifted right by this IntegerValue.
+     */
+    public abstract IntegerValue shiftRightOf(IntegerValue other);
+
+    /**
+     * Returns the given unsigned IntegerValue, shifted left by this
+     * IntegerValue.
+     */
+    public abstract IntegerValue unsignedShiftRightOf(IntegerValue other);
+
+    /**
+     * Returns the given LongValue, shifted left by this IntegerValue.
+     */
+    public abstract LongValue shiftLeftOf(LongValue other);
+
+    /**
+     * Returns the given LongValue, shifted right by this IntegerValue.
+     */
+    public abstract LongValue shiftRightOf(LongValue other);
+
+    /**
+     * Returns the given unsigned LongValue, shifted right by this IntegerValue.
+     */
+    public abstract LongValue unsignedShiftRightOf(LongValue other);
+
+    /**
+     * Returns the logical <i>and</i> of this IntegerValue and the given
+     * IntegerValue.
+     */
+    public abstract IntegerValue and(IntegerValue other);
+
+    /**
+     * Returns the logical <i>or</i> of this IntegerValue and the given
+     * IntegerValue.
+     */
+    public abstract IntegerValue or(IntegerValue other);
+
+    /**
+     * Returns the logical <i>xor</i> of this IntegerValue and the given
+     * IntegerValue.
+     */
+    public abstract IntegerValue xor(IntegerValue other);
+
+    /**
+     * Returns whether this IntegerValue and the given IntegerValue are equal:
+     * <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public abstract int equal(IntegerValue other);
+
+    /**
+     * Returns whether this IntegerValue is less than the given IntegerValue:
+     * <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public abstract int lessThan(IntegerValue other);
+
+    /**
+     * Returns whether this IntegerValue is less than or equal to the given
+     * IntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public abstract int lessThanOrEqual(IntegerValue other);
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns whether this IntegerValue and the given IntegerValue are different:
+     * <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public final int notEqual(IntegerValue other)
+    {
+        return -equal(other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is greater than the given IntegerValue:
+     * <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public final int greaterThan(IntegerValue other)
+    {
+        return -lessThanOrEqual(other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is greater than or equal to the given IntegerValue:
+     * <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public final int greaterThanOrEqual(IntegerValue other)
+    {
+        return -lessThan(other);
+    }
+
+
+    // Similar binary methods, but this time with unknown arguments.
+
+    /**
+     * Returns the generalization of this IntegerValue and the given other
+     * UnknownIntegerValue.
+     */
+    public IntegerValue generalize(UnknownIntegerValue other)
+    {
+        return generalize((IntegerValue)other);
+    }
+
+
+    /**
+     * Returns the sum of this IntegerValue and the given UnknownIntegerValue.
+     */
+    public IntegerValue add(UnknownIntegerValue other)
+    {
+        return add((IntegerValue)other);
+    }
+
+    /**
+     * Returns the difference of this IntegerValue and the given UnknownIntegerValue.
+     */
+    public IntegerValue subtract(UnknownIntegerValue other)
+    {
+        return subtract((IntegerValue)other);
+    }
+
+    /**
+     * Returns the difference of the given UnknownIntegerValue and this IntegerValue.
+     */
+    public IntegerValue subtractFrom(UnknownIntegerValue other)
+    {
+        return subtractFrom((IntegerValue)other);
+    }
+
+    /**
+     * Returns the product of this IntegerValue and the given UnknownIntegerValue.
+     */
+    public IntegerValue multiply(UnknownIntegerValue other)
+    {
+        return multiply((IntegerValue)other);
+    }
+
+    /**
+     * Returns the quotient of this IntegerValue and the given
+     * UnknownIntegerValue.
+     */
+    public IntegerValue divide(UnknownIntegerValue other)
+    {
+        return divide((IntegerValue)other);
+    }
+
+    /**
+     * Returns the quotient of the given UnknownIntegerValue and this
+     * IntegerValue.
+     */
+    public IntegerValue divideOf(UnknownIntegerValue other)
+    {
+        return divideOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns the remainder of this IntegerValue divided by the given
+     * UnknownIntegerValue.
+     */
+    public IntegerValue remainder(UnknownIntegerValue other)
+    {
+        return remainder((IntegerValue)other);
+    }
+
+    /**
+     * Returns the remainder of the given UnknownIntegerValue divided by this
+     * IntegerValue.
+     */
+    public IntegerValue remainderOf(UnknownIntegerValue other)
+    {
+        return remainderOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns this IntegerValue, shifted left by the given UnknownIntegerValue.
+     */
+    public IntegerValue shiftLeft(UnknownIntegerValue other)
+    {
+        return shiftLeft((IntegerValue)other);
+    }
+
+    /**
+     * Returns this IntegerValue, shifted right by the given UnknownIntegerValue.
+     */
+    public IntegerValue shiftRight(UnknownIntegerValue other)
+    {
+        return shiftRight((IntegerValue)other);
+    }
+
+    /**
+     * Returns this unsigned IntegerValue, shifted right by the given
+     * UnknownIntegerValue.
+     */
+    public IntegerValue unsignedShiftRight(UnknownIntegerValue other)
+    {
+        return unsignedShiftRight((IntegerValue)other);
+    }
+
+    /**
+     * Returns the given UnknownIntegerValue, shifted left by this IntegerValue.
+     */
+    public IntegerValue shiftLeftOf(UnknownIntegerValue other)
+    {
+        return shiftLeftOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns the given UnknownIntegerValue, shifted right by this IntegerValue.
+     */
+    public IntegerValue shiftRightOf(UnknownIntegerValue other)
+    {
+        return shiftRightOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns the given unsigned UnknownIntegerValue, shifted right by this
+     * IntegerValue.
+     */
+    public IntegerValue unsignedShiftRightOf(UnknownIntegerValue other)
+    {
+        return unsignedShiftRightOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns the given UnknownLongValue, shifted left by this IntegerValue.
+     */
+    public LongValue shiftLeftOf(UnknownLongValue other)
+    {
+        return shiftLeftOf((LongValue)other);
+    }
+
+    /**
+     * Returns the given UnknownLongValue, shifted right by this IntegerValue.
+     */
+    public LongValue shiftRightOf(UnknownLongValue other)
+    {
+        return shiftRightOf((LongValue)other);
+    }
+
+    /**
+     * Returns the given unsigned UnknownLongValue, shifted right by this
+     * IntegerValue.
+     */
+    public LongValue unsignedShiftRightOf(UnknownLongValue other)
+    {
+        return unsignedShiftRightOf((LongValue)other);
+    }
+
+    /**
+     * Returns the logical <i>and</i> of this IntegerValue and the given
+     * UnknownIntegerValue.
+     */
+    public IntegerValue and(UnknownIntegerValue other)
+    {
+        return and((IntegerValue)other);
+    }
+
+    /**
+     * Returns the logical <i>or</i> of this IntegerValue and the given
+     * UnknownIntegerValue.
+     */
+    public IntegerValue or(UnknownIntegerValue other)
+    {
+        return or((IntegerValue)other);
+    }
+
+    /**
+     * Returns the logical <i>xor</i> of this IntegerValue and the given
+     * UnknownIntegerValue.
+     */
+    public IntegerValue xor(UnknownIntegerValue other)
+    {
+        return xor((IntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue and the given UnknownIntegerValue are
+     * equal: <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public int equal(UnknownIntegerValue other)
+    {
+        return equal((IntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is less than the given
+     * UnknownIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public int lessThan(UnknownIntegerValue other)
+    {
+        return lessThan((IntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is less than or equal to the given
+     * UnknownIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public int lessThanOrEqual(UnknownIntegerValue other)
+    {
+        return lessThanOrEqual((IntegerValue)other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns whether this IntegerValue and the given UnknownIntegerValue are
+     * different: <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public final int notEqual(UnknownIntegerValue other)
+    {
+        return -equal(other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is greater than the given
+     * UnknownIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public final int greaterThan(UnknownIntegerValue other)
+    {
+        return -lessThanOrEqual(other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is greater than or equal to the given
+     * UnknownIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public final int greaterThanOrEqual(UnknownIntegerValue other)
+    {
+        return -lessThan(other);
+    }
+
+
+    // Similar binary methods, but this time with specific arguments.
+
+    /**
+     * Returns the generalization of this IntegerValue and the given other
+     * SpecificIntegerValue.
+     */
+    public IntegerValue generalize(SpecificIntegerValue other)
+    {
+        return generalize((IntegerValue)other);
+    }
+
+
+    /**
+     * Returns the sum of this IntegerValue and the given SpecificIntegerValue.
+     */
+    public IntegerValue add(SpecificIntegerValue other)
+    {
+        return add((IntegerValue)other);
+    }
+
+    /**
+     * Returns the difference of this IntegerValue and the given SpecificIntegerValue.
+     */
+    public IntegerValue subtract(SpecificIntegerValue other)
+    {
+        return subtract((IntegerValue)other);
+    }
+
+    /**
+     * Returns the difference of the given SpecificIntegerValue and this IntegerValue.
+     */
+    public IntegerValue subtractFrom(SpecificIntegerValue other)
+    {
+        return subtractFrom((IntegerValue)other);
+    }
+
+    /**
+     * Returns the product of this IntegerValue and the given SpecificIntegerValue.
+     */
+    public IntegerValue multiply(SpecificIntegerValue other)
+    {
+        return multiply((IntegerValue)other);
+    }
+
+    /**
+     * Returns the quotient of this IntegerValue and the given
+     * SpecificIntegerValue.
+     */
+    public IntegerValue divide(SpecificIntegerValue other)
+    {
+        return divide((IntegerValue)other);
+    }
+
+    /**
+     * Returns the quotient of the given SpecificIntegerValue and this
+     * IntegerValue.
+     */
+    public IntegerValue divideOf(SpecificIntegerValue other)
+    {
+        return divideOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns the remainder of this IntegerValue divided by the given
+     * SpecificIntegerValue.
+     */
+    public IntegerValue remainder(SpecificIntegerValue other)
+    {
+        return remainder((IntegerValue)other);
+    }
+
+    /**
+     * Returns the remainder of the given SpecificIntegerValue divided by this
+     * IntegerValue.
+     */
+    public IntegerValue remainderOf(SpecificIntegerValue other)
+    {
+        return remainderOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns this IntegerValue, shifted left by the given SpecificIntegerValue.
+     */
+    public IntegerValue shiftLeft(SpecificIntegerValue other)
+    {
+        return shiftLeft((IntegerValue)other);
+    }
+
+    /**
+     * Returns this IntegerValue, shifted right by the given SpecificIntegerValue.
+     */
+    public IntegerValue shiftRight(SpecificIntegerValue other)
+    {
+        return shiftRight((IntegerValue)other);
+    }
+
+    /**
+     * Returns this unsigned IntegerValue, shifted right by the given
+     * SpecificIntegerValue.
+     */
+    public IntegerValue unsignedShiftRight(SpecificIntegerValue other)
+    {
+        return unsignedShiftRight((IntegerValue)other);
+    }
+
+    /**
+     * Returns the given SpecificIntegerValue, shifted left by this IntegerValue.
+     */
+    public IntegerValue shiftLeftOf(SpecificIntegerValue other)
+    {
+        return shiftLeftOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns the given SpecificIntegerValue, shifted right by this IntegerValue.
+     */
+    public IntegerValue shiftRightOf(SpecificIntegerValue other)
+    {
+        return shiftRightOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns the given unsigned SpecificIntegerValue, shifted right by this
+     * IntegerValue.
+     */
+    public IntegerValue unsignedShiftRightOf(SpecificIntegerValue other)
+    {
+        return unsignedShiftRightOf((IntegerValue)other);
+    }
+
+    /**
+     * Returns the given SpecificLongValue, shifted left by this IntegerValue.
+     */
+    public LongValue shiftLeftOf(SpecificLongValue other)
+    {
+        return shiftLeftOf((LongValue)other);
+    }
+
+    /**
+     * Returns the given SpecificLongValue, shifted right by this IntegerValue.
+     */
+    public LongValue shiftRightOf(SpecificLongValue other)
+    {
+        return shiftRightOf((LongValue)other);
+    }
+
+    /**
+     * Returns the given unsigned SpecificLongValue, shifted right by this
+     * IntegerValue.
+     */
+    public LongValue unsignedShiftRightOf(SpecificLongValue other)
+    {
+        return unsignedShiftRightOf((LongValue)other);
+    }
+
+    /**
+     * Returns the logical <i>and</i> of this IntegerValue and the given
+     * SpecificIntegerValue.
+     */
+    public IntegerValue and(SpecificIntegerValue other)
+    {
+        return and((IntegerValue)other);
+    }
+
+    /**
+     * Returns the logical <i>or</i> of this IntegerValue and the given
+     * SpecificIntegerValue.
+     */
+    public IntegerValue or(SpecificIntegerValue other)
+    {
+        return or((IntegerValue)other);
+    }
+
+    /**
+     * Returns the logical <i>xor</i> of this IntegerValue and the given
+     * SpecificIntegerValue.
+     */
+    public IntegerValue xor(SpecificIntegerValue other)
+    {
+        return xor((IntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue and the given SpecificIntegerValue are
+     * equal: <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public int equal(SpecificIntegerValue other)
+    {
+        return equal((IntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is less than the given
+     * SpecificIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public int lessThan(SpecificIntegerValue other)
+    {
+        return lessThan((IntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is less than or equal to the given
+     * SpecificIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public int lessThanOrEqual(SpecificIntegerValue other)
+    {
+        return lessThanOrEqual((IntegerValue)other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns whether this IntegerValue and the given SpecificIntegerValue are
+     * different: <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public final int notEqual(SpecificIntegerValue other)
+    {
+        return -equal(other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is greater than the given
+     * SpecificIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public final int greaterThan(SpecificIntegerValue other)
+    {
+        return -lessThanOrEqual(other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is greater than or equal to the given
+     * SpecificIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public final int greaterThanOrEqual(SpecificIntegerValue other)
+    {
+        return -lessThan(other);
+    }
+
+
+    // Similar binary methods, but this time with particular arguments.
+
+    /**
+     * Returns the generalization of this IntegerValue and the given other
+     * ParticularIntegerValue.
+     */
+    public IntegerValue generalize(ParticularIntegerValue other)
+    {
+        return generalize((SpecificIntegerValue)other);
+    }
+
+
+    /**
+     * Returns the sum of this IntegerValue and the given ParticularIntegerValue.
+     */
+    public IntegerValue add(ParticularIntegerValue other)
+    {
+        return add((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the difference of this IntegerValue and the given ParticularIntegerValue.
+     */
+    public IntegerValue subtract(ParticularIntegerValue other)
+    {
+        return subtract((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the difference of the given ParticularIntegerValue and this IntegerValue.
+     */
+    public IntegerValue subtractFrom(ParticularIntegerValue other)
+    {
+        return subtractFrom((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the product of this IntegerValue and the given ParticularIntegerValue.
+     */
+    public IntegerValue multiply(ParticularIntegerValue other)
+    {
+        return multiply((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the quotient of this IntegerValue and the given
+     * ParticularIntegerValue.
+     */
+    public IntegerValue divide(ParticularIntegerValue other)
+    {
+        return divide((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the quotient of the given ParticularIntegerValue and this
+     * IntegerValue.
+     */
+    public IntegerValue divideOf(ParticularIntegerValue other)
+    {
+        return divideOf((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the remainder of this IntegerValue divided by the given
+     * ParticularIntegerValue.
+     */
+    public IntegerValue remainder(ParticularIntegerValue other)
+    {
+        return remainder((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the remainder of the given ParticularIntegerValue divided by this
+     * IntegerValue.
+     */
+    public IntegerValue remainderOf(ParticularIntegerValue other)
+    {
+        return remainderOf((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns this IntegerValue, shifted left by the given ParticularIntegerValue.
+     */
+    public IntegerValue shiftLeft(ParticularIntegerValue other)
+    {
+        return shiftLeft((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns this IntegerValue, shifted right by the given ParticularIntegerValue.
+     */
+    public IntegerValue shiftRight(ParticularIntegerValue other)
+    {
+        return shiftRight((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns this unsigned IntegerValue, shifted right by the given
+     * ParticularIntegerValue.
+     */
+    public IntegerValue unsignedShiftRight(ParticularIntegerValue other)
+    {
+        return unsignedShiftRight((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the given ParticularIntegerValue, shifted left by this IntegerValue.
+     */
+    public IntegerValue shiftLeftOf(ParticularIntegerValue other)
+    {
+        return shiftLeftOf((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the given ParticularIntegerValue, shifted right by this IntegerValue.
+     */
+    public IntegerValue shiftRightOf(ParticularIntegerValue other)
+    {
+        return shiftRightOf((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the given unsigned ParticularIntegerValue, shifted right by this
+     * IntegerValue.
+     */
+    public IntegerValue unsignedShiftRightOf(ParticularIntegerValue other)
+    {
+        return unsignedShiftRightOf((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the given ParticularLongValue, shifted left by this IntegerValue.
+     */
+    public LongValue shiftLeftOf(ParticularLongValue other)
+    {
+        return shiftLeftOf((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the given ParticularLongValue, shifted right by this IntegerValue.
+     */
+    public LongValue shiftRightOf(ParticularLongValue other)
+    {
+        return shiftRightOf((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the given unsigned ParticularLongValue, shifted right by this
+     * IntegerValue.
+     */
+    public LongValue unsignedShiftRightOf(ParticularLongValue other)
+    {
+        return unsignedShiftRightOf((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the logical <i>and</i> of this IntegerValue and the given
+     * ParticularIntegerValue.
+     */
+    public IntegerValue and(ParticularIntegerValue other)
+    {
+        return and((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the logical <i>or</i> of this IntegerValue and the given
+     * ParticularIntegerValue.
+     */
+    public IntegerValue or(ParticularIntegerValue other)
+    {
+        return or((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the logical <i>xor</i> of this IntegerValue and the given
+     * ParticularIntegerValue.
+     */
+    public IntegerValue xor(ParticularIntegerValue other)
+    {
+        return xor((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue and the given ParticularIntegerValue are
+     * equal: <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public int equal(ParticularIntegerValue other)
+    {
+        return equal((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is less than the given
+     * ParticularIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public int lessThan(ParticularIntegerValue other)
+    {
+        return lessThan((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is less than or equal to the given
+     * ParticularIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public int lessThanOrEqual(ParticularIntegerValue other)
+    {
+        return lessThanOrEqual((SpecificIntegerValue)other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns whether this IntegerValue and the given ParticularIntegerValue are
+     * different: <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public final int notEqual(ParticularIntegerValue other)
+    {
+        return -equal(other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is greater than the given
+     * ParticularIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public final int greaterThan(ParticularIntegerValue other)
+    {
+        return -lessThanOrEqual(other);
+    }
+
+    /**
+     * Returns whether this IntegerValue is greater than or equal to the given
+     * ParticularIntegerValue: <code>NEVER</code>, <code>MAYBE</code>, or
+     * <code>ALWAYS</code>.
+     */
+    public final int greaterThanOrEqual(ParticularIntegerValue other)
+    {
+        return -lessThan(other);
+    }
+
+
+    // Implementations for Value.
+
+    public final IntegerValue integerValue()
+    {
+        return this;
+    }
+
+    public final Value generalize(Value other)
+    {
+        return this.generalize(other.integerValue());
+    }
+
+    public final int computationalType()
+    {
+        return TYPE_INTEGER;
+    }
+
+    public final String internalType()
+    {
+        return String.valueOf(ClassConstants.INTERNAL_TYPE_INT);
+    }
+}
diff --git a/src/proguard/evaluation/value/LongValue.java b/src/proguard/evaluation/value/LongValue.java
new file mode 100644
index 0000000..f7ba162
--- /dev/null
+++ b/src/proguard/evaluation/value/LongValue.java
@@ -0,0 +1,554 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.ClassConstants;
+
+/**
+ * This class represents a partially evaluated long value.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class LongValue extends Category2Value
+{
+    /**
+     * Returns the specific long value, if applicable.
+     */
+    public long value()
+    {
+        return 0;
+    }
+
+
+    // Basic unary methods.
+
+    /**
+     * Returns the negated value of this LongValue.
+     */
+    public abstract LongValue negate();
+
+
+    /**
+     * Converts this LongValue to an IntegerValue.
+     */
+    public abstract IntegerValue convertToInteger();
+
+    /**
+     * Converts this LongValue to a FloatValue.
+     */
+    public abstract FloatValue convertToFloat();
+
+    /**
+     * Converts this LongValue to a DoubleValue.
+     */
+    public abstract DoubleValue convertToDouble();
+
+
+    // Basic binary methods.
+
+    /**
+     * Returns the generalization of this LongValue and the given other
+     * LongValue.
+     */
+    public LongValue generalize(LongValue other)
+    {
+        return other.generalize(this);
+    }
+
+    /**
+     * Returns the sum of this LongValue and the given LongValue.
+     */
+    public LongValue add(LongValue other)
+    {
+        return other.add(this);
+    }
+
+    /**
+     * Returns the difference of this LongValue and the given LongValue.
+     */
+    public LongValue subtract(LongValue other)
+    {
+        return other.subtractFrom(this);
+    }
+
+    /**
+     * Returns the difference of the given LongValue and this LongValue.
+     */
+    public LongValue subtractFrom(LongValue other)
+    {
+        return other.subtract(this);
+    }
+
+    /**
+     * Returns the product of this LongValue and the given LongValue.
+     */
+    public LongValue multiply(LongValue other)
+    throws ArithmeticException
+    {
+        return other.multiply(this);
+    }
+
+    /**
+     * Returns the quotient of this LongValue and the given LongValue.
+     */
+    public LongValue divide(LongValue other)
+    throws ArithmeticException
+    {
+        return other.divideOf(this);
+    }
+
+    /**
+     * Returns the quotient of the given LongValue and this LongValue.
+     */
+    public LongValue divideOf(LongValue other)
+    throws ArithmeticException
+    {
+        return other.divide(this);
+    }
+
+    /**
+     * Returns the remainder of this LongValue divided by the given
+     * LongValue.
+     */
+    public LongValue remainder(LongValue other)
+    throws ArithmeticException
+    {
+        return other.remainderOf(this);
+    }
+
+    /**
+     * Returns the remainder of the given LongValue divided by this
+     * LongValue.
+     */
+    public LongValue remainderOf(LongValue other)
+    throws ArithmeticException
+    {
+        return other.remainder(this);
+    }
+
+    /**
+     * Returns this LongValue, shifted left by the given IntegerValue.
+     */
+    public LongValue shiftLeft(IntegerValue other)
+    {
+        return other.shiftLeftOf(this);
+    }
+
+    /**
+     * Returns this LongValue, shifted right by the given IntegerValue.
+     */
+    public LongValue shiftRight(IntegerValue other)
+    {
+        return other.shiftRightOf(this);
+    }
+
+    /**
+     * Returns this unsigned LongValue, shifted left by the given
+     * IntegerValue.
+     */
+    public LongValue unsignedShiftRight(IntegerValue other)
+    {
+        return other.unsignedShiftRightOf(this);
+    }
+
+    /**
+     * Returns the logical <i>and</i> of this LongValue and the given
+     * LongValue.
+     */
+    public LongValue and(LongValue other)
+    {
+        return other.and(this);
+    }
+
+    /**
+     * Returns the logical <i>or</i> of this LongValue and the given
+     * LongValue.
+     */
+    public LongValue or(LongValue other)
+    {
+        return other.or(this);
+    }
+
+    /**
+     * Returns the logical <i>xor</i> of this LongValue and the given
+     * LongValue.
+     */
+    public LongValue xor(LongValue other)
+    {
+        return other.xor(this);
+    }
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this LongValue is
+     * less than, equal to, or greater than the given LongValue, respectively.
+     */
+    public IntegerValue compare(LongValue other)
+    {
+        return other.compareReverse(this);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this LongValue is
+     * less than, equal to, or greater than the given LongValue, respectively.
+     */
+    public final IntegerValue compareReverse(LongValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Similar binary methods, but this time with more specific arguments.
+
+    /**
+     * Returns the generalization of this LongValue and the given other
+     * SpecificLongValue.
+     */
+    public LongValue generalize(SpecificLongValue other)
+    {
+        return this;
+    }
+
+
+    /**
+     * Returns the sum of this LongValue and the given SpecificLongValue.
+     */
+    public LongValue add(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the difference of this LongValue and the given SpecificLongValue.
+     */
+    public LongValue subtract(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the difference of the given SpecificLongValue and this LongValue.
+     */
+    public LongValue subtractFrom(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the product of this LongValue and the given SpecificLongValue.
+     */
+    public LongValue multiply(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the quotient of this LongValue and the given
+     * SpecificLongValue.
+     */
+    public LongValue divide(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the quotient of the given SpecificLongValue and this
+     * LongValue.
+     */
+    public LongValue divideOf(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the remainder of this LongValue divided by the given
+     * SpecificLongValue.
+     */
+    public LongValue remainder(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the remainder of the given SpecificLongValue divided by this
+     * LongValue.
+     */
+    public LongValue remainderOf(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns this LongValue, shifted left by the given SpecificLongValue.
+     */
+    public LongValue shiftLeft(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns this LongValue, shifted right by the given SpecificLongValue.
+     */
+    public LongValue shiftRight(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns this unsigned LongValue, shifted right by the given
+     * SpecificLongValue.
+     */
+    public LongValue unsignedShiftRight(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the logical <i>and</i> of this LongValue and the given
+     * SpecificLongValue.
+     */
+    public LongValue and(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the logical <i>or</i> of this LongValue and the given
+     * SpecificLongValue.
+     */
+    public LongValue or(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns the logical <i>xor</i> of this LongValue and the given
+     * SpecificLongValue.
+     */
+    public LongValue xor(SpecificLongValue other)
+    {
+        return this;
+    }
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this LongValue is
+     * less than, equal to, or greater than the given SpecificLongValue,
+     * respectively.
+     */
+    public IntegerValue compare(SpecificLongValue other)
+    {
+        return new ComparisonValue(this, other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this LongValue is
+     * less than, equal to, or greater than the given SpecificLongValue,
+     * respectively.
+     */
+    public final IntegerValue compareReverse(SpecificLongValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Similar binary methods, but this time with particular arguments.
+
+    /**
+     * Returns the generalization of this LongValue and the given other
+     * ParticularLongValue.
+     */
+    public LongValue generalize(ParticularLongValue other)
+    {
+        return generalize((SpecificLongValue)other);
+    }
+
+
+    /**
+     * Returns the sum of this LongValue and the given ParticularLongValue.
+     */
+    public LongValue add(ParticularLongValue other)
+    {
+        return add((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the difference of this LongValue and the given ParticularLongValue.
+     */
+    public LongValue subtract(ParticularLongValue other)
+    {
+        return subtract((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the difference of the given ParticularLongValue and this LongValue.
+     */
+    public LongValue subtractFrom(ParticularLongValue other)
+    {
+        return subtractFrom((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the product of this LongValue and the given ParticularLongValue.
+     */
+    public LongValue multiply(ParticularLongValue other)
+    {
+        return multiply((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the quotient of this LongValue and the given
+     * ParticularLongValue.
+     */
+    public LongValue divide(ParticularLongValue other)
+    {
+        return divide((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the quotient of the given ParticularLongValue and this
+     * LongValue.
+     */
+    public LongValue divideOf(ParticularLongValue other)
+    {
+        return divideOf((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the remainder of this LongValue divided by the given
+     * ParticularLongValue.
+     */
+    public LongValue remainder(ParticularLongValue other)
+    {
+        return remainder((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the remainder of the given ParticularLongValue divided by this
+     * LongValue.
+     */
+    public LongValue remainderOf(ParticularLongValue other)
+    {
+        return remainderOf((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns this LongValue, shifted left by the given ParticularIntegerValue.
+     */
+    public LongValue shiftLeft(ParticularIntegerValue other)
+    {
+        return shiftLeft((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns this LongValue, shifted right by the given ParticularIntegerValue.
+     */
+    public LongValue shiftRight(ParticularIntegerValue other)
+    {
+        return shiftRight((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns this unsigned LongValue, shifted right by the given
+     * ParticularIntegerValue.
+     */
+    public LongValue unsignedShiftRight(ParticularIntegerValue other)
+    {
+        return unsignedShiftRight((SpecificIntegerValue)other);
+    }
+
+    /**
+     * Returns the logical <i>and</i> of this LongValue and the given
+     * ParticularLongValue.
+     */
+    public LongValue and(ParticularLongValue other)
+    {
+        return and((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the logical <i>or</i> of this LongValue and the given
+     * ParticularLongValue.
+     */
+    public LongValue or(ParticularLongValue other)
+    {
+        return or((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns the logical <i>xor</i> of this LongValue and the given
+     * ParticularLongValue.
+     */
+    public LongValue xor(ParticularLongValue other)
+    {
+        return xor((SpecificLongValue)other);
+    }
+
+    /**
+     * Returns an IntegerValue with value -1, 0, or 1, if this LongValue is
+     * less than, equal to, or greater than the given ParticularLongValue,
+     * respectively.
+     */
+    public IntegerValue compare(ParticularLongValue other)
+    {
+        return compare((SpecificLongValue)other);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns an IntegerValue with value 1, 0, or -1, if this LongValue is
+     * less than, equal to, or greater than the given ParticularLongValue,
+     * respectively.
+     */
+    public final IntegerValue compareReverse(ParticularLongValue other)
+    {
+        return compare(other).negate();
+    }
+
+
+    // Implementations for Value.
+
+    public final LongValue longValue()
+    {
+        return this;
+    }
+
+    public final Value generalize(Value other)
+    {
+        return this.generalize(other.longValue());
+    }
+
+    public final int computationalType()
+    {
+        return TYPE_LONG;
+    }
+
+    public final String internalType()
+    {
+        return String.valueOf(ClassConstants.INTERNAL_TYPE_INT);
+    }
+}
diff --git a/src/proguard/evaluation/value/NegatedDoubleValue.java b/src/proguard/evaluation/value/NegatedDoubleValue.java
new file mode 100644
index 0000000..2bc9423
--- /dev/null
+++ b/src/proguard/evaluation/value/NegatedDoubleValue.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This DoubleValue represents a double value that is negated.
+ *
+ * @author Eric Lafortune
+ */
+final class NegatedDoubleValue extends SpecificDoubleValue
+{
+    private final DoubleValue doubleValue;
+
+
+    /**
+     * Creates a new negated double value of the given double value.
+     */
+    public NegatedDoubleValue(DoubleValue doubleValue)
+    {
+        this.doubleValue = doubleValue;
+    }
+
+
+    // Implementations of unary methods of DoubleValue.
+
+    public DoubleValue negate()
+    {
+        return doubleValue;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.doubleValue.equals(((NegatedDoubleValue)object).doubleValue);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               doubleValue.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "-"+doubleValue;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/NegatedFloatValue.java b/src/proguard/evaluation/value/NegatedFloatValue.java
new file mode 100644
index 0000000..0ddf4ab
--- /dev/null
+++ b/src/proguard/evaluation/value/NegatedFloatValue.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This FloatValue represents a float value that is negated.
+ *
+ * @author Eric Lafortune
+ */
+final class NegatedFloatValue extends SpecificFloatValue
+{
+    private final FloatValue floatValue;
+
+
+    /**
+     * Creates a new negated float value of the given float value.
+     */
+    public NegatedFloatValue(FloatValue floatValue)
+    {
+        this.floatValue = floatValue;
+    }
+
+
+    // Implementations of unary methods of FloatValue.
+
+    public FloatValue negate()
+    {
+        return floatValue;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.floatValue.equals(((NegatedFloatValue)object).floatValue);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               floatValue.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "-"+floatValue;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/NegatedIntegerValue.java b/src/proguard/evaluation/value/NegatedIntegerValue.java
new file mode 100644
index 0000000..a89a2d9
--- /dev/null
+++ b/src/proguard/evaluation/value/NegatedIntegerValue.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents a integer value that is negated.
+ *
+ * @author Eric Lafortune
+ */
+final class NegatedIntegerValue extends SpecificIntegerValue
+{
+    private final IntegerValue integerValue;
+
+
+    /**
+     * Creates a new negated integer value of the given integer value.
+     */
+    public NegatedIntegerValue(IntegerValue integerValue)
+    {
+        this.integerValue = integerValue;
+    }
+
+
+    // Implementations of unary methods of IntegerValue.
+
+    public IntegerValue negate()
+    {
+        return integerValue;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.integerValue.equals(((NegatedIntegerValue)object).integerValue);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               integerValue.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "-"+integerValue;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/NegatedLongValue.java b/src/proguard/evaluation/value/NegatedLongValue.java
new file mode 100644
index 0000000..c3b22bb
--- /dev/null
+++ b/src/proguard/evaluation/value/NegatedLongValue.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This LongValue represents a long value that is negated.
+ *
+ * @author Eric Lafortune
+ */
+final class NegatedLongValue extends SpecificLongValue
+{
+    private final LongValue longValue;
+
+
+    /**
+     * Creates a new negated long value of the given long value.
+     */
+    public NegatedLongValue(LongValue longValue)
+    {
+        this.longValue = longValue;
+    }
+
+
+    // Implementations of unary methods of LongValue.
+
+    public LongValue negate()
+    {
+        return longValue;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return this == object ||
+               super.equals(object) &&
+               this.longValue.equals(((NegatedLongValue)object).longValue);
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               longValue.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "-"+longValue;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ParticularDoubleValue.java b/src/proguard/evaluation/value/ParticularDoubleValue.java
new file mode 100644
index 0000000..05bc559
--- /dev/null
+++ b/src/proguard/evaluation/value/ParticularDoubleValue.java
@@ -0,0 +1,210 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This DoubleValue represents a particular double value.
+ *
+ * @author Eric Lafortune
+ */
+final class ParticularDoubleValue extends SpecificDoubleValue
+{
+    private final double value;
+
+
+    /**
+     * Creates a new particular double value.
+     */
+    public ParticularDoubleValue(double value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for DoubleValue.
+
+    public double value()
+    {
+        return value;
+    }
+
+
+    // Implementations of unary methods of DoubleValue.
+
+    public DoubleValue negate()
+    {
+        return new ParticularDoubleValue(-value);
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return new ParticularIntegerValue((int)value);
+    }
+
+    public LongValue convertToLong()
+    {
+        return new ParticularLongValue((long)value);
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return new ParticularFloatValue((float)value);
+    }
+
+
+    // Implementations of binary methods of DoubleValue.
+
+    public DoubleValue generalize(DoubleValue other)
+    {
+        return other.generalize(this);
+    }
+
+    public DoubleValue add(DoubleValue other)
+    {
+        return value == 0.0 ? other : other.add(this);
+    }
+
+    public DoubleValue subtract(DoubleValue other)
+    {
+        return value == 0.0 ? other.negate() : other.subtractFrom(this);
+    }
+
+    public DoubleValue subtractFrom(DoubleValue other)
+    {
+        return value == 0.0 ? other : other.subtract(this);
+    }
+
+    public DoubleValue multiply(DoubleValue other)
+    {
+        return other.multiply(this);
+    }
+
+    public DoubleValue divide(DoubleValue other)
+    {
+        return other.divideOf(this);
+    }
+
+    public DoubleValue divideOf(DoubleValue other)
+    {
+        return other.divide(this);
+    }
+
+    public DoubleValue remainder(DoubleValue other)
+    {
+        return other.remainderOf(this);
+    }
+
+    public DoubleValue remainderOf(DoubleValue other)
+    {
+        return other.remainder(this);
+    }
+
+    public IntegerValue compare(DoubleValue other)
+    {
+        return other.compareReverse(this);
+    }
+
+
+    // Implementations of binary DoubleValue methods with ParticularDoubleValue
+    // arguments.
+
+    public DoubleValue generalize(ParticularDoubleValue other)
+    {
+        return this.value == other.value ? this : ValueFactory.DOUBLE_VALUE;
+    }
+
+    public DoubleValue add(ParticularDoubleValue other)
+    {
+        return new ParticularDoubleValue(this.value + other.value);
+    }
+
+    public DoubleValue subtract(ParticularDoubleValue other)
+    {
+        return new ParticularDoubleValue(this.value - other.value);
+    }
+
+    public DoubleValue subtractFrom(ParticularDoubleValue other)
+    {
+        return new ParticularDoubleValue(other.value - this.value);
+    }
+
+    public DoubleValue multiply(ParticularDoubleValue other)
+    {
+        return new ParticularDoubleValue(this.value * other.value);
+    }
+
+    public DoubleValue divide(ParticularDoubleValue other)
+    {
+        return new ParticularDoubleValue(this.value / other.value);
+    }
+
+    public DoubleValue divideOf(ParticularDoubleValue other)
+    {
+        return new ParticularDoubleValue(other.value / this.value);
+    }
+
+    public DoubleValue remainder(ParticularDoubleValue other)
+    {
+        return new ParticularDoubleValue(this.value % other.value);
+    }
+
+    public DoubleValue remainderOf(ParticularDoubleValue other)
+    {
+        return new ParticularDoubleValue(other.value % this.value);
+    }
+
+    public IntegerValue compare(ParticularDoubleValue other)
+    {
+        return this.value <  other.value ? SpecificValueFactory.INTEGER_VALUE_M1 :
+               this.value == other.value ? SpecificValueFactory.INTEGER_VALUE_0  :
+                                           SpecificValueFactory.INTEGER_VALUE_1;
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isParticular()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return super.equals(object) &&
+               this.value == ((ParticularDoubleValue)object).value;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               (int)Double.doubleToLongBits(value);
+    }
+
+
+    public String toString()
+    {
+        return value+"d";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ParticularFloatValue.java b/src/proguard/evaluation/value/ParticularFloatValue.java
new file mode 100644
index 0000000..59e4357
--- /dev/null
+++ b/src/proguard/evaluation/value/ParticularFloatValue.java
@@ -0,0 +1,210 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This FloatValue represents a particular float value.
+ *
+ * @author Eric Lafortune
+ */
+final class ParticularFloatValue extends SpecificFloatValue
+{
+    private final float value;
+
+
+    /**
+     * Creates a new particular float value.
+     */
+    public ParticularFloatValue(float value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for FloatValue.
+
+    public float value()
+    {
+        return value;
+    }
+
+
+    // Implementations of unary methods of FloatValue.
+
+    public FloatValue negate()
+    {
+        return new ParticularFloatValue(-value);
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return new ParticularIntegerValue((int)value);
+    }
+
+    public LongValue convertToLong()
+    {
+        return new ParticularLongValue((long)value);
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return new ParticularDoubleValue((float)value);
+    }
+
+
+    // Implementations of binary methods of FloatValue.
+
+    public FloatValue generalize(FloatValue other)
+    {
+        return other.generalize(this);
+    }
+
+    public FloatValue add(FloatValue other)
+    {
+        return value == 0.0 ? other : other.add(this);
+    }
+
+    public FloatValue subtract(FloatValue other)
+    {
+        return value == 0.0 ? other.negate() : other.subtractFrom(this);
+    }
+
+    public FloatValue subtractFrom(FloatValue other)
+    {
+        return value == 0.0 ? other : other.subtract(this);
+    }
+
+    public FloatValue multiply(FloatValue other)
+    {
+        return other.multiply(this);
+    }
+
+    public FloatValue divide(FloatValue other)
+    {
+        return other.divideOf(this);
+    }
+
+    public FloatValue divideOf(FloatValue other)
+    {
+        return other.divide(this);
+    }
+
+    public FloatValue remainder(FloatValue other)
+    {
+        return other.remainderOf(this);
+    }
+
+    public FloatValue remainderOf(FloatValue other)
+    {
+        return other.remainder(this);
+    }
+
+    public IntegerValue compare(FloatValue other)
+    {
+        return other.compareReverse(this);
+    }
+
+
+    // Implementations of binary FloatValue methods with ParticularFloatValue
+    // arguments.
+
+    public FloatValue generalize(ParticularFloatValue other)
+    {
+        return this.value == other.value ? this : ValueFactory.FLOAT_VALUE;
+    }
+
+    public FloatValue add(ParticularFloatValue other)
+    {
+        return new ParticularFloatValue(this.value + other.value);
+    }
+
+    public FloatValue subtract(ParticularFloatValue other)
+    {
+        return new ParticularFloatValue(this.value - other.value);
+    }
+
+    public FloatValue subtractFrom(ParticularFloatValue other)
+    {
+        return new ParticularFloatValue(other.value - this.value);
+    }
+
+    public FloatValue multiply(ParticularFloatValue other)
+    {
+        return new ParticularFloatValue(this.value * other.value);
+    }
+
+    public FloatValue divide(ParticularFloatValue other)
+    {
+        return new ParticularFloatValue(this.value / other.value);
+    }
+
+    public FloatValue divideOf(ParticularFloatValue other)
+    {
+        return new ParticularFloatValue(other.value / this.value);
+    }
+
+    public FloatValue remainder(ParticularFloatValue other)
+    {
+        return new ParticularFloatValue(this.value % other.value);
+    }
+
+    public FloatValue remainderOf(ParticularFloatValue other)
+    {
+        return new ParticularFloatValue(other.value % this.value);
+    }
+
+    public IntegerValue compare(ParticularFloatValue other)
+    {
+        return this.value <  other.value ? SpecificValueFactory.INTEGER_VALUE_M1 :
+               this.value == other.value ? SpecificValueFactory.INTEGER_VALUE_0  :
+                                           SpecificValueFactory.INTEGER_VALUE_1;
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isParticular()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return super.equals(object) &&
+               this.value == ((ParticularFloatValue)object).value;
+    }
+
+
+    public int hashCode()
+    {
+        return super.hashCode() ^
+               Float.floatToIntBits(value);
+    }
+
+
+    public String toString()
+    {
+        return value+"f";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ParticularIntegerValue.java b/src/proguard/evaluation/value/ParticularIntegerValue.java
new file mode 100644
index 0000000..8a4ac1d
--- /dev/null
+++ b/src/proguard/evaluation/value/ParticularIntegerValue.java
@@ -0,0 +1,383 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents a particular integer value.
+ *
+ * @author Eric Lafortune
+ */
+final class ParticularIntegerValue extends SpecificIntegerValue
+{
+    private final int value;
+
+
+    /**
+     * Creates a new particular integer value.
+     */
+    public ParticularIntegerValue(int value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for IntegerValue.
+
+    public int value()
+    {
+        return value;
+    }
+
+
+    // Implementations of unary methods of IntegerValue.
+
+    public IntegerValue negate()
+    {
+        return new ParticularIntegerValue(-value);
+    }
+
+    public IntegerValue convertToByte()
+    {
+        int byteValue = (byte)value;
+
+        return byteValue == value ?
+            this :
+            new ParticularIntegerValue(byteValue);
+    }
+
+    public IntegerValue convertToCharacter()
+    {
+        int charValue = (char)value;
+
+        return charValue == value ?
+            this :
+            new ParticularIntegerValue(charValue);
+    }
+
+    public IntegerValue convertToShort()
+    {
+        int shortValue = (short)value;
+
+        return shortValue == value ?
+            this :
+            new ParticularIntegerValue(shortValue);
+    }
+
+    public LongValue convertToLong()
+    {
+        return new ParticularLongValue((long)value);
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return new ParticularFloatValue((float)value);
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return new ParticularDoubleValue((double)value);
+    }
+
+
+    // Implementations of binary methods of IntegerValue.
+
+    public IntegerValue generalize(IntegerValue other)
+    {
+        return other.generalize(this);
+    }
+
+    public IntegerValue add(IntegerValue other)
+    {
+        return other.add(this);
+    }
+
+    public IntegerValue subtract(IntegerValue other)
+    {
+        return other.subtractFrom(this);
+    }
+
+    public IntegerValue subtractFrom(IntegerValue other)
+    {
+        return other.subtract(this);
+    }
+
+    public IntegerValue multiply(IntegerValue other)
+    {
+        return other.multiply(this);
+    }
+
+    public IntegerValue divide(IntegerValue other)
+    throws ArithmeticException
+    {
+        return other.divideOf(this);
+    }
+
+    public IntegerValue divideOf(IntegerValue other)
+    throws ArithmeticException
+    {
+        return other.divide(this);
+    }
+
+    public IntegerValue remainder(IntegerValue other)
+    throws ArithmeticException
+    {
+        return other.remainderOf(this);
+    }
+
+    public IntegerValue remainderOf(IntegerValue other)
+    throws ArithmeticException
+    {
+        return other.remainder(this);
+    }
+
+    public IntegerValue shiftLeft(IntegerValue other)
+    {
+        return other.shiftLeftOf(this);
+    }
+
+    public IntegerValue shiftLeftOf(IntegerValue other)
+    {
+        return other.shiftLeft(this);
+    }
+
+    public IntegerValue shiftRight(IntegerValue other)
+    {
+        return other.shiftRightOf(this);
+    }
+
+    public IntegerValue shiftRightOf(IntegerValue other)
+    {
+        return other.shiftRight(this);
+    }
+
+    public IntegerValue unsignedShiftRight(IntegerValue other)
+    {
+        return other.unsignedShiftRightOf(this);
+    }
+
+    public IntegerValue unsignedShiftRightOf(IntegerValue other)
+    {
+        return other.unsignedShiftRight(this);
+    }
+
+    public LongValue shiftLeftOf(LongValue other)
+    {
+        return other.shiftLeft(this);
+    }
+
+    public LongValue shiftRightOf(LongValue other)
+    {
+        return other.shiftRight(this);
+    }
+
+    public LongValue unsignedShiftRightOf(LongValue other)
+    {
+        return other.unsignedShiftRight(this);
+    }
+
+    public IntegerValue and(IntegerValue other)
+    {
+        return other.and(this);
+    }
+
+    public IntegerValue or(IntegerValue other)
+    {
+        return other.or(this);
+    }
+
+    public IntegerValue xor(IntegerValue other)
+    {
+        return other.xor(this);
+    }
+
+    public int equal(IntegerValue other)
+    {
+        return other.equal(this);
+    }
+
+    public int lessThan(IntegerValue other)
+    {
+        return other.greaterThan(this);
+    }
+
+    public int lessThanOrEqual(IntegerValue other)
+    {
+        return other.greaterThanOrEqual(this);
+    }
+
+
+    // Implementations of binary IntegerValue methods with ParticularIntegerValue
+    // arguments.
+
+    public IntegerValue generalize(ParticularIntegerValue other)
+    {
+        return generalize((SpecificIntegerValue)other);
+    }
+
+    public IntegerValue add(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value + other.value);
+    }
+
+    public IntegerValue subtract(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value - other.value);
+    }
+
+    public IntegerValue subtractFrom(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(other.value - this.value);
+    }
+
+    public IntegerValue multiply(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value * other.value);
+    }
+
+    public IntegerValue divide(ParticularIntegerValue other)
+    throws ArithmeticException
+    {
+        return new ParticularIntegerValue(this.value / other.value);
+    }
+
+    public IntegerValue divideOf(ParticularIntegerValue other)
+    throws ArithmeticException
+    {
+        return new ParticularIntegerValue(other.value / this.value);
+    }
+
+    public IntegerValue remainder(ParticularIntegerValue other)
+    throws ArithmeticException
+    {
+        return new ParticularIntegerValue(this.value % other.value);
+    }
+
+    public IntegerValue remainderOf(ParticularIntegerValue other)
+    throws ArithmeticException
+    {
+        return new ParticularIntegerValue(other.value % this.value);
+    }
+
+    public IntegerValue shiftLeft(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value << other.value);
+    }
+
+    public IntegerValue shiftRight(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value >> other.value);
+    }
+
+    public IntegerValue unsignedShiftRight(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value >>> other.value);
+    }
+
+    public IntegerValue shiftLeftOf(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(other.value << this.value);
+    }
+
+    public IntegerValue shiftRightOf(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(other.value >> this.value);
+    }
+
+    public IntegerValue unsignedShiftRightOf(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(other.value >>> this.value);
+    }
+
+    public LongValue shiftLeftOf(ParticularLongValue other)
+    {
+        return new ParticularLongValue(other.value() << this.value);
+    }
+
+    public LongValue shiftRightOf(ParticularLongValue other)
+    {
+        return new ParticularLongValue(other.value() >> this.value);
+    }
+
+    public LongValue unsignedShiftRightOf(ParticularLongValue other)
+    {
+        return new ParticularLongValue(other.value() >>> this.value);
+    }
+
+    public IntegerValue and(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value & other.value);
+    }
+
+    public IntegerValue or(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value | other.value);
+    }
+
+    public IntegerValue xor(ParticularIntegerValue other)
+    {
+        return new ParticularIntegerValue(this.value ^ other.value);
+    }
+
+    public int equal(ParticularIntegerValue other)
+    {
+        return this.value == other.value ? ALWAYS : NEVER;
+    }
+
+    public int lessThan(ParticularIntegerValue other)
+    {
+        return this.value <  other.value ? ALWAYS : NEVER;
+    }
+
+    public int lessThanOrEqual(ParticularIntegerValue other)
+    {
+        return this.value <= other.value ? ALWAYS : NEVER;
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isParticular()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return super.equals(object) &&
+               this.value == ((ParticularIntegerValue)object).value;
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode() ^
+               value;
+    }
+
+
+    public String toString()
+    {
+        return Integer.toString(value);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ParticularLongValue.java b/src/proguard/evaluation/value/ParticularLongValue.java
new file mode 100644
index 0000000..61d2e04
--- /dev/null
+++ b/src/proguard/evaluation/value/ParticularLongValue.java
@@ -0,0 +1,271 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This LongValue represents a particular long value.
+ *
+ * @author Eric Lafortune
+ */
+final class ParticularLongValue extends SpecificLongValue
+{
+    private final long value;
+
+
+    /**
+     * Creates a new particular long value.
+     */
+    public ParticularLongValue(long value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementations for LongValue.
+
+    public long value()
+    {
+        return value;
+    }
+
+
+    // Implementations of unary methods of LongValue.
+
+    public LongValue negate()
+    {
+        return new ParticularLongValue(-value);
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return new ParticularIntegerValue((int)value);
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return new ParticularFloatValue((float)value);
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return new ParticularDoubleValue((double)value);
+    }
+
+
+    // Implementations of binary methods of LongValue.
+
+    public LongValue generalize(LongValue other)
+    {
+        return other.generalize(this);
+    }
+
+    public LongValue add(LongValue other)
+    {
+        return other.add(this);
+    }
+
+    public LongValue subtract(LongValue other)
+    {
+        return other.subtractFrom(this);
+    }
+
+    public LongValue subtractFrom(LongValue other)
+    {
+        return other.subtract(this);
+    }
+
+    public LongValue multiply(LongValue other)
+    {
+        return other.multiply(this);
+    }
+
+    public LongValue divide(LongValue other)
+    throws ArithmeticException
+    {
+        return other.divideOf(this);
+    }
+
+    public LongValue divideOf(LongValue other)
+    throws ArithmeticException
+    {
+        return other.divide(this);
+    }
+
+    public LongValue remainder(LongValue other)
+    throws ArithmeticException
+    {
+        return other.remainderOf(this);
+    }
+
+    public LongValue remainderOf(LongValue other)
+    throws ArithmeticException
+    {
+        return other.remainder(this);
+    }
+
+    public LongValue shiftLeft(IntegerValue other)
+    {
+        return other.shiftLeftOf(this);
+    }
+
+    public LongValue shiftRight(IntegerValue other)
+    {
+        return other.shiftRightOf(this);
+    }
+
+    public LongValue unsignedShiftRight(IntegerValue other)
+    {
+        return other.unsignedShiftRightOf(this);
+    }
+
+    public LongValue and(LongValue other)
+    {
+        return other.and(this);
+    }
+
+    public LongValue or(LongValue other)
+    {
+        return other.or(this);
+    }
+
+    public LongValue xor(LongValue other)
+    {
+        return other.xor(this);
+    }
+
+    public IntegerValue compare(LongValue other)
+    {
+        return other.compareReverse(this);
+    }
+
+
+    // Implementations of binary LongValue methods with ParticularLongValue
+    // arguments.
+
+    public LongValue generalize(ParticularLongValue other)
+    {
+        return generalize((SpecificLongValue)other);
+    }
+
+    public LongValue add(ParticularLongValue other)
+    {
+        return new ParticularLongValue(this.value + other.value);
+    }
+
+    public LongValue subtract(ParticularLongValue other)
+    {
+        return new ParticularLongValue(this.value - other.value);
+    }
+
+    public LongValue subtractFrom(ParticularLongValue other)
+    {
+        return new ParticularLongValue(other.value - this.value);
+    }
+
+    public LongValue multiply(ParticularLongValue other)
+    {
+        return new ParticularLongValue(this.value * other.value);
+    }
+
+    public LongValue divide(ParticularLongValue other)
+    throws ArithmeticException
+    {
+        return new ParticularLongValue(this.value / other.value);
+    }
+
+    public LongValue divideOf(ParticularLongValue other)
+    throws ArithmeticException
+    {
+        return new ParticularLongValue(other.value / this.value);
+    }
+
+    public LongValue remainder(ParticularLongValue other)
+    throws ArithmeticException
+    {
+        return new ParticularLongValue(this.value % other.value);
+    }
+
+    public LongValue remainderOf(ParticularLongValue other)
+    throws ArithmeticException
+    {
+        return new ParticularLongValue(other.value % this.value);
+    }
+
+    public LongValue shiftLeft(ParticularIntegerValue other)
+    {
+        return new ParticularLongValue(this.value << other.value());
+    }
+
+    public LongValue shiftRight(ParticularIntegerValue other)
+    {
+        return new ParticularLongValue(this.value >> other.value());
+    }
+
+    public LongValue unsignedShiftRight(ParticularIntegerValue other)
+    {
+        return new ParticularLongValue(this.value >>> other.value());
+    }
+
+    public LongValue and(ParticularLongValue other)
+    {
+        return new ParticularLongValue(this.value & other.value);
+    }
+
+    public LongValue or(ParticularLongValue other)
+    {
+        return new ParticularLongValue(this.value | other.value);
+    }
+
+    public LongValue xor(ParticularLongValue other)
+    {
+        return new ParticularLongValue(this.value ^ other.value);
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isParticular()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return super.equals(object) &&
+               this.value == ((ParticularLongValue)object).value;
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode() ^
+               (int)value;
+    }
+
+
+    public String toString()
+    {
+        return value+"L";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/ReferenceValue.java b/src/proguard/evaluation/value/ReferenceValue.java
new file mode 100644
index 0000000..418c6f8
--- /dev/null
+++ b/src/proguard/evaluation/value/ReferenceValue.java
@@ -0,0 +1,526 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.*;
+import proguard.classfile.util.ClassUtil;
+import proguard.classfile.visitor.ClassCollector;
+
+import java.util.*;
+
+/**
+ * This class represents a partially evaluated reference value. It has a type
+ * and a flag that indicates whether the value could be <code>null</code>. If
+ * the type is <code>null</code>, the value is <code>null</code>.
+ *
+ * @author Eric Lafortune
+ */
+public class ReferenceValue extends Category1Value
+{
+    private static final boolean DEBUG = false;
+
+
+    protected final String  type;
+    protected final Clazz   referencedClass;
+    protected final boolean mayBeNull;
+
+
+    /**
+     * Creates a new ReferenceValue.
+     */
+    public ReferenceValue(String  type,
+                          Clazz   referencedClass,
+                          boolean mayBeNull)
+    {
+        this.type            = type;
+        this.referencedClass = referencedClass;
+        this.mayBeNull       = mayBeNull;
+    }
+
+
+    /**
+     * Returns the type.
+     */
+    public String getType()
+    {
+        return type;
+    }
+
+
+    /**
+     * Returns the class that is referenced by the type.
+     */
+    public Clazz getReferencedClass()
+    {
+        return referencedClass;
+    }
+
+
+    // Basic unary methods.
+
+    /**
+     * Returns whether the type is <code>null</code>.
+     */
+    public int isNull()
+    {
+        return type == null ? ALWAYS :
+               mayBeNull    ? MAYBE  :
+                              NEVER;
+    }
+
+
+    /**
+     * Returns whether the type is an instance of the given type.
+     */
+    public int instanceOf(String otherType, Clazz otherReferencedClass)
+    {
+        String thisType = this.type;
+
+        // If this type is null, it is never an instance of any class.
+        if (thisType == null)
+        {
+            return NEVER;
+        }
+
+        // Start taking into account the type dimensions.
+        int thisDimensionCount   = ClassUtil.internalArrayTypeDimensionCount(thisType);
+        int otherDimensionCount  = ClassUtil.internalArrayTypeDimensionCount(otherType);
+        int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);
+
+        // Strip any common array prefixes.
+        thisType  = thisType.substring(commonDimensionCount);
+        otherType = otherType.substring(commonDimensionCount);
+
+        // If either stripped type is a primitive type, we can tell right away.
+        if (commonDimensionCount > 0 &&
+            (ClassUtil.isInternalPrimitiveType(thisType.charAt(0)) ||
+             ClassUtil.isInternalPrimitiveType(otherType.charAt(0))))
+        {
+            return !thisType.equals(otherType) ? NEVER :
+                   mayBeNull                   ? MAYBE :
+                                                 ALWAYS;
+        }
+
+        // Strip the class type prefix and suffix of this type, if any.
+        if (thisDimensionCount == commonDimensionCount)
+        {
+            thisType = ClassUtil.internalClassNameFromClassType(thisType);
+        }
+
+        // Strip the class type prefix and suffix of the other type, if any.
+        if (otherDimensionCount == commonDimensionCount)
+        {
+            otherType = ClassUtil.internalClassNameFromClassType(otherType);
+        }
+
+        // If this type is an array type, and the other type is not
+        // java.lang.Object, java.lang.Cloneable, or java.io.Serializable,
+        // this type can never be an instance.
+        if (thisDimensionCount > otherDimensionCount &&
+            !ClassUtil.isInternalArrayInterfaceName(otherType))
+        {
+            return NEVER;
+        }
+
+        // If the other type is an array type, and this type is not
+        // java.lang.Object, java.lang.Cloneable, or java.io.Serializable,
+        // this type can never be an instance.
+        if (thisDimensionCount < otherDimensionCount &&
+            !ClassUtil.isInternalArrayInterfaceName(thisType))
+        {
+            return NEVER;
+        }
+
+        // If this type may be null, it might not be an instance of any class.
+        if (mayBeNull)
+        {
+            return MAYBE;
+        }
+
+        // If this type is equal to the other type, or if the other type is
+        // java.lang.Object, this type is always an instance.
+        if (thisType.equals(otherType) ||
+            ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT.equals(otherType))
+        {
+            return ALWAYS;
+        }
+
+        // If this type is an array type, it's ok.
+        if (thisDimensionCount > otherDimensionCount)
+        {
+            return ALWAYS;
+        }
+
+        // If the other type is an array type, it might be ok.
+        if (thisDimensionCount < otherDimensionCount)
+        {
+            return MAYBE;
+        }
+
+        // If the value extends the type, we're sure.
+        return referencedClass      != null &&
+               otherReferencedClass != null &&
+               referencedClass.extendsOrImplements(otherReferencedClass) ?
+                   ALWAYS :
+                   MAYBE;
+    }
+
+
+    /**
+     * Returns the length of the array, assuming this type is an array.
+     */
+    public IntegerValue arrayLength(ValueFactory valueFactory)
+    {
+        return valueFactory.createIntegerValue();
+    }
+
+
+    /**
+     * Returns the value of the array at the given index, assuming this type
+     * is an array.
+     */
+    public Value arrayLoad(IntegerValue integerValue, ValueFactory valueFactory)
+    {
+        return
+            type == null                         ? ValueFactory.REFERENCE_VALUE_NULL                        :
+            !ClassUtil.isInternalArrayType(type) ? ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL :
+                                                   valueFactory.createValue(type.substring(1),
+                                                                            referencedClass,
+                                                                            true);
+    }
+
+
+    // Basic binary methods.
+
+    /**
+     * Returns the generalization of this ReferenceValue and the given other
+     * ReferenceValue.
+     */
+    public ReferenceValue generalize(ReferenceValue other)
+    {
+        // If both types are identical, the generalization is the same too.
+        if (this.equals(other))
+        {
+            return this;
+        }
+
+        String thisType  = this.type;
+        String otherType = other.type;
+
+        // If both types are nul, the generalization is null too.
+        if (thisType == null && otherType == null)
+        {
+            return ValueFactory.REFERENCE_VALUE_NULL;
+        }
+
+        // If this type is null, the generalization is the other type, maybe null.
+        if (thisType == null)
+        {
+            return other.generalizeMayBeNull(true);
+        }
+
+        // If the other type is null, the generalization is this type, maybe null.
+        if (otherType == null)
+        {
+            return this.generalizeMayBeNull(true);
+        }
+
+        boolean mayBeNull = this.mayBeNull || other.mayBeNull;
+
+        // If the two types are equal, the generalization remains the same, maybe null.
+        if (thisType.equals(otherType))
+        {
+            return this.generalizeMayBeNull(mayBeNull);
+        }
+
+        // Start taking into account the type dimensions.
+        int thisDimensionCount   = ClassUtil.internalArrayTypeDimensionCount(thisType);
+        int otherDimensionCount  = ClassUtil.internalArrayTypeDimensionCount(otherType);
+        int commonDimensionCount = Math.min(thisDimensionCount, otherDimensionCount);
+
+        if (thisDimensionCount == otherDimensionCount)
+        {
+            // See if we can take into account the referenced classes.
+            Clazz thisReferencedClass  = this.referencedClass;
+            Clazz otherReferencedClass = other.referencedClass;
+
+            if (thisReferencedClass  != null &&
+                otherReferencedClass != null)
+            {
+                if (thisReferencedClass.extendsOrImplements(otherReferencedClass))
+                {
+                    return other.generalizeMayBeNull(mayBeNull);
+                }
+
+                if (otherReferencedClass.extendsOrImplements(thisReferencedClass))
+                {
+                    return this.generalizeMayBeNull(mayBeNull);
+                }
+
+                // Collect the superclasses and interfaces of this class.
+                Set thisSuperClasses = new HashSet();
+                thisReferencedClass.hierarchyAccept(false, true, true, false,
+                                                    new ClassCollector(thisSuperClasses));
+
+                // Collect the superclasses and interfaces of the other class.
+                Set otherSuperClasses = new HashSet();
+                otherReferencedClass.hierarchyAccept(false, true, true, false,
+                                                     new ClassCollector(otherSuperClasses));
+
+                if (DEBUG)
+                {
+                    System.out.println("ReferenceValue.generalize this ["+thisReferencedClass.getName()+"] with other ["+otherReferencedClass.getName()+"]");
+                    System.out.println("  This super classes:  "+thisSuperClasses);
+                    System.out.println("  Other super classes: "+otherSuperClasses);
+                }
+
+                // Find the common superclasses.
+                thisSuperClasses.retainAll(otherSuperClasses);
+
+                if (DEBUG)
+                {
+                    System.out.println("  Common super classes: "+thisSuperClasses);
+                }
+
+                // Find a class that is a subclass of all common superclasses,
+                // or that at least has the maximum number of common superclasses.
+                Clazz commonClazz = null;
+
+                int maximumSuperClassCount = -1;
+
+                // Go over all common superclasses to find it. In case of
+                // multiple subclasses, keep the lowest one alphabetically,
+                // in order to ensure that the choice is deterministic.
+                Iterator commonSuperClasses = thisSuperClasses.iterator();
+                while (commonSuperClasses.hasNext())
+                {
+                    Clazz commonSuperClass = (Clazz)commonSuperClasses.next();
+
+                    int superClassCount = superClassCount(commonSuperClass, thisSuperClasses);
+                    if (maximumSuperClassCount < superClassCount ||
+                        (maximumSuperClassCount == superClassCount &&
+                         commonClazz != null                       &&
+                         commonClazz.getName().compareTo(commonSuperClass.getName()) > 0))
+                    {
+                        commonClazz            = commonSuperClass;
+                        maximumSuperClassCount = superClassCount;
+                    }
+                }
+
+                if (commonClazz == null)
+                {
+                    throw new IllegalArgumentException("Can't find common super class of ["+thisType+"] and ["+otherType+"]");
+                }
+
+                if (DEBUG)
+                {
+                    System.out.println("  Best common class: ["+commonClazz.getName()+"]");
+                }
+
+                // TODO: Handle more difficult cases, with multiple global subclasses.
+
+                return new ReferenceValue(commonDimensionCount == 0 ?
+                                              commonClazz.getName() :
+                                              ClassUtil.internalArrayTypeFromClassName(commonClazz.getName(),
+                                                                                       commonDimensionCount),
+                                          commonClazz,
+                                          mayBeNull);
+            }
+        }
+        else if (thisDimensionCount > otherDimensionCount)
+        {
+            // See if the other type is an interface type of arrays.
+            if (ClassUtil.isInternalArrayInterfaceName(ClassUtil.internalClassNameFromClassType(otherType)))
+            {
+                return other.generalizeMayBeNull(mayBeNull);
+            }
+        }
+        else if (thisDimensionCount < otherDimensionCount)
+        {
+            // See if this type is an interface type of arrays.
+            if (ClassUtil.isInternalArrayInterfaceName(ClassUtil.internalClassNameFromClassType(thisType)))
+            {
+                return this.generalizeMayBeNull(mayBeNull);
+            }
+        }
+
+        // Reduce the common dimension count if either type is an array of
+        // primitives type of this dimension.
+        if (commonDimensionCount > 0 &&
+            (ClassUtil.isInternalPrimitiveType(otherType.charAt(commonDimensionCount))) ||
+             ClassUtil.isInternalPrimitiveType(thisType.charAt(commonDimensionCount)))
+        {
+            commonDimensionCount--;
+        }
+
+        // Fall back on a basic Object or array of Objects type.
+        return commonDimensionCount == 0 ?
+            mayBeNull ?
+                ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL :
+                ValueFactory.REFERENCE_VALUE_JAVA_LANG_OBJECT_NOT_NULL   :
+            new ReferenceValue(ClassUtil.internalArrayTypeFromClassName(ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT,
+                                                                        commonDimensionCount),
+                               null,
+                               mayBeNull);
+    }
+
+
+    /**
+     * Returns if the number of superclasses of the given class in the given
+     * set of classes.
+     */
+    private int superClassCount(Clazz subClass, Set classes)
+    {
+        int count = 0;
+
+        Iterator iterator = classes.iterator();
+
+        while (iterator.hasNext())
+        {
+            Clazz clazz = (Clazz)iterator.next();
+            if (subClass.extendsOrImplements(clazz))
+            {
+                count++;
+            }
+        }
+
+        //System.out.println("ReferenceValue.superClassCount: ["+subClass.getName()+"]: "+count);
+
+        return count;
+    }
+
+
+    /**
+     * Returns whether this ReferenceValue is equal to the given other
+     * ReferenceValue.
+     * @return <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public int equal(ReferenceValue other)
+    {
+        return this.type  == null && other.type == null ? ALWAYS : MAYBE;
+    }
+
+
+    // Derived unary methods.
+
+    /**
+     * Returns whether this ReferenceValue is not <code>null</code>.
+     * @return <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public final int isNotNull()
+    {
+        return -isNull();
+    }
+
+
+    /**
+     * Returns the generalization of this ReferenceValue and the given other
+     * ReferenceValue.
+     */
+    private ReferenceValue generalizeMayBeNull(boolean mayBeNull)
+    {
+        return this.mayBeNull || !mayBeNull ?
+            this :
+            new ReferenceValue(this.type, this.referencedClass, true);
+    }
+
+
+    // Derived binary methods.
+
+    /**
+     * Returns whether this ReferenceValue and the given ReferenceValue are different.
+     * @return <code>NEVER</code>, <code>MAYBE</code>, or <code>ALWAYS</code>.
+     */
+    public final int notEqual(ReferenceValue other)
+    {
+        return -equal(other);
+    }
+
+
+    // Implementations for Value.
+
+    public final ReferenceValue referenceValue()
+    {
+        return this;
+    }
+
+    public final Value generalize(Value other)
+    {
+        return this.generalize(other.referenceValue());
+    }
+
+    public boolean isParticular()
+    {
+        return type == null;
+    }
+
+    public final int computationalType()
+    {
+        return TYPE_REFERENCE;
+    }
+
+    public final String internalType()
+    {
+        return
+            type == null                        ? ClassConstants.INTERNAL_TYPE_JAVA_LANG_OBJECT :
+            ClassUtil.isInternalArrayType(type) ? type                                          :
+                                                  ClassConstants.INTERNAL_TYPE_CLASS_START +
+                                                  type +
+                                                  ClassConstants.INTERNAL_TYPE_CLASS_END;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        if (this == object)
+        {
+            return true;
+        }
+
+        if (object == null ||
+            this.getClass() != object.getClass())
+        {
+            return false;
+        }
+
+        ReferenceValue other = (ReferenceValue)object;
+        return this.type == null ? other.type == null :
+                                   (this.mayBeNull == other.mayBeNull &&
+                                    this.type.equals(other.type));
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode() ^
+               (type == null ? 0 : type.hashCode() ^ (mayBeNull ? 0 : 1));
+    }
+
+
+    public String toString()
+    {
+        return type == null ?
+            "null" :
+            type + (referencedClass == null ? "?" : "") + (mayBeNull ? "" : "!");
+    }
+}
diff --git a/src/proguard/evaluation/value/SpecificDoubleValue.java b/src/proguard/evaluation/value/SpecificDoubleValue.java
new file mode 100644
index 0000000..572d891
--- /dev/null
+++ b/src/proguard/evaluation/value/SpecificDoubleValue.java
@@ -0,0 +1,183 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This DoubleValue represents a specific double value.
+ *
+ * @author Eric Lafortune
+ */
+abstract class SpecificDoubleValue extends DoubleValue
+{
+    // Implementations of unary methods of DoubleValue.
+
+    public DoubleValue negate()
+    {
+        return new NegatedDoubleValue(this);
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return new ConvertedIntegerValue(this);
+    }
+
+    public LongValue convertToLong()
+    {
+        return new ConvertedLongValue(this);
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return new ConvertedFloatValue(this);
+    }
+
+
+    // Implementations of binary methods of DoubleValue.
+
+    public DoubleValue generalize(DoubleValue other)
+    {
+        return other.generalize(this);
+    }
+
+    public DoubleValue add(DoubleValue other)
+    {
+        return other.add(this);
+    }
+
+    public DoubleValue subtract(DoubleValue other)
+    {
+        return other.subtractFrom(this);
+    }
+
+    public DoubleValue subtractFrom(DoubleValue other)
+    {
+        return other.subtract(this);
+    }
+
+    public DoubleValue multiply(DoubleValue other)
+    {
+        return other.multiply(this);
+    }
+
+    public DoubleValue divide(DoubleValue other)
+    {
+        return other.divideOf(this);
+    }
+
+    public DoubleValue divideOf(DoubleValue other)
+    {
+        return other.divide(this);
+    }
+
+    public DoubleValue remainder(DoubleValue other)
+    {
+        return other.remainderOf(this);
+    }
+
+    public DoubleValue remainderOf(DoubleValue other)
+    {
+        return other.remainder(this);
+    }
+
+    public IntegerValue compare(DoubleValue other)
+    {
+        return other.compareReverse(this);
+    }
+
+
+    // Implementations of binary DoubleValue methods with SpecificDoubleValue
+    // arguments.
+
+    public DoubleValue generalize(SpecificDoubleValue other)
+    {
+        return this.equals(other) ? this : ValueFactory.DOUBLE_VALUE;
+    }
+
+    public DoubleValue add(SpecificDoubleValue other)
+    {
+        return new CompositeDoubleValue(this, CompositeDoubleValue.ADD, other);
+    }
+
+    public DoubleValue subtract(SpecificDoubleValue other)
+    {
+        return new CompositeDoubleValue(this, CompositeDoubleValue.SUBTRACT, other);
+    }
+
+    public DoubleValue subtractFrom(SpecificDoubleValue other)
+    {
+        return new CompositeDoubleValue(other, CompositeDoubleValue.SUBTRACT, this);
+    }
+
+    public DoubleValue multiply(SpecificDoubleValue other)
+    {
+        return new CompositeDoubleValue(this, CompositeDoubleValue.MULTIPLY, other);
+    }
+
+    public DoubleValue divide(SpecificDoubleValue other)
+    {
+        return new CompositeDoubleValue(this, CompositeDoubleValue.DIVIDE, other);
+    }
+
+    public DoubleValue divideOf(SpecificDoubleValue other)
+    {
+        return new CompositeDoubleValue(other, CompositeDoubleValue.DIVIDE, this);
+    }
+
+    public DoubleValue remainder(SpecificDoubleValue other)
+    {
+        return new CompositeDoubleValue(this, CompositeDoubleValue.REMAINDER, other);
+    }
+
+    public DoubleValue remainderOf(SpecificDoubleValue other)
+    {
+        return new CompositeDoubleValue(other, CompositeDoubleValue.REMAINDER, this);
+    }
+
+    public IntegerValue compare(SpecificDoubleValue other)
+    {
+        return this.equals(other) ?
+            SpecificValueFactory.INTEGER_VALUE_0 :
+            new ComparisonValue(this, other);
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isSpecific()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+}
diff --git a/src/proguard/evaluation/value/SpecificFloatValue.java b/src/proguard/evaluation/value/SpecificFloatValue.java
new file mode 100644
index 0000000..3bc3679
--- /dev/null
+++ b/src/proguard/evaluation/value/SpecificFloatValue.java
@@ -0,0 +1,183 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This FloatValue represents a specific float value.
+ *
+ * @author Eric Lafortune
+ */
+abstract class SpecificFloatValue extends FloatValue
+{
+    // Implementations of unary methods of FloatValue.
+
+    public FloatValue negate()
+    {
+        return new NegatedFloatValue(this);
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return new ConvertedIntegerValue(this);
+    }
+
+    public LongValue convertToLong()
+    {
+        return new ConvertedLongValue(this);
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return new ConvertedDoubleValue(this);
+    }
+
+
+    // Implementations of binary methods of FloatValue.
+
+    public FloatValue generalize(FloatValue other)
+    {
+        return other.generalize(this);
+    }
+
+    public FloatValue add(FloatValue other)
+    {
+        return other.add(this);
+    }
+
+    public FloatValue subtract(FloatValue other)
+    {
+        return other.subtractFrom(this);
+    }
+
+    public FloatValue subtractFrom(FloatValue other)
+    {
+        return other.subtract(this);
+    }
+
+    public FloatValue multiply(FloatValue other)
+    {
+        return other.multiply(this);
+    }
+
+    public FloatValue divide(FloatValue other)
+    {
+        return other.divideOf(this);
+    }
+
+    public FloatValue divideOf(FloatValue other)
+    {
+        return other.divide(this);
+    }
+
+    public FloatValue remainder(FloatValue other)
+    {
+        return other.remainderOf(this);
+    }
+
+    public FloatValue remainderOf(FloatValue other)
+    {
+        return other.remainder(this);
+    }
+
+    public IntegerValue compare(FloatValue other)
+    {
+        return other.compareReverse(this);
+    }
+
+
+    // Implementations of binary FloatValue methods with SpecificFloatValue
+    // arguments.
+
+    public FloatValue generalize(SpecificFloatValue other)
+    {
+        return this.equals(other) ? this : ValueFactory.FLOAT_VALUE;
+    }
+
+    public FloatValue add(SpecificFloatValue other)
+    {
+        return new CompositeFloatValue(this, CompositeFloatValue.ADD, other);
+    }
+
+    public FloatValue subtract(SpecificFloatValue other)
+    {
+        return new CompositeFloatValue(this, CompositeFloatValue.SUBTRACT, other);
+    }
+
+    public FloatValue subtractFrom(SpecificFloatValue other)
+    {
+        return new CompositeFloatValue(other, CompositeFloatValue.SUBTRACT, this);
+    }
+
+    public FloatValue multiply(SpecificFloatValue other)
+    {
+        return new CompositeFloatValue(this, CompositeFloatValue.MULTIPLY, other);
+    }
+
+    public FloatValue divide(SpecificFloatValue other)
+    {
+        return new CompositeFloatValue(this, CompositeFloatValue.DIVIDE, other);
+    }
+
+    public FloatValue divideOf(SpecificFloatValue other)
+    {
+        return new CompositeFloatValue(other, CompositeFloatValue.DIVIDE, this);
+    }
+
+    public FloatValue remainder(SpecificFloatValue other)
+    {
+        return new CompositeFloatValue(this, CompositeFloatValue.REMAINDER, other);
+    }
+
+    public FloatValue remainderOf(SpecificFloatValue other)
+    {
+        return new CompositeFloatValue(other, CompositeFloatValue.REMAINDER, this);
+    }
+
+    public IntegerValue compare(SpecificFloatValue other)
+    {
+        return this.equals(other) ?
+            SpecificValueFactory.INTEGER_VALUE_0 :
+            new ComparisonValue(this, other);
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isSpecific()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+}
diff --git a/src/proguard/evaluation/value/SpecificIntegerValue.java b/src/proguard/evaluation/value/SpecificIntegerValue.java
new file mode 100644
index 0000000..57c48b1
--- /dev/null
+++ b/src/proguard/evaluation/value/SpecificIntegerValue.java
@@ -0,0 +1,354 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This IntegerValue represents a specific integer value.
+ *
+ * @author Eric Lafortune
+ */
+abstract class SpecificIntegerValue extends IntegerValue
+{
+    // Implementations of unary methods of IntegerValue.
+
+    public IntegerValue negate()
+    {
+        return new NegatedIntegerValue(this);
+    }
+
+    public IntegerValue convertToByte()
+    {
+        return new ConvertedByteValue(this);
+    }
+
+    public IntegerValue convertToCharacter()
+    {
+        return new ConvertedCharacterValue(this);
+    }
+
+    public IntegerValue convertToShort()
+    {
+        return new ConvertedShortValue(this);
+    }
+
+    public LongValue convertToLong()
+    {
+        return new ConvertedLongValue(this);
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return new ConvertedFloatValue(this);
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return new ConvertedDoubleValue(this);
+    }
+
+
+    // Implementations of binary methods of IntegerValue.
+
+    public IntegerValue generalize(IntegerValue other)
+    {
+        return other.generalize(this);
+    }
+
+    public IntegerValue add(IntegerValue other)
+    {
+        return other.add(this);
+    }
+
+    public IntegerValue subtract(IntegerValue other)
+    {
+        return other.subtractFrom(this);
+    }
+
+    public IntegerValue subtractFrom(IntegerValue other)
+    {
+        return other.subtract(this);
+    }
+
+    public IntegerValue multiply(IntegerValue other)
+    {
+        return other.multiply(this);
+    }
+
+    public IntegerValue divide(IntegerValue other)
+    throws ArithmeticException
+    {
+        return other.divideOf(this);
+    }
+
+    public IntegerValue divideOf(IntegerValue other)
+    throws ArithmeticException
+    {
+        return other.divide(this);
+    }
+
+    public IntegerValue remainder(IntegerValue other)
+    throws ArithmeticException
+    {
+        return other.remainderOf(this);
+    }
+
+    public IntegerValue remainderOf(IntegerValue other)
+    throws ArithmeticException
+    {
+        return other.remainder(this);
+    }
+
+    public IntegerValue shiftLeft(IntegerValue other)
+    {
+        return other.shiftLeftOf(this);
+    }
+
+    public IntegerValue shiftLeftOf(IntegerValue other)
+    {
+        return other.shiftLeft(this);
+    }
+
+    public IntegerValue shiftRight(IntegerValue other)
+    {
+        return other.shiftRightOf(this);
+    }
+
+    public IntegerValue shiftRightOf(IntegerValue other)
+    {
+        return other.shiftRight(this);
+    }
+
+    public IntegerValue unsignedShiftRight(IntegerValue other)
+    {
+        return other.unsignedShiftRightOf(this);
+    }
+
+    public IntegerValue unsignedShiftRightOf(IntegerValue other)
+    {
+        return other.unsignedShiftRight(this);
+    }
+
+    public LongValue shiftLeftOf(LongValue other)
+    {
+        return other.shiftLeft(this);
+    }
+
+    public LongValue shiftRightOf(LongValue other)
+    {
+        return other.shiftRight(this);
+    }
+
+    public LongValue unsignedShiftRightOf(LongValue other)
+    {
+        return other.unsignedShiftRight(this);
+    }
+
+    public IntegerValue and(IntegerValue other)
+    {
+        return other.and(this);
+    }
+
+    public IntegerValue or(IntegerValue other)
+    {
+        return other.or(this);
+    }
+
+    public IntegerValue xor(IntegerValue other)
+    {
+        return other.xor(this);
+    }
+
+    public int equal(IntegerValue other)
+    {
+        return other.equal(this);
+    }
+
+    public int lessThan(IntegerValue other)
+    {
+        return other.greaterThan(this);
+    }
+
+    public int lessThanOrEqual(IntegerValue other)
+    {
+        return other.greaterThanOrEqual(this);
+    }
+
+
+    // Implementations of binary IntegerValue methods with SpecificIntegerValue
+    // arguments.
+
+    public IntegerValue generalize(SpecificIntegerValue other)
+    {
+        return this.equals(other) ? this : ValueFactory.INTEGER_VALUE;
+    }
+
+    public IntegerValue add(SpecificIntegerValue other)
+    {
+        return new CompositeIntegerValue(this, CompositeIntegerValue.ADD, other);
+    }
+
+    public IntegerValue subtract(SpecificIntegerValue other)
+    {
+        return this.equals(other) ?
+            SpecificValueFactory.INTEGER_VALUE_0 :
+            new CompositeIntegerValue(this, CompositeIntegerValue.SUBTRACT, other);
+    }
+
+    public IntegerValue subtractFrom(SpecificIntegerValue other)
+    {
+        return this.equals(other) ?
+            SpecificValueFactory.INTEGER_VALUE_0 :
+            new CompositeIntegerValue(other, CompositeIntegerValue.SUBTRACT, this);
+    }
+
+    public IntegerValue multiply(SpecificIntegerValue other)
+    {
+        return new CompositeIntegerValue(this, CompositeIntegerValue.MULTIPLY, other);
+    }
+
+    public IntegerValue divide(SpecificIntegerValue other)
+    throws ArithmeticException
+    {
+        return new CompositeIntegerValue(this, CompositeIntegerValue.DIVIDE, other);
+    }
+
+    public IntegerValue divideOf(SpecificIntegerValue other)
+    throws ArithmeticException
+    {
+        return new CompositeIntegerValue(other, CompositeIntegerValue.DIVIDE, this);
+    }
+
+    public IntegerValue remainder(SpecificIntegerValue other)
+    throws ArithmeticException
+    {
+        return new CompositeIntegerValue(this, CompositeIntegerValue.REMAINDER, other);
+    }
+
+    public IntegerValue remainderOf(SpecificIntegerValue other)
+    throws ArithmeticException
+    {
+        return new CompositeIntegerValue(other, CompositeIntegerValue.REMAINDER, this);
+    }
+
+    public IntegerValue shiftLeft(SpecificIntegerValue other)
+    {
+        return new CompositeIntegerValue(this, CompositeIntegerValue.SHIFT_LEFT, other);
+    }
+
+    public IntegerValue shiftRight(SpecificIntegerValue other)
+    {
+        return new CompositeIntegerValue(this, CompositeIntegerValue.SHIFT_RIGHT, other);
+    }
+
+    public IntegerValue unsignedShiftRight(SpecificIntegerValue other)
+    {
+        return new CompositeIntegerValue(this, CompositeIntegerValue.UNSIGNED_SHIFT_RIGHT, other);
+    }
+
+    public IntegerValue shiftLeftOf(SpecificIntegerValue other)
+    {
+        return new CompositeIntegerValue(other, CompositeIntegerValue.SHIFT_LEFT, this);
+    }
+
+    public IntegerValue shiftRightOf(SpecificIntegerValue other)
+    {
+        return new CompositeIntegerValue(other, CompositeIntegerValue.SHIFT_RIGHT, this);
+    }
+
+    public IntegerValue unsignedShiftRightOf(SpecificIntegerValue other)
+    {
+        return new CompositeIntegerValue(other, CompositeIntegerValue.UNSIGNED_SHIFT_RIGHT, this);
+    }
+
+    public LongValue shiftLeftOf(SpecificLongValue other)
+    {
+        return new CompositeLongValue(other, CompositeLongValue.SHIFT_LEFT, this);
+    }
+
+    public LongValue shiftRightOf(SpecificLongValue other)
+    {
+        return new CompositeLongValue(other, CompositeLongValue.SHIFT_RIGHT, this);
+    }
+
+    public LongValue unsignedShiftRightOf(SpecificLongValue other)
+    {
+        return new CompositeLongValue(other, CompositeLongValue.UNSIGNED_SHIFT_RIGHT, this);
+    }
+
+    public IntegerValue and(SpecificIntegerValue other)
+    {
+        return this.equals(other) ?
+            this :
+            new CompositeIntegerValue(other, CompositeIntegerValue.AND, this);
+    }
+
+    public IntegerValue or(SpecificIntegerValue other)
+    {
+        return this.equals(other) ?
+            this :
+            new CompositeIntegerValue(other, CompositeIntegerValue.OR, this);
+    }
+
+    public IntegerValue xor(SpecificIntegerValue other)
+    {
+        return this.equals(other) ?
+            SpecificValueFactory.INTEGER_VALUE_0 :
+            new CompositeIntegerValue(other, CompositeIntegerValue.XOR, this);
+    }
+
+    public int equal(SpecificIntegerValue other)
+    {
+        return this.equals(other) ? ALWAYS : MAYBE;
+    }
+
+    public int lessThan(SpecificIntegerValue other)
+    {
+        return this.equals(other) ? NEVER : MAYBE;
+    }
+
+    public int lessThanOrEqual(SpecificIntegerValue other)
+    {
+        return this.equals(other) ? ALWAYS : MAYBE;
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isSpecific()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+}
diff --git a/src/proguard/evaluation/value/SpecificLongValue.java b/src/proguard/evaluation/value/SpecificLongValue.java
new file mode 100644
index 0000000..5e12bde
--- /dev/null
+++ b/src/proguard/evaluation/value/SpecificLongValue.java
@@ -0,0 +1,259 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This LongValue represents a specific long value.
+ *
+ * @author Eric Lafortune
+ */
+abstract class SpecificLongValue extends LongValue
+{
+    // Implementations of unary methods of LongValue.
+
+    public LongValue negate()
+    {
+        return new NegatedLongValue(this);
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return new ConvertedIntegerValue(this);
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return new ConvertedFloatValue(this);
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return new ConvertedDoubleValue(this);
+    }
+
+
+    // Implementations of binary methods of LongValue.
+
+    public LongValue generalize(LongValue other)
+    {
+        return other.generalize(this);
+    }
+
+    public LongValue add(LongValue other)
+    {
+        return other.add(this);
+    }
+
+    public LongValue subtract(LongValue other)
+    {
+        return other.subtractFrom(this);
+    }
+
+    public LongValue subtractFrom(LongValue other)
+    {
+        return other.subtract(this);
+    }
+
+    public LongValue multiply(LongValue other)
+    {
+        return other.multiply(this);
+    }
+
+    public LongValue divide(LongValue other)
+    throws ArithmeticException
+    {
+        return other.divideOf(this);
+    }
+
+    public LongValue divideOf(LongValue other)
+    throws ArithmeticException
+    {
+        return other.divide(this);
+    }
+
+    public LongValue remainder(LongValue other)
+    throws ArithmeticException
+    {
+        return other.remainderOf(this);
+    }
+
+    public LongValue remainderOf(LongValue other)
+    throws ArithmeticException
+    {
+        return other.remainder(this);
+    }
+
+    public LongValue shiftLeft(IntegerValue other)
+    {
+        return other.shiftLeftOf(this);
+    }
+
+    public LongValue shiftRight(IntegerValue other)
+    {
+        return other.shiftRightOf(this);
+    }
+
+    public LongValue unsignedShiftRight(IntegerValue other)
+    {
+        return other.unsignedShiftRightOf(this);
+    }
+
+    public LongValue and(LongValue other)
+    {
+        return other.and(this);
+    }
+
+    public LongValue or(LongValue other)
+    {
+        return other.or(this);
+    }
+
+    public LongValue xor(LongValue other)
+    {
+        return other.xor(this);
+    }
+
+    public IntegerValue compare(LongValue other)
+    {
+        return other.compareReverse(this);
+    }
+
+
+    // Implementations of binary LongValue methods with SpecificLongValue
+    // arguments.
+
+    public LongValue generalize(SpecificLongValue other)
+    {
+        return this.equals(other) ? this : ValueFactory.LONG_VALUE;
+    }
+
+    public LongValue add(SpecificLongValue other)
+    {
+        return new CompositeLongValue(this, CompositeLongValue.ADD, other);
+    }
+
+    public LongValue subtract(SpecificLongValue other)
+    {
+        return this.equals(other) ?
+            SpecificValueFactory.LONG_VALUE_0 :
+            new CompositeLongValue(this, CompositeLongValue.SUBTRACT, other);
+    }
+
+    public LongValue subtractFrom(SpecificLongValue other)
+    {
+        return this.equals(other) ?
+            SpecificValueFactory.LONG_VALUE_0 :
+            new CompositeLongValue(other, CompositeLongValue.SUBTRACT, this);
+    }
+
+    public LongValue multiply(SpecificLongValue other)
+    {
+        return new CompositeLongValue(this, CompositeLongValue.MULTIPLY, other);
+    }
+
+    public LongValue divide(SpecificLongValue other)
+    throws ArithmeticException
+    {
+        return new CompositeLongValue(this, CompositeLongValue.DIVIDE, other);
+    }
+
+    public LongValue divideOf(SpecificLongValue other)
+    throws ArithmeticException
+    {
+        return new CompositeLongValue(other, CompositeLongValue.DIVIDE, this);
+    }
+
+    public LongValue remainder(SpecificLongValue other)
+    throws ArithmeticException
+    {
+        return new CompositeLongValue(this, CompositeLongValue.REMAINDER, other);
+    }
+
+    public LongValue remainderOf(SpecificLongValue other)
+    throws ArithmeticException
+    {
+        return new CompositeLongValue(other, CompositeLongValue.REMAINDER, this);
+    }
+
+    public LongValue shiftLeft(SpecificLongValue other)
+    {
+        return new CompositeLongValue(this, CompositeLongValue.SHIFT_LEFT, other);
+    }
+
+    public LongValue shiftRight(SpecificLongValue other)
+    {
+        return new CompositeLongValue(this, CompositeLongValue.SHIFT_RIGHT, other);
+    }
+
+    public LongValue unsignedShiftRight(SpecificLongValue other)
+    {
+        return new CompositeLongValue(this, CompositeLongValue.UNSIGNED_SHIFT_RIGHT, other);
+    }
+
+    public LongValue and(SpecificLongValue other)
+    {
+        return this.equals(other) ?
+            this :
+            new CompositeLongValue(other, CompositeLongValue.AND, this);
+    }
+
+    public LongValue or(SpecificLongValue other)
+    {
+        return this.equals(other) ?
+            this :
+            new CompositeLongValue(other, CompositeLongValue.OR, this);
+    }
+
+    public LongValue xor(SpecificLongValue other)
+    {
+        return this.equals(other) ?
+            SpecificValueFactory.LONG_VALUE_0 :
+            new CompositeLongValue(other, CompositeLongValue.XOR, this);
+    }
+
+    public IntegerValue compare(SpecificLongValue other)
+    {
+        return new ComparisonValue(this, other);
+    }
+
+
+    // Implementations for Value.
+
+    public boolean isSpecific()
+    {
+        return true;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+}
diff --git a/src/proguard/evaluation/value/SpecificValueFactory.java b/src/proguard/evaluation/value/SpecificValueFactory.java
new file mode 100644
index 0000000..72dd1d3
--- /dev/null
+++ b/src/proguard/evaluation/value/SpecificValueFactory.java
@@ -0,0 +1,89 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This class provides methods to create and reuse IntegerValue objects.
+ *
+ * @author Eric Lafortune
+ */
+public class SpecificValueFactory
+extends      ValueFactory
+{
+    // Shared copies of Value objects, to avoid creating a lot of objects.
+    static final IntegerValue INTEGER_VALUE_M1 = new ParticularIntegerValue(-1);
+    static final IntegerValue INTEGER_VALUE_0  = new ParticularIntegerValue(0);
+    static final IntegerValue INTEGER_VALUE_1  = new ParticularIntegerValue(1);
+    static final IntegerValue INTEGER_VALUE_2  = new ParticularIntegerValue(2);
+    static final IntegerValue INTEGER_VALUE_3  = new ParticularIntegerValue(3);
+    static final IntegerValue INTEGER_VALUE_4  = new ParticularIntegerValue(4);
+    static final IntegerValue INTEGER_VALUE_5  = new ParticularIntegerValue(5);
+    static final LongValue    LONG_VALUE_0     = new ParticularLongValue(0);
+    static final LongValue    LONG_VALUE_1     = new ParticularLongValue(1);
+    static final FloatValue   FLOAT_VALUE_0    = new ParticularFloatValue(0.0f);
+    static final FloatValue   FLOAT_VALUE_1    = new ParticularFloatValue(1.0f);
+    static final FloatValue   FLOAT_VALUE_2    = new ParticularFloatValue(2.0f);
+    static final DoubleValue  DOUBLE_VALUE_0   = new ParticularDoubleValue(0.0);
+    static final DoubleValue  DOUBLE_VALUE_1   = new ParticularDoubleValue(1.0);
+
+
+    // Implementations for ValueFactory.
+
+    public IntegerValue createIntegerValue(int value)
+    {
+        switch (value)
+        {
+            case -1: return INTEGER_VALUE_M1;
+            case  0: return INTEGER_VALUE_0;
+            case  1: return INTEGER_VALUE_1;
+            case  2: return INTEGER_VALUE_2;
+            case  3: return INTEGER_VALUE_3;
+            case  4: return INTEGER_VALUE_4;
+            case  5: return INTEGER_VALUE_5;
+            default: return new ParticularIntegerValue(value);
+        }
+    }
+
+
+    public LongValue createLongValue(long value)
+    {
+        return value == 0 ? LONG_VALUE_0 :
+               value == 1 ? LONG_VALUE_1 :
+                            new ParticularLongValue(value);
+    }
+
+
+    public FloatValue createFloatValue(float value)
+    {
+        return value == 0.0f ? FLOAT_VALUE_0 :
+               value == 1.0f ? FLOAT_VALUE_1 :
+               value == 2.0f ? FLOAT_VALUE_2 :
+                               new ParticularFloatValue(value);
+    }
+
+
+    public DoubleValue createDoubleValue(double value)
+    {
+        return value == 0.0 ? DOUBLE_VALUE_0 :
+               value == 1.0 ? DOUBLE_VALUE_1 :
+                              new ParticularDoubleValue(value);
+    }
+}
diff --git a/src/proguard/evaluation/value/TopValue.java b/src/proguard/evaluation/value/TopValue.java
new file mode 100644
index 0000000..048a1ff
--- /dev/null
+++ b/src/proguard/evaluation/value/TopValue.java
@@ -0,0 +1,79 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This class represents a partially evaluated top value. A top value is the
+ * dummy value that takes up the extra space when storing a long value or a
+ * double value.
+ *
+ * @author Eric Lafortune
+ */
+public class TopValue extends Category1Value
+{
+    // Implementations for Value.
+
+    public boolean isSpecific()
+    {
+        return true;
+    }
+
+    public boolean isParticular()
+    {
+        return true;
+    }
+
+    public final Value generalize(Value other)
+    {
+        return this.getClass() == other.getClass() ? this : null;
+    }
+
+    public final int computationalType()
+    {
+        return TYPE_TOP;
+    }
+
+    public final String internalType()
+    {
+        return null;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "T";
+    }
+}
diff --git a/src/proguard/evaluation/value/UnknownDoubleValue.java b/src/proguard/evaluation/value/UnknownDoubleValue.java
new file mode 100644
index 0000000..79cc4de
--- /dev/null
+++ b/src/proguard/evaluation/value/UnknownDoubleValue.java
@@ -0,0 +1,125 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This class represents a partially evaluated double value.
+ *
+ * @author Eric Lafortune
+ */
+public class UnknownDoubleValue extends DoubleValue
+{
+    // Basic unary methods.
+
+    public DoubleValue negate()
+    {
+        return this;
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return ValueFactory.INTEGER_VALUE;
+    }
+
+    public LongValue convertToLong()
+    {
+        return ValueFactory.LONG_VALUE;
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return ValueFactory.FLOAT_VALUE;
+    }
+
+
+    // Basic binary methods.
+
+    public DoubleValue generalize(DoubleValue other)
+    {
+        return this;
+    }
+
+    public DoubleValue add(DoubleValue other)
+    {
+        return this;
+    }
+
+    public DoubleValue subtract(DoubleValue other)
+    {
+        return this;
+    }
+
+    public DoubleValue subtractFrom(DoubleValue other)
+    {
+        return this;
+    }
+
+    public DoubleValue multiply(DoubleValue other)
+    {
+        return this;
+    }
+
+    public DoubleValue divide(DoubleValue other)
+    {
+        return this;
+    }
+
+    public DoubleValue divideOf(DoubleValue other)
+    {
+        return this;
+    }
+
+    public DoubleValue remainder(DoubleValue other)
+    {
+        return this;
+    }
+
+    public DoubleValue remainderOf(DoubleValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue compare(DoubleValue other)
+    {
+        return ValueFactory.INTEGER_VALUE;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "d";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/UnknownFloatValue.java b/src/proguard/evaluation/value/UnknownFloatValue.java
new file mode 100644
index 0000000..3f9622a
--- /dev/null
+++ b/src/proguard/evaluation/value/UnknownFloatValue.java
@@ -0,0 +1,125 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This class represents a partially evaluated float value.
+ *
+ * @author Eric Lafortune
+ */
+public class UnknownFloatValue extends FloatValue
+{
+    // Basic unary methods.
+
+    public FloatValue negate()
+    {
+        return this;
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return ValueFactory.INTEGER_VALUE;
+    }
+
+    public LongValue convertToLong()
+    {
+        return ValueFactory.LONG_VALUE;
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return ValueFactory.DOUBLE_VALUE;
+    }
+
+
+    // Basic binary methods.
+
+    public FloatValue generalize(FloatValue other)
+    {
+        return this;
+    }
+
+    public FloatValue add(FloatValue other)
+    {
+        return this;
+    }
+
+    public FloatValue subtract(FloatValue other)
+    {
+        return this;
+    }
+
+    public FloatValue subtractFrom(FloatValue other)
+    {
+        return this;
+    }
+
+    public FloatValue multiply(FloatValue other)
+    {
+        return this;
+    }
+
+    public FloatValue divide(FloatValue other)
+    {
+        return this;
+    }
+
+    public FloatValue divideOf(FloatValue other)
+    {
+        return this;
+    }
+
+    public FloatValue remainder(FloatValue other)
+    {
+        return this;
+    }
+
+    public FloatValue remainderOf(FloatValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue compare(FloatValue other)
+    {
+        return ValueFactory.INTEGER_VALUE;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "d";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/UnknownIntegerValue.java b/src/proguard/evaluation/value/UnknownIntegerValue.java
new file mode 100644
index 0000000..be5e79d
--- /dev/null
+++ b/src/proguard/evaluation/value/UnknownIntegerValue.java
@@ -0,0 +1,216 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This class represents a partially evaluated integer value.
+ *
+ * @author Eric Lafortune
+ */
+public class UnknownIntegerValue extends IntegerValue
+{
+    // Basic unary methods.
+
+    public IntegerValue negate()
+    {
+        return this;
+    }
+
+    public IntegerValue convertToByte()
+    {
+        return this;
+    }
+
+    public IntegerValue convertToCharacter()
+    {
+        return this;
+    }
+
+    public IntegerValue convertToShort()
+    {
+        return this;
+    }
+
+    public LongValue convertToLong()
+    {
+        return ValueFactory.LONG_VALUE;
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return ValueFactory.FLOAT_VALUE;
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return ValueFactory.DOUBLE_VALUE;
+    }
+
+
+    // Basic binary methods.
+
+    public IntegerValue generalize(IntegerValue other)
+    {
+        return this;
+    }
+
+
+    public IntegerValue add(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue subtract(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue subtractFrom(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue multiply(IntegerValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public IntegerValue divide(IntegerValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public IntegerValue divideOf(IntegerValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public IntegerValue remainder(IntegerValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public IntegerValue remainderOf(IntegerValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public IntegerValue shiftLeft(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue shiftLeftOf(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue shiftRight(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue shiftRightOf(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue unsignedShiftRight(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue unsignedShiftRightOf(IntegerValue other)
+    {
+        return this;
+    }
+
+    public LongValue shiftLeftOf(LongValue other)
+    {
+        return ValueFactory.LONG_VALUE;
+    }
+
+    public LongValue shiftRightOf(LongValue other)
+    {
+        return ValueFactory.LONG_VALUE;
+    }
+
+    public LongValue unsignedShiftRightOf(LongValue other)
+    {
+        return ValueFactory.LONG_VALUE;
+    }
+
+    public IntegerValue and(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue or(IntegerValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue xor(IntegerValue other)
+    {
+        return this;
+    }
+
+    public int equal(IntegerValue other)
+    {
+        return MAYBE;
+    }
+
+    public int lessThan(IntegerValue other)
+    {
+        return MAYBE;
+    }
+
+    public int lessThanOrEqual(IntegerValue other)
+    {
+        return MAYBE;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "i";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/UnknownLongValue.java b/src/proguard/evaluation/value/UnknownLongValue.java
new file mode 100644
index 0000000..83a75dc
--- /dev/null
+++ b/src/proguard/evaluation/value/UnknownLongValue.java
@@ -0,0 +1,160 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This class represents a partially evaluated long value.
+ *
+ * @author Eric Lafortune
+ */
+public class UnknownLongValue extends LongValue
+{
+    // Basic unary methods.
+
+    public LongValue negate()
+    {
+        return this;
+    }
+
+    public IntegerValue convertToInteger()
+    {
+        return ValueFactory.INTEGER_VALUE;
+    }
+
+    public FloatValue convertToFloat()
+    {
+        return ValueFactory.FLOAT_VALUE;
+    }
+
+    public DoubleValue convertToDouble()
+    {
+        return ValueFactory.DOUBLE_VALUE;
+    }
+
+
+    // Basic binary methods.
+
+    public LongValue generalize(LongValue other)
+    {
+        return this;
+    }
+
+    public LongValue add(LongValue other)
+    {
+        return this;
+    }
+
+    public LongValue subtract(LongValue other)
+    {
+        return this;
+    }
+
+    public LongValue subtractFrom(LongValue other)
+    {
+        return this;
+    }
+
+    public LongValue multiply(LongValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public LongValue divide(LongValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public LongValue divideOf(LongValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public LongValue remainder(LongValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public LongValue remainderOf(LongValue other)
+    throws ArithmeticException
+    {
+        return this;
+    }
+
+    public LongValue shiftLeft(IntegerValue other)
+    {
+        return this;
+    }
+
+    public LongValue shiftRight(IntegerValue other)
+    {
+        return this;
+    }
+
+    public LongValue unsignedShiftRight(IntegerValue other)
+    {
+        return this;
+    }
+
+    public LongValue and(LongValue other)
+    {
+        return this;
+    }
+
+    public LongValue or(LongValue other)
+    {
+        return this;
+    }
+
+    public LongValue xor(LongValue other)
+    {
+        return this;
+    }
+
+    public IntegerValue compare(LongValue other)
+    {
+        return ValueFactory.INTEGER_VALUE;
+    }
+
+
+    // Implementations for Object.
+
+    public boolean equals(Object object)
+    {
+        return object != null &&
+               this.getClass() == object.getClass();
+    }
+
+
+    public int hashCode()
+    {
+        return this.getClass().hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "l";
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/evaluation/value/Value.java b/src/proguard/evaluation/value/Value.java
new file mode 100644
index 0000000..e24ece1
--- /dev/null
+++ b/src/proguard/evaluation/value/Value.java
@@ -0,0 +1,169 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+/**
+ * This abstract class represents a partially evaluated value.
+ *
+ * @author Eric Lafortune
+ */
+public abstract class Value
+{
+    public static final int NEVER  = -1;
+    public static final int MAYBE  = 0;
+    public static final int ALWAYS = 1;
+
+    public static final int TYPE_INTEGER            = 1;
+    public static final int TYPE_LONG               = 2;
+    public static final int TYPE_FLOAT              = 3;
+    public static final int TYPE_DOUBLE             = 4;
+    public static final int TYPE_REFERENCE          = 5;
+    public static final int TYPE_INSTRUCTION_OFFSET = 6;
+    public static final int TYPE_TOP                = 7;
+
+
+    /**
+     * Returns this Value as a Category1Value.
+     */
+    public Category1Value category1Value()
+    {
+        throw new IllegalArgumentException("Value is not a Category 1 value [" + this.getClass().getName() + "]");
+    }
+
+    /**
+     * Returns this Value as a Category2Value.
+     */
+    public Category2Value category2Value()
+    {
+        throw new IllegalArgumentException("Value is not a Category 2 value [" + this.getClass().getName() + "]");
+    }
+
+
+    /**
+     * Returns this Value as an IntegerValue.
+     */
+    public IntegerValue integerValue()
+    {
+        throw new IllegalArgumentException("Value is not an integer value [" + this.getClass().getName() + "]");
+    }
+
+    /**
+     * Returns this Value as a LongValue.
+     */
+    public LongValue longValue()
+    {
+        throw new IllegalArgumentException("Value is not a long value [" + this.getClass().getName() + "]");
+    }
+
+    /**
+     * Returns this Value as a FloatValue.
+     */
+    public FloatValue floatValue()
+    {
+        throw new IllegalArgumentException("Value is not a float value [" + this.getClass().getName() + "]");
+    }
+
+    /**
+     * Returns this Value as a DoubleValue.
+     */
+    public DoubleValue doubleValue()
+    {
+        throw new IllegalArgumentException("Value is not a double value [" + this.getClass().getName() + "]");
+    }
+
+    /**
+     * Returns this Value as a ReferenceValue.
+     */
+    public ReferenceValue referenceValue()
+    {
+        throw new IllegalArgumentException("Value is not a reference value [" + this.getClass().getName() + "]");
+    }
+
+    /**
+     * Returns this Value as an InstructionOffsetValue.
+     */
+    public InstructionOffsetValue instructionOffsetValue()
+    {
+        throw new IllegalArgumentException("Value is not an instruction offset value [" + this.getClass().getName() + "]");
+    }
+
+
+    /**
+     * Returns whether this Value represents a single specific (but possibly
+     * unknown) value.
+     */
+    public boolean isSpecific()
+    {
+        return false;
+    }
+
+
+    /**
+     * Returns whether this Value represents a single particular (known)
+     * value.
+     */
+    public boolean isParticular()
+    {
+        return false;
+    }
+
+
+    /**
+     * Returns the generalization of this Value and the given other Value.
+     */
+    public abstract Value generalize(Value other);
+
+
+    /**
+     * Returns whether the computational type of this Value is a category 2 type.
+     * This means that it takes up the space of two category 1 types on the
+     * stack, for instance.
+     */
+    public abstract boolean isCategory2();
+
+
+    /**
+     * Returns the computational type of this Value.
+     * @return <code>TYPE_INTEGER</code>,
+     *         <code>TYPE_LONG</code>,
+     *         <code>TYPE_FLOAT</code>,
+     *         <code>TYPE_DOUBLE</code>,
+     *         <code>TYPE_REFERENCE</code>, or
+     *         <code>TYPE_INSTRUCTION_OFFSET</code>.
+     */
+    public abstract int computationalType();
+
+
+    /**
+     * Returns the internal type of this Value.
+     * @return <code>ClassConstants.INTERNAL_TYPE_BOOLEAN</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_BYTE</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_CHAR</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_SHORT</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_INT</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_LONG</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_FLOAT</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_DOUBLE</code>,
+     *         <code>ClassConstants.INTERNAL_TYPE_CLASS_START ... ClassConstants.INTERNAL_TYPE_CLASS_END</code>, or
+     *         an array type containing any of these types (always as String).
+     */
+    public abstract String internalType();
+}
diff --git a/src/proguard/evaluation/value/ValueFactory.java b/src/proguard/evaluation/value/ValueFactory.java
new file mode 100644
index 0000000..8415381
--- /dev/null
+++ b/src/proguard/evaluation/value/ValueFactory.java
@@ -0,0 +1,193 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.evaluation.value;
+
+import proguard.classfile.*;
+import proguard.classfile.util.ClassUtil;
+
+/**
+ * This class provides methods to create and reuse Value objects.
+ *
+ * @author Eric Lafortune
+ */
+public class ValueFactory
+{
+    // Shared copies of Value objects, to avoid creating a lot of objects.
+    static final IntegerValue INTEGER_VALUE = new UnknownIntegerValue();
+    static final LongValue    LONG_VALUE    = new UnknownLongValue();
+    static final FloatValue   FLOAT_VALUE   = new UnknownFloatValue();
+    static final DoubleValue  DOUBLE_VALUE  = new UnknownDoubleValue();
+
+    static final ReferenceValue REFERENCE_VALUE_NULL                        = new ReferenceValue(null, null, true);
+    static final ReferenceValue REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL = new ReferenceValue(ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT, null, true);
+    static final ReferenceValue REFERENCE_VALUE_JAVA_LANG_OBJECT_NOT_NULL   = new ReferenceValue(ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT, null, false);
+
+
+    /**
+     * Creates a new Value of the given type.
+     * The type must be a fully specified internal type for primitives, classes,
+     * or arrays.
+     */
+    public Value createValue(String type, Clazz referencedClass, boolean mayBeNull)
+    {
+        switch (type.charAt(0))
+        {
+            case ClassConstants.INTERNAL_TYPE_VOID:    return null;
+            case ClassConstants.INTERNAL_TYPE_BOOLEAN:
+            case ClassConstants.INTERNAL_TYPE_BYTE:
+            case ClassConstants.INTERNAL_TYPE_CHAR:
+            case ClassConstants.INTERNAL_TYPE_SHORT:
+            case ClassConstants.INTERNAL_TYPE_INT:     return createIntegerValue();
+            case ClassConstants.INTERNAL_TYPE_LONG:    return createLongValue();
+            case ClassConstants.INTERNAL_TYPE_FLOAT:   return createFloatValue();
+            case ClassConstants.INTERNAL_TYPE_DOUBLE:  return createDoubleValue();
+            default:                                   return createReferenceValue(ClassUtil.isInternalArrayType(type) ?
+                                                                                       type :
+                                                                                       ClassUtil.internalClassNameFromClassType(type),
+                                                                                   referencedClass,
+                                                                                   mayBeNull);
+        }
+    }
+
+    /**
+     * Creates a new IntegerValue with an undefined value.
+     */
+    public IntegerValue createIntegerValue()
+    {
+        return INTEGER_VALUE;
+    }
+
+    /**
+     * Creates a new IntegerValue with a given particular value.
+     */
+    public IntegerValue createIntegerValue(int value)
+    {
+        return createIntegerValue();
+    }
+
+
+    /**
+     * Creates a new LongValue with an undefined value.
+     */
+    public LongValue createLongValue()
+    {
+        return LONG_VALUE;
+    }
+
+    /**
+     * Creates a new LongValue with a given particular value.
+     */
+    public LongValue createLongValue(long value)
+    {
+        return createLongValue();
+    }
+
+
+    /**
+     * Creates a new FloatValue with an undefined value.
+     */
+    public FloatValue createFloatValue()
+    {
+        return FLOAT_VALUE;
+    }
+
+    /**
+     * Creates a new FloatValue with a given particular value.
+     */
+    public FloatValue createFloatValue(float value)
+    {
+        return createFloatValue();
+    }
+
+
+    /**
+     * Creates a new DoubleValue with an undefined value.
+     */
+    public DoubleValue createDoubleValue()
+    {
+        return DOUBLE_VALUE;
+    }
+
+    /**
+     * Creates a new DoubleValue with a given particular value.
+     */
+    public DoubleValue createDoubleValue(double value)
+    {
+        return createDoubleValue();
+    }
+
+
+    /**
+     * Creates a new ReferenceValue that represents <code>null</code>.
+     */
+    public ReferenceValue createReferenceValueNull()
+    {
+        return REFERENCE_VALUE_NULL;
+    }
+
+
+    /**
+     * Creates a new ReferenceValue of the given type. The type must be an
+     * internal class name or an array type. If the type is <code>null</code>,
+     * the ReferenceValue represents <code>null</code>.
+     */
+    public ReferenceValue createReferenceValue(String  type,
+                                               Clazz   referencedClass,
+                                               boolean mayBeNull)
+    {
+        return type == null                                                ? REFERENCE_VALUE_NULL                                 :
+               !type.equals(ClassConstants.INTERNAL_NAME_JAVA_LANG_OBJECT) ? new ReferenceValue(type, referencedClass, mayBeNull) :
+               mayBeNull                                                   ? REFERENCE_VALUE_JAVA_LANG_OBJECT_MAYBE_NULL          :
+                                                                             REFERENCE_VALUE_JAVA_LANG_OBJECT_NOT_NULL;
+    }
+
+
+    /**
+     * Creates a new ReferenceValue for arrays of the given type and length.
+     * The type must be a fully specified internal type for primitives, classes,
+     * or arrays.
+     */
+    public ReferenceValue createArrayReferenceValue(String       type,
+                                                    Clazz        referencedClass,
+                                                    IntegerValue arrayLength)
+    {
+        return createArrayReferenceValue(type,
+                                         referencedClass,
+                                         arrayLength,
+                                         createValue(type, referencedClass, false));
+    }
+
+
+    /**
+     * Creates a new ReferenceValue for arrays of the given type and length,
+     * containing the given element. The type must be a fully specified internal
+     * type for primitives, classes, or arrays.
+     */
+    public ReferenceValue createArrayReferenceValue(String       type,
+                                                    Clazz        referencedClass,
+                                                    IntegerValue arrayLength,
+                                                    Value        elementValue)
+    {
+        return createReferenceValue(ClassConstants.INTERNAL_TYPE_ARRAY + type,
+                                    referencedClass,
+                                    false);
+    }
+}
diff --git a/src/proguard/evaluation/value/package.html b/src/proguard/evaluation/value/package.html
new file mode 100644
index 0000000..71e1b13
--- /dev/null
+++ b/src/proguard/evaluation/value/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains classes that represent partial evaluation values.
+</body>
diff --git a/src/proguard/gui/ClassPathPanel.java b/src/proguard/gui/ClassPathPanel.java
new file mode 100644
index 0000000..95f3d1b
--- /dev/null
+++ b/src/proguard/gui/ClassPathPanel.java
@@ -0,0 +1,441 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.*;
+import proguard.util.ListUtil;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.util.List;
+
+/**
+ * This <code>ListPanel</code> allows the user to add, edit, filter, move, and
+ * remove ClassPathEntry objects in a ClassPath object.
+ *
+ * @author Eric Lafortune
+ */
+class ClassPathPanel extends ListPanel
+{
+    private final JFrame       owner;
+    private final boolean      inputAndOutput;
+    private final JFileChooser chooser;
+    private final FilterDialog filterDialog;
+
+
+    public ClassPathPanel(JFrame owner, boolean inputAndOutput)
+    {
+        super();
+
+        super.firstSelectionButton = inputAndOutput ? 3 : 2;
+
+        this.owner          = owner;
+        this.inputAndOutput = inputAndOutput;
+
+        list.setCellRenderer(new MyListCellRenderer());
+
+        chooser = new JFileChooser("");
+        chooser.setMultiSelectionEnabled(true);
+        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+        chooser.addChoosableFileFilter(
+            new ExtensionFileFilter(msg("jarWarEarZipExtensions"),
+                                    new String[] { ".jar", ".war", ".ear", ".zip" }));
+        chooser.setApproveButtonText(msg("ok"));
+
+        filterDialog = new FilterDialog(owner, msg("enterFilter"));
+
+        addAddButton(inputAndOutput, false);
+        if (inputAndOutput)
+        {
+            addAddButton(inputAndOutput, true);
+        }
+        addEditButton();
+        addFilterButton();
+        addRemoveButton();
+        addUpButton();
+        addDownButton();
+
+        enableSelectionButtons();
+    }
+
+
+    protected void addAddButton(boolean       inputAndOutput,
+                                final boolean isOutput)
+    {
+        JButton addButton = new JButton(msg(inputAndOutput ?
+                                            isOutput       ? "addOutput" :
+                                                             "addInput" :
+                                                             "add"));
+        addButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                chooser.setDialogTitle(msg("addJars"));
+                chooser.setSelectedFile(null);
+                chooser.setSelectedFiles(null);
+
+                int returnValue = chooser.showOpenDialog(owner);
+                if (returnValue == JFileChooser.APPROVE_OPTION)
+                {
+                    File[] selectedFiles = chooser.getSelectedFiles();
+                    ClassPathEntry[] entries = classPathEntries(selectedFiles, isOutput);
+
+                    // Add the new elements.
+                    addElements(entries);
+                }
+            }
+        });
+
+        addButton(tip(addButton, inputAndOutput ?
+                                 isOutput       ? "addOutputTip" :
+                                                  "addInputTip" :
+                                                  "addTip"));
+    }
+
+
+    protected void addEditButton()
+    {
+        JButton editButton = new JButton(msg("edit"));
+        editButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                boolean isOutput = false;
+
+                int[] selectedIndices = list.getSelectedIndices();
+
+                // Copy the Object array into a File array.
+                File[] selectedFiles = new File[selectedIndices.length];
+                for (int index = 0; index < selectedFiles.length; index++)
+                {
+                    ClassPathEntry entry =
+                        (ClassPathEntry)listModel.getElementAt(selectedIndices[index]);
+
+                    isOutput = entry.isOutput();
+
+                    selectedFiles[index] = entry.getFile();
+                }
+
+                chooser.setDialogTitle(msg("chooseJars"));
+
+                // Up to JDK 1.3.1, setSelectedFiles doesn't show in the file
+                // chooser, so we just use setSelectedFile first. It also sets
+                // the current directory.
+                chooser.setSelectedFile(selectedFiles[0]);
+                chooser.setSelectedFiles(selectedFiles);
+
+                int returnValue = chooser.showOpenDialog(owner);
+                if (returnValue == JFileChooser.APPROVE_OPTION)
+                {
+                    selectedFiles = chooser.getSelectedFiles();
+                    ClassPathEntry[] entries = classPathEntries(selectedFiles, isOutput);
+
+                    // If there are the same number of files selected now as
+                    // there were before, we can just replace the old ones.
+                    if (selectedIndices.length == selectedFiles.length)
+                    {
+                        // Replace the old elements.
+                        setElementsAt(entries, selectedIndices);
+                    }
+                    else
+                    {
+                        // Remove the old elements.
+                        removeElementsAt(selectedIndices);
+
+                        // Add the new elements.
+                        addElements(entries);
+                    }
+                }
+            }
+        });
+
+        addButton(tip(editButton, "editTip"));
+    }
+
+
+    protected void addFilterButton()
+    {
+        JButton filterButton = new JButton(msg("filter"));
+        filterButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                if (!list.isSelectionEmpty())
+                {
+                    int[] selectedIndices = list.getSelectedIndices();
+
+                    // Put the filters of the first selected entry in the dialog.
+                    getFiltersFrom(selectedIndices[0]);
+
+                    int returnValue = filterDialog.showDialog();
+                    if (returnValue == FilterDialog.APPROVE_OPTION)
+                    {
+                        // Apply the entered filters to all selected entries.
+                        setFiltersAt(selectedIndices);
+                    }
+                }
+            }
+        });
+
+        addButton(tip(filterButton, "filterTip"));
+    }
+
+
+    /**
+     * Sets the ClassPath to be represented in this panel.
+     */
+    public void setClassPath(ClassPath classPath)
+    {
+        listModel.clear();
+
+        if (classPath != null)
+        {
+            for (int index = 0; index < classPath.size(); index++)
+            {
+                listModel.addElement(classPath.get(index));
+            }
+        }
+
+        // Make sure the selection buttons are properly enabled,
+        // since the clear method doesn't seem to notify the listener.
+        enableSelectionButtons();
+    }
+
+
+    /**
+     * Returns the ClassPath currently represented in this panel.
+     */
+    public ClassPath getClassPath()
+    {
+        int size = listModel.size();
+        if (size == 0)
+        {
+            return null;
+        }
+
+        ClassPath classPath = new ClassPath();
+        for (int index = 0; index < size; index++)
+        {
+            classPath.add((ClassPathEntry)listModel.get(index));
+        }
+
+        return classPath;
+    }
+
+
+    /**
+     * Converts the given array of File objects into a corresponding array of
+     * ClassPathEntry objects.
+     */
+    private ClassPathEntry[] classPathEntries(File[] files, boolean isOutput)
+    {
+        ClassPathEntry[] entries = new ClassPathEntry[files.length];
+        for (int index = 0; index < entries.length; index++)
+        {
+            entries[index] = new ClassPathEntry(files[index], isOutput);
+        }
+        return entries;
+    }
+
+
+    /**
+     * Sets up the filter dialog with the filters from the specified class path
+     * entry.
+     */
+    private void getFiltersFrom(int index)
+    {
+        ClassPathEntry firstEntry = (ClassPathEntry)listModel.get(index);
+
+        filterDialog.setFilter(firstEntry.getFilter());
+        filterDialog.setJarFilter(firstEntry.getJarFilter());
+        filterDialog.setWarFilter(firstEntry.getWarFilter());
+        filterDialog.setEarFilter(firstEntry.getEarFilter());
+        filterDialog.setZipFilter(firstEntry.getZipFilter());
+    }
+
+
+    /**
+     * Applies the entered filter to the specified class path entries.
+     * Any previously set filters are discarded.
+     */
+    private void setFiltersAt(int[] indices)
+    {
+        for (int index = indices.length - 1; index >= 0; index--)
+        {
+            ClassPathEntry entry = (ClassPathEntry)listModel.get(indices[index]);
+            entry.setFilter(filterDialog.getFilter());
+            entry.setJarFilter(filterDialog.getJarFilter());
+            entry.setWarFilter(filterDialog.getWarFilter());
+            entry.setEarFilter(filterDialog.getEarFilter());
+            entry.setZipFilter(filterDialog.getZipFilter());
+        }
+
+        // Make sure they are selected and thus repainted.
+        list.setSelectedIndices(indices);
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+
+
+    /**
+     * This ListCellRenderer renders ClassPathEntry objects.
+     */
+    private class MyListCellRenderer implements ListCellRenderer
+    {
+        private static final String ARROW_IMAGE_FILE = "arrow.gif";
+
+        private final JPanel cellPanel    = new JPanel(new GridBagLayout());
+        private final JLabel iconLabel    = new JLabel("", JLabel.RIGHT);
+        private final JLabel jarNameLabel = new JLabel("", JLabel.RIGHT);
+        private final JLabel filterLabel  = new JLabel("", JLabel.RIGHT);
+
+        private final Icon arrowIcon;
+
+
+        public MyListCellRenderer()
+        {
+            GridBagConstraints jarNameLabelConstraints = new GridBagConstraints();
+            jarNameLabelConstraints.anchor             = GridBagConstraints.WEST;
+            jarNameLabelConstraints.insets             = new Insets(1, 2, 1, 2);
+
+            GridBagConstraints filterLabelConstraints  = new GridBagConstraints();
+            filterLabelConstraints.gridwidth           = GridBagConstraints.REMAINDER;
+            filterLabelConstraints.fill                = GridBagConstraints.HORIZONTAL;
+            filterLabelConstraints.weightx             = 1.0;
+            filterLabelConstraints.anchor              = GridBagConstraints.EAST;
+            filterLabelConstraints.insets              = jarNameLabelConstraints.insets;
+
+            arrowIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(this.getClass().getResource(ARROW_IMAGE_FILE)));
+
+            cellPanel.add(iconLabel,    jarNameLabelConstraints);
+            cellPanel.add(jarNameLabel, jarNameLabelConstraints);
+            cellPanel.add(filterLabel,  filterLabelConstraints);
+        }
+
+
+        // Implementations for ListCellRenderer.
+
+        public Component getListCellRendererComponent(JList   list,
+                                                      Object  value,
+                                                      int     index,
+                                                      boolean isSelected,
+                                                      boolean cellHasFocus)
+        {
+            ClassPathEntry entry = (ClassPathEntry)value;
+
+            // Prepend an arrow to the output entries.
+            if (inputAndOutput && entry.isOutput())
+            {
+                iconLabel.setIcon(arrowIcon);
+            }
+            else
+            {
+                iconLabel.setIcon(null);
+            }
+
+            // Set the entry name text.
+            jarNameLabel.setText(entry.getName());
+
+            // Set the filter text.
+            StringBuffer filter = null;
+            filter = appendFilter(filter, entry.getZipFilter());
+            filter = appendFilter(filter, entry.getEarFilter());
+            filter = appendFilter(filter, entry.getWarFilter());
+            filter = appendFilter(filter, entry.getJarFilter());
+            filter = appendFilter(filter, entry.getFilter());
+
+            if (filter != null)
+            {
+                filter.append(')');
+            }
+
+            filterLabel.setText(filter != null ? filter.toString() : "");
+
+            // Set the colors.
+            if (isSelected)
+            {
+                cellPanel.setBackground(list.getSelectionBackground());
+                jarNameLabel.setForeground(list.getSelectionForeground());
+                filterLabel.setForeground(list.getSelectionForeground());
+            }
+            else
+            {
+                cellPanel.setBackground(list.getBackground());
+                jarNameLabel.setForeground(list.getForeground());
+                filterLabel.setForeground(list.getForeground());
+            }
+
+            // Make the font color red if this is an input file that can't be read.
+            if (!(inputAndOutput && entry.isOutput()) &&
+                !entry.getFile().canRead())
+            {
+                jarNameLabel.setForeground(Color.red);
+            }
+
+            cellPanel.setOpaque(true);
+
+            return cellPanel;
+        }
+
+
+        private StringBuffer appendFilter(StringBuffer filter, List additionalFilter)
+        {
+            if (filter != null)
+            {
+                filter.append(';');
+            }
+
+            if (additionalFilter != null)
+            {
+                if (filter == null)
+                {
+                    filter = new StringBuffer().append('(');
+                }
+
+                filter.append(ListUtil.commaSeparatedString(additionalFilter));
+            }
+
+            return filter;
+        }
+    }
+}
diff --git a/src/proguard/gui/ClassSpecificationDialog.java b/src/proguard/gui/ClassSpecificationDialog.java
new file mode 100644
index 0000000..36d80d4
--- /dev/null
+++ b/src/proguard/gui/ClassSpecificationDialog.java
@@ -0,0 +1,542 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.*;
+import proguard.classfile.ClassConstants;
+import proguard.classfile.util.ClassUtil;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+
+/**
+ * This <code>JDialog</code> allows the user to enter a String.
+ *
+ * @author Eric Lafortune
+ */
+final class ClassSpecificationDialog extends JDialog
+{
+    /**
+     * Return value if the dialog is canceled (with the Cancel button or by
+     * closing the dialog window).
+     */
+    public static final int CANCEL_OPTION = 1;
+
+    /**
+     * Return value if the dialog is approved (with the Ok button).
+     */
+    public static final int APPROVE_OPTION = 0;
+
+
+    private final JTextArea commentsTextArea = new JTextArea(4, 20);
+
+    private final JRadioButton keepClassesAndMembersRadioButton  = new JRadioButton(msg("keep"));
+    private final JRadioButton keepClassMembersRadioButton       = new JRadioButton(msg("keepClassMembers"));
+    private final JRadioButton keepClassesWithMembersRadioButton = new JRadioButton(msg("keepClassesWithMembers"));
+
+    private final JCheckBox allowShrinkingCheckBox    = new JCheckBox(msg("allowShrinking"));
+    private final JCheckBox allowOptimizationCheckBox = new JCheckBox(msg("allowOptimization"));
+    private final JCheckBox allowObfuscationCheckBox  = new JCheckBox(msg("allowObfuscation"));
+
+
+    private final JRadioButton[] publicRadioButtons;
+    private final JRadioButton[] finalRadioButtons;
+    private final JRadioButton[] abstractRadioButtons;
+    private final JRadioButton[] enumRadioButtons;
+    private final JRadioButton[] annotationRadioButtons;
+    private final JRadioButton[] interfaceRadioButtons;
+
+    private final JTextField annotationTypeTextField        = new JTextField(20);
+    private final JTextField classNameTextField             = new JTextField(20);
+    private final JTextField extendsAnnotationTypeTextField = new JTextField(20);
+    private final JTextField extendsClassNameTextField      = new JTextField(20);
+
+    private final MemberSpecificationsPanel memberSpecificationsPanel;
+
+    private int returnValue;
+
+
+    public ClassSpecificationDialog(JFrame owner, boolean fullKeepOptions)
+    {
+        super(owner, msg("specifyClasses"), true);
+        setResizable(true);
+
+        // Create some constraints that can be reused.
+        GridBagConstraints constraints = new GridBagConstraints();
+        constraints.anchor = GridBagConstraints.WEST;
+        constraints.insets = new Insets(1, 2, 1, 2);
+
+        GridBagConstraints constraintsStretch = new GridBagConstraints();
+        constraintsStretch.fill    = GridBagConstraints.HORIZONTAL;
+        constraintsStretch.weightx = 1.0;
+        constraintsStretch.anchor  = GridBagConstraints.WEST;
+        constraintsStretch.insets  = constraints.insets;
+
+        GridBagConstraints constraintsLast = new GridBagConstraints();
+        constraintsLast.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLast.anchor    = GridBagConstraints.WEST;
+        constraintsLast.insets    = constraints.insets;
+
+        GridBagConstraints constraintsLastStretch = new GridBagConstraints();
+        constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLastStretch.fill      = GridBagConstraints.HORIZONTAL;
+        constraintsLastStretch.weightx   = 1.0;
+        constraintsLastStretch.anchor    = GridBagConstraints.WEST;
+        constraintsLastStretch.insets    = constraints.insets;
+
+        GridBagConstraints panelConstraints = new GridBagConstraints();
+        panelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        panelConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        panelConstraints.weightx   = 1.0;
+        panelConstraints.weighty   = 0.0;
+        panelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        panelConstraints.insets    = constraints.insets;
+
+        GridBagConstraints stretchPanelConstraints = new GridBagConstraints();
+        stretchPanelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        stretchPanelConstraints.fill      = GridBagConstraints.BOTH;
+        stretchPanelConstraints.weightx   = 1.0;
+        stretchPanelConstraints.weighty   = 1.0;
+        stretchPanelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        stretchPanelConstraints.insets    = constraints.insets;
+
+        GridBagConstraints labelConstraints = new GridBagConstraints();
+        labelConstraints.anchor = GridBagConstraints.CENTER;
+        labelConstraints.insets = new Insets(2, 10, 2, 10);
+
+        GridBagConstraints lastLabelConstraints = new GridBagConstraints();
+        lastLabelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        lastLabelConstraints.anchor    = GridBagConstraints.CENTER;
+        lastLabelConstraints.insets    = labelConstraints.insets;
+
+        GridBagConstraints advancedButtonConstraints = new GridBagConstraints();
+        advancedButtonConstraints.weightx = 1.0;
+        advancedButtonConstraints.weighty = 1.0;
+        advancedButtonConstraints.anchor  = GridBagConstraints.SOUTHWEST;
+        advancedButtonConstraints.insets  = new Insets(4, 4, 8, 4);
+
+        GridBagConstraints okButtonConstraints = new GridBagConstraints();
+        okButtonConstraints.weightx = 1.0;
+        okButtonConstraints.weighty = 1.0;
+        okButtonConstraints.anchor  = GridBagConstraints.SOUTHEAST;
+        okButtonConstraints.insets  = advancedButtonConstraints.insets;
+
+        GridBagConstraints cancelButtonConstraints = new GridBagConstraints();
+        cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        cancelButtonConstraints.weighty   = 1.0;
+        cancelButtonConstraints.anchor    = GridBagConstraints.SOUTHEAST;
+        cancelButtonConstraints.insets    = advancedButtonConstraints.insets;
+
+        GridBagLayout layout = new GridBagLayout();
+
+        Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
+
+        // Create the comments panel.
+        JPanel commentsPanel = new JPanel(layout);
+        commentsPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                 msg("comments")));
+
+        JScrollPane commentsScrollPane = new JScrollPane(commentsTextArea);
+        commentsScrollPane.setBorder(classNameTextField.getBorder());
+
+        commentsPanel.add(tip(commentsScrollPane, "commentsTip"), constraintsLastStretch);
+
+        // Create the keep option panel.
+        ButtonGroup keepButtonGroup = new ButtonGroup();
+        keepButtonGroup.add(keepClassesAndMembersRadioButton);
+        keepButtonGroup.add(keepClassMembersRadioButton);
+        keepButtonGroup.add(keepClassesWithMembersRadioButton);
+
+        JPanel keepOptionPanel = new JPanel(layout);
+        keepOptionPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                   msg("keepTitle")));
+
+        keepOptionPanel.add(tip(keepClassesAndMembersRadioButton,  "keepTip"),                   constraintsLastStretch);
+        keepOptionPanel.add(tip(keepClassMembersRadioButton,       "keepClassMembersTip"),       constraintsLastStretch);
+        keepOptionPanel.add(tip(keepClassesWithMembersRadioButton, "keepClassesWithMembersTip"), constraintsLastStretch);
+
+        // Create the allow option panel.
+        final JPanel allowOptionPanel = new JPanel(layout);
+        allowOptionPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                    msg("allowTitle")));
+
+        allowOptionPanel.add(tip(allowShrinkingCheckBox,    "allowShrinkingTip"),    constraintsLastStretch);
+        allowOptionPanel.add(tip(allowOptimizationCheckBox, "allowOptimizationTip"), constraintsLastStretch);
+        allowOptionPanel.add(tip(allowObfuscationCheckBox,  "allowObfuscationTip"),  constraintsLastStretch);
+
+        // Create the access panel.
+        JPanel accessPanel = new JPanel(layout);
+        accessPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                               msg("access")));
+
+        accessPanel.add(Box.createGlue(),                                labelConstraints);
+        accessPanel.add(tip(new JLabel(msg("required")), "requiredTip"), labelConstraints);
+        accessPanel.add(tip(new JLabel(msg("not")),      "notTip"),      labelConstraints);
+        accessPanel.add(tip(new JLabel(msg("dontCare")), "dontCareTip"), labelConstraints);
+        accessPanel.add(Box.createGlue(),                                constraintsLastStretch);
+
+        publicRadioButtons     = addRadioButtonTriplet("Public",     accessPanel);
+        finalRadioButtons      = addRadioButtonTriplet("Final",      accessPanel);
+        abstractRadioButtons   = addRadioButtonTriplet("Abstract",   accessPanel);
+        enumRadioButtons       = addRadioButtonTriplet("Enum",       accessPanel);
+        annotationRadioButtons = addRadioButtonTriplet("Annotation", accessPanel);
+        interfaceRadioButtons  = addRadioButtonTriplet("Interface",  accessPanel);
+
+        // Create the annotation type panel.
+        final JPanel annotationTypePanel = new JPanel(layout);
+        annotationTypePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                       msg("annotation")));
+
+        annotationTypePanel.add(tip(annotationTypeTextField, "classNameTip"), constraintsLastStretch);
+
+        // Create the class name panel.
+        JPanel classNamePanel = new JPanel(layout);
+        classNamePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                  msg("class")));
+
+        classNamePanel.add(tip(classNameTextField, "classNameTip"), constraintsLastStretch);
+
+        // Create the extends annotation type panel.
+        final JPanel extendsAnnotationTypePanel = new JPanel(layout);
+        extendsAnnotationTypePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                              msg("extendsImplementsAnnotation")));
+
+        extendsAnnotationTypePanel.add(tip(extendsAnnotationTypeTextField, "classNameTip"), constraintsLastStretch);
+
+        // Create the extends class name panel.
+        JPanel extendsClassNamePanel = new JPanel(layout);
+        extendsClassNamePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                         msg("extendsImplementsClass")));
+
+        extendsClassNamePanel.add(tip(extendsClassNameTextField, "classNameTip"), constraintsLastStretch);
+
+
+        // Create the class member list panel.
+        memberSpecificationsPanel = new MemberSpecificationsPanel(this, fullKeepOptions);
+        memberSpecificationsPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                             msg("classMembers")));
+
+        // Create the Advanced button.
+        final JButton advancedButton = new JButton(msg("basic"));
+        advancedButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                boolean visible = !allowOptionPanel.isVisible();
+
+                allowOptionPanel          .setVisible(visible);
+                annotationTypePanel       .setVisible(visible);
+                extendsAnnotationTypePanel.setVisible(visible);
+
+                advancedButton.setText(msg(visible ? "basic" : "advanced"));
+
+                pack();
+            }
+        });
+        advancedButton.doClick();
+
+        // Create the Ok button.
+        JButton okButton = new JButton(msg("ok"));
+        okButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                returnValue = APPROVE_OPTION;
+                hide();
+            }
+        });
+
+        // Create the Cancel button.
+        JButton cancelButton = new JButton(msg("cancel"));
+        cancelButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                hide();
+            }
+        });
+
+        // Add all panels to the main panel.
+        JPanel mainPanel = new JPanel(layout);
+        mainPanel.add(tip(commentsPanel,              "commentsTip"),                    panelConstraints);
+        if (fullKeepOptions)
+        {
+            mainPanel.add(tip(keepOptionPanel,        "keepTitleTip"),                   panelConstraints);
+            mainPanel.add(tip(allowOptionPanel,       "allowTitleTip"),                  panelConstraints);
+        }
+        mainPanel.add(tip(accessPanel,                "accessTip"),                      panelConstraints);
+        mainPanel.add(tip(annotationTypePanel,        "annotationTip"),                  panelConstraints);
+        mainPanel.add(tip(classNamePanel,             "classTip"),                       panelConstraints);
+        mainPanel.add(tip(extendsAnnotationTypePanel, "extendsImplementsAnnotationTip"), panelConstraints);
+        mainPanel.add(tip(extendsClassNamePanel,      "extendsImplementsClassTip"),      panelConstraints);
+        mainPanel.add(tip(memberSpecificationsPanel,  "classMembersTip"),                stretchPanelConstraints);
+
+        mainPanel.add(tip(advancedButton, "advancedTip"), advancedButtonConstraints);
+        mainPanel.add(okButton,                           okButtonConstraints);
+        mainPanel.add(cancelButton,                       cancelButtonConstraints);
+
+        getContentPane().add(new JScrollPane(mainPanel));
+    }
+
+
+    /**
+     * Adds a JLabel and three JRadioButton instances in a ButtonGroup to the
+     * given panel with a GridBagLayout, and returns the buttons in an array.
+     */
+    private JRadioButton[] addRadioButtonTriplet(String labelText,
+                                                 JPanel panel)
+    {
+        GridBagConstraints labelConstraints = new GridBagConstraints();
+        labelConstraints.anchor = GridBagConstraints.WEST;
+        labelConstraints.insets = new Insets(2, 10, 2, 10);
+
+        GridBagConstraints buttonConstraints = new GridBagConstraints();
+        buttonConstraints.insets = labelConstraints.insets;
+
+        GridBagConstraints lastGlueConstraints = new GridBagConstraints();
+        lastGlueConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        lastGlueConstraints.weightx   = 1.0;
+
+        // Create the radio buttons.
+        JRadioButton radioButton0 = new JRadioButton();
+        JRadioButton radioButton1 = new JRadioButton();
+        JRadioButton radioButton2 = new JRadioButton();
+
+        // Put them in a button group.
+        ButtonGroup buttonGroup = new ButtonGroup();
+        buttonGroup.add(radioButton0);
+        buttonGroup.add(radioButton1);
+        buttonGroup.add(radioButton2);
+
+        // Add the label and the buttons to the panel.
+        panel.add(new JLabel(labelText), labelConstraints);
+        panel.add(radioButton0,          buttonConstraints);
+        panel.add(radioButton1,          buttonConstraints);
+        panel.add(radioButton2,          buttonConstraints);
+        panel.add(Box.createGlue(),      lastGlueConstraints);
+
+        return new JRadioButton[]
+        {
+             radioButton0,
+             radioButton1,
+             radioButton2
+        };
+    }
+
+
+    /**
+     * Sets the KeepClassSpecification to be represented in this dialog.
+     */
+    public void setKeepSpecification(KeepClassSpecification keepClassSpecification)
+    {
+        boolean markClasses       = keepClassSpecification.markClasses;
+        boolean markConditionally = keepClassSpecification.markConditionally;
+        boolean allowShrinking    = keepClassSpecification.allowShrinking;
+        boolean allowOptimization = keepClassSpecification.allowOptimization;
+        boolean allowObfuscation  = keepClassSpecification.allowObfuscation;
+
+        // Figure out the proper keep radio button and set it.
+        JRadioButton keepOptionRadioButton =
+            markConditionally ? keepClassesWithMembersRadioButton :
+            markClasses       ? keepClassesAndMembersRadioButton  :
+                                keepClassMembersRadioButton;
+
+        keepOptionRadioButton.setSelected(true);
+
+        // Set the allow radio buttons.
+        allowShrinkingCheckBox   .setSelected(allowShrinking);
+        allowOptimizationCheckBox.setSelected(allowOptimization);
+        allowObfuscationCheckBox .setSelected(allowObfuscation);
+
+        setClassSpecification(keepClassSpecification);
+    }
+
+
+    /**
+     * Sets the ClassSpecification to be represented in this dialog.
+     */
+    public void setClassSpecification(ClassSpecification classSpecification)
+    {
+        String comments              = classSpecification.comments;
+        String annotationType        = classSpecification.annotationType;
+        String className             = classSpecification.className;
+        String extendsAnnotationType = classSpecification.extendsAnnotationType;
+        String extendsClassName      = classSpecification.extendsClassName;
+        List   keepFieldOptions      = classSpecification.fieldSpecifications;
+        List   keepMethodOptions     = classSpecification.methodSpecifications;
+
+        // Set the comments text area.
+        commentsTextArea.setText(comments == null ? "" : comments);
+
+        // Set the access radio buttons.
+        setClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_PUBLIC,      publicRadioButtons);
+        setClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_FINAL,       finalRadioButtons);
+        setClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_ABSTRACT,    abstractRadioButtons);
+        setClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_ENUM,        enumRadioButtons);
+        setClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_ANNOTATTION, annotationRadioButtons);
+        setClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_INTERFACE,   interfaceRadioButtons);
+
+        // Set the class and annotation text fields.
+        annotationTypeTextField       .setText(annotationType        == null ? ""  : ClassUtil.externalType(annotationType));
+        classNameTextField            .setText(className             == null ? "*" : ClassUtil.externalClassName(className));
+        extendsAnnotationTypeTextField.setText(extendsAnnotationType == null ? ""  : ClassUtil.externalType(extendsAnnotationType));
+        extendsClassNameTextField     .setText(extendsClassName      == null ? ""  : ClassUtil.externalClassName(extendsClassName));
+
+        // Set the keep class member option list.
+        memberSpecificationsPanel.setMemberSpecifications(keepFieldOptions, keepMethodOptions);
+    }
+
+
+    /**
+     * Returns the KeepClassSpecification currently represented in this dialog.
+     */
+    public KeepClassSpecification getKeepSpecification()
+    {
+        boolean markClasses       = !keepClassMembersRadioButton     .isSelected();
+        boolean markConditionally = keepClassesWithMembersRadioButton.isSelected();
+        boolean allowShrinking    = allowShrinkingCheckBox           .isSelected();
+        boolean allowOptimization = allowOptimizationCheckBox        .isSelected();
+        boolean allowObfuscation  = allowObfuscationCheckBox         .isSelected();
+
+        return new KeepClassSpecification(markClasses,
+                                     markConditionally,
+                                     allowShrinking,
+                                     allowOptimization,
+                                     allowObfuscation,
+                                     getClassSpecification());
+    }
+
+
+    /**
+     * Returns the ClassSpecification currently represented in this dialog.
+     */
+    public ClassSpecification getClassSpecification()
+    {
+        String comments              = commentsTextArea.getText();
+        String annotationType        = annotationTypeTextField.getText();
+        String className             = classNameTextField.getText();
+        String extendsAnnotationType = extendsAnnotationTypeTextField.getText();
+        String extendsClassName      = extendsClassNameTextField.getText();
+
+        ClassSpecification classSpecification =
+            new ClassSpecification(comments.equals("")              ? null : comments,
+                                   0,
+                                   0,
+                                   annotationType.equals("")        ? null : ClassUtil.internalType(annotationType),
+                                   className.equals("") ||
+                                   className.equals("*")            ? null : ClassUtil.internalClassName(className),
+                                   extendsAnnotationType.equals("") ? null : ClassUtil.internalType(extendsAnnotationType),
+                                   extendsClassName.equals("")      ? null : ClassUtil.internalClassName(extendsClassName));
+
+        // Also get the access radio button settings.
+        getClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_PUBLIC,      publicRadioButtons);
+        getClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_FINAL,       finalRadioButtons);
+        getClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_ABSTRACT,    abstractRadioButtons);
+        getClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_ENUM,        enumRadioButtons);
+        getClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_ANNOTATTION, annotationRadioButtons);
+        getClassSpecificationRadioButtons(classSpecification, ClassConstants.INTERNAL_ACC_INTERFACE,   interfaceRadioButtons);
+
+        // Get the keep class member option lists.
+        classSpecification.fieldSpecifications  = memberSpecificationsPanel.getMemberSpecifications(true);
+        classSpecification.methodSpecifications = memberSpecificationsPanel.getMemberSpecifications(false);
+
+        return classSpecification;
+    }
+
+
+    /**
+     * Shows this dialog. This method only returns when the dialog is closed.
+     *
+     * @return <code>CANCEL_OPTION</code> or <code>APPROVE_OPTION</code>,
+     *         depending on the choice of the user.
+     */
+    public int showDialog()
+    {
+        returnValue = CANCEL_OPTION;
+
+        // Open the dialog in the right place, then wait for it to be closed,
+        // one way or another.
+        pack();
+        setLocationRelativeTo(getOwner());
+        show();
+
+        return returnValue;
+    }
+
+
+    /**
+     * Sets the appropriate radio button of a given triplet, based on the access
+     * flags of the given keep option.
+     */
+    private void setClassSpecificationRadioButtons(ClassSpecification classSpecification,
+                                                   int                flag,
+                                                   JRadioButton[]     radioButtons)
+    {
+        int index = (classSpecification.requiredSetAccessFlags   & flag) != 0 ? 0 :
+                    (classSpecification.requiredUnsetAccessFlags & flag) != 0 ? 1 :
+                                                                                 2;
+        radioButtons[index].setSelected(true);
+    }
+
+
+    /**
+     * Updates the access flag of the given keep option, based on the given radio
+     * button triplet.
+     */
+    private void getClassSpecificationRadioButtons(ClassSpecification classSpecification,
+                                                   int                flag,
+                                                   JRadioButton[]     radioButtons)
+    {
+        if      (radioButtons[0].isSelected())
+        {
+            classSpecification.requiredSetAccessFlags   |= flag;
+        }
+        else if (radioButtons[1].isSelected())
+        {
+            classSpecification.requiredUnsetAccessFlags |= flag;
+        }
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+}
diff --git a/src/proguard/gui/ClassSpecificationsPanel.java b/src/proguard/gui/ClassSpecificationsPanel.java
new file mode 100644
index 0000000..2cf0b1d
--- /dev/null
+++ b/src/proguard/gui/ClassSpecificationsPanel.java
@@ -0,0 +1,231 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.ClassSpecification;
+import proguard.classfile.util.ClassUtil;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import java.util.List;
+
+
+/**
+ * This <code>ListPanel</code> allows the user to add, edit, move, and remove
+ * ClassSpecification entries in a list.
+ *
+ * @author Eric Lafortune
+ */
+class ClassSpecificationsPanel extends ListPanel
+{
+    protected final ClassSpecificationDialog classSpecificationDialog;
+
+
+    public ClassSpecificationsPanel(JFrame owner, boolean fullKeepOptions)
+    {
+        super();
+
+        list.setCellRenderer(new MyListCellRenderer());
+
+        classSpecificationDialog = new ClassSpecificationDialog(owner, fullKeepOptions);
+
+        addAddButton();
+        addEditButton();
+        addRemoveButton();
+        addUpButton();
+        addDownButton();
+
+        enableSelectionButtons();
+    }
+
+
+    protected void addAddButton()
+    {
+        JButton addButton = new JButton(msg("add"));
+        addButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                setClassSpecification(createClassSpecification());
+                int returnValue = classSpecificationDialog.showDialog();
+                if (returnValue == ClassSpecificationDialog.APPROVE_OPTION)
+                {
+                    // Add the new element.
+                    addElement(getClassSpecification());
+                }
+            }
+        });
+
+        addButton(tip(addButton, "addTip"));
+    }
+
+
+    protected void addEditButton()
+    {
+        JButton editButton = new JButton(msg("edit"));
+        editButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                ClassSpecification selectedClassSpecification =
+                    (ClassSpecification)list.getSelectedValue();
+
+                setClassSpecification(selectedClassSpecification);
+                int returnValue = classSpecificationDialog.showDialog();
+                if (returnValue == ClassSpecificationDialog.APPROVE_OPTION)
+                {
+                    // Replace the old element.
+                    setElementAt(getClassSpecification(),
+                                 list.getSelectedIndex());
+                }
+            }
+        });
+
+        addButton(tip(editButton, "editTip"));
+    }
+
+
+    protected ClassSpecification createClassSpecification()
+    {
+        return new ClassSpecification();
+    }
+
+
+    protected void setClassSpecification(ClassSpecification classSpecification)
+    {
+        classSpecificationDialog.setClassSpecification(classSpecification);
+    }
+
+
+    protected ClassSpecification getClassSpecification()
+    {
+        return classSpecificationDialog.getClassSpecification();
+    }
+
+
+    /**
+     * Sets the ClassSpecification objects to be represented in this panel.
+     */
+    public void setClassSpecifications(List classSpecifications)
+    {
+        listModel.clear();
+
+        if (classSpecifications != null)
+        {
+            for (int index = 0; index < classSpecifications.size(); index++)
+            {
+                listModel.addElement(classSpecifications.get(index));
+            }
+        }
+
+        // Make sure the selection buttons are properly enabled,
+        // since the clear method doesn't seem to notify the listener.
+        enableSelectionButtons();
+    }
+
+
+    /**
+     * Returns the ClassSpecification objects currently represented in this panel.
+     */
+    public List getClassSpecifications()
+    {
+        int size = listModel.size();
+        if (size == 0)
+        {
+            return null;
+        }
+
+        List classSpecifications = new ArrayList(size);
+        for (int index = 0; index < size; index++)
+        {
+            classSpecifications.add(listModel.get(index));
+        }
+
+        return classSpecifications;
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+
+
+    /**
+     * This ListCellRenderer renders ClassSpecification objects.
+     */
+    private static class MyListCellRenderer implements ListCellRenderer
+    {
+        private final JLabel label = new JLabel();
+
+
+        // Implementations for ListCellRenderer.
+
+        public Component getListCellRendererComponent(JList   list,
+                                                      Object  value,
+                                                      int     index,
+                                                      boolean isSelected,
+                                                      boolean cellHasFocus)
+        {
+            ClassSpecification classSpecification = (ClassSpecification)value;
+
+            String comments = classSpecification.comments;
+
+            label.setText(comments                            != null ? comments.trim()                                                                                        :
+                          classSpecification.className        != null ? (msg("class") + ' ' + ClassUtil.externalClassName(classSpecification.className))               :
+                          classSpecification.extendsClassName != null ? (msg("extensionsOf") + ' ' + ClassUtil.externalClassName(classSpecification.extendsClassName)) :
+                                                                        (msg("specificationNumber") + index));
+
+            if (isSelected)
+            {
+                label.setBackground(list.getSelectionBackground());
+                label.setForeground(list.getSelectionForeground());
+            }
+            else
+            {
+                label.setBackground(list.getBackground());
+                label.setForeground(list.getForeground());
+            }
+
+            label.setOpaque(true);
+
+            return label;
+        }
+    }
+}
diff --git a/src/proguard/gui/ExtensionFileFilter.java b/src/proguard/gui/ExtensionFileFilter.java
new file mode 100644
index 0000000..d67be40
--- /dev/null
+++ b/src/proguard/gui/ExtensionFileFilter.java
@@ -0,0 +1,78 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import javax.swing.filechooser.FileFilter;
+import java.io.File;
+
+
+/**
+ * This <code>FileFilter</code> accepts files that end in one of the given
+ * extensions.
+ *
+ * @author Eric Lafortune
+ */
+final class ExtensionFileFilter extends FileFilter
+{
+    private final String   description;
+    private final String[] extensions;
+
+
+    /**
+     * Creates a new ExtensionFileFilter.
+     * @param description a description of the filter.
+     * @param extensions  an array of acceptable extensions.
+     */
+    public ExtensionFileFilter(String description, String[] extensions)
+    {
+        this.description = description;
+        this.extensions  = extensions;
+    }
+
+
+    // Implemntations for FileFilter
+
+    public String getDescription()
+    {
+        return description;
+    }
+
+
+    public boolean accept(File file)
+    {
+        if (file.isDirectory())
+        {
+            return true;
+        }
+
+        String fileName = file.getName().toLowerCase();
+
+        for (int index = 0; index < extensions.length; index++)
+        {
+            if (fileName.endsWith(extensions[index]))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/src/proguard/gui/FilterBuilder.java b/src/proguard/gui/FilterBuilder.java
new file mode 100644
index 0000000..e46193f
--- /dev/null
+++ b/src/proguard/gui/FilterBuilder.java
@@ -0,0 +1,208 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import javax.swing.*;
+
+/**
+ * This class builds filters corresponding to the selections and names of a
+ * given list of check boxes.
+ */
+public class FilterBuilder
+{
+    private JCheckBox[] checkBoxes;
+    private char        separator;
+
+
+    /**
+     * Creates a new FilterBuilder.
+     * @param checkBoxes the check boxes with names and selections that should
+     *                   be reflected in the output filter.
+     * @param separator  the separator for the names in the check boxes.
+     */
+    public FilterBuilder(JCheckBox[] checkBoxes, char separator)
+    {
+        this.checkBoxes = checkBoxes;
+        this.separator  = separator;
+    }
+
+
+    /**
+     * Builds a filter for the current names and selections of the check boxes.
+     */
+    public String buildFilter()
+    {
+        StringBuffer positive = new StringBuffer();
+        StringBuffer negative = new StringBuffer();
+
+        buildFilter("", positive, negative);
+
+        return positive.length() <= negative.length() ?
+            positive.toString() :
+            negative.toString();
+    }
+
+
+    /**
+     * Builds two versions of the filter for the given prefix.
+     * @param prefix   the prefix.
+     * @param positive the filter to be extended, assuming the matching
+     *                 strings are accepted.
+     * @param negative the filter to be extended, assuming the matching
+     *                 strings are rejected.
+     */
+    private void buildFilter(String       prefix,
+                             StringBuffer positive,
+                             StringBuffer negative)
+    {
+        int positiveCount = 0;
+        int negativeCount = 0;
+
+        // Count all selected and unselected check boxes with the prefix.
+        for (int index = 0; index < checkBoxes.length; index++)
+        {
+            JCheckBox checkBox = checkBoxes[index];
+            String    name     = checkBox.getText();
+
+            if (name.startsWith(prefix))
+            {
+                if (checkBox.isSelected())
+                {
+                    positiveCount++;
+                }
+                else
+                {
+                    negativeCount++;
+                }
+            }
+        }
+
+        // Are there only unselected check boxes?
+        if (positiveCount == 0)
+        {
+            // Extend the positive filter with exceptions and return.
+            if (positive.length() > 0)
+            {
+                positive.append(',');
+            }
+            positive.append('!').append(prefix);
+            if (prefix.length() == 0 ||
+                prefix.charAt(prefix.length()-1) == separator)
+            {
+                positive.append('*');
+            }
+
+            return;
+        }
+
+        // Are there only selected check boxes?
+        if (negativeCount == 0)
+        {
+            // Extend the negative filter with exceptions and return.
+            if (negative.length() > 0)
+            {
+                negative.append(',');
+            }
+            negative.append(prefix);
+            if (prefix.length() == 0 ||
+                prefix.charAt(prefix.length()-1) == separator)
+            {
+                negative.append('*');
+            }
+
+            return;
+        }
+
+        // Create new positive and negative filters for names starting with the
+        // prefix only.
+        StringBuffer positiveFilter = new StringBuffer();
+        StringBuffer negativeFilter = new StringBuffer();
+
+        String newPrefix = null;
+
+        for (int index = 0; index < checkBoxes.length; index++)
+        {
+            String name = checkBoxes[index].getText();
+
+            if (name.startsWith(prefix))
+            {
+                if (newPrefix == null ||
+                    !name.startsWith(newPrefix))
+                {
+                    int prefixIndex =
+                        name.indexOf(separator, prefix.length()+1);
+
+                    newPrefix = prefixIndex >= 0 ?
+                        name.substring(0, prefixIndex+1) :
+                        name;
+
+                    buildFilter(newPrefix,
+                                positiveFilter,
+                                negativeFilter);
+                }
+            }
+        }
+
+        // Extend the positive filter.
+        if (positiveFilter.length() <= negativeFilter.length() + prefix.length() + 3)
+        {
+            if (positive.length() > 0 &&
+                positiveFilter.length() > 0)
+            {
+                positive.append(',');
+            }
+
+            positive.append(positiveFilter);
+        }
+        else
+        {
+            if (positive.length() > 0 &&
+                negativeFilter.length() > 0)
+            {
+                positive.append(',');
+            }
+
+            positive.append(negativeFilter).append(",!").append(prefix).append('*');
+        }
+
+        // Extend the negative filter.
+        if (negativeFilter.length() <= positiveFilter.length() + prefix.length() + 4)
+        {
+            if (negative.length() > 0 &&
+                negativeFilter.length() > 0)
+            {
+                negative.append(',');
+            }
+
+            negative.append(negativeFilter);
+        }
+        else
+        {
+            if (negative.length() > 0 &&
+                positiveFilter.length() > 0)
+            {
+                negative.append(',');
+            }
+
+            negative.append(positiveFilter).append(',').append(prefix).append('*');
+        }
+    }
+}
diff --git a/src/proguard/gui/FilterDialog.java b/src/proguard/gui/FilterDialog.java
new file mode 100644
index 0000000..1567a31
--- /dev/null
+++ b/src/proguard/gui/FilterDialog.java
@@ -0,0 +1,320 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.util.ListUtil;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+
+/**
+ * This <code>JDialog</code> allows the user to enter a String.
+ *
+ * @author Eric Lafortune
+ */
+public class FilterDialog extends JDialog
+{
+    /**
+     * Return value if the dialog is canceled (with the Cancel button or by
+     * closing the dialog window).
+     */
+    public static final int CANCEL_OPTION = 1;
+
+    /**
+     * Return value if the dialog is approved (with the Ok button).
+     */
+    public static final int APPROVE_OPTION = 0;
+
+    private static final String DEFAULT_FILTER     = "**";
+    private static final String DEFAULT_JAR_FILTER = "**.jar";
+    private static final String DEFAULT_WAR_FILTER = "**.war";
+    private static final String DEFAULT_EAR_FILTER = "**.ear";
+    private static final String DEFAULT_ZIP_FILTER = "**.zip";
+
+
+    private final JTextField filterTextField    = new JTextField(40);
+    private final JTextField jarFilterTextField = new JTextField(40);
+    private final JTextField warFilterTextField = new JTextField(40);
+    private final JTextField earFilterTextField = new JTextField(40);
+    private final JTextField zipFilterTextField = new JTextField(40);
+    private int        returnValue;
+
+
+    public FilterDialog(JFrame owner,
+                        String explanation)
+    {
+        super(owner, true);
+        setResizable(true);
+
+        // Create some constraints that can be reused.
+        GridBagConstraints textConstraints = new GridBagConstraints();
+        textConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        textConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        textConstraints.weightx   = 1.0;
+        textConstraints.weighty   = 1.0;
+        textConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        textConstraints.insets    = new Insets(10, 10, 10, 10);
+
+        GridBagConstraints labelConstraints = new GridBagConstraints();
+        labelConstraints.anchor = GridBagConstraints.WEST;
+        labelConstraints.insets = new Insets(1, 2, 1, 2);
+
+        GridBagConstraints textFieldConstraints = new GridBagConstraints();
+        textFieldConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        textFieldConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        textFieldConstraints.weightx   = 1.0;
+        textFieldConstraints.anchor    = GridBagConstraints.WEST;
+        textFieldConstraints.insets    = labelConstraints.insets;
+
+        GridBagConstraints panelConstraints = new GridBagConstraints();
+        panelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        panelConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        panelConstraints.weightx   = 1.0;
+        panelConstraints.weighty   = 0.0;
+        panelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        panelConstraints.insets    = labelConstraints.insets;
+
+        GridBagConstraints okButtonConstraints = new GridBagConstraints();
+        okButtonConstraints.weightx = 1.0;
+        okButtonConstraints.weighty = 1.0;
+        okButtonConstraints.anchor  = GridBagConstraints.SOUTHEAST;
+        okButtonConstraints.insets  = new Insets(4, 4, 8, 4);
+
+        GridBagConstraints cancelButtonConstraints = new GridBagConstraints();
+        cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        cancelButtonConstraints.weighty   = 1.0;
+        cancelButtonConstraints.anchor    = GridBagConstraints.SOUTHEAST;
+        cancelButtonConstraints.insets    = okButtonConstraints.insets;
+
+        GridBagLayout layout = new GridBagLayout();
+
+        Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
+
+        // Create the panel with the explanation.
+        JTextArea explanationTextArea = new JTextArea(explanation, 3, 0);
+        explanationTextArea.setOpaque(false);
+        explanationTextArea.setEditable(false);
+        explanationTextArea.setLineWrap(true);
+        explanationTextArea.setWrapStyleWord(true);
+
+        // Create the filter labels.
+        JLabel filterLabel    = new JLabel(msg("nameFilter"));
+        JLabel jarFilterLabel = new JLabel(msg("jarNameFilter"));
+        JLabel warFilterLabel = new JLabel(msg("warNameFilter"));
+        JLabel earFilterLabel = new JLabel(msg("earNameFilter"));
+        JLabel zipFilterLabel = new JLabel(msg("zipNameFilter"));
+
+        // Create the filter panel.
+        JPanel filterPanel = new JPanel(layout);
+        filterPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                               msg("filters")));
+
+        filterPanel.add(explanationTextArea, textConstraints);
+
+        filterPanel.add(tip(filterLabel,        "nameFilterTip"),     labelConstraints);
+        filterPanel.add(tip(filterTextField,    "fileNameFilterTip"), textFieldConstraints);
+
+        filterPanel.add(tip(jarFilterLabel,     "jarNameFilterTip"),  labelConstraints);
+        filterPanel.add(tip(jarFilterTextField, "fileNameFilterTip"), textFieldConstraints);
+
+        filterPanel.add(tip(warFilterLabel,     "warNameFilterTip"),  labelConstraints);
+        filterPanel.add(tip(warFilterTextField, "fileNameFilterTip"), textFieldConstraints);
+
+        filterPanel.add(tip(earFilterLabel,     "earNameFilterTip"),  labelConstraints);
+        filterPanel.add(tip(earFilterTextField, "fileNameFilterTip"), textFieldConstraints);
+
+        filterPanel.add(tip(zipFilterLabel,     "zipNameFilterTip"),  labelConstraints);
+        filterPanel.add(tip(zipFilterTextField, "fileNameFilterTip"), textFieldConstraints);
+
+
+        JButton okButton = new JButton(msg("ok"));
+        okButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                returnValue = APPROVE_OPTION;
+                hide();
+            }
+        });
+
+        JButton cancelButton = new JButton(msg("cancel"));
+        cancelButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                hide();
+            }
+        });
+
+        // Add all panels to the main panel.
+        JPanel mainPanel = new JPanel(layout);
+        mainPanel.add(filterPanel,  panelConstraints);
+        mainPanel.add(okButton,     okButtonConstraints);
+        mainPanel.add(cancelButton, cancelButtonConstraints);
+
+        getContentPane().add(mainPanel);
+    }
+
+
+    /**
+     * Sets the filter to be represented in this dialog.
+     */
+    public void setFilter(List filter)
+    {
+        filterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter) : DEFAULT_FILTER);
+    }
+
+
+    /**
+     * Returns the filter currently represented in this dialog.
+     */
+    public List getFilter()
+    {
+        String filter = filterTextField.getText();
+
+        return filter.equals(DEFAULT_FILTER) ? null : ListUtil.commaSeparatedList(filter);
+    }
+
+
+    /**
+     * Sets the jar filter to be represented in this dialog.
+     */
+    public void setJarFilter(List filter)
+    {
+        jarFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter) : DEFAULT_JAR_FILTER);
+    }
+
+
+    /**
+     * Returns the jar filter currently represented in this dialog.
+     */
+    public List getJarFilter()
+    {
+        String filter = jarFilterTextField.getText();
+
+        return filter.equals(DEFAULT_JAR_FILTER) ? null : ListUtil.commaSeparatedList(filter);
+    }
+
+
+    /**
+     * Sets the war filter to be represented in this dialog.
+     */
+    public void setWarFilter(List filter)
+    {
+        warFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter) : DEFAULT_WAR_FILTER);
+    }
+
+
+    /**
+     * Returns the war filter currently represented in this dialog.
+     */
+    public List getWarFilter()
+    {
+        String filter = warFilterTextField.getText();
+
+        return filter.equals(DEFAULT_WAR_FILTER) ? null : ListUtil.commaSeparatedList(filter);
+    }
+
+
+    /**
+     * Sets the ear filter to be represented in this dialog.
+     */
+    public void setEarFilter(List filter)
+    {
+        earFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter) : DEFAULT_EAR_FILTER);
+    }
+
+
+    /**
+     * Returns the ear filter currently represented in this dialog.
+     */
+    public List getEarFilter()
+    {
+        String filter = earFilterTextField.getText();
+
+        return filter.equals(DEFAULT_EAR_FILTER) ? null : ListUtil.commaSeparatedList(filter);
+    }
+
+
+    /**
+     * Sets the zip filter to be represented in this dialog.
+     */
+    public void setZipFilter(List filter)
+    {
+        zipFilterTextField.setText(filter != null ? ListUtil.commaSeparatedString(filter) : DEFAULT_ZIP_FILTER);
+    }
+
+
+    /**
+     * Returns the zip filter currently represented in this dialog.
+     */
+    public List getZipFilter()
+    {
+        String filter = zipFilterTextField.getText();
+
+        return filter.equals(DEFAULT_ZIP_FILTER) ? null : ListUtil.commaSeparatedList(filter);
+    }
+
+
+    /**
+     * Shows this dialog. This method only returns when the dialog is closed.
+     *
+     * @return <code>CANCEL_OPTION</code> or <code>APPROVE_OPTION</code>,
+     *         depending on the choice of the user.
+     */
+    public int showDialog()
+    {
+        returnValue = CANCEL_OPTION;
+
+        // Open the dialog in the right place, then wait for it to be closed,
+        // one way or another.
+        pack();
+        setLocationRelativeTo(getOwner());
+        show();
+
+        return returnValue;
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+}
diff --git a/src/proguard/gui/GUIResources.java b/src/proguard/gui/GUIResources.java
new file mode 100644
index 0000000..85d582c
--- /dev/null
+++ b/src/proguard/gui/GUIResources.java
@@ -0,0 +1,56 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+
+/**
+ * This class provides some utility methods for working with resource bundles.
+ *
+ * @author Eric Lafortune
+ */
+class GUIResources
+{
+    private static final ResourceBundle messages  = ResourceBundle.getBundle(GUIResources.class.getName());
+    private static final MessageFormat  formatter = new MessageFormat("");
+
+
+    /**
+     * Returns an internationalized message, based on its key.
+     */
+    public static String getMessage(String messageKey)
+    {
+        return messages.getString(messageKey);
+    }
+
+
+    /**
+     * Returns an internationalized, formatted message, based on its key, with
+     * the given arguments.
+     */
+    public static String getMessage(String messageKey, Object[] messageArguments)
+    {
+        formatter.applyPattern(messages.getString(messageKey));
+        return formatter.format(messageArguments);
+    }
+}
diff --git a/src/proguard/gui/GUIResources.properties b/src/proguard/gui/GUIResources.properties
new file mode 100644
index 0000000..86ab7a1
--- /dev/null
+++ b/src/proguard/gui/GUIResources.properties
@@ -0,0 +1,631 @@
+# ProGuard -- shrinking, optimization, and obfuscation of Java class files.
+# Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+
+#
+# Tab names.
+#
+proGuardTab     = ProGuard
+inputOutputTab  = Input/Output
+shrinkingTab    = Shrinking
+obfuscationTab  = Obfuscation
+optimizationTab = Optimization
+informationTab  = Information
+processTab      = Process
+reTraceTab      = ReTrace
+
+#
+# Splash text.
+#
+developed       = Developed by Eric Lafortune
+shrinking       = Shrinking
+optimization    = Optimization
+obfuscation     = Obfuscation
+preverification = Preverification
+
+#
+# Panel titles.
+#
+welcome                       = Welcome to ProGuard, version 4.4
+options                       = Options
+keepAdditional                = Keep additional classes and class members
+keepNamesAdditional           = Keep additional class names and class member names
+assumeNoSideEffectsAdditional = Assume no side effects for additional methods
+whyAreYouKeeping              = Why are you keeping
+preverificationAndTargeting   = Preverification and targeting
+consistencyAndCorrectness     = Consistency and correctness
+processingConsole             = Processing console
+reTraceSettings               = ReTrace settings
+deobfuscatedStackTrace        = De-obfuscated stack trace
+
+keepAdditionalTip = \
+  If required, keep additional classes, fields, and methods as entry points.
+keepNamesAdditionalTip = \
+  If required, keep the names of additional classes, fields, and methods.
+assumeNoSideEffectsAdditionalTip = \
+  <html>Optionally specify additional methods that don't have any side effects.<br>\
+  <i>Only add entries if you know what you're doing!</i></html>
+whyAreYouKeepingTip = \
+  Ask ProGuard why it is keeping certain classes, fields, or methods.
+
+#
+# Info texts.
+#
+proGuardInfo = \
+  ProGuard is a free class file shrinker, optimizer, obfuscator, and preverifier.\
+  \n\n\
+  With this GUI, you can create, load, modify, and save ProGuard configurations. \
+  \n\
+  You can then process your code right away, or you can run ProGuard from the \
+  command line using your saved configuration. \
+  \n\n\
+  With the ReTrace part of this GUI you can de-obfuscate your stack traces.\
+  \n\n\
+  ProGuard and ReTrace are written and maintained by Eric Lafortune.\
+  \n\n\
+  Distributed under the GNU General Public License.\
+  \n\
+  Copyright (c) 2002-2009.
+
+processingInfo = \
+  You can now start processing your code, \
+  or you can run ProGuard from the command line using your saved configuration.
+
+reTraceInfo = \
+  If you had ProGuard write out a mapping file, \
+  you can de-obfuscate your obfuscated stack traces with ReTrace!\
+  \n\n\
+  You can load an obfuscated stack trace from a file, \
+  or you can paste it straight into the text area above.
+
+#
+# Titles and labels corresponding to common ProGuard options.
+#
+programJars = Program jars, wars, ears, zips, and directories
+libraryJars = Library jars, wars, ears, zips, and directories
+
+shrink                           = Shrink
+printUsage                       = Print usage
+
+optimize                         = Optimize
+allowAccessModification          = Allow access modification
+mergeInterfacesAggressively      = Merge interfaces aggressively
+optimizations                    = Optimizations
+optimizationPasses               = Optimization passes
+
+obfuscate                        = Obfuscate
+printMapping                     = Print mapping
+applyMapping                     = Apply mapping
+obfuscationDictionary            = Obfuscation dictionary
+classObfuscationDictionary       = Class obfuscation dictionary
+packageObfuscationDictionary     = Package obfuscation dictionary
+overloadAggressively             = Overload aggressively
+useUniqueClassMemberNames        = Use unique class member names
+keepPackageNames                 = Keep package names
+flattenPackageHierarchy          = Flatten package hierarchy
+repackageClasses                 = Repackage classes
+useMixedCaseClassNames           = Use mixed-case class names
+keepAttributes                   = Keep attributes
+renameSourceFileAttribute        = Rename SourceFile attribute
+adaptClassStrings                = Adapt class strings
+adaptResourceFileNames           = Adapt resource file names
+adaptResourceFileContents        = Adapt resource file contents
+
+preverify                        = Preverify
+microEdition                     = Micro Edition
+
+verbose                          = Verbose
+note                             = Note potential mistakes in the configuration
+warn                             = Warn about possibly erronous input
+ignoreWarnings                   = Ignore warnings about possibly erronous input
+skipNonPublicLibraryClasses      = Skip non-public library classes
+skipNonPublicLibraryClassMembers = Skip non-public library class members
+keepDirectories                  = Keep directories
+forceProcessing                  = Force processing
+target                           = Target
+targets                          = 1.0,1.1,1.2,1.3,1.4,1.5,1.6
+printSeeds                       = Print seeds
+printConfiguration               = Print configuration
+dump                             = Print class files
+
+mappingFile                      = Mapping file
+obfuscatedStackTrace             = Obfuscated stack trace
+
+programJarsTip = \
+  <html>The input jars (wars, ears, zips, directories), followed by<br>\
+  their corresponding output jars (wars, ears, zips, directories).</html>
+libraryJarsTip = \
+  <html>The library jars (wars, ears, zips, directories), on which the program jars depend.<br>\
+  The library jars are required for processing, but they are not copied to the output.</html>
+
+shrinkTip = \
+  Remove unused classes, fields, and methods from the output.
+printUsageTip = \
+  Print out the list of unused classes, fields, and methods.
+
+optimizeTip = \
+  Optimize the bytecode of the processed classes.
+allowAccessModificationTip = \
+  Allow the optimization step to modify the access modifiers of classes, fields, and methods.
+mergeInterfacesAggressivelyTip = \
+  <html>Allow interfaces to be merged, even if their implementations don't implement all<br>\
+  interface methods.  This is not allowed in the Java language, but it is allowed in bytecode.</html>
+optimizationsTip = \
+  Specify the types of optimizations to be performed.
+optimizationsFilterTip = \
+  A filter for the names of the optimizations to be performed.
+optimizationsSelectTip = \
+  Select from the currently available optimizations...
+optimizationPassesTip = \
+  Specify the number of optimization passes to be performed.
+
+obfuscateTip = \
+  Obfuscate the names of the processed classes, fields, and methods.
+printMappingTip = \
+  Print out the obfuscation mapping of original names to obfuscated names.
+applyMappingTip = \
+  Apply the given mapping of original names to obfuscated names.
+obfuscationDictionaryTip = \
+  Use the words in the given file for obfuscating field names and method names.
+classObfuscationDictionaryTip = \
+  Use the words in the given file for obfuscating class names.
+packageObfuscationDictionaryTip = \
+  Use the words in the given file for obfuscating package names.
+overloadAggressivelyTip = \
+  <html>Allow fields and methods to get the same obfuscated names, even if only their types or<br>\
+  return types differ. This is not allowed in the Java language, but it is allowed in bytecode.</html>
+useUniqueClassMemberNamesTip = \
+  <html>Make sure fields and methods get the same obfuscation mapping across classes, even<br>\
+  if they are unrelated. This is advisable if the output is to be obfuscated incrementally.</html>
+keepPackageNamesTip = \
+  Keep the specified package names from being obfuscated.
+packageNamesTip = \
+  <html>An optional comma-separated list of package names,<br>\
+  e.g. <code>myapplication,mylibrary.**</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>?</code> for any single character, except the package separator.\
+  <li><code>*</code> for any number of any characters, except the package separator.\
+  <li><code>**</code> for any number of any characters.\
+  </ul>\
+  The negator <code>!</code> is also supported.</html>
+flattenPackageHierarchyTip = \
+  Move all packages that are renamed into the given parent package.
+repackageClassesTip = \
+  Move all classes that are renamed into the given package.
+packageTip = \
+  The optional package name.
+useMixedCaseClassNamesTip = \
+  <html>Generate mixed-case obfucated class names. This will complicate unpacking<br>\
+  the resulting jars on case-insensitive file systems, should that be necessary.</html>
+keepAttributesTip = \
+  Keep the specified optional class file attributes.
+attributesTip = \
+  <html>An optional comma-separated list of class file attributes.\
+  <ul>\
+  <li>"Exceptions,Innerclasses, Signature" are necessary if the output is to be used as a library.\
+  <li>"Deprecated" is optional if the output is to be used as a library.\
+  <li>"LocalVariable*Table" can be useful for debugging.\
+  <li>"Sourcefile,LineNumberTable" are necessary for generating stack traces.\
+  <li>"*Annotations*" is necessary for preserving annotations.\
+  </ul>\
+  The wildcard <code>*</code> and the negator <code>!</code> are allowed.</html>
+renameSourceFileAttributeTip = \
+  <html>Put the given string in the "SourceFile" attribute of the processed class files.<br>\
+  It will appear as the file name of the classes in stack traces.</html>
+sourceFileAttributeTip = \
+  The replacement "SourceFile" string.
+adaptClassStringsTip = \
+  <html>Adapt string constants in the specified classes, based<br>\
+  on the obfuscated names of corresponding classes.</html>
+adaptResourceFileNamesTip = \
+  <html>Rename the specified resource files, based on the<br>\
+  obfuscated names of the corresponding class files.</html>
+adaptResourceFileContentsTip = \
+  <html>Adapt the contents of the specified resource files, based<br>\
+  on the obfuscated names of the processed classes.</html>
+fileNameFilterTip = \
+  <html>A filter on file names,<br>\
+  e.g. <code>mydirectory1/**,mydirectory2/**</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>?</code> for any single character, except the directory separator.\
+  <li><code>*</code> for any number of any characters, except the directory separator.\
+  <li><code>**</code> for any number of any characters.\
+  </ul>\
+  The negator <code>!</code> is also supported.</html>
+
+preverifyTip = \
+  Preverify the processed classes, for Java Micro Edition or for Java 6.
+microEditionTip = \
+  Target Java Micro Edition.
+
+verboseTip = \
+  Print out verbose messages while processing.
+noteTip = \
+  Print out notes about special or unusual input.
+noteFilterTip = \
+  A filter matching classes for which no notes should be printed.
+warnTip = \
+  <html>Print out warnings about possibly erronous input.<br>\
+  <i>Only unset this option if you know what you're doing!</i></html>
+warnFilterTip = \
+  A filter matching classes for which no warnings should be printed.
+ignoreWarningsTip = \
+  <html>Ignore any warnings about possibly erronous input.<br>\
+  <i>Only set this option if you know what you're doing!</i></html>
+skipNonPublicLibraryClassesTip = \
+  <html>Skip reading non-public library classes, for efficiency.<br>\
+  You may have to unset this option if ProGuard complains about missing classes.</html>
+skipNonPublicLibraryClassMembersTip = \
+  <html>Skip reading non-public library fields and methods, for efficiency.<br>\
+  You may have to unset this option if ProGuard complains about missing class members.</html>
+keepDirectoriesTip = \
+  Keep the specified directories in the output jars, wars, ears, zips, or directories.
+directoriesTip = \
+  <html>A filter on directory names,<br>\
+  e.g. <code>mydirectory1,mydirectory2/**</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>?</code> for any single character, except the directory separator.\
+  <li><code>*</code> for any number of any characters, except the directory separator.\
+  <li><code>**</code> for any number of any characters.\
+  </ul>\
+  The negator <code>!</code> is also supported.</html>
+forceProcessingTip = \
+  Always process the input, even if the output seems up to date.
+targetTip = \
+  Target the specified version of Java.
+printSeedsTip = \
+  Print out the list of kept classes, fields, and methods.
+printConfigurationTip = \
+  Print out the configuration.
+dumpTip = \
+  Print out the internal structure of the processed class files.
+
+mappingFileTip = \
+  The file containing the mapping of original names to obfuscated names.
+obfuscatedStackTraceTip = \
+  A stack trace produced by previously obfuscated code.
+
+#
+# Titles and labels corresponding to ProGuard keep options.
+#
+keepTitle = Keep
+
+keep                   = Keep classes and class members
+keepClassMembers       = Keep class members only
+keepClassesWithMembers = Keep classes and class members, if members are present
+
+allowTitle = Allow
+
+allowShrinking    = Allow shrinking
+allowOptimization = Allow optimization
+allowObfuscation  = Allow obfuscation
+
+keepTitleTip = Keep the specified classes and/or their fields and methods.
+
+keepTip = \
+  <html>Keep the specified classes, fields, and methods as entry points.<br>\
+  This is the most common option.</html>
+keepClassMembersTip = \
+  Only keep the specified fields and methods as entry points.
+keepClassesWithMembersTip = \
+  <html>Keep the specified classes, fields, and methods,<br>\
+  on the condition that the fields and methods are present.</html>
+
+allowTitleTip = \
+  <html>Optionally relax keeping the specified classes, fields, and methods.<br>\
+  <i>These are advanced options.</i></html>
+
+allowShrinkingTip = \
+  Remove the specified classes, fields, and methods anyway, if they are not used.
+allowOptimizationTip = \
+  <html>Optimize the specified classes, fields, and methods as entry points anyway.<br>\
+  <i>Only set this option if you know what you're doing!</i></html>
+allowObfuscationTip = \
+  <html>Obfuscate the names of the specified classes, fields, and methods anyway.<br>\
+  <i>Only set this option if you know what you're doing!</i></html>
+
+#
+# Further keep titles and labels.
+#
+specifyClasses = Specify classes and class members...
+specifyFields  = Specify fields...
+specifyMethods = Specify methods...
+
+comments                    = Comments
+access                      = Access
+required                    = Required
+not                         = Not
+dontCare                    = Don't care
+annotation                  = Annotation
+class                       = Class
+extendsImplementsAnnotation = Extends/implements class with annotation
+extendsImplementsClass      = Extends/implements class
+classMembers                = Class members
+
+extensionsOf = Extensions of
+specificationNumber = Specification #
+
+fieldType     = Field type
+returnType    = Return type
+name          = Name
+argumentTypes = Argument types
+
+commentsTip = \
+  Optionally add a comment for this option in the configuration file.
+accessTip = \
+  <html>Optionally place constraints on the access modifiers of this element.<br>\
+  E.g. only match public elements.</html>
+requiredTip = \
+  The access modifier has to be set.
+notTip = \
+  The access modifier must not be set.
+dontCareTip = \
+  The access modifier is irrelevant.
+annotationTip = \
+  <html>Optionally require the given annotation to be present on this element.<br>\
+  E.g. only match elements that have an annotation <code>myPackage.MyAnnotation</code>.<br>\
+  <i>This is an advanced option.</i></html>
+classTip = \
+  The name of the class or interface.
+extendsImplementsAnnotationTip = \
+  <html>Optionally require the given annotation to be present on the<br>\
+  extended or implemented class or interface.<br>\
+  E.g. only match classes that extend a class that has an annotation<br>\
+  <code>myPackage.MyAnnotation</code>.<br>\
+  <i>This is an advanced option.</i></html>
+extendsImplementsClassTip = \
+  <html>Optionally require the class to implement or extend the given class or interface.<br>\
+  E.g. only match classes that implement an interface <code>myPackage.MyInterface</code>.</html>
+classMembersTip = \
+  <html>Optionally keep fields and methods as entry points in the matching class or classes.<br>\
+  E.g. keep all public '<code>get*</code>' methods as entry points.</html>
+
+fieldTypeTip     = The field type.
+returnTypeTip    = The method return type, if any.
+nameTip          = The name.
+argumentTypesTip = The method argument types, if any.
+
+classNameTip = \
+  <html>The class name, e.g. <code>myPackage.MyClass</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>?</code> for any single character, except the package separator.\
+  <li><code>*</code> for any number of any characters, except the package separator.\
+  <li><code>**</code> for any number of any characters.\
+  </ul></html>
+classNamesTip = \
+  <html>A regular expression to further constrain the class names,<br>\
+  e.g. <code>myPackage1.MyClass,myPackage2.**</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>?</code> for any single character, except the package separator.\
+  <li><code>*</code> for any number of any characters, except the package separator.\
+  <li><code>**</code> for any number of any characters.\
+  </ul>\
+  The negator <code>!</code> is also supported.</html>
+typeTip = \
+  <html>The type, e.g. <code>int</code>, or <code>java.lang.String[]</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>%</code> for any primitive type.\
+  <li><code>?</code> for any single character, except the package separator.\
+  <li><code>*</code> for any number of any characters, except the package separator.\
+  <li><code>**</code> for any number of any characters.\
+  <li><code>***</code> (or empty) for any type.\
+  </ul></html>
+fieldNameTip = \
+  <html>The field name, e.g. <code>myField</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>?</code> for any single character.\
+  <li><code>*</code> for any number of any characters.\
+  </ul></html>
+methodNameTip = \
+  <html>The method name, e.g. <code>myMethod</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>?</code> for any single character.\
+  <li><code>*</code> for any number of any characters.\
+  </ul></html>
+argumentTypes2Tip = \
+  <html>The comma-separated list of argument types,<br>\
+  e.g. <code>java.lang.String[],int,boolean</code><br>\
+  Possible wildcards:\
+  <ul>\
+  <li><code>%</code> for any primitive type.\
+  <li><code>?</code> for any single character, except the package separator.\
+  <li><code>*</code> for any number of any characters, except the package separator.\
+  <li><code>**</code> for any number of any characters.\
+  <li><code>***</code> for any type.\
+  <li><code>...</code> for any number of any arguments.\
+  </ul></html>
+
+#
+# Titles and labels corresponding to optimization options.
+#
+selectOptimizations = Select optimizations...
+
+field  = Field
+method = Method
+code   = Code
+
+class_marking_finalTip = \
+  Mark classes as final, whenever possible.
+class_merging_verticalTip = \
+  Merge classes vertically in the class hierarchy, whenever possible.
+class_merging_horizontalTip = \
+  Merge classes horizontally in the class hierarchy, whenever possible.
+field_removal_writeonlyTip = \
+  Remove write-only fields.
+field_marking_privateTip = \
+  Mark fields as private, whenever possible.
+field_propagation_valueTip = \
+  Propagate the values of fields across methods.
+method_marking_privateTip = \
+  Mark methods as private, whenever possible (devirtualization).
+method_marking_staticTip = \
+  Mark methods as static, whenever possible (devirtualization).
+method_marking_finalTip = \
+  Mark methods as final, whenever possible.
+method_removal_parameterTip = \
+  Remove unused method parameters.
+method_propagation_parameterTip = \
+  Propagate the values of method parameters from method invocations to \
+  the invoked methods.
+method_propagation_returnvalueTip = \
+  Propagate the values of method return values from methods to their \
+  invocations.
+method_inlining_shortTip = \
+  Inline short methods.
+method_inlining_uniqueTip = \
+  Inline methods that are only called once.
+method_inlining_tailrecursionTip = \
+  Simplify tail recursion calls, whenever possible.
+code_mergingTip = \
+  Merge identical blocks of code by modifying branch targets.
+code_simplification_variableTip = \
+  Perform peephole optimizations for variable loading and storing.
+code_simplification_arithmeticTip = \
+  Perform peephole optimizations for arithmetic instructions.
+code_simplification_castTip = \
+  Perform peephole optimizations for casting operations.
+code_simplification_fieldTip = \
+  Perform peephole optimizations for field loading and storing.
+code_simplification_branchTip = \
+  Perform peephole optimizations for branch instructions.
+code_simplification_advancedTip = \
+  Simplify code based on control flow analysis and data flow analysis.
+code_removal_advancedTip = \
+  Remove dead code based on control flow analysis and data flow analysis.
+code_removal_simpleTip = \
+  Remove dead code based on a simple control flow analysis.
+code_removal_variableTip = \
+  Remove unused variables from the local variable frame.
+code_removal_exceptionTip = \
+  Remove exceptions with empty catch blocks.
+code_allocation_variableTip = \
+  Optimize variable allocation on the local variable frame.
+
+
+#
+# File selection titles.
+#
+selectConfigurationFile         = Select a configuration file...
+saveConfigurationFile           = Save configuration...
+selectUsageFile                 = Select a usage output file...
+selectPrintMappingFile          = Select an output mapping file...
+selectApplyMappingFile          = Select an input mapping file...
+selectObfuscationDictionaryFile = Select an obfuscation dictionary...
+selectSeedsFile                 = Select a seeds output file...
+selectDumpFile                  = Select a class dump file...
+selectStackTraceFile            = Select a stack trace file...
+
+cantOpenConfigurationFile  = Can''t open the configuration file [{0}]
+cantParseConfigurationFile = Can''t parse the configuration file [{0}]
+cantSaveConfigurationFile  = Can''t save the configuration file [{0}]
+cantOpenStackTraceFile     = Can''t open the stack trace file [{0}]
+
+jarWarEarZipExtensions = *.jar, *.war, *.ear, *.zip (archives and directories)
+proExtension           = *.pro (ProGuard configurations)
+
+addJars     = Add one or more jars or directories...
+chooseJars  = Choose different jars or directories...
+enterFilter = Optionally filter the file names contained in the selected entries.
+
+filters       = Filters
+nameFilter    = File name filter
+jarNameFilter = Jar name filter
+warNameFilter = War name filter
+earNameFilter = Ear name filter
+zipNameFilter = Zip name filter
+
+outputFileTip = The optional output file.
+inputFileTip  = The input file.
+
+nameFilterTip    = A filter on plain class file names and resource file names.
+jarNameFilterTip = A filter on jar file names.
+warNameFilterTip = A filter on war file names.
+earNameFilterTip = A filter on ear file names.
+zipNameFilterTip = A filter on zip file names.
+
+#
+# Simple button texts.
+#
+previous   = Previous
+next       = Next
+browse     = Browse...
+advanced   = Advanced options
+basic      = Basic options
+selectAll  = Select all
+selectNone = Select none
+ok         = Ok
+cancel     = Cancel
+
+add        = Add...
+addInput   = Add input...
+addOutput  = Add output...
+edit       = Edit...
+filter     = Filter...
+remove     = Remove
+moveUp     = Move up
+moveDown   = Move down
+
+moveToLibraries = Move to libraries
+moveToProgram   = Move to program
+
+addField  = Add field...
+addMethod = Add method...
+
+select = Select...
+
+loadConfiguration = Load configuration...
+viewConfiguration = View configuration
+saveConfiguration = Save configuration...
+loadStackTrace    = Load stack trace...
+process           = Process!
+reTrace           = ReTrace!
+
+advancedTip  = Toggle between showing basic options and advanced options.
+
+addInputTip  = Add an input jar, war, ear, zip, or directory.
+addOutputTip = Add an output jar, war, ear, zip, or directory.
+addTip       = Add an entry.
+editTip      = Edit the selected entries.
+filterTip    = Put filters on the contents of the selected entries.
+removeTip    = Remove the selected entries.
+moveUpTip    = Move the selected entries up in the list.
+moveDownTip  = Move the selected entries down in the list.
+
+moveToLibrariesTip = Move to selected entries to the libraries.
+moveToProgramTip   = Move to selected entries to the program.
+
+addFieldTip  = Add a field to the specification.
+addMethodTip = Add a method to the specification.
+
+loadConfigurationTip = Optionally load an initial configuration.
+viewConfigurationTip = View the current configuration.
+saveConfigurationTip = Save the current configuration.
+loadStackTraceTip    = Load a stack trace from a file.
+processTip           = Start processing, based on the current configuration.
+reTraceTip           = De-obfuscate the given stack trace.
+
+#
+# Progress messages and error messages.
+#
+warning         = Warning
+outOfMemory     = Out of memory
+outOfMemoryInfo = \n\
+  You should run the ProGuard GUI with a larger java heap size, \
+  with a command like\
+  \n\n\t\
+  java -Xms128m -Xmx192m -jar proguardgui.jar {0}\
+  \n\n\
+  or you can try running ProGuard from the command line. \
+  with a command like\
+  \n\n\t\
+  java -jar proguard.jar @{0}
+sampleConfigurationFileName = configuration.pro
+errorProcessing = Error during processing
+errorReTracing  = Error during retracing
diff --git a/src/proguard/gui/KeepSpecificationsPanel.java b/src/proguard/gui/KeepSpecificationsPanel.java
new file mode 100644
index 0000000..4c3c953
--- /dev/null
+++ b/src/proguard/gui/KeepSpecificationsPanel.java
@@ -0,0 +1,81 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.*;
+
+import javax.swing.*;
+
+/**
+ * This <code>ListPanel</code> allows the user to add, edit, move, and remove
+ * KeepClassSpecification entries in a list.
+ *
+ * @author Eric Lafortune
+ */
+final class KeepSpecificationsPanel extends ClassSpecificationsPanel
+{
+    private final boolean markClasses;
+    private final boolean markConditionally;
+    private final boolean allowShrinking;
+    private final boolean allowOptimization;
+    private final boolean allowObfuscation;
+
+
+    public KeepSpecificationsPanel(JFrame  owner,
+                                   boolean markClasses,
+                                   boolean markConditionally,
+                                   boolean allowShrinking,
+                                   boolean allowOptimization,
+                                   boolean allowObfuscation)
+    {
+        super(owner, true);
+
+        this.markClasses       = markClasses;
+        this.markConditionally = markConditionally;
+        this.allowShrinking    = allowShrinking;
+        this.allowOptimization = allowOptimization;
+        this.allowObfuscation  = allowObfuscation;
+    }
+
+
+    // Factory methods for ClassSpecificationsPanel.
+
+    protected ClassSpecification createClassSpecification()
+    {
+        return new KeepClassSpecification(markClasses,
+                                     markConditionally,
+                                     allowShrinking,
+                                     allowOptimization,
+                                     allowObfuscation);
+    }
+
+
+    protected void setClassSpecification(ClassSpecification classSpecification)
+    {
+        classSpecificationDialog.setKeepSpecification((KeepClassSpecification)classSpecification);
+    }
+
+
+    protected ClassSpecification getClassSpecification()
+    {
+        return classSpecificationDialog.getKeepSpecification();
+    }
+}
diff --git a/src/proguard/gui/ListPanel.java b/src/proguard/gui/ListPanel.java
new file mode 100644
index 0000000..0132340
--- /dev/null
+++ b/src/proguard/gui/ListPanel.java
@@ -0,0 +1,341 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+/**
+ * This <code>Jpanel</code> allows the user to move and remove entries in a
+ * list and between lists. Extensions of this class should add buttons to add
+ * and possibly edit entries, and to set and get the resulting list.
+ *
+ * @author Eric Lafortune
+ */
+abstract class ListPanel extends JPanel
+{
+    protected final DefaultListModel listModel = new DefaultListModel();
+    protected final JList            list      = new JList(listModel);
+
+    protected int firstSelectionButton = 2;
+
+
+    protected ListPanel()
+    {
+        GridBagLayout layout = new GridBagLayout();
+        setLayout(layout);
+
+        GridBagConstraints listConstraints = new GridBagConstraints();
+        listConstraints.gridheight = GridBagConstraints.REMAINDER;
+        listConstraints.fill       = GridBagConstraints.BOTH;
+        listConstraints.weightx    = 1.0;
+        listConstraints.weighty    = 1.0;
+        listConstraints.anchor     = GridBagConstraints.NORTHWEST;
+        listConstraints.insets     = new Insets(0, 2, 0, 2);
+
+        // Make sure some buttons are disabled or enabled depending on whether
+        // the selection is empty or not.
+        list.addListSelectionListener(new ListSelectionListener()
+        {
+            public void valueChanged(ListSelectionEvent e)
+            {
+                enableSelectionButtons();
+            }
+        });
+
+        add(new JScrollPane(list), listConstraints);
+
+        // something like the following calls are up to the extending class:
+        //addAddButton();
+        //addEditButton();
+        //addRemoveButton();
+        //addUpButton();
+        //addDownButton();
+        //
+        //enableSelectionButtons();
+    }
+
+
+    protected void addRemoveButton()
+    {
+        JButton removeButton = new JButton(msg("remove"));
+        removeButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                // Remove the selected elements.
+                removeElementsAt(list.getSelectedIndices());
+            }
+        });
+
+        addButton(tip(removeButton, "removeTip"));
+    }
+
+
+    protected void addUpButton()
+    {
+        JButton upButton = new JButton(msg("moveUp"));
+        upButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                int[] selectedIndices = list.getSelectedIndices();
+                if (selectedIndices.length > 0 &&
+                    selectedIndices[0] > 0)
+                {
+                    // Move the selected elements up.
+                    moveElementsAt(selectedIndices, -1);
+                }
+            }
+        });
+
+        addButton(tip(upButton, "moveUpTip"));
+    }
+
+
+    protected void addDownButton()
+    {
+        JButton downButton = new JButton(msg("moveDown"));
+        downButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                int[] selectedIndices = list.getSelectedIndices();
+                if (selectedIndices.length > 0 &&
+                    selectedIndices[selectedIndices.length-1] < listModel.getSize()-1)
+                {
+                    // Move the selected elements down.
+                    moveElementsAt(selectedIndices, 1);
+                }
+            }
+        });
+
+        addButton(tip(downButton, "moveDownTip"));
+    }
+
+
+    /**
+     * Adds a button that allows to copy or move entries to another ListPanel.
+     *
+     * @param buttonTextKey the button text key.
+     * @param tipKey        the tool tip key.
+     * @param panel         the other ListPanel.
+     */
+    public void addCopyToPanelButton(String          buttonTextKey,
+                                     String          tipKey,
+                                     final ListPanel panel)
+    {
+        JButton moveButton = new JButton(msg(buttonTextKey));
+        moveButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                int[]    selectedIndices  = list.getSelectedIndices();
+                Object[] selectedElements = list.getSelectedValues();
+
+                // Remove the selected elements from this panel.
+                removeElementsAt(selectedIndices);
+
+                // Add the elements to the other panel.
+                panel.addElements(selectedElements);
+            }
+        });
+
+        addButton(tip(moveButton, tipKey));
+    }
+
+
+    protected void addButton(JComponent button)
+    {
+        GridBagConstraints buttonConstraints = new GridBagConstraints();
+        buttonConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        buttonConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        buttonConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        buttonConstraints.insets    = new Insets(0, 2, 0, 2);
+
+        add(button, buttonConstraints);
+    }
+
+
+    /**
+     * Returns a list of all right-hand side buttons.
+     */
+    public List getButtons()
+    {
+        List list = new ArrayList(getComponentCount()-1);
+
+        // Add all buttons.
+        for (int index = 1; index < getComponentCount(); index++)
+        {
+            list.add(getComponent(index));
+        }
+
+        return list;
+    }
+
+
+    protected void addElement(Object element)
+    {
+        listModel.addElement(element);
+
+        // Make sure it is selected.
+        list.setSelectedIndex(listModel.size() - 1);
+    }
+
+
+    protected void addElements(Object[] elements)
+    {
+        // Add the elements one by one.
+        for (int index = 0; index < elements.length; index++)
+        {
+            listModel.addElement(elements[index]);
+        }
+
+        // Make sure they are selected.
+        int[] selectedIndices = new int[elements.length];
+        for (int index = 0; index < selectedIndices.length; index++)
+        {
+            selectedIndices[index] =
+                listModel.size() - selectedIndices.length + index;
+        }
+        list.setSelectedIndices(selectedIndices);
+    }
+
+
+    protected void moveElementsAt(int[] indices, int offset)
+    {
+        // Remember the selected elements.
+        Object[] selectedElements = list.getSelectedValues();
+
+        // Remove the selected elements.
+        removeElementsAt(indices);
+
+        // Update the element indices.
+        for (int index = 0; index < indices.length; index++)
+        {
+            indices[index] += offset;
+        }
+
+        // Reinsert the selected elements.
+        insertElementsAt(selectedElements, indices);
+    }
+
+
+    protected void insertElementsAt(Object[] elements, int[] indices)
+    {
+        for (int index = 0; index < elements.length; index++)
+        {
+            listModel.insertElementAt(elements[index], indices[index]);
+        }
+
+        // Make sure they are selected.
+        list.setSelectedIndices(indices);
+    }
+
+
+    protected void setElementAt(Object element, int index)
+    {
+        listModel.setElementAt(element, index);
+
+        // Make sure it is selected.
+        list.setSelectedIndex(index);
+    }
+
+
+    protected void setElementsAt(Object[] elements, int[] indices)
+    {
+        for (int index = 0; index < elements.length; index++)
+        {
+            listModel.setElementAt(elements[index], indices[index]);
+        }
+
+        // Make sure they are selected.
+        list.setSelectedIndices(indices);
+    }
+
+
+    protected void removeElementsAt(int[] indices)
+    {
+        for (int index = indices.length - 1; index >= 0; index--)
+        {
+            listModel.removeElementAt(indices[index]);
+        }
+
+        // Make sure nothing is selected.
+        list.clearSelection();
+
+        // Make sure the selection buttons are properly enabled,
+        // since the above method doesn't seem to notify the listener.
+        enableSelectionButtons();
+    }
+
+
+    protected void removeAllElements()
+    {
+        listModel.removeAllElements();
+
+        // Make sure the selection buttons are properly enabled,
+        // since the above method doesn't seem to notify the listener.
+        enableSelectionButtons();
+    }
+
+
+    /**
+     * Enables or disables the buttons that depend on a selection.
+     */
+    protected void enableSelectionButtons()
+    {
+        boolean selected = !list.isSelectionEmpty();
+
+        // Loop over all components, except the list itself and the Add button.
+        for (int index = firstSelectionButton; index < getComponentCount(); index++)
+        {
+            getComponent(index).setEnabled(selected);
+        }
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+}
diff --git a/src/proguard/gui/MANIFEST.MF b/src/proguard/gui/MANIFEST.MF
new file mode 100644
index 0000000..05403b4
--- /dev/null
+++ b/src/proguard/gui/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: proguard.gui.ProGuardGUI
+Class-Path: proguard.jar retrace.jar
diff --git a/src/proguard/gui/MemberSpecificationDialog.java b/src/proguard/gui/MemberSpecificationDialog.java
new file mode 100644
index 0000000..46a3f6f
--- /dev/null
+++ b/src/proguard/gui/MemberSpecificationDialog.java
@@ -0,0 +1,497 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.MemberSpecification;
+import proguard.classfile.ClassConstants;
+import proguard.classfile.util.ClassUtil;
+import proguard.util.ListUtil;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+ * This <code>JDialog</code> allows the user to enter a String.
+ *
+ * @author Eric Lafortune
+ */
+final class MemberSpecificationDialog extends JDialog
+{
+    /**
+     * Return value if the dialog is canceled (with the Cancel button or by
+     * closing the dialog window).
+     */
+    public static final int CANCEL_OPTION = 1;
+
+    /**
+     * Return value if the dialog is approved (with the Ok button).
+     */
+    public static final int APPROVE_OPTION = 0;
+
+
+    private final boolean isField;
+
+    private final JRadioButton[] publicRadioButtons;
+    private final JRadioButton[] privateRadioButtons;
+    private final JRadioButton[] protectedRadioButtons;
+    private final JRadioButton[] staticRadioButtons;
+    private final JRadioButton[] finalRadioButtons;
+
+    private JRadioButton[] volatileRadioButtons;
+    private JRadioButton[] transientRadioButtons;
+
+    private JRadioButton[] synchronizedRadioButtons;
+    private JRadioButton[] nativeRadioButtons;
+    private JRadioButton[] abstractRadioButtons;
+    private JRadioButton[] strictRadioButtons;
+
+    private final JTextField annotationTypeTextField = new JTextField(20);
+    private final JTextField nameTextField           = new JTextField(20);
+    private final JTextField typeTextField           = new JTextField(20);
+    private final JTextField argumentTypesTextField  = new JTextField(20);
+
+    private int returnValue;
+
+
+    public MemberSpecificationDialog(JDialog owner, boolean isField)
+    {
+        super(owner, msg(isField ? "specifyFields" : "specifyMethods"), true);
+        setResizable(true);
+
+        // Create some constraints that can be reused.
+        GridBagConstraints constraints = new GridBagConstraints();
+        constraints.anchor = GridBagConstraints.WEST;
+        constraints.insets = new Insets(1, 2, 1, 2);
+
+        GridBagConstraints constraintsStretch = new GridBagConstraints();
+        constraintsStretch.fill    = GridBagConstraints.HORIZONTAL;
+        constraintsStretch.weightx = 1.0;
+        constraintsStretch.anchor  = GridBagConstraints.WEST;
+        constraintsStretch.insets  = constraints.insets;
+
+        GridBagConstraints constraintsLast = new GridBagConstraints();
+        constraintsLast.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLast.anchor    = GridBagConstraints.WEST;
+        constraintsLast.insets    = constraints.insets;
+
+        GridBagConstraints constraintsLastStretch = new GridBagConstraints();
+        constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLastStretch.fill      = GridBagConstraints.HORIZONTAL;
+        constraintsLastStretch.weightx   = 1.0;
+        constraintsLastStretch.anchor    = GridBagConstraints.WEST;
+        constraintsLastStretch.insets    = constraints.insets;
+
+        GridBagConstraints panelConstraints = new GridBagConstraints();
+        panelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        panelConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        panelConstraints.weightx   = 1.0;
+        panelConstraints.weighty   = 0.0;
+        panelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        panelConstraints.insets    = constraints.insets;
+
+        GridBagConstraints stretchPanelConstraints = new GridBagConstraints();
+        stretchPanelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        stretchPanelConstraints.fill      = GridBagConstraints.BOTH;
+        stretchPanelConstraints.weightx   = 1.0;
+        stretchPanelConstraints.weighty   = 1.0;
+        stretchPanelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        stretchPanelConstraints.insets    = constraints.insets;
+
+        GridBagConstraints labelConstraints = new GridBagConstraints();
+        labelConstraints.anchor = GridBagConstraints.CENTER;
+        labelConstraints.insets = new Insets(2, 10, 2, 10);
+
+        GridBagConstraints lastLabelConstraints = new GridBagConstraints();
+        lastLabelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        lastLabelConstraints.anchor    = GridBagConstraints.CENTER;
+        lastLabelConstraints.insets    = labelConstraints.insets;
+
+        GridBagConstraints advancedButtonConstraints = new GridBagConstraints();
+        advancedButtonConstraints.weightx = 1.0;
+        advancedButtonConstraints.weighty = 1.0;
+        advancedButtonConstraints.anchor  = GridBagConstraints.SOUTHWEST;
+        advancedButtonConstraints.insets  = new Insets(4, 4, 8, 4);
+
+        GridBagConstraints okButtonConstraints = new GridBagConstraints();
+        okButtonConstraints.weightx = 1.0;
+        okButtonConstraints.weighty = 1.0;
+        okButtonConstraints.anchor  = GridBagConstraints.SOUTHEAST;
+        okButtonConstraints.insets  = advancedButtonConstraints.insets;
+
+        GridBagConstraints cancelButtonConstraints = new GridBagConstraints();
+        cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        cancelButtonConstraints.weighty   = 1.0;
+        cancelButtonConstraints.anchor    = GridBagConstraints.SOUTHEAST;
+        cancelButtonConstraints.insets    = okButtonConstraints.insets;
+
+        GridBagLayout layout = new GridBagLayout();
+
+        Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
+
+        this.isField = isField;
+
+        // Create the access panel.
+        JPanel accessPanel = new JPanel(layout);
+        accessPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                               msg("access")));
+
+        accessPanel.add(Box.createGlue(),                                labelConstraints);
+        accessPanel.add(tip(new JLabel(msg("required")), "requiredTip"), labelConstraints);
+        accessPanel.add(tip(new JLabel(msg("not")),      "notTip"),      labelConstraints);
+        accessPanel.add(tip(new JLabel(msg("dontCare")), "dontCareTip"), labelConstraints);
+        accessPanel.add(Box.createGlue(),                                constraintsLastStretch);
+
+        publicRadioButtons    = addRadioButtonTriplet("Public",    accessPanel);
+        privateRadioButtons   = addRadioButtonTriplet("Private",   accessPanel);
+        protectedRadioButtons = addRadioButtonTriplet("Protected", accessPanel);
+        staticRadioButtons    = addRadioButtonTriplet("Static",    accessPanel);
+        finalRadioButtons     = addRadioButtonTriplet("Final",     accessPanel);
+
+        if (isField)
+        {
+            volatileRadioButtons  = addRadioButtonTriplet("Volatile",  accessPanel);
+            transientRadioButtons = addRadioButtonTriplet("Transient", accessPanel);
+        }
+        else
+        {
+            synchronizedRadioButtons = addRadioButtonTriplet("Synchronized", accessPanel);
+            nativeRadioButtons       = addRadioButtonTriplet("Native",       accessPanel);
+            abstractRadioButtons     = addRadioButtonTriplet("Abstract",     accessPanel);
+            strictRadioButtons       = addRadioButtonTriplet("Strict",       accessPanel);
+        }
+
+        // Create the type panel.
+        JPanel typePanel = new JPanel(layout);
+        typePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                             msg(isField ? "fieldType" :
+                                                                           "returnType")));
+
+        typePanel.add(tip(typeTextField, "typeTip"), constraintsLastStretch);
+
+        // Create the annotation type panel.
+        final JPanel annotationTypePanel = new JPanel(layout);
+        annotationTypePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                       msg("annotation")));
+
+        annotationTypePanel.add(tip(annotationTypeTextField, "classNameTip"), constraintsLastStretch);
+
+        // Create the name panel.
+        JPanel namePanel = new JPanel(layout);
+        namePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                             msg("name")));
+
+        namePanel.add(tip(nameTextField, isField ? "fieldNameTip" :
+                                                   "methodNameTip"), constraintsLastStretch);
+
+        // Create the arguments panel.
+        JPanel argumentsPanel = new JPanel(layout);
+        argumentsPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
+                                                                  msg("argumentTypes")));
+
+        argumentsPanel.add(tip(argumentTypesTextField, "argumentTypes2Tip"), constraintsLastStretch);
+
+        // Create the Advanced button.
+        final JButton advancedButton = new JButton(msg("basic"));
+        advancedButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                boolean visible = !annotationTypePanel.isVisible();
+
+                annotationTypePanel.setVisible(visible);
+
+                advancedButton.setText(msg(visible ? "basic" : "advanced"));
+
+                pack();
+            }
+        });
+        advancedButton.doClick();
+
+        // Create the Ok button.
+        JButton okButton = new JButton(msg("ok"));
+        okButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                returnValue = APPROVE_OPTION;
+                hide();
+            }
+        });
+
+        // Create the Cancel button.
+        JButton cancelButton = new JButton(msg("cancel"));
+        cancelButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                hide();
+            }
+        });
+
+        // Add all panels to the main panel.
+        JPanel mainPanel = new JPanel(layout);
+        mainPanel.add(tip(accessPanel,         "accessTip"),       panelConstraints);
+        mainPanel.add(tip(annotationTypePanel, "annotationTip"),   panelConstraints);
+        mainPanel.add(tip(typePanel, isField ? "fieldTypeTip" :
+                                               "returnTypeTip"),   panelConstraints);
+        mainPanel.add(tip(namePanel,           "nameTip"),         panelConstraints);
+
+        if (!isField)
+        {
+            mainPanel.add(tip(argumentsPanel, "argumentTypesTip"), panelConstraints);
+        }
+
+        mainPanel.add(tip(advancedButton, "advancedTip"), advancedButtonConstraints);
+        mainPanel.add(okButton,                           okButtonConstraints);
+        mainPanel.add(cancelButton,                       cancelButtonConstraints);
+
+        getContentPane().add(new JScrollPane(mainPanel));
+    }
+
+
+    /**
+     * Adds a JLabel and three JRadioButton instances in a ButtonGroup to the
+     * given panel with a GridBagLayout, and returns the buttons in an array.
+     */
+    private JRadioButton[] addRadioButtonTriplet(String labelText,
+                                                 JPanel panel)
+    {
+        GridBagConstraints labelConstraints = new GridBagConstraints();
+        labelConstraints.anchor = GridBagConstraints.WEST;
+        labelConstraints.insets = new Insets(2, 10, 2, 10);
+
+        GridBagConstraints buttonConstraints = new GridBagConstraints();
+        buttonConstraints.insets = labelConstraints.insets;
+
+        GridBagConstraints lastGlueConstraints = new GridBagConstraints();
+        lastGlueConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        lastGlueConstraints.weightx   = 1.0;
+
+        // Create the radio buttons.
+        JRadioButton radioButton0 = new JRadioButton();
+        JRadioButton radioButton1 = new JRadioButton();
+        JRadioButton radioButton2 = new JRadioButton();
+
+        // Put them in a button group.
+        ButtonGroup buttonGroup = new ButtonGroup();
+        buttonGroup.add(radioButton0);
+        buttonGroup.add(radioButton1);
+        buttonGroup.add(radioButton2);
+
+        // Add the label and the buttons to the panel.
+        panel.add(new JLabel(labelText), labelConstraints);
+        panel.add(radioButton0,          buttonConstraints);
+        panel.add(radioButton1,          buttonConstraints);
+        panel.add(radioButton2,          buttonConstraints);
+        panel.add(Box.createGlue(),      lastGlueConstraints);
+
+        return new JRadioButton[]
+        {
+             radioButton0,
+             radioButton1,
+             radioButton2
+        };
+    }
+
+
+    /**
+     * Sets the MemberSpecification to be represented in this dialog.
+     */
+    public void setMemberSpecification(MemberSpecification memberSpecification)
+    {
+        String annotationType = memberSpecification.annotationType;
+        String name           = memberSpecification.name;
+        String descriptor     = memberSpecification.descriptor;
+
+        // Set the class name text fields.
+        annotationTypeTextField.setText(annotationType == null ? "" : ClassUtil.externalType(annotationType));
+
+        // Set the access radio buttons.
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_PUBLIC,       publicRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_PRIVATE,      privateRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_PROTECTED,    protectedRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_STATIC,       staticRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_FINAL,        finalRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_VOLATILE,     volatileRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_TRANSIENT,    transientRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_SYNCHRONIZED, synchronizedRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_NATIVE,       nativeRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_ABSTRACT,     abstractRadioButtons);
+        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_STRICT,       strictRadioButtons);
+
+        // Set the class name text fields.
+        nameTextField.setText(name == null ? "*" : name);
+
+        if (isField)
+        {
+            typeTextField         .setText(descriptor == null ? "***" : ClassUtil.externalType(descriptor));
+        }
+        else
+        {
+            typeTextField         .setText(descriptor == null ? "***" : ClassUtil.externalMethodReturnType(descriptor));
+            argumentTypesTextField.setText(descriptor == null ? "..." : ClassUtil.externalMethodArguments(descriptor));
+        }
+    }
+
+
+    /**
+     * Returns the MemberSpecification currently represented in this dialog.
+     */
+    public MemberSpecification getMemberSpecification()
+    {
+        String annotationType = annotationTypeTextField.getText();
+        String name           = nameTextField.getText();
+        String type           = typeTextField.getText();
+        String arguments      = argumentTypesTextField.getText();
+
+        // Convert all class member specifications into the internal format.
+        annotationType =
+            annotationType.equals("") ||
+            annotationType.equals("***") ? null : ClassUtil.internalType(annotationType);
+
+        if (name.equals("") ||
+            name.equals("*"))
+        {
+            name = null;
+        }
+
+        if (isField)
+        {
+            type =
+                type.equals("") ||
+                type.equals("***") ? null : ClassUtil.internalType(type);
+        }
+        else
+        {
+            if (type.equals(""))
+            {
+                type = ClassConstants.EXTERNAL_TYPE_VOID;
+            }
+
+            type =
+                type     .equals("***") &&
+                arguments.equals("...") ? null :
+                    ClassUtil.internalMethodDescriptor(type, ListUtil.commaSeparatedList(arguments));
+        }
+
+        MemberSpecification memberSpecification =
+            new MemberSpecification(0, 0, annotationType, name, type);
+
+        // Also get the access radio button settings.
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_PUBLIC,       publicRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_PRIVATE,      privateRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_PROTECTED,    protectedRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_STATIC,       staticRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_FINAL,        finalRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_VOLATILE,     volatileRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_TRANSIENT,    transientRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_SYNCHRONIZED, synchronizedRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_NATIVE,       nativeRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_ABSTRACT,     abstractRadioButtons);
+        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.INTERNAL_ACC_STRICT,       strictRadioButtons);
+
+        return memberSpecification;
+    }
+
+
+    /**
+     * Shows this dialog. This method only returns when the dialog is closed.
+     *
+     * @return <code>CANCEL_OPTION</code> or <code>APPROVE_OPTION</code>,
+     *         depending on the choice of the user.
+     */
+    public int showDialog()
+    {
+        returnValue = CANCEL_OPTION;
+
+        // Open the dialog in the right place, then wait for it to be closed,
+        // one way or another.
+        pack();
+        setLocationRelativeTo(getOwner());
+        show();
+
+        return returnValue;
+    }
+
+
+    /**
+     * Sets the appropriate radio button of a given triplet, based on the access
+     * flags of the given keep option.
+     */
+    private void setMemberSpecificationRadioButtons(MemberSpecification memberSpecification,
+                                                    int                 flag,
+                                                    JRadioButton[]      radioButtons)
+    {
+        if (radioButtons != null)
+        {
+            int index = (memberSpecification.requiredSetAccessFlags   & flag) != 0 ? 0 :
+                        (memberSpecification.requiredUnsetAccessFlags & flag) != 0 ? 1 :
+                                                                                       2;
+            radioButtons[index].setSelected(true);
+        }
+    }
+
+
+    /**
+     * Updates the access flag of the given keep option, based on the given radio
+     * button triplet.
+     */
+    private void getMemberSpecificationRadioButtons(MemberSpecification memberSpecification,
+                                                    int                 flag,
+                                                    JRadioButton[]      radioButtons)
+    {
+        if (radioButtons != null)
+        {
+            if      (radioButtons[0].isSelected())
+            {
+                memberSpecification.requiredSetAccessFlags   |= flag;
+            }
+            else if (radioButtons[1].isSelected())
+            {
+                memberSpecification.requiredUnsetAccessFlags |= flag;
+            }
+        }
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+}
diff --git a/src/proguard/gui/MemberSpecificationsPanel.java b/src/proguard/gui/MemberSpecificationsPanel.java
new file mode 100644
index 0000000..20b2f17
--- /dev/null
+++ b/src/proguard/gui/MemberSpecificationsPanel.java
@@ -0,0 +1,304 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.*;
+import proguard.classfile.ClassConstants;
+import proguard.classfile.util.ClassUtil;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import java.util.List;
+
+
+/**
+ * This <code>ListPanel</code> allows the user to add, edit, move, and remove
+ * MemberSpecification entries in a list.
+ *
+ * @author Eric Lafortune
+ */
+final class MemberSpecificationsPanel extends ListPanel
+{
+    private final MemberSpecificationDialog fieldSpecificationDialog;
+    private final MemberSpecificationDialog methodSpecificationDialog;
+
+
+    public MemberSpecificationsPanel(JDialog owner, boolean fullKeepOptions)
+    {
+        super();
+
+        super.firstSelectionButton = fullKeepOptions ? 3 : 2;
+
+        list.setCellRenderer(new MyListCellRenderer());
+
+        fieldSpecificationDialog  = new MemberSpecificationDialog(owner, true);
+        methodSpecificationDialog = new MemberSpecificationDialog(owner, false);
+
+        if (fullKeepOptions)
+        {
+            addAddFieldButton();
+        }
+        addAddMethodButton();
+        addEditButton();
+        addRemoveButton();
+        addUpButton();
+        addDownButton();
+
+        enableSelectionButtons();
+    }
+
+
+    protected void addAddFieldButton()
+    {
+        JButton addFieldButton = new JButton(msg("addField"));
+        addFieldButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                fieldSpecificationDialog.setMemberSpecification(new MemberSpecification());
+                int returnValue = fieldSpecificationDialog.showDialog();
+                if (returnValue == MemberSpecificationDialog.APPROVE_OPTION)
+                {
+                    // Add the new element.
+                    addElement(new MyMemberSpecificationWrapper(fieldSpecificationDialog.getMemberSpecification(),
+                                                                  true));
+                }
+            }
+        });
+
+        addButton(tip(addFieldButton, "addFieldTip"));
+    }
+
+
+    protected void addAddMethodButton()
+    {
+        JButton addMethodButton = new JButton(msg("addMethod"));
+        addMethodButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                methodSpecificationDialog.setMemberSpecification(new MemberSpecification());
+                int returnValue = methodSpecificationDialog.showDialog();
+                if (returnValue == MemberSpecificationDialog.APPROVE_OPTION)
+                {
+                    // Add the new element.
+                    addElement(new MyMemberSpecificationWrapper(methodSpecificationDialog.getMemberSpecification(),
+                                                                  false));
+                }
+            }
+        });
+
+        addButton(tip(addMethodButton, "addMethodTip"));
+    }
+
+
+    protected void addEditButton()
+    {
+        JButton editButton = new JButton(msg("edit"));
+        editButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                MyMemberSpecificationWrapper wrapper =
+                    (MyMemberSpecificationWrapper)list.getSelectedValue();
+
+                MemberSpecificationDialog memberSpecificationDialog =
+                    wrapper.isField ?
+                        fieldSpecificationDialog :
+                        methodSpecificationDialog;
+
+                memberSpecificationDialog.setMemberSpecification(wrapper.memberSpecification);
+                int returnValue = memberSpecificationDialog.showDialog();
+                if (returnValue == MemberSpecificationDialog.APPROVE_OPTION)
+                {
+                    // Replace the old element.
+                    wrapper.memberSpecification = memberSpecificationDialog.getMemberSpecification();
+                    setElementAt(wrapper,
+                                 list.getSelectedIndex());
+                }
+            }
+        });
+
+        addButton(tip(editButton, "editTip"));
+    }
+
+
+    /**
+     * Sets the MemberSpecification instances to be represented in this panel.
+     */
+    public void setMemberSpecifications(List fieldSpecifications,
+                                        List methodSpecifications)
+    {
+        listModel.clear();
+
+        if (fieldSpecifications != null)
+        {
+            for (int index = 0; index < fieldSpecifications.size(); index++)
+            {
+                listModel.addElement(
+                    new MyMemberSpecificationWrapper((MemberSpecification)fieldSpecifications.get(index),
+                                                     true));
+            }
+        }
+
+        if (methodSpecifications != null)
+        {
+            for (int index = 0; index < methodSpecifications.size(); index++)
+            {
+                listModel.addElement(
+                    new MyMemberSpecificationWrapper((MemberSpecification)methodSpecifications.get(index),
+                                                     false));
+            }
+        }
+
+        // Make sure the selection buttons are properly enabled,
+        // since the clear method doesn't seem to notify the listener.
+        enableSelectionButtons();
+    }
+
+
+    /**
+     * Returns the MemberSpecification instances currently represented in
+     * this panel, referring to fields or to methods.
+     *
+     * @param isField specifies whether specifications referring to fields or
+     *                specifications referring to methods should be returned.
+     */
+    public List getMemberSpecifications(boolean isField)
+    {
+        int size = listModel.size();
+        if (size == 0)
+        {
+            return null;
+        }
+
+        List memberSpecifications = new ArrayList(size);
+        for (int index = 0; index < size; index++)
+        {
+            MyMemberSpecificationWrapper wrapper =
+                (MyMemberSpecificationWrapper)listModel.get(index);
+
+            if (wrapper.isField == isField)
+            {
+                memberSpecifications.add(wrapper.memberSpecification);
+            }
+        }
+
+        return memberSpecifications;
+    }
+
+
+    /**
+     * This ListCellRenderer renders MemberSpecification objects.
+     */
+    private static class MyListCellRenderer implements ListCellRenderer
+    {
+        private final JLabel label = new JLabel();
+
+
+        // Implementations for ListCellRenderer.
+
+        public Component getListCellRendererComponent(JList   list,
+                                                      Object  value,
+                                                      int     index,
+                                                      boolean isSelected,
+                                                      boolean cellHasFocus)
+        {
+            MyMemberSpecificationWrapper wrapper = (MyMemberSpecificationWrapper)value;
+
+            MemberSpecification option = wrapper.memberSpecification;
+            String name       = option.name;
+            String descriptor = option.descriptor;
+
+            label.setText(wrapper.isField ?
+                (descriptor == null ? name == null ?
+                    "<fields>" :
+                    "***" + ' ' + name :
+                    ClassUtil.externalFullFieldDescription(0,
+                                                           name == null ? "*" : name,
+                                                           descriptor)) :
+                (descriptor == null ? name == null ?
+                    "<methods>" :
+                    "***" + ' ' + name + "(...)" :
+                    ClassUtil.externalFullMethodDescription(ClassConstants.INTERNAL_METHOD_NAME_INIT,
+                                                            0,
+                                                            name == null ? "*" : name,
+                                                            descriptor)));
+
+            if (isSelected)
+            {
+                label.setBackground(list.getSelectionBackground());
+                label.setForeground(list.getSelectionForeground());
+            }
+            else
+            {
+                label.setBackground(list.getBackground());
+                label.setForeground(list.getForeground());
+            }
+
+            label.setOpaque(true);
+
+            return label;
+        }
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+
+
+    /**
+     * This class wraps a MemberSpecification, additionally storing whether
+     * the option refers to a field or to a method.
+     */
+    private static class MyMemberSpecificationWrapper
+    {
+        public MemberSpecification memberSpecification;
+        public final boolean             isField;
+
+        public MyMemberSpecificationWrapper(MemberSpecification memberSpecification,
+                                            boolean             isField)
+        {
+            this.memberSpecification = memberSpecification;
+            this.isField                  = isField;
+        }
+    }
+}
diff --git a/src/proguard/gui/MessageDialogRunnable.java b/src/proguard/gui/MessageDialogRunnable.java
new file mode 100644
index 0000000..e58f1c6
--- /dev/null
+++ b/src/proguard/gui/MessageDialogRunnable.java
@@ -0,0 +1,90 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * This <code>Runnable</code> can show a message dialog.
+ *
+ * @author Eric Lafortune
+ */
+final class MessageDialogRunnable implements Runnable
+{
+    private final Component parentComponent;
+    private final Object    message;
+    private final String    title;
+    private final int       messageType;
+
+
+    /**
+     * Creates a new MessageDialogRunnable object.
+     * @see JOptionPane#showMessageDialog(Component, Object, String, int)
+     */
+    public static void showMessageDialog(Component parentComponent,
+                                         Object    message,
+                                         String    title,
+                                         int       messageType)
+    {
+        try
+        {
+            SwingUtil.invokeAndWait(new MessageDialogRunnable(parentComponent,
+                                                              message,
+                                                              title,
+                                                              messageType));
+        }
+        catch (Exception e)
+        {
+            // Nothing.
+        }
+    }
+
+
+    /**
+     * Creates a new MessageDialogRunnable object.
+     * @see JOptionPane#showMessageDialog(Component, Object, String, int)
+     */
+    public MessageDialogRunnable(Component parentComponent,
+                                 Object    message,
+                                 String    title,
+                                 int       messageType)
+    {
+        this.parentComponent = parentComponent;
+        this.message         = message;
+        this.title           = title;
+        this.messageType     = messageType;
+    }
+
+
+
+    // Implementation for Runnable.
+
+    public void run()
+    {
+        JOptionPane.showMessageDialog(parentComponent,
+                                      message,
+                                      title,
+                                      messageType);
+    }
+}
diff --git a/src/proguard/gui/OptimizationsDialog.java b/src/proguard/gui/OptimizationsDialog.java
new file mode 100644
index 0000000..044c338
--- /dev/null
+++ b/src/proguard/gui/OptimizationsDialog.java
@@ -0,0 +1,251 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.optimize.Optimizer;
+import proguard.util.*;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+ * This <code>JDialog</code> allows the user to enter a String.
+ *
+ * @author Eric Lafortune
+ */
+final class OptimizationsDialog extends JDialog
+{
+    /**
+     * Return value if the dialog is canceled (with the Cancel button or by
+     * closing the dialog window).
+     */
+    public static final int CANCEL_OPTION = 1;
+
+    /**
+     * Return value if the dialog is approved (with the Ok button).
+     */
+    public static final int APPROVE_OPTION = 0;
+
+
+    private final JCheckBox[] optimizationCheckBoxes = new JCheckBox[Optimizer.OPTIMIZATION_NAMES.length];
+
+    private int returnValue;
+
+
+    public OptimizationsDialog(JFrame owner)
+    {
+        super(owner, msg("selectOptimizations"), true);
+        setResizable(true);
+
+        // Create some constraints that can be reused.
+        GridBagConstraints constraintsLast = new GridBagConstraints();
+        constraintsLast.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLast.anchor    = GridBagConstraints.WEST;
+        constraintsLast.insets    = new Insets(1, 2, 1, 2);
+
+        GridBagConstraints constraintsLastStretch = new GridBagConstraints();
+        constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLastStretch.fill      = GridBagConstraints.HORIZONTAL;
+        constraintsLastStretch.weightx   = 1.0;
+        constraintsLastStretch.anchor    = GridBagConstraints.WEST;
+        constraintsLastStretch.insets    = constraintsLast.insets;
+
+        GridBagConstraints panelConstraints = new GridBagConstraints();
+        panelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        panelConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        panelConstraints.weightx   = 1.0;
+        panelConstraints.weighty   = 0.0;
+        panelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        panelConstraints.insets    = constraintsLast.insets;
+
+        GridBagConstraints selectButtonConstraints = new GridBagConstraints();
+        selectButtonConstraints.weighty = 1.0;
+        selectButtonConstraints.anchor  = GridBagConstraints.SOUTHWEST;
+        selectButtonConstraints.insets  = new Insets(4, 4, 8, 4);
+
+        GridBagConstraints okButtonConstraints = new GridBagConstraints();
+        okButtonConstraints.weightx = 1.0;
+        okButtonConstraints.weighty = 1.0;
+        okButtonConstraints.anchor  = GridBagConstraints.SOUTHEAST;
+        okButtonConstraints.insets  = selectButtonConstraints.insets;
+
+        GridBagConstraints cancelButtonConstraints = new GridBagConstraints();
+        cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        cancelButtonConstraints.weighty   = 1.0;
+        cancelButtonConstraints.anchor    = GridBagConstraints.SOUTHEAST;
+        cancelButtonConstraints.insets    = selectButtonConstraints.insets;
+
+        GridBagLayout layout = new GridBagLayout();
+
+        Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
+
+        // Create the optimizations panel.
+        JPanel optimizationsPanel     = new JPanel(layout);
+        JPanel optimizationSubpanel   = null;
+        String lastOptimizationPrefix = null;
+
+        for (int index = 0; index < Optimizer.OPTIMIZATION_NAMES.length; index++)
+        {
+            String optimizationName = Optimizer.OPTIMIZATION_NAMES[index];
+
+            String optimizationPrefix = optimizationName.substring(0, optimizationName.indexOf('/'));
+
+            if (optimizationSubpanel == null || !optimizationPrefix.equals(lastOptimizationPrefix))
+            {
+                // Create a new keep subpanel and add it.
+                optimizationSubpanel = new JPanel(layout);
+                optimizationSubpanel.setBorder(BorderFactory.createTitledBorder(etchedBorder, msg(optimizationPrefix)));
+                optimizationsPanel.add(optimizationSubpanel, panelConstraints);
+
+                lastOptimizationPrefix = optimizationPrefix;
+            }
+
+            JCheckBox optimizationCheckBox = new JCheckBox(optimizationName);
+            optimizationCheckBoxes[index] = optimizationCheckBox;
+
+            optimizationSubpanel.add(tip(optimizationCheckBox, optimizationName.replace('/', '_')+"Tip"), constraintsLastStretch);
+        }
+
+        // Create the Select All button.
+        JButton selectAllButton = new JButton(msg("selectAll"));
+        selectAllButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                for (int index = 0; index < optimizationCheckBoxes.length; index++)
+                {
+                    optimizationCheckBoxes[index].setSelected(true);
+                }
+            }
+        });
+
+        // Create the Select All button.
+        JButton selectNoneButton = new JButton(msg("selectNone"));
+        selectNoneButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                for (int index = 0; index < optimizationCheckBoxes.length; index++)
+                {
+                    optimizationCheckBoxes[index].setSelected(false);
+                }
+            }
+        });
+
+        // Create the Ok button.
+        JButton okButton = new JButton(msg("ok"));
+        okButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                returnValue = APPROVE_OPTION;
+                hide();
+            }
+        });
+
+        // Create the Cancel button.
+        JButton cancelButton = new JButton(msg("cancel"));
+        cancelButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                hide();
+            }
+        });
+
+        // Add all panels to the main panel.
+        optimizationsPanel.add(selectAllButton,  selectButtonConstraints);
+        optimizationsPanel.add(selectNoneButton, selectButtonConstraints);
+        optimizationsPanel.add(okButton,         okButtonConstraints);
+        optimizationsPanel.add(cancelButton,     cancelButtonConstraints);
+
+        getContentPane().add(new JScrollPane(optimizationsPanel));
+    }
+
+
+    /**
+     * Sets the initial optimization filter to be used by the dialog.
+     */
+    public void setFilter(String optimizations)
+    {
+        StringMatcher filter = optimizations != null && optimizations.length() > 0 ?
+            new ListParser(new NameParser()).parse(optimizations) :
+            new FixedStringMatcher("");
+
+        for (int index = 0; index < Optimizer.OPTIMIZATION_NAMES.length; index++)
+        {
+            optimizationCheckBoxes[index].setSelected(filter.matches(Optimizer.OPTIMIZATION_NAMES[index]));
+        }
+    }
+
+
+    /**
+     * Returns the optimization filter composed from the settings in the dialog.
+     */
+    public String getFilter()
+    {
+        return new FilterBuilder(optimizationCheckBoxes, '/').buildFilter();
+    }
+
+
+    /**
+     * Shows this dialog. This method only returns when the dialog is closed.
+     *
+     * @return <code>CANCEL_OPTION</code> or <code>APPROVE_OPTION</code>,
+     *         depending on the choice of the user.
+     */
+    public int showDialog()
+    {
+        returnValue = CANCEL_OPTION;
+
+        // Open the dialog in the right place, then wait for it to be closed,
+        // one way or another.
+        pack();
+        setLocationRelativeTo(getOwner());
+        show();
+
+        return returnValue;
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/gui/ProGuardGUI.java b/src/proguard/gui/ProGuardGUI.java
new file mode 100644
index 0000000..f27d698
--- /dev/null
+++ b/src/proguard/gui/ProGuardGUI.java
@@ -0,0 +1,1738 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.*;
+import proguard.classfile.util.ClassUtil;
+import proguard.gui.splash.*;
+import proguard.util.ListUtil;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+import java.util.List;
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * GUI for configuring and executing ProGuard and ReTrace.
+ *
+ * @author Eric Lafortune
+ */
+public class ProGuardGUI extends JFrame
+{
+    private static final String NO_SPLASH_OPTION = "-nosplash";
+
+    private static final String TITLE_IMAGE_FILE          = "vtitle.png";
+    private static final String BOILERPLATE_CONFIGURATION = "boilerplate.pro";
+    private static final String DEFAULT_CONFIGURATION     = "default.pro";
+
+    private static final String OPTIMIZATIONS_DEFAULT                = "*";
+    private static final String KEEP_ATTRIBUTE_DEFAULT               = "Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod";
+    private static final String SOURCE_FILE_ATTRIBUTE_DEFAULT        = "SourceFile";
+    private static final String ADAPT_RESOURCE_FILE_NAMES_DEFAULT    = "**.properties";
+    private static final String ADAPT_RESOURCE_FILE_CONTENTS_DEFAULT = "**.properties,META-INF/MANIFEST.MF";
+
+    private static final Border BORDER = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
+
+    static boolean systemOutRedirected;
+
+    private final JFileChooser configurationChooser = new JFileChooser("");
+    private final JFileChooser fileChooser          = new JFileChooser("");
+
+    private final SplashPanel splashPanel;
+
+    private final ClassPathPanel programPanel = new ClassPathPanel(this, true);
+    private final ClassPathPanel libraryPanel = new ClassPathPanel(this, false);
+
+    private       KeepClassSpecification[] boilerplateKeep;
+    private final JCheckBox[]              boilerplateKeepCheckBoxes;
+    private final JTextField[]             boilerplateKeepTextFields;
+
+    private final KeepSpecificationsPanel additionalKeepPanel = new KeepSpecificationsPanel(this, true, false, false, false, false);
+
+    private       KeepClassSpecification[] boilerplateKeepNames;
+    private final JCheckBox[]              boilerplateKeepNamesCheckBoxes;
+    private final JTextField[]             boilerplateKeepNamesTextFields;
+
+    private final KeepSpecificationsPanel additionalKeepNamesPanel = new KeepSpecificationsPanel(this, true, false, true, false, false);
+
+    private       ClassSpecification[] boilerplateNoSideEffectMethods;
+    private final JCheckBox[]          boilerplateNoSideEffectMethodCheckBoxes;
+
+    private final ClassSpecificationsPanel additionalNoSideEffectsPanel = new ClassSpecificationsPanel(this, false);
+
+    private final ClassSpecificationsPanel whyAreYouKeepingPanel = new ClassSpecificationsPanel(this, false);
+
+    private final JCheckBox shrinkCheckBox     = new JCheckBox(msg("shrink"));
+    private final JCheckBox printUsageCheckBox = new JCheckBox(msg("printUsage"));
+
+    private final JCheckBox optimizeCheckBox                    = new JCheckBox(msg("optimize"));
+    private final JCheckBox allowAccessModificationCheckBox     = new JCheckBox(msg("allowAccessModification"));
+    private final JCheckBox mergeInterfacesAggressivelyCheckBox = new JCheckBox(msg("mergeInterfacesAggressively"));
+    private final JLabel    optimizationsLabel                  = new JLabel(msg("optimizations"));
+    private final JLabel    optimizationPassesLabel             = new JLabel(msg("optimizationPasses"));
+
+    private final JSpinner optimizationPassesSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 9, 1));
+
+    private final JCheckBox obfuscateCheckBox                    = new JCheckBox(msg("obfuscate"));
+    private final JCheckBox printMappingCheckBox                 = new JCheckBox(msg("printMapping"));
+    private final JCheckBox applyMappingCheckBox                 = new JCheckBox(msg("applyMapping"));
+    private final JCheckBox obfuscationDictionaryCheckBox        = new JCheckBox(msg("obfuscationDictionary"));
+    private final JCheckBox classObfuscationDictionaryCheckBox   = new JCheckBox(msg("classObfuscationDictionary"));
+    private final JCheckBox packageObfuscationDictionaryCheckBox = new JCheckBox(msg("packageObfuscationDictionary"));
+    private final JCheckBox overloadAggressivelyCheckBox         = new JCheckBox(msg("overloadAggressively"));
+    private final JCheckBox useUniqueClassMemberNamesCheckBox    = new JCheckBox(msg("useUniqueClassMemberNames"));
+    private final JCheckBox useMixedCaseClassNamesCheckBox       = new JCheckBox(msg("useMixedCaseClassNames"));
+    private final JCheckBox keepPackageNamesCheckBox             = new JCheckBox(msg("keepPackageNames"));
+    private final JCheckBox flattenPackageHierarchyCheckBox      = new JCheckBox(msg("flattenPackageHierarchy"));
+    private final JCheckBox repackageClassesCheckBox             = new JCheckBox(msg("repackageClasses"));
+    private final JCheckBox keepAttributesCheckBox               = new JCheckBox(msg("keepAttributes"));
+    private final JCheckBox newSourceFileAttributeCheckBox       = new JCheckBox(msg("renameSourceFileAttribute"));
+    private final JCheckBox adaptClassStringsCheckBox            = new JCheckBox(msg("adaptClassStrings"));
+    private final JCheckBox adaptResourceFileNamesCheckBox       = new JCheckBox(msg("adaptResourceFileNames"));
+    private final JCheckBox adaptResourceFileContentsCheckBox    = new JCheckBox(msg("adaptResourceFileContents"));
+
+    private final JCheckBox preverifyCheckBox    = new JCheckBox(msg("preverify"));
+    private final JCheckBox microEditionCheckBox = new JCheckBox(msg("microEdition"));
+    private final JCheckBox targetCheckBox       = new JCheckBox(msg("target"));
+
+    private final JComboBox targetComboBox = new JComboBox(ListUtil.commaSeparatedList(msg("targets")).toArray());
+
+    private final JCheckBox verboseCheckBox                          = new JCheckBox(msg("verbose"));
+    private final JCheckBox noteCheckBox                             = new JCheckBox(msg("note"));
+    private final JCheckBox warnCheckBox                             = new JCheckBox(msg("warn"));
+    private final JCheckBox ignoreWarningsCheckBox                   = new JCheckBox(msg("ignoreWarnings"));
+    private final JCheckBox skipNonPublicLibraryClassesCheckBox      = new JCheckBox(msg("skipNonPublicLibraryClasses"));
+    private final JCheckBox skipNonPublicLibraryClassMembersCheckBox = new JCheckBox(msg("skipNonPublicLibraryClassMembers"));
+    private final JCheckBox keepDirectoriesCheckBox                  = new JCheckBox(msg("keepDirectories"));
+    private final JCheckBox forceProcessingCheckBox                  = new JCheckBox(msg("forceProcessing"));
+    private final JCheckBox printSeedsCheckBox                       = new JCheckBox(msg("printSeeds"));
+    private final JCheckBox printConfigurationCheckBox               = new JCheckBox(msg("printConfiguration"));
+    private final JCheckBox dumpCheckBox                             = new JCheckBox(msg("dump"));
+
+    private final JTextField printUsageTextField                   = new JTextField(40);
+    private final JTextField optimizationsTextField                = new JTextField(40);
+    private final JTextField printMappingTextField                 = new JTextField(40);
+    private final JTextField applyMappingTextField                 = new JTextField(40);
+    private final JTextField obfuscationDictionaryTextField        = new JTextField(40);
+    private final JTextField classObfuscationDictionaryTextField   = new JTextField(40);
+    private final JTextField packageObfuscationDictionaryTextField = new JTextField(40);
+    private final JTextField keepPackageNamesTextField             = new JTextField(40);
+    private final JTextField flattenPackageHierarchyTextField      = new JTextField(40);
+    private final JTextField repackageClassesTextField             = new JTextField(40);
+    private final JTextField keepAttributesTextField               = new JTextField(40);
+    private final JTextField newSourceFileAttributeTextField       = new JTextField(40);
+    private final JTextField adaptClassStringsTextField            = new JTextField(40);
+    private final JTextField adaptResourceFileNamesTextField       = new JTextField(40);
+    private final JTextField adaptResourceFileContentsTextField    = new JTextField(40);
+    private final JTextField noteTextField                         = new JTextField(40);
+    private final JTextField warnTextField                         = new JTextField(40);
+    private final JTextField keepDirectoriesTextField              = new JTextField(40);
+    private final JTextField printSeedsTextField                   = new JTextField(40);
+    private final JTextField printConfigurationTextField           = new JTextField(40);
+    private final JTextField dumpTextField                         = new JTextField(40);
+
+    private final JTextArea  consoleTextArea = new JTextArea(msg("processingInfo"), 3, 40);
+
+    private final JCheckBox  reTraceVerboseCheckBox  = new JCheckBox(msg("verbose"));
+    private final JTextField reTraceMappingTextField = new JTextField(40);
+    private final JTextArea  stackTraceTextArea      = new JTextArea(3, 40);
+    private final JTextArea  reTraceTextArea         = new JTextArea(msg("reTraceInfo"), 3, 40);
+
+
+    /**
+     * Creates a new ProGuardGUI.
+     */
+    public ProGuardGUI()
+    {
+        setTitle("ProGuard");
+        setDefaultCloseOperation(EXIT_ON_CLOSE);
+
+        // Create some constraints that can be reused.
+        GridBagConstraints constraints = new GridBagConstraints();
+        constraints.anchor = GridBagConstraints.WEST;
+        constraints.insets = new Insets(0, 4, 0, 4);
+
+        GridBagConstraints constraintsStretch = new GridBagConstraints();
+        constraintsStretch.fill    = GridBagConstraints.HORIZONTAL;
+        constraintsStretch.weightx = 1.0;
+        constraintsStretch.anchor  = GridBagConstraints.WEST;
+        constraintsStretch.insets  = constraints.insets;
+
+        GridBagConstraints constraintsLast = new GridBagConstraints();
+        constraintsLast.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLast.anchor    = GridBagConstraints.WEST;
+        constraintsLast.insets    = constraints.insets;
+
+        GridBagConstraints constraintsLastStretch = new GridBagConstraints();
+        constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLastStretch.fill      = GridBagConstraints.HORIZONTAL;
+        constraintsLastStretch.weightx   = 1.0;
+        constraintsLastStretch.anchor    = GridBagConstraints.WEST;
+        constraintsLastStretch.insets    = constraints.insets;
+
+        GridBagConstraints splashPanelConstraints = new GridBagConstraints();
+        splashPanelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        splashPanelConstraints.fill      = GridBagConstraints.BOTH;
+        splashPanelConstraints.weightx   = 1.0;
+        splashPanelConstraints.weighty   = 0.02;
+        splashPanelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        //splashPanelConstraints.insets    = constraints.insets;
+
+        GridBagConstraints welcomeTextAreaConstraints = new GridBagConstraints();
+        welcomeTextAreaConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        welcomeTextAreaConstraints.fill      = GridBagConstraints.NONE;
+        welcomeTextAreaConstraints.weightx   = 1.0;
+        welcomeTextAreaConstraints.weighty   = 0.01;
+        welcomeTextAreaConstraints.anchor    = GridBagConstraints.CENTER;//NORTHWEST;
+        welcomeTextAreaConstraints.insets    = new Insets(20, 40, 20, 40);
+
+        GridBagConstraints panelConstraints = new GridBagConstraints();
+        panelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        panelConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        panelConstraints.weightx   = 1.0;
+        panelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        panelConstraints.insets    = constraints.insets;
+
+        GridBagConstraints stretchPanelConstraints = new GridBagConstraints();
+        stretchPanelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        stretchPanelConstraints.fill      = GridBagConstraints.BOTH;
+        stretchPanelConstraints.weightx   = 1.0;
+        stretchPanelConstraints.weighty   = 1.0;
+        stretchPanelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        stretchPanelConstraints.insets    = constraints.insets;
+
+        GridBagConstraints glueConstraints = new GridBagConstraints();
+        glueConstraints.fill    = GridBagConstraints.BOTH;
+        glueConstraints.weightx = 0.01;
+        glueConstraints.weighty = 0.01;
+        glueConstraints.anchor  = GridBagConstraints.NORTHWEST;
+        glueConstraints.insets  = constraints.insets;
+
+        GridBagConstraints bottomButtonConstraints = new GridBagConstraints();
+        bottomButtonConstraints.anchor = GridBagConstraints.SOUTHEAST;
+        bottomButtonConstraints.insets = new Insets(2, 2, 4, 6);
+        bottomButtonConstraints.ipadx  = 10;
+        bottomButtonConstraints.ipady  = 2;
+
+        GridBagConstraints lastBottomButtonConstraints = new GridBagConstraints();
+        lastBottomButtonConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        lastBottomButtonConstraints.anchor    = GridBagConstraints.SOUTHEAST;
+        lastBottomButtonConstraints.insets    = bottomButtonConstraints.insets;
+        lastBottomButtonConstraints.ipadx     = bottomButtonConstraints.ipadx;
+        lastBottomButtonConstraints.ipady     = bottomButtonConstraints.ipady;
+
+        // Leave room for a growBox on Mac OS X.
+        if (System.getProperty("os.name").toLowerCase().startsWith("mac os x"))
+        {
+            lastBottomButtonConstraints.insets = new Insets(2, 2, 4, 6 + 16);
+        }
+
+        GridBagLayout layout = new GridBagLayout();
+
+        configurationChooser.addChoosableFileFilter(
+            new ExtensionFileFilter(msg("proExtension"), new String[] { ".pro" }));
+
+        // Create the opening panel.
+        Sprite splash =
+            new CompositeSprite(new Sprite[]
+        {
+            new ColorSprite(new ConstantColor(Color.gray),
+            new FontSprite(new ConstantFont(new Font("sansserif", Font.BOLD, 90)),
+            new TextSprite(new ConstantString("ProGuard"),
+                           new ConstantInt(160),
+                           new LinearInt(-10, 120, new SmoothTiming(500, 1000))))),
+
+            new ColorSprite(new ConstantColor(Color.white),
+            new FontSprite(new ConstantFont(new Font("sansserif", Font.BOLD, 45)),
+            new ShadowedSprite(new ConstantInt(3),
+                               new ConstantInt(3),
+                               new ConstantDouble(0.4),
+                               new ConstantInt(1),
+                               new CompositeSprite(new Sprite[]
+            {
+                new TextSprite(new ConstantString(msg("shrinking")),
+                               new LinearInt(1000, 60, new SmoothTiming(1000, 2000)),
+                               new ConstantInt(70)),
+                new TextSprite(new ConstantString(msg("optimization")),
+                               new LinearInt(1000, 400, new SmoothTiming(1500, 2500)),
+                               new ConstantInt(60)),
+                new TextSprite(new ConstantString(msg("obfuscation")),
+                               new LinearInt(1000, 10, new SmoothTiming(2000, 3000)),
+                               new ConstantInt(145)),
+                new TextSprite(new ConstantString(msg("preverification")),
+                               new LinearInt(1000, 350, new SmoothTiming(2500, 3500)),
+                               new ConstantInt(140)),
+                new FontSprite(new ConstantFont(new Font("sansserif", Font.BOLD, 30)),
+                new TextSprite(new TypeWriterString(msg("developed"), new LinearTiming(3500, 5500)),
+                               new ConstantInt(250),
+                               new ConstantInt(200))),
+            })))),
+        });
+        splashPanel = new SplashPanel(splash, 0.5, 5500L);
+        splashPanel.setPreferredSize(new Dimension(0, 200));
+
+        JTextArea welcomeTextArea = new JTextArea(msg("proGuardInfo"), 18, 50);
+        welcomeTextArea.setOpaque(false);
+        welcomeTextArea.setEditable(false);
+        welcomeTextArea.setLineWrap(true);
+        welcomeTextArea.setWrapStyleWord(true);
+        welcomeTextArea.setPreferredSize(new Dimension(0, 0));
+        welcomeTextArea.setBorder(new EmptyBorder(20, 20, 20, 20));
+        addBorder(welcomeTextArea, "welcome");
+
+        JPanel proGuardPanel = new JPanel(layout);
+        proGuardPanel.add(splashPanel,      splashPanelConstraints);
+        proGuardPanel.add(welcomeTextArea,  welcomeTextAreaConstraints);
+
+        // Create the input panel.
+        // TODO: properly clone the ClassPath objects.
+        // This is awkward to implement in the generic ListPanel.addElements(...)
+        // method, since the Object.clone() method is not public.
+        programPanel.addCopyToPanelButton("moveToLibraries", "moveToLibrariesTip", libraryPanel);
+        libraryPanel.addCopyToPanelButton("moveToProgram",   "moveToProgramTip",   programPanel);
+
+        // Collect all buttons of these panels and make sure they are equally
+        // sized.
+        List panelButtons = new ArrayList();
+        panelButtons.addAll(programPanel.getButtons());
+        panelButtons.addAll(libraryPanel.getButtons());
+        setCommonPreferredSize(panelButtons);
+
+        addBorder(programPanel, "programJars" );
+        addBorder(libraryPanel, "libraryJars" );
+
+        JPanel inputOutputPanel = new JPanel(layout);
+        inputOutputPanel.add(tip(programPanel, "programJarsTip"), stretchPanelConstraints);
+        inputOutputPanel.add(tip(libraryPanel, "libraryJarsTip"), stretchPanelConstraints);
+
+        // Load the boiler plate options.
+        loadBoilerplateConfiguration();
+
+        // Create the boiler plate keep panels.
+        boilerplateKeepCheckBoxes = new JCheckBox[boilerplateKeep.length];
+        boilerplateKeepTextFields = new JTextField[boilerplateKeep.length];
+
+        JButton printUsageBrowseButton   = createBrowseButton(printUsageTextField,
+                                                              msg("selectUsageFile"));
+
+        JPanel shrinkingOptionsPanel = new JPanel(layout);
+        addBorder(shrinkingOptionsPanel, "options");
+
+        shrinkingOptionsPanel.add(tip(shrinkCheckBox,         "shrinkTip"),       constraintsLastStretch);
+        shrinkingOptionsPanel.add(tip(printUsageCheckBox,     "printUsageTip"),   constraints);
+        shrinkingOptionsPanel.add(tip(printUsageTextField,    "outputFileTip"),   constraintsStretch);
+        shrinkingOptionsPanel.add(tip(printUsageBrowseButton, "selectUsageFile"), constraintsLast);
+
+        JPanel shrinkingPanel = new JPanel(layout);
+
+        shrinkingPanel.add(shrinkingOptionsPanel, panelConstraints);
+        addClassSpecifications(extractClassSpecifications(boilerplateKeep),
+                               shrinkingPanel,
+                               boilerplateKeepCheckBoxes,
+                               boilerplateKeepTextFields);
+
+        addBorder(additionalKeepPanel, "keepAdditional");
+        shrinkingPanel.add(tip(additionalKeepPanel, "keepAdditionalTip"), stretchPanelConstraints);
+
+        // Create the boiler plate keep names panels.
+        boilerplateKeepNamesCheckBoxes = new JCheckBox[boilerplateKeepNames.length];
+        boilerplateKeepNamesTextFields = new JTextField[boilerplateKeepNames.length];
+
+        JButton printMappingBrowseButton                = createBrowseButton(printMappingTextField,
+                                                                             msg("selectPrintMappingFile"));
+        JButton applyMappingBrowseButton                = createBrowseButton(applyMappingTextField,
+                                                                             msg("selectApplyMappingFile"));
+        JButton obfucationDictionaryBrowseButton        = createBrowseButton(obfuscationDictionaryTextField,
+                                                                             msg("selectObfuscationDictionaryFile"));
+        JButton classObfucationDictionaryBrowseButton   = createBrowseButton(classObfuscationDictionaryTextField,
+                                                                             msg("selectObfuscationDictionaryFile"));
+        JButton packageObfucationDictionaryBrowseButton = createBrowseButton(packageObfuscationDictionaryTextField,
+                                                                             msg("selectObfuscationDictionaryFile"));
+
+        JPanel obfuscationOptionsPanel = new JPanel(layout);
+        addBorder(obfuscationOptionsPanel, "options");
+
+        obfuscationOptionsPanel.add(tip(obfuscateCheckBox,                       "obfuscateTip"),                    constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(printMappingCheckBox,                    "printMappingTip"),                 constraints);
+        obfuscationOptionsPanel.add(tip(printMappingTextField,                   "outputFileTip"),                   constraintsStretch);
+        obfuscationOptionsPanel.add(tip(printMappingBrowseButton,                "selectPrintMappingFile"),          constraintsLast);
+        obfuscationOptionsPanel.add(tip(applyMappingCheckBox,                    "applyMappingTip"),                 constraints);
+        obfuscationOptionsPanel.add(tip(applyMappingTextField,                   "inputFileTip"),                    constraintsStretch);
+        obfuscationOptionsPanel.add(tip(applyMappingBrowseButton,                "selectApplyMappingFile"),          constraintsLast);
+        obfuscationOptionsPanel.add(tip(obfuscationDictionaryCheckBox,           "obfuscationDictionaryTip"),        constraints);
+        obfuscationOptionsPanel.add(tip(obfuscationDictionaryTextField,          "inputFileTip"),                    constraintsStretch);
+        obfuscationOptionsPanel.add(tip(obfucationDictionaryBrowseButton,        "selectObfuscationDictionaryFile"), constraintsLast);
+        obfuscationOptionsPanel.add(tip(classObfuscationDictionaryCheckBox,      "classObfuscationDictionaryTip"),   constraints);
+        obfuscationOptionsPanel.add(tip(classObfuscationDictionaryTextField,     "inputFileTip"),                    constraintsStretch);
+        obfuscationOptionsPanel.add(tip(classObfucationDictionaryBrowseButton,   "selectObfuscationDictionaryFile"), constraintsLast);
+        obfuscationOptionsPanel.add(tip(packageObfuscationDictionaryCheckBox,    "packageObfuscationDictionaryTip"), constraints);
+        obfuscationOptionsPanel.add(tip(packageObfuscationDictionaryTextField,   "inputFileTip"),                    constraintsStretch);
+        obfuscationOptionsPanel.add(tip(packageObfucationDictionaryBrowseButton, "selectObfuscationDictionaryFile"), constraintsLast);
+        obfuscationOptionsPanel.add(tip(overloadAggressivelyCheckBox,            "overloadAggressivelyTip"),         constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(useUniqueClassMemberNamesCheckBox,       "useUniqueClassMemberNamesTip"),    constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(useMixedCaseClassNamesCheckBox,          "useMixedCaseClassNamesTip"),       constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(keepPackageNamesCheckBox,                "keepPackageNamesTip"),             constraints);
+        obfuscationOptionsPanel.add(tip(keepPackageNamesTextField,               "packageNamesTip"),                 constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(flattenPackageHierarchyCheckBox,         "flattenPackageHierarchyTip"),      constraints);
+        obfuscationOptionsPanel.add(tip(flattenPackageHierarchyTextField,        "packageTip"),                      constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(repackageClassesCheckBox,                "repackageClassesTip"),             constraints);
+        obfuscationOptionsPanel.add(tip(repackageClassesTextField,               "packageTip"),                      constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(keepAttributesCheckBox,                  "keepAttributesTip"),               constraints);
+        obfuscationOptionsPanel.add(tip(keepAttributesTextField,                 "attributesTip"),                   constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(newSourceFileAttributeCheckBox,          "renameSourceFileAttributeTip"),    constraints);
+        obfuscationOptionsPanel.add(tip(newSourceFileAttributeTextField,         "sourceFileAttributeTip"),          constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(adaptClassStringsCheckBox,               "adaptClassStringsTip"),            constraints);
+        obfuscationOptionsPanel.add(tip(adaptClassStringsTextField,              "classNamesTip"),                   constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(adaptResourceFileNamesCheckBox,          "adaptResourceFileNamesTip"),       constraints);
+        obfuscationOptionsPanel.add(tip(adaptResourceFileNamesTextField,         "fileNameFilterTip"),               constraintsLastStretch);
+        obfuscationOptionsPanel.add(tip(adaptResourceFileContentsCheckBox,       "adaptResourceFileContentsTip"),    constraints);
+        obfuscationOptionsPanel.add(tip(adaptResourceFileContentsTextField,      "fileNameFilterTip"),               constraintsLastStretch);
+
+        JPanel obfuscationPanel = new JPanel(layout);
+
+        obfuscationPanel.add(obfuscationOptionsPanel, panelConstraints);
+        addClassSpecifications(extractClassSpecifications(boilerplateKeepNames),
+                               obfuscationPanel,
+                               boilerplateKeepNamesCheckBoxes,
+                               boilerplateKeepNamesTextFields);
+
+        addBorder(additionalKeepNamesPanel, "keepNamesAdditional");
+        obfuscationPanel.add(tip(additionalKeepNamesPanel, "keepNamesAdditionalTip"), stretchPanelConstraints);
+
+        // Create the boiler plate "no side effect methods" panels.
+        boilerplateNoSideEffectMethodCheckBoxes = new JCheckBox[boilerplateNoSideEffectMethods.length];
+
+        JPanel optimizationOptionsPanel = new JPanel(layout);
+        addBorder(optimizationOptionsPanel, "options");
+
+        JButton optimizationsButton =
+            createOptimizationsButton(optimizationsTextField);
+
+        optimizationOptionsPanel.add(tip(optimizeCheckBox,                    "optimizeTip"),                    constraintsLastStretch);
+        optimizationOptionsPanel.add(tip(allowAccessModificationCheckBox,     "allowAccessModificationTip"),     constraintsLastStretch);
+        optimizationOptionsPanel.add(tip(mergeInterfacesAggressivelyCheckBox, "mergeInterfacesAggressivelyTip"), constraintsLastStretch);
+        optimizationOptionsPanel.add(tip(optimizationsLabel,                  "optimizationsTip"),               constraints);
+        optimizationOptionsPanel.add(tip(optimizationsTextField,              "optimizationsFilterTip"),         constraintsStretch);
+        optimizationOptionsPanel.add(tip(optimizationsButton,                 "optimizationsSelectTip"),         constraintsLast);
+        optimizationOptionsPanel.add(tip(optimizationPassesLabel,             "optimizationPassesTip"),          constraints);
+        optimizationOptionsPanel.add(tip(optimizationPassesSpinner,           "optimizationPassesTip"),          constraintsLast);
+
+        JPanel optimizationPanel = new JPanel(layout);
+
+        optimizationPanel.add(optimizationOptionsPanel, panelConstraints);
+        addClassSpecifications(boilerplateNoSideEffectMethods,
+                               optimizationPanel,
+                               boilerplateNoSideEffectMethodCheckBoxes,
+                               null);
+
+        addBorder(additionalNoSideEffectsPanel, "assumeNoSideEffectsAdditional");
+        optimizationPanel.add(tip(additionalNoSideEffectsPanel, "assumeNoSideEffectsAdditionalTip"), stretchPanelConstraints);
+
+        // Create the options panel.
+        JPanel preverificationOptionsPanel = new JPanel(layout);
+        addBorder(preverificationOptionsPanel, "preverificationAndTargeting");
+
+        preverificationOptionsPanel.add(tip(preverifyCheckBox,    "preverifyTip"),    constraintsLastStretch);
+        preverificationOptionsPanel.add(tip(microEditionCheckBox, "microEditionTip"), constraintsLastStretch);
+        preverificationOptionsPanel.add(tip(targetCheckBox,       "targetTip"),       constraints);
+        preverificationOptionsPanel.add(tip(targetComboBox,       "targetTip"),       constraintsLast);
+
+        JButton printSeedsBrowseButton =
+            createBrowseButton(printSeedsTextField, msg("selectSeedsFile"));
+
+        JButton printConfigurationBrowseButton =
+            createBrowseButton(printConfigurationTextField, msg( "selectConfigurationFile"));
+
+        JButton dumpBrowseButton =
+            createBrowseButton(dumpTextField, msg("selectDumpFile"));
+
+        // Select the most recent target by default.
+        targetComboBox.setSelectedIndex(targetComboBox.getItemCount() - 1);
+
+        JPanel consistencyPanel = new JPanel(layout);
+        addBorder(consistencyPanel, "consistencyAndCorrectness");
+
+        consistencyPanel.add(tip(verboseCheckBox,                          "verboseTip"),                          constraintsLastStretch);
+        consistencyPanel.add(tip(noteCheckBox,                             "noteTip"),                             constraints);
+        consistencyPanel.add(tip(noteTextField,                            "noteFilterTip"),                             constraintsLastStretch);
+        consistencyPanel.add(tip(warnCheckBox,                             "warnTip"),                             constraints);
+        consistencyPanel.add(tip(warnTextField,                            "warnFilterTip"),                             constraintsLastStretch);
+        consistencyPanel.add(tip(ignoreWarningsCheckBox,                   "ignoreWarningsTip"),                   constraintsLastStretch);
+        consistencyPanel.add(tip(skipNonPublicLibraryClassesCheckBox,      "skipNonPublicLibraryClassesTip"),      constraintsLastStretch);
+        consistencyPanel.add(tip(skipNonPublicLibraryClassMembersCheckBox, "skipNonPublicLibraryClassMembersTip"), constraintsLastStretch);
+        consistencyPanel.add(tip(keepDirectoriesCheckBox,                  "keepDirectoriesTip"),                  constraints);
+        consistencyPanel.add(tip(keepDirectoriesTextField,                 "directoriesTip"),                      constraintsLastStretch);
+        consistencyPanel.add(tip(forceProcessingCheckBox,                  "forceProcessingTip"),                  constraintsLastStretch);
+        consistencyPanel.add(tip(printSeedsCheckBox,                       "printSeedsTip"),                       constraints);
+        consistencyPanel.add(tip(printSeedsTextField,                      "outputFileTip"),                       constraintsStretch);
+        consistencyPanel.add(tip(printSeedsBrowseButton,                   "selectSeedsFile"),                     constraintsLast);
+        consistencyPanel.add(tip(printConfigurationCheckBox,               "printConfigurationTip"),               constraints);
+        consistencyPanel.add(tip(printConfigurationTextField,              "outputFileTip"),                       constraintsStretch);
+        consistencyPanel.add(tip(printConfigurationBrowseButton,           "selectConfigurationFile"),             constraintsLast);
+        consistencyPanel.add(tip(dumpCheckBox,                             "dumpTip"),                             constraints);
+        consistencyPanel.add(tip(dumpTextField,                            "outputFileTip"),                       constraintsStretch);
+        consistencyPanel.add(tip(dumpBrowseButton,                         "selectDumpFile"),                      constraintsLast);
+
+        // Collect all components that are followed by text fields and make
+        // sure they are equally sized. That way the text fields start at the
+        // same horizontal position.
+        setCommonPreferredSize(Arrays.asList(new JComponent[] {
+            printMappingCheckBox,
+            applyMappingCheckBox,
+            flattenPackageHierarchyCheckBox,
+            repackageClassesCheckBox,
+            newSourceFileAttributeCheckBox,
+        }));
+
+        JPanel optionsPanel = new JPanel(layout);
+
+        optionsPanel.add(preverificationOptionsPanel, panelConstraints);
+        optionsPanel.add(consistencyPanel,            panelConstraints);
+
+        addBorder(whyAreYouKeepingPanel, "whyAreYouKeeping");
+        optionsPanel.add(tip(whyAreYouKeepingPanel, "whyAreYouKeepingTip"), stretchPanelConstraints);
+
+        // Create the process panel.
+        consoleTextArea.setOpaque(false);
+        consoleTextArea.setEditable(false);
+        consoleTextArea.setLineWrap(false);
+        consoleTextArea.setWrapStyleWord(false);
+        JScrollPane consoleScrollPane = new JScrollPane(consoleTextArea);
+        consoleScrollPane.setBorder(new EmptyBorder(1, 1, 1, 1));
+        addBorder(consoleScrollPane, "processingConsole");
+
+        JPanel processPanel = new JPanel(layout);
+        processPanel.add(consoleScrollPane, stretchPanelConstraints);
+
+        // Create the load, save, and process buttons.
+        JButton loadButton = new JButton(msg("loadConfiguration"));
+        loadButton.addActionListener(new MyLoadConfigurationActionListener());
+
+        JButton viewButton = new JButton(msg("viewConfiguration"));
+        viewButton.addActionListener(new MyViewConfigurationActionListener());
+
+        JButton saveButton = new JButton(msg("saveConfiguration"));
+        saveButton.addActionListener(new MySaveConfigurationActionListener());
+
+        JButton processButton = new JButton(msg("process"));
+        processButton.addActionListener(new MyProcessActionListener());
+
+        // Create the ReTrace panel.
+        JPanel reTraceSettingsPanel = new JPanel(layout);
+        addBorder(reTraceSettingsPanel, "reTraceSettings");
+
+        JButton reTraceMappingBrowseButton = createBrowseButton(reTraceMappingTextField,
+                                                                msg("selectApplyMappingFile"));
+
+        JLabel reTraceMappingLabel = new JLabel(msg("mappingFile"));
+        reTraceMappingLabel.setForeground(reTraceVerboseCheckBox.getForeground());
+
+        reTraceSettingsPanel.add(tip(reTraceVerboseCheckBox,     "verboseTip"),             constraintsLastStretch);
+        reTraceSettingsPanel.add(tip(reTraceMappingLabel,        "mappingFileTip"),         constraints);
+        reTraceSettingsPanel.add(tip(reTraceMappingTextField,    "inputFileTip"),           constraintsStretch);
+        reTraceSettingsPanel.add(tip(reTraceMappingBrowseButton, "selectApplyMappingFile"), constraintsLast);
+
+        stackTraceTextArea.setOpaque(true);
+        stackTraceTextArea.setEditable(true);
+        stackTraceTextArea.setLineWrap(false);
+        stackTraceTextArea.setWrapStyleWord(true);
+        JScrollPane stackTraceScrollPane = new JScrollPane(stackTraceTextArea);
+        addBorder(stackTraceScrollPane, "obfuscatedStackTrace");
+
+        reTraceTextArea.setOpaque(false);
+        reTraceTextArea.setEditable(false);
+        reTraceTextArea.setLineWrap(true);
+        reTraceTextArea.setWrapStyleWord(true);
+        JScrollPane reTraceScrollPane = new JScrollPane(reTraceTextArea);
+        reTraceScrollPane.setBorder(new EmptyBorder(1, 1, 1, 1));
+        addBorder(reTraceScrollPane, "deobfuscatedStackTrace");
+
+        JPanel reTracePanel = new JPanel(layout);
+        reTracePanel.add(reTraceSettingsPanel,                                 panelConstraints);
+        reTracePanel.add(tip(stackTraceScrollPane, "obfuscatedStackTraceTip"), panelConstraints);
+        reTracePanel.add(reTraceScrollPane,                                    stretchPanelConstraints);
+
+        // Create the load button.
+        JButton loadStackTraceButton = new JButton(msg("loadStackTrace"));
+        loadStackTraceButton.addActionListener(new MyLoadStackTraceActionListener());
+
+        JButton reTraceButton = new JButton(msg("reTrace"));
+        reTraceButton.addActionListener(new MyReTraceActionListener());
+
+        // Create the main tabbed pane.
+        TabbedPane tabs = new TabbedPane();
+        tabs.add(msg("proGuardTab"),     proGuardPanel);
+        tabs.add(msg("inputOutputTab"),  inputOutputPanel);
+        tabs.add(msg("shrinkingTab"),    shrinkingPanel);
+        tabs.add(msg("obfuscationTab"),  obfuscationPanel);
+        tabs.add(msg("optimizationTab"), optimizationPanel);
+        tabs.add(msg("informationTab"),  optionsPanel);
+        tabs.add(msg("processTab"),      processPanel);
+        tabs.add(msg("reTraceTab"),      reTracePanel);
+        tabs.addImage(Toolkit.getDefaultToolkit().getImage(
+            this.getClass().getResource(TITLE_IMAGE_FILE)));
+
+        // Add the bottom buttons to each panel.
+        proGuardPanel     .add(Box.createGlue(),                                      glueConstraints);
+        proGuardPanel     .add(tip(loadButton,             "loadConfigurationTip"),   bottomButtonConstraints);
+        proGuardPanel     .add(createNextButton(tabs),                                lastBottomButtonConstraints);
+
+        inputOutputPanel  .add(Box.createGlue(),           glueConstraints);
+        inputOutputPanel  .add(createPreviousButton(tabs), bottomButtonConstraints);
+        inputOutputPanel  .add(createNextButton(tabs),     lastBottomButtonConstraints);
+
+        shrinkingPanel    .add(Box.createGlue(),           glueConstraints);
+        shrinkingPanel    .add(createPreviousButton(tabs), bottomButtonConstraints);
+        shrinkingPanel    .add(createNextButton(tabs),     lastBottomButtonConstraints);
+
+        obfuscationPanel  .add(Box.createGlue(),           glueConstraints);
+        obfuscationPanel  .add(createPreviousButton(tabs), bottomButtonConstraints);
+        obfuscationPanel  .add(createNextButton(tabs),     lastBottomButtonConstraints);
+
+        optimizationPanel .add(Box.createGlue(),           glueConstraints);
+        optimizationPanel .add(createPreviousButton(tabs), bottomButtonConstraints);
+        optimizationPanel .add(createNextButton(tabs),     lastBottomButtonConstraints);
+
+        optionsPanel      .add(Box.createGlue(),           glueConstraints);
+        optionsPanel      .add(createPreviousButton(tabs), bottomButtonConstraints);
+        optionsPanel      .add(createNextButton(tabs),     lastBottomButtonConstraints);
+
+        processPanel      .add(Box.createGlue(),                                      glueConstraints);
+        processPanel      .add(createPreviousButton(tabs),                            bottomButtonConstraints);
+        processPanel      .add(tip(viewButton,             "viewConfigurationTip"),   bottomButtonConstraints);
+        processPanel      .add(tip(saveButton,             "saveConfigurationTip"),   bottomButtonConstraints);
+        processPanel      .add(tip(processButton,          "processTip"),             lastBottomButtonConstraints);
+
+        reTracePanel      .add(Box.createGlue(),                                      glueConstraints);
+        reTracePanel      .add(tip(loadStackTraceButton,   "loadStackTraceTip"),      bottomButtonConstraints);
+        reTracePanel      .add(tip(reTraceButton,          "reTraceTip"),             lastBottomButtonConstraints);
+
+        // Initialize the GUI settings to reasonable defaults.
+        loadConfiguration(this.getClass().getResource(DEFAULT_CONFIGURATION));
+
+        // Add the main tabs to the frame and pack it.
+        getContentPane().add(tabs);
+    }
+
+
+    public void startSplash()
+    {
+        splashPanel.start();
+    }
+
+
+    public void skipSplash()
+    {
+        splashPanel.stop();
+    }
+
+
+    /**
+     * Loads the boilerplate keep class options from the boilerplate file
+     * into the boilerplate array.
+     */
+    private void loadBoilerplateConfiguration()
+    {
+        try
+        {
+            // Parse the boilerplate configuration file.
+            ConfigurationParser parser = new ConfigurationParser(
+                this.getClass().getResource(BOILERPLATE_CONFIGURATION));
+            Configuration configuration = new Configuration();
+
+            try
+            {
+                parser.parse(configuration);
+
+                // We're interested in the keep options.
+                boilerplateKeep =
+                    extractKeepSpecifications(configuration.keep, false, false);
+
+                // We're interested in the keep options.
+                boilerplateKeepNames =
+                    extractKeepSpecifications(configuration.keep, true, false);
+
+                // We're interested in the side effects options.
+                boilerplateNoSideEffectMethods = new ClassSpecification[configuration.assumeNoSideEffects.size()];
+                configuration.assumeNoSideEffects.toArray(boilerplateNoSideEffectMethods);
+            }
+            finally
+            {
+                parser.close();
+            }
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+
+
+    /**
+     * Returns an array containing the ClassSpecifications instances with
+     * matching flags.
+     */
+    private KeepClassSpecification[] extractKeepSpecifications(List    keepSpecifications,
+                                                               boolean allowShrinking,
+                                                               boolean allowObfuscation)
+    {
+        List matches = new ArrayList();
+
+        for (int index = 0; index < keepSpecifications.size(); index++)
+        {
+            KeepClassSpecification keepClassSpecification = (KeepClassSpecification)keepSpecifications.get(index);
+            if (keepClassSpecification.allowShrinking   == allowShrinking &&
+                keepClassSpecification.allowObfuscation == allowObfuscation)
+            {
+                 matches.add(keepClassSpecification);
+            }
+        }
+
+        KeepClassSpecification[] matchingKeepClassSpecifications = new KeepClassSpecification[matches.size()];
+        matches.toArray(matchingKeepClassSpecifications);
+
+        return matchingKeepClassSpecifications;
+    }
+
+
+    /**
+     * Returns an array containing the ClassSpecification instances of the
+     * given array of KeepClassSpecification instances.
+     */
+    private ClassSpecification[] extractClassSpecifications(KeepClassSpecification[] keepClassSpecifications)
+    {
+        ClassSpecification[] classSpecifications = new ClassSpecification[keepClassSpecifications.length];
+
+        for (int index = 0; index < classSpecifications.length; index++)
+        {
+            classSpecifications[index] = keepClassSpecifications[index];
+        }
+
+        return classSpecifications;
+    }
+
+
+    /**
+     * Creates a panel with the given boiler plate class specifications.
+     */
+    private void addClassSpecifications(ClassSpecification[] boilerplateClassSpecifications,
+                                        JPanel               classSpecificationsPanel,
+                                        JCheckBox[]          boilerplateCheckBoxes,
+                                        JTextField[]         boilerplateTextFields)
+    {
+        // Create some constraints that can be reused.
+        GridBagConstraints constraints = new GridBagConstraints();
+        constraints.anchor = GridBagConstraints.WEST;
+        constraints.insets = new Insets(0, 4, 0, 4);
+
+        GridBagConstraints constraintsLastStretch = new GridBagConstraints();
+        constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER;
+        constraintsLastStretch.fill      = GridBagConstraints.HORIZONTAL;
+        constraintsLastStretch.weightx   = 1.0;
+        constraintsLastStretch.anchor    = GridBagConstraints.WEST;
+        constraintsLastStretch.insets    = constraints.insets;
+
+        GridBagConstraints panelConstraints = new GridBagConstraints();
+        panelConstraints.gridwidth = GridBagConstraints.REMAINDER;
+        panelConstraints.fill      = GridBagConstraints.HORIZONTAL;
+        panelConstraints.weightx   = 1.0;
+        panelConstraints.anchor    = GridBagConstraints.NORTHWEST;
+        panelConstraints.insets    = constraints.insets;
+
+        GridBagLayout layout = new GridBagLayout();
+
+        String lastPanelName = null;
+        JPanel keepSubpanel  = null;
+        for (int index = 0; index < boilerplateClassSpecifications.length; index++)
+        {
+            // The panel structure is derived from the comments.
+            String comments    = boilerplateClassSpecifications[index].comments;
+            int    dashIndex   = comments.indexOf('-');
+            int    periodIndex = comments.indexOf('.', dashIndex);
+            String panelName   = comments.substring(0, dashIndex).trim();
+            String optionName  = comments.substring(dashIndex + 1, periodIndex).replace('_', '.').trim();
+            String toolTip     = comments.substring(periodIndex + 1);
+            if (keepSubpanel == null || !panelName.equals(lastPanelName))
+            {
+                // Create a new keep subpanel and add it.
+                keepSubpanel = new JPanel(layout);
+                keepSubpanel.setBorder(BorderFactory.createTitledBorder(BORDER, panelName));
+                classSpecificationsPanel.add(keepSubpanel, panelConstraints);
+
+                lastPanelName = panelName;
+            }
+
+            // Add the check box to the subpanel.
+            JCheckBox boilerplateCheckBox = new JCheckBox(optionName);
+            boilerplateCheckBox.setToolTipText(toolTip);
+            boilerplateCheckBoxes[index] = boilerplateCheckBox;
+            keepSubpanel.add(boilerplateCheckBox,
+                             boilerplateTextFields != null ?
+                                 constraints :
+                                 constraintsLastStretch);
+
+            if (boilerplateTextFields != null)
+            {
+                // Add the text field to the subpanel.
+                boilerplateTextFields[index] = new JTextField(40);
+                keepSubpanel.add(tip(boilerplateTextFields[index], "classNamesTip"), constraintsLastStretch);
+            }
+        }
+    }
+
+
+    /**
+     * Adds a standard border with the title that corresponds to the given key
+     * in the GUI resources.
+     */
+    private void addBorder(JComponent component, String titleKey)
+    {
+        Border oldBorder = component.getBorder();
+        Border newBorder = BorderFactory.createTitledBorder(BORDER, msg(titleKey));
+
+        component.setBorder(oldBorder == null ?
+            newBorder :
+            new CompoundBorder(newBorder, oldBorder));
+    }
+
+
+    /**
+     * Creates a Previous button for the given tabbed pane.
+     */
+    private JButton createPreviousButton(final TabbedPane tabbedPane)
+    {
+        JButton browseButton = new JButton(msg("previous"));
+        browseButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                tabbedPane.previous();
+            }
+        });
+
+        return browseButton;
+    }
+
+
+    /**
+     * Creates a Next button for the given tabbed pane.
+     */
+    private JButton createNextButton(final TabbedPane tabbedPane)
+    {
+        JButton browseButton = new JButton(msg("next"));
+        browseButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                tabbedPane.next();
+            }
+        });
+
+        return browseButton;
+    }
+
+
+    /**
+     * Creates a browse button that opens a file browser for the given text field.
+     */
+    private JButton createBrowseButton(final JTextField textField,
+                                       final String     title)
+    {
+        JButton browseButton = new JButton(msg("browse"));
+        browseButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                // Update the file chooser.
+                fileChooser.setDialogTitle(title);
+                fileChooser.setSelectedFile(new File(textField.getText()));
+
+                int returnVal = fileChooser.showDialog(ProGuardGUI.this, msg("ok"));
+                if (returnVal == JFileChooser.APPROVE_OPTION)
+                {
+                    // Update the text field.
+                    textField.setText(fileChooser.getSelectedFile().getPath());
+                }
+            }
+        });
+
+        return browseButton;
+    }
+
+
+    protected JButton createOptimizationsButton(final JTextField textField)
+    {
+        final OptimizationsDialog optimizationsDialog = new OptimizationsDialog(ProGuardGUI.this);
+
+        JButton optimizationsButton = new JButton(msg("select"));
+        optimizationsButton.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                // Update the dialog.
+                optimizationsDialog.setFilter(textField.getText());
+
+                int returnValue = optimizationsDialog.showDialog();
+                if (returnValue == OptimizationsDialog.APPROVE_OPTION)
+                {
+                    // Update the text field.
+                    textField.setText(optimizationsDialog.getFilter());
+                }
+            }
+        });
+
+        return optimizationsButton;
+    }
+
+
+    /**
+     * Sets the preferred sizes of the given components to the maximum of their
+     * current preferred sizes.
+     */
+    private void setCommonPreferredSize(List components)
+    {
+        // Find the maximum preferred size.
+        Dimension maximumSize = null;
+        for (int index = 0; index < components.size(); index++)
+        {
+            JComponent component = (JComponent)components.get(index);
+            Dimension  size      = component.getPreferredSize();
+            if (maximumSize == null ||
+                size.getWidth() > maximumSize.getWidth())
+            {
+                maximumSize = size;
+            }
+        }
+
+        // Set the size that we found as the preferred size for all components.
+        for (int index = 0; index < components.size(); index++)
+        {
+            JComponent component = (JComponent)components.get(index);
+            component.setPreferredSize(maximumSize);
+        }
+    }
+
+
+    /**
+     * Updates to GUI settings to reflect the given ProGuard configuration.
+     */
+    private void setProGuardConfiguration(Configuration configuration)
+    {
+        // Set up the input and output jars and directories.
+        programPanel.setClassPath(configuration.programJars);
+        libraryPanel.setClassPath(configuration.libraryJars);
+
+        // Set up the boilerplate keep options.
+        for (int index = 0; index < boilerplateKeep.length; index++)
+        {
+            String classNames =
+                findMatchingKeepSpecifications(boilerplateKeep[index],
+                                               configuration.keep);
+
+            boilerplateKeepCheckBoxes[index].setSelected(classNames != null);
+            boilerplateKeepTextFields[index].setText(classNames == null ? "*" : classNames);
+        }
+
+
+        // Set up the boilerplate keep names options.
+        for (int index = 0; index < boilerplateKeepNames.length; index++)
+        {
+            String classNames =
+                findMatchingKeepSpecifications(boilerplateKeepNames[index],
+                                               configuration.keep);
+
+            boilerplateKeepNamesCheckBoxes[index].setSelected(classNames != null);
+            boilerplateKeepNamesTextFields[index].setText(classNames == null ? "*" : classNames);
+        }
+
+        // Set up the additional keep options. Note that the matched boilerplate
+        // options have been removed from the list.
+        additionalKeepPanel.setClassSpecifications(filteredKeepSpecifications(configuration.keep,
+                                                                              false));
+
+        // Set up the additional keep options. Note that the matched boilerplate
+        // options have been removed from the list.
+        additionalKeepNamesPanel.setClassSpecifications(filteredKeepSpecifications(configuration.keep,
+                                                                                   true));
+
+
+        // Set up the boilerplate "no side effect methods" options.
+        for (int index = 0; index < boilerplateNoSideEffectMethods.length; index++)
+        {
+            boolean found =
+                findClassSpecification(boilerplateNoSideEffectMethods[index],
+                                       configuration.assumeNoSideEffects);
+
+            boilerplateNoSideEffectMethodCheckBoxes[index].setSelected(found);
+        }
+
+        // Set up the additional keep options. Note that the matched boilerplate
+        // options have been removed from the list.
+        additionalNoSideEffectsPanel.setClassSpecifications(configuration.assumeNoSideEffects);
+
+        // Set up the "why are you keeping" options.
+        whyAreYouKeepingPanel.setClassSpecifications(configuration.whyAreYouKeeping);
+
+        // Set up the other options.
+        shrinkCheckBox                          .setSelected(configuration.shrink);
+        printUsageCheckBox                      .setSelected(configuration.printUsage != null);
+
+        optimizeCheckBox                        .setSelected(configuration.optimize);
+        allowAccessModificationCheckBox         .setSelected(configuration.allowAccessModification);
+        mergeInterfacesAggressivelyCheckBox     .setSelected(configuration.mergeInterfacesAggressively);
+        optimizationPassesSpinner.getModel()    .setValue(new Integer(configuration.optimizationPasses));
+
+        obfuscateCheckBox                       .setSelected(configuration.obfuscate);
+        printMappingCheckBox                    .setSelected(configuration.printMapping                 != null);
+        applyMappingCheckBox                    .setSelected(configuration.applyMapping                 != null);
+        obfuscationDictionaryCheckBox           .setSelected(configuration.obfuscationDictionary        != null);
+        classObfuscationDictionaryCheckBox      .setSelected(configuration.classObfuscationDictionary   != null);
+        packageObfuscationDictionaryCheckBox    .setSelected(configuration.packageObfuscationDictionary != null);
+        overloadAggressivelyCheckBox            .setSelected(configuration.overloadAggressively);
+        useUniqueClassMemberNamesCheckBox       .setSelected(configuration.useUniqueClassMemberNames);
+        useMixedCaseClassNamesCheckBox          .setSelected(configuration.useMixedCaseClassNames);
+        keepPackageNamesCheckBox                .setSelected(configuration.keepPackageNames             != null);
+        flattenPackageHierarchyCheckBox         .setSelected(configuration.flattenPackageHierarchy      != null);
+        repackageClassesCheckBox                .setSelected(configuration.repackageClasses             != null);
+        keepAttributesCheckBox                  .setSelected(configuration.keepAttributes               != null);
+        newSourceFileAttributeCheckBox          .setSelected(configuration.newSourceFileAttribute       != null);
+        adaptClassStringsCheckBox               .setSelected(configuration.adaptClassStrings            != null);
+        adaptResourceFileNamesCheckBox          .setSelected(configuration.adaptResourceFileNames       != null);
+        adaptResourceFileContentsCheckBox       .setSelected(configuration.adaptResourceFileContents    != null);
+
+        preverifyCheckBox                       .setSelected(configuration.preverify);
+        microEditionCheckBox                    .setSelected(configuration.microEdition);
+        targetCheckBox                          .setSelected(configuration.targetClassVersion != 0);
+
+        verboseCheckBox                         .setSelected(configuration.verbose);
+        noteCheckBox                            .setSelected(configuration.note == null || !configuration.note.isEmpty());
+        warnCheckBox                            .setSelected(configuration.warn == null || !configuration.warn.isEmpty());
+        ignoreWarningsCheckBox                  .setSelected(configuration.ignoreWarnings);
+        skipNonPublicLibraryClassesCheckBox     .setSelected(configuration.skipNonPublicLibraryClasses);
+        skipNonPublicLibraryClassMembersCheckBox.setSelected(configuration.skipNonPublicLibraryClassMembers);
+        keepDirectoriesCheckBox                 .setSelected(configuration.keepDirectories    != null);
+        forceProcessingCheckBox                 .setSelected(configuration.lastModified == Long.MAX_VALUE);
+        printSeedsCheckBox                      .setSelected(configuration.printSeeds         != null);
+        printConfigurationCheckBox              .setSelected(configuration.printConfiguration != null);
+        dumpCheckBox                            .setSelected(configuration.dump               != null);
+
+        printUsageTextField                     .setText(fileName(configuration.printUsage));
+        optimizationsTextField                  .setText(configuration.optimizations             == null ? OPTIMIZATIONS_DEFAULT                : ListUtil.commaSeparatedString(configuration.optimizations));
+        printMappingTextField                   .setText(fileName(configuration.printMapping));
+        applyMappingTextField                   .setText(fileName(configuration.applyMapping));
+        obfuscationDictionaryTextField          .setText(fileName(configuration.obfuscationDictionary));
+        keepPackageNamesTextField               .setText(configuration.keepPackageNames          == null ? ""                                   : ClassUtil.externalClassName(ListUtil.commaSeparatedString(configuration.keepPackageNames)));
+        flattenPackageHierarchyTextField        .setText(configuration.flattenPackageHierarchy);
+        repackageClassesTextField               .setText(configuration.repackageClasses);
+        keepAttributesTextField                 .setText(configuration.keepAttributes            == null ? KEEP_ATTRIBUTE_DEFAULT               : ListUtil.commaSeparatedString(configuration.keepAttributes));
+        newSourceFileAttributeTextField         .setText(configuration.newSourceFileAttribute    == null ? SOURCE_FILE_ATTRIBUTE_DEFAULT        : configuration.newSourceFileAttribute);
+        adaptClassStringsTextField              .setText(configuration.adaptClassStrings         == null ? ""                                   : ClassUtil.externalClassName(ListUtil.commaSeparatedString(configuration.adaptClassStrings)));
+        adaptResourceFileNamesTextField         .setText(configuration.adaptResourceFileNames    == null ? ADAPT_RESOURCE_FILE_NAMES_DEFAULT    : ListUtil.commaSeparatedString(configuration.adaptResourceFileNames));
+        adaptResourceFileContentsTextField      .setText(configuration.adaptResourceFileContents == null ? ADAPT_RESOURCE_FILE_CONTENTS_DEFAULT : ListUtil.commaSeparatedString(configuration.adaptResourceFileContents));
+        noteTextField                           .setText(ListUtil.commaSeparatedString(configuration.note));
+        warnTextField                           .setText(ListUtil.commaSeparatedString(configuration.warn));
+        keepDirectoriesTextField                .setText(ListUtil.commaSeparatedString(configuration.keepDirectories));
+        printSeedsTextField                     .setText(fileName(configuration.printSeeds));
+        printConfigurationTextField             .setText(fileName(configuration.printConfiguration));
+        dumpTextField                           .setText(fileName(configuration.dump));
+
+        if (configuration.targetClassVersion != 0)
+        {
+            targetComboBox.setSelectedItem(ClassUtil.externalClassVersion(configuration.targetClassVersion));
+        }
+        else
+        {
+            targetComboBox.setSelectedIndex(targetComboBox.getItemCount() - 1);
+        }
+
+        if (configuration.printMapping != null)
+        {
+            reTraceMappingTextField.setText(fileName(configuration.printMapping));
+        }
+    }
+
+
+    /**
+     * Returns the ProGuard configuration that reflects the current GUI settings.
+     */
+    private Configuration getProGuardConfiguration()
+    {
+        Configuration configuration = new Configuration();
+
+        // Get the input and output jars and directories.
+        configuration.programJars = programPanel.getClassPath();
+        configuration.libraryJars = libraryPanel.getClassPath();
+
+        List keep = new ArrayList();
+
+        // Collect the additional keep options.
+        List additionalKeep = additionalKeepPanel.getClassSpecifications();
+        if (additionalKeep != null)
+        {
+            keep.addAll(additionalKeep);
+        }
+
+        // Collect the additional keep names options.
+        List additionalKeepNames = additionalKeepNamesPanel.getClassSpecifications();
+        if (additionalKeepNames != null)
+        {
+            keep.addAll(additionalKeepNames);
+        }
+
+        // Collect the boilerplate keep options.
+        for (int index = 0; index < boilerplateKeep.length; index++)
+        {
+            if (boilerplateKeepCheckBoxes[index].isSelected())
+            {
+                keep.add(classSpecification(boilerplateKeep[index],
+                                            boilerplateKeepTextFields[index].getText()));
+            }
+        }
+
+        // Collect the boilerplate keep names options.
+        for (int index = 0; index < boilerplateKeepNames.length; index++)
+        {
+            if (boilerplateKeepNamesCheckBoxes[index].isSelected())
+            {
+                keep.add(classSpecification(boilerplateKeepNames[index],
+                                            boilerplateKeepNamesTextFields[index].getText()));
+            }
+        }
+
+        // Put the list of keep specifications in the configuration.
+        if (keep.size() > 0)
+        {
+            configuration.keep = keep;
+        }
+
+
+        // Collect the boilerplate "no side effect methods" options.
+        List noSideEffectMethods = new ArrayList();
+
+        for (int index = 0; index < boilerplateNoSideEffectMethods.length; index++)
+        {
+            if (boilerplateNoSideEffectMethodCheckBoxes[index].isSelected())
+            {
+                noSideEffectMethods.add(boilerplateNoSideEffectMethods[index]);
+            }
+        }
+
+        // Collect the additional "no side effect methods" options.
+        List additionalNoSideEffectOptions = additionalNoSideEffectsPanel.getClassSpecifications();
+        if (additionalNoSideEffectOptions != null)
+        {
+            noSideEffectMethods.addAll(additionalNoSideEffectOptions);
+        }
+
+        // Put the list of "no side effect methods" options in the configuration.
+        if (noSideEffectMethods.size() > 0)
+        {
+            configuration.assumeNoSideEffects = noSideEffectMethods;
+        }
+
+
+        // Collect the "why are you keeping" options.
+        configuration.whyAreYouKeeping = whyAreYouKeepingPanel.getClassSpecifications();
+
+
+        // Get the other options.
+        configuration.shrink                           = shrinkCheckBox                          .isSelected();
+        configuration.printUsage                       = printUsageCheckBox                      .isSelected() ? new File(printUsageTextField                                         .getText()) : null;
+
+        configuration.optimize                         = optimizeCheckBox                        .isSelected();
+        configuration.allowAccessModification          = allowAccessModificationCheckBox         .isSelected();
+        configuration.mergeInterfacesAggressively      = mergeInterfacesAggressivelyCheckBox     .isSelected();
+        configuration.optimizations                    = optimizationsTextField.getText().length() > 1 ?         ListUtil.commaSeparatedList(optimizationsTextField                   .getText()) : null;
+        configuration.optimizationPasses               = ((SpinnerNumberModel)optimizationPassesSpinner.getModel()).getNumber().intValue();
+
+        configuration.obfuscate                        = obfuscateCheckBox                       .isSelected();
+        configuration.printMapping                     = printMappingCheckBox                    .isSelected() ? new File(printMappingTextField                                       .getText()) : null;
+        configuration.applyMapping                     = applyMappingCheckBox                    .isSelected() ? new File(applyMappingTextField                                       .getText()) : null;
+        configuration.obfuscationDictionary            = obfuscationDictionaryCheckBox           .isSelected() ? new File(obfuscationDictionaryTextField                              .getText()) : null;
+        configuration.classObfuscationDictionary       = classObfuscationDictionaryCheckBox      .isSelected() ? new File(classObfuscationDictionaryTextField                         .getText()) : null;
+        configuration.packageObfuscationDictionary     = packageObfuscationDictionaryCheckBox    .isSelected() ? new File(packageObfuscationDictionaryTextField                       .getText()) : null;
+        configuration.overloadAggressively             = overloadAggressivelyCheckBox            .isSelected();
+        configuration.useUniqueClassMemberNames        = useUniqueClassMemberNamesCheckBox       .isSelected();
+        configuration.useMixedCaseClassNames           = useMixedCaseClassNamesCheckBox          .isSelected();
+        configuration.keepPackageNames                 = keepPackageNamesCheckBox                .isSelected() ? keepPackageNamesTextField.getText().length()  > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(keepPackageNamesTextField.getText()))  : new ArrayList() : null;
+        configuration.flattenPackageHierarchy          = flattenPackageHierarchyCheckBox         .isSelected() ? ClassUtil.internalClassName(flattenPackageHierarchyTextField         .getText()) : null;
+        configuration.repackageClasses                 = repackageClassesCheckBox                .isSelected() ? ClassUtil.internalClassName(repackageClassesTextField                .getText()) : null;
+        configuration.keepAttributes                   = keepAttributesCheckBox                  .isSelected() ? ListUtil.commaSeparatedList(keepAttributesTextField                  .getText()) : null;
+        configuration.newSourceFileAttribute           = newSourceFileAttributeCheckBox          .isSelected() ? newSourceFileAttributeTextField                                      .getText()  : null;
+        configuration.adaptClassStrings                = adaptClassStringsCheckBox               .isSelected() ? adaptClassStringsTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(adaptClassStringsTextField.getText())) : new ArrayList() : null;
+        configuration.adaptResourceFileNames           = adaptResourceFileNamesCheckBox          .isSelected() ? ListUtil.commaSeparatedList(adaptResourceFileNamesTextField          .getText()) : null;
+        configuration.adaptResourceFileContents        = adaptResourceFileContentsCheckBox       .isSelected() ? ListUtil.commaSeparatedList(adaptResourceFileContentsTextField       .getText()) : null;
+
+        configuration.preverify                        = preverifyCheckBox                       .isSelected();
+        configuration.microEdition                     = microEditionCheckBox                    .isSelected();
+        configuration.targetClassVersion               = targetCheckBox                          .isSelected() ? ClassUtil.internalClassVersion(targetComboBox.getSelectedItem().toString()) : 0;
+
+        configuration.verbose                          = verboseCheckBox                         .isSelected();
+        configuration.note                             = noteCheckBox                            .isSelected() ? noteTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(noteTextField.getText())) : null : new ArrayList();
+        configuration.warn                             = warnCheckBox                            .isSelected() ? warnTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(warnTextField.getText())) : null : new ArrayList();
+        configuration.ignoreWarnings                   = ignoreWarningsCheckBox                  .isSelected();
+        configuration.skipNonPublicLibraryClasses      = skipNonPublicLibraryClassesCheckBox     .isSelected();
+        configuration.skipNonPublicLibraryClassMembers = skipNonPublicLibraryClassMembersCheckBox.isSelected();
+        configuration.keepDirectories                  = keepDirectoriesCheckBox                 .isSelected() ? keepDirectoriesTextField.getText().length() > 0 ? ListUtil.commaSeparatedList(ClassUtil.internalClassName(keepDirectoriesTextField.getText())) : new ArrayList() : null;
+        configuration.lastModified                     = forceProcessingCheckBox                 .isSelected() ? Long.MAX_VALUE : System.currentTimeMillis();
+        configuration.printSeeds                       = printSeedsCheckBox                      .isSelected() ? new File(printSeedsTextField                                         .getText()) : null;
+        configuration.printConfiguration               = printConfigurationCheckBox              .isSelected() ? new File(printConfigurationTextField                                 .getText()) : null;
+        configuration.dump                             = dumpCheckBox                            .isSelected() ? new File(dumpTextField                                               .getText()) : null;
+
+        return configuration;
+    }
+
+
+    /**
+     * Looks in the given list for a class specification that is identical to
+     * the given template. Returns true if it is found, and removes the matching
+     * class specification as a side effect.
+     */
+    private boolean findClassSpecification(ClassSpecification classSpecificationTemplate,
+                                           List                classSpecifications)
+    {
+        if (classSpecifications == null)
+        {
+            return false;
+        }
+
+        for (int index = 0; index < classSpecifications.size(); index++)
+        {
+            if (classSpecificationTemplate.equals(classSpecifications.get(index)))
+            {
+                // Remove the matching option as a side effect.
+                classSpecifications.remove(index);
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns the subset of the given list of keep specifications, with
+     * matching shrinking flag.
+     */
+    private List filteredKeepSpecifications(List    keepSpecifications,
+                                            boolean allowShrinking)
+    {
+        List filteredKeepSpecifications = new ArrayList();
+
+        for (int index = 0; index < keepSpecifications.size(); index++)
+        {
+            KeepClassSpecification keepClassSpecification =
+                (KeepClassSpecification)keepSpecifications.get(index);
+
+            if (keepClassSpecification.allowShrinking == allowShrinking)
+            {
+                filteredKeepSpecifications.add(keepClassSpecification);
+            }
+        }
+
+        return filteredKeepSpecifications;
+    }
+
+
+    /**
+     * Looks in the given list for keep specifications that match the given
+     * template. Returns a comma-separated string of class names from
+     * matching keep specifications, and removes the matching keep
+     * specifications as a side effect.
+     */
+    private String findMatchingKeepSpecifications(KeepClassSpecification keepClassSpecificationTemplate,
+                                                  List              keepSpecifications)
+    {
+        if (keepSpecifications == null)
+        {
+            return null;
+        }
+
+        StringBuffer buffer = null;
+
+        for (int index = 0; index < keepSpecifications.size(); index++)
+        {
+            KeepClassSpecification listedKeepClassSpecification =
+                (KeepClassSpecification)keepSpecifications.get(index);
+            String className = listedKeepClassSpecification.className;
+            keepClassSpecificationTemplate.className = className;
+            if (keepClassSpecificationTemplate.equals(listedKeepClassSpecification))
+            {
+                if (buffer == null)
+                {
+                    buffer = new StringBuffer();
+                }
+                else
+                {
+                    buffer.append(',');
+                }
+                buffer.append(className == null ? "*" : ClassUtil.externalClassName(className));
+
+                // Remove the matching option as a side effect.
+                keepSpecifications.remove(index--);
+            }
+        }
+
+        return buffer == null ? null : buffer.toString();
+    }
+
+
+    /**
+     * Returns a class specification or keep specification, based on the given
+     * template and the class name to be filled in.
+     */
+    private ClassSpecification classSpecification(ClassSpecification classSpecificationTemplate,
+                                                  String             className)
+    {
+        // Create a copy of the template.
+        ClassSpecification classSpecification =
+            (ClassSpecification)classSpecificationTemplate.clone();
+
+        // Set the class name in the copy.
+        classSpecification.className =
+            className.equals("") ||
+            className.equals("*") ?
+                null :
+                ClassUtil.internalClassName(className);
+
+        // Return the modified copy.
+        return classSpecification;
+    }
+
+
+    // Methods and internal classes related to actions.
+
+    /**
+     * Loads the given ProGuard configuration into the GUI.
+     */
+    private void loadConfiguration(File file)
+    {
+        // Set the default directory and file in the file choosers.
+        configurationChooser.setSelectedFile(file.getAbsoluteFile());
+        fileChooser.setCurrentDirectory(file.getAbsoluteFile().getParentFile());
+
+        try
+        {
+            // Parse the configuration file.
+            ConfigurationParser parser = new ConfigurationParser(file);
+            Configuration configuration = new Configuration();
+
+            try
+            {
+                parser.parse(configuration);
+
+                // Let the GUI reflect the configuration.
+                setProGuardConfiguration(configuration);
+            }
+            catch (ParseException ex)
+            {
+                JOptionPane.showMessageDialog(getContentPane(),
+                                              msg("cantParseConfigurationFile", file.getPath()),
+                                              msg("warning"),
+                                              JOptionPane.ERROR_MESSAGE);
+            }
+            finally
+            {
+                parser.close();
+            }
+        }
+        catch (IOException ex)
+        {
+            JOptionPane.showMessageDialog(getContentPane(),
+                                          msg("cantOpenConfigurationFile", file.getPath()),
+                                          msg("warning"),
+                                          JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+
+    /**
+     * Loads the given ProGuard configuration into the GUI.
+     */
+    private void loadConfiguration(URL url)
+    {
+        try
+        {
+            // Parse the configuration file.
+            ConfigurationParser parser = new ConfigurationParser(url);
+            Configuration configuration = new Configuration();
+
+            try
+            {
+                parser.parse(configuration);
+
+                // Let the GUI reflect the configuration.
+                setProGuardConfiguration(configuration);
+            }
+            catch (ParseException ex)
+            {
+                JOptionPane.showMessageDialog(getContentPane(),
+                                              msg("cantParseConfigurationFile", url),
+                                              msg("warning"),
+                                              JOptionPane.ERROR_MESSAGE);
+            }
+            finally
+            {
+                parser.close();
+            }
+        }
+        catch (IOException ex)
+        {
+            JOptionPane.showMessageDialog(getContentPane(),
+                                          msg("cantOpenConfigurationFile", url),
+                                          msg("warning"),
+                                          JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+
+    /**
+     * Saves the current ProGuard configuration to the given file.
+     */
+    private void saveConfiguration(File file)
+    {
+        try
+        {
+            // Save the configuration file.
+            ConfigurationWriter writer = new ConfigurationWriter(file);
+            writer.write(getProGuardConfiguration());
+            writer.close();
+        }
+        catch (Exception ex)
+        {
+            JOptionPane.showMessageDialog(getContentPane(),
+                                          msg("cantSaveConfigurationFile", file.getPath()),
+                                          msg("warning"),
+                                          JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+
+    /**
+     * Loads the given stack trace into the GUI.
+     */
+    private void loadStackTrace(String fileName)
+    {
+        try
+        {
+            StringBuffer buffer = new StringBuffer(1024);
+
+            Reader reader = new BufferedReader(new FileReader(fileName));
+            try
+            {
+                while (true)
+                {
+                    int c = reader.read();
+                    if (c < 0)
+                    {
+                        break;
+                    }
+
+                    buffer.append(c);
+                }
+            }
+            finally
+            {
+                reader.close();
+            }
+
+            // Put the stack trace in the text area.
+            stackTraceTextArea.setText(buffer.toString());
+        }
+        catch (IOException ex)
+        {
+            JOptionPane.showMessageDialog(getContentPane(),
+                                          msg("cantOpenStackTraceFile", fileName),
+                                          msg("warning"),
+                                          JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+
+    /**
+     * This ActionListener loads a ProGuard configuration file and initializes
+     * the GUI accordingly.
+     */
+    private class MyLoadConfigurationActionListener implements ActionListener
+    {
+        public void actionPerformed(ActionEvent e)
+        {
+            configurationChooser.setDialogTitle(msg("selectConfigurationFile"));
+
+            int returnValue = configurationChooser.showOpenDialog(ProGuardGUI.this);
+            if (returnValue == JFileChooser.APPROVE_OPTION)
+            {
+                loadConfiguration(configurationChooser.getSelectedFile());
+            }
+        }
+    }
+
+
+    /**
+     * This ActionListener saves a ProGuard configuration file based on the
+     * current GUI settings.
+     */
+    private class MySaveConfigurationActionListener implements ActionListener
+    {
+        public void actionPerformed(ActionEvent e)
+        {
+            configurationChooser.setDialogTitle(msg("saveConfigurationFile"));
+
+            int returnVal = configurationChooser.showSaveDialog(ProGuardGUI.this);
+            if (returnVal == JFileChooser.APPROVE_OPTION)
+            {
+                saveConfiguration(configurationChooser.getSelectedFile());
+            }
+        }
+    }
+
+
+    /**
+     * This ActionListener displays the ProGuard configuration specified by the
+     * current GUI settings.
+     */
+    private class MyViewConfigurationActionListener implements ActionListener
+    {
+        public void actionPerformed(ActionEvent e)
+        {
+            // Make sure System.out has not been redirected yet.
+            if (!systemOutRedirected)
+            {
+                consoleTextArea.setText("");
+
+                TextAreaOutputStream outputStream =
+                    new TextAreaOutputStream(consoleTextArea);
+
+                try
+                {
+                    // TODO: write out relative path names and path names with system properties.
+
+                    // Write the configuration.
+                    ConfigurationWriter writer = new ConfigurationWriter(outputStream);
+                    try
+                    {
+                        writer.write(getProGuardConfiguration());
+                    }
+                    finally
+                    {
+                        writer.close();
+                    }
+                }
+                catch (IOException ex)
+                {
+                    // This shouldn't happen.
+                }
+
+                // Scroll to the top of the configuration.
+                consoleTextArea.setCaretPosition(0);
+            }
+        }
+    }
+
+
+    /**
+     * This ActionListener executes ProGuard based on the current GUI settings.
+     */
+    private class MyProcessActionListener implements ActionListener
+    {
+        public void actionPerformed(ActionEvent e)
+        {
+            // Make sure System.out has not been redirected yet.
+            if (!systemOutRedirected)
+            {
+                systemOutRedirected = true;
+
+                // Get the informational configuration file name.
+                File configurationFile = configurationChooser.getSelectedFile();
+                String configurationFileName = configurationFile != null ?
+                    configurationFile.getName() :
+                    msg("sampleConfigurationFileName");
+
+                // Create the ProGuard thread.
+                Thread proGuardThread =
+                    new Thread(new ProGuardRunnable(consoleTextArea,
+                                                    getProGuardConfiguration(),
+                                                    configurationFileName));
+
+                // Run it.
+                proGuardThread.start();
+            }
+        }
+    }
+
+
+    /**
+     * This ActionListener loads an obfuscated stack trace from a file and puts
+     * it in the proper text area.
+     */
+    private class MyLoadStackTraceActionListener implements ActionListener
+    {
+        public void actionPerformed(ActionEvent e)
+        {
+            fileChooser.setDialogTitle(msg("selectStackTraceFile"));
+            fileChooser.setSelectedFile(null);
+
+            int returnValue = fileChooser.showOpenDialog(ProGuardGUI.this);
+            if (returnValue == JFileChooser.APPROVE_OPTION)
+            {
+                File selectedFile = fileChooser.getSelectedFile();
+                String fileName = selectedFile.getPath();
+
+                loadStackTrace(fileName);
+            }
+        }
+    }
+
+
+    /**
+     * This ActionListener executes ReTrace based on the current GUI settings.
+     */
+    private class MyReTraceActionListener implements ActionListener
+    {
+        public void actionPerformed(ActionEvent e)
+        {
+            // Make sure System.out has not been redirected yet.
+            if (!systemOutRedirected)
+            {
+                systemOutRedirected = true;
+
+                boolean verbose            = reTraceVerboseCheckBox.isSelected();
+                File    retraceMappingFile = new File(reTraceMappingTextField.getText());
+                String  stackTrace         = stackTraceTextArea.getText();
+
+                // Create the ReTrace runnable.
+                Runnable reTraceRunnable = new ReTraceRunnable(reTraceTextArea,
+                                                               verbose,
+                                                               retraceMappingFile,
+                                                               stackTrace);
+
+                // Run it in this thread, because it won't take long anyway.
+                reTraceRunnable.run();
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the file name of the given file, if any.
+     */
+    private static String fileName(File file)
+    {
+        return file == null ? "" : file.getAbsolutePath();
+    }
+
+
+    /**
+     * Attaches the tool tip from the GUI resources that corresponds to the
+     * given key, to the given component.
+     */
+    private static JComponent tip(JComponent component, String messageKey)
+    {
+        component.setToolTipText(msg(messageKey));
+
+        return component;
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private static String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key and argument.
+     */
+    private String msg(String messageKey,
+                       Object messageArgument)
+    {
+         return GUIResources.getMessage(messageKey, new Object[] {messageArgument});
+    }
+
+
+    /**
+     * The main method for the ProGuard GUI.
+     */
+    public static void main(final String[] args)
+    {
+        try
+        {
+            SwingUtil.invokeAndWait(new Runnable()
+            {
+                public void run()
+                {
+                    ProGuardGUI gui = new ProGuardGUI();
+                    gui.pack();
+
+                    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+                    Dimension guiSize    = gui.getSize();
+                    gui.setLocation((screenSize.width - guiSize.width)   / 2,
+                                    (screenSize.height - guiSize.height) / 2);
+                    gui.show();
+
+                    // Start the splash animation, unless specified otherwise.
+                    int argIndex = 0;
+                    if (argIndex < args.length &&
+                        NO_SPLASH_OPTION.startsWith(args[argIndex]))
+                    {
+                        gui.skipSplash();
+                        argIndex++;
+                    }
+                    else
+                    {
+                        gui.startSplash();
+                    }
+
+                    // Load an initial configuration, if specified.
+                    if (argIndex < args.length)
+                    {
+                        gui.loadConfiguration(new File(args[argIndex]));
+                        argIndex++;
+                    }
+
+                    if (argIndex < args.length)
+                    {
+                        System.out.println(gui.getClass().getName() + ": ignoring extra arguments [" + args[argIndex] + "...]");
+                    }
+
+                }
+            });
+        }
+        catch (Exception e)
+        {
+            // Nothing.
+        }
+    }
+}
diff --git a/src/proguard/gui/ProGuardRunnable.java b/src/proguard/gui/ProGuardRunnable.java
new file mode 100644
index 0000000..c5c5937
--- /dev/null
+++ b/src/proguard/gui/ProGuardRunnable.java
@@ -0,0 +1,154 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.*;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.PrintStream;
+
+
+/**
+ * This <code>Runnable</code> runs ProGuard, sending console output to a text
+ * area and any exceptions to message dialogs.
+ *
+ * @see ProGuard
+ * @author Eric Lafortune
+ */
+final class ProGuardRunnable implements Runnable
+{
+    private final JTextArea     consoleTextArea;
+    private final Configuration configuration;
+    private final String        configurationFileName;
+
+
+    /**
+     * Creates a new ProGuardRunnable object.
+     * @param consoleTextArea       the text area to send the console output to.
+     * @param configuration         the ProGuard configuration.
+     * @param configurationFileName the optional file name of the configuration,
+     *                              for informational purposes.
+     */
+    public ProGuardRunnable(JTextArea     consoleTextArea,
+                            Configuration configuration,
+                            String        configurationFileName)
+    {
+        this.consoleTextArea       = consoleTextArea;
+        this.configuration         = configuration;
+        this.configurationFileName = configurationFileName;
+    }
+
+
+    // Implementation for Runnable.
+
+    public void run()
+    {
+        consoleTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+        consoleTextArea.setText("");
+
+        // Redirect the System's out and err streams to the console text area.
+        PrintStream oldOut = System.out;
+        PrintStream oldErr = System.err;
+
+        PrintStream printStream =
+            new PrintStream(new TextAreaOutputStream(consoleTextArea), true);
+
+        System.setOut(printStream);
+        System.setErr(printStream);
+
+        try
+        {
+            // Create a new ProGuard object with the GUI's configuration.
+            ProGuard proGuard = new ProGuard(configuration);
+
+            // Run it.
+            proGuard.execute();
+
+            // Print out the completion message.
+            System.out.println("Processing completed successfully");
+        }
+        catch (Exception ex)
+        {
+            //ex.printStackTrace();
+
+            // Print out the exception message.
+            System.out.println(ex.getMessage());
+
+            // Show a dialog as well.
+            MessageDialogRunnable.showMessageDialog(consoleTextArea,
+                                                    ex.getMessage(),
+                                                    msg("errorProcessing"),
+                                                    JOptionPane.ERROR_MESSAGE);
+        }
+        catch (OutOfMemoryError er)
+        {
+            // Forget about the ProGuard object as quickly as possible.
+            System.gc();
+
+            // Print out a message suggesting what to do next.
+            System.out.println(msg("outOfMemoryInfo", configurationFileName));
+
+            // Show a dialog as well.
+            MessageDialogRunnable.showMessageDialog(consoleTextArea,
+                                                    msg("outOfMemory"),
+                                                    msg("errorProcessing"),
+                                                    JOptionPane.ERROR_MESSAGE);
+        }
+        finally
+        {
+            // Make sure all output has been sent to the console text area.
+            printStream.close();
+
+            // Restore the old System's out and err streams.
+            System.setOut(oldOut);
+            System.setErr(oldErr);
+        }
+
+        consoleTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+
+        // Reset the global static redirection lock.
+        ProGuardGUI.systemOutRedirected = false;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key and argument.
+     */
+    private String msg(String messageKey,
+                       Object messageArgument)
+    {
+         return GUIResources.getMessage(messageKey, new Object[] {messageArgument});
+    }
+}
diff --git a/src/proguard/gui/ReTraceRunnable.java b/src/proguard/gui/ReTraceRunnable.java
new file mode 100644
index 0000000..1ca19ca
--- /dev/null
+++ b/src/proguard/gui/ReTraceRunnable.java
@@ -0,0 +1,149 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import proguard.retrace.ReTrace;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.*;
+
+
+/**
+ * This <code>Runnable</code> runs ReTrace, sending console output to a text
+ * area and any exceptions to message dialogs.
+ *
+ * @see ReTrace
+ * @author Eric Lafortune
+ */
+final class ReTraceRunnable implements Runnable
+{
+    private final JTextArea consoleTextArea;
+    private final boolean   verbose;
+    private final File      mappingFile;
+    private final String    stackTrace;
+
+
+    /**
+     * Creates a new ProGuardRunnable object.
+     * @param consoleTextArea the text area to send the console output to.
+     * @param verbose         specifies whether the de-obfuscated stack trace
+     *                        should be verbose.
+     * @param mappingFile     the mapping file that was written out by ProGuard.
+     */
+    public ReTraceRunnable(JTextArea consoleTextArea,
+                           boolean   verbose,
+                           File      mappingFile,
+                           String    stackTrace)
+    {
+        this.consoleTextArea = consoleTextArea;
+        this.verbose         = verbose;
+        this.mappingFile     = mappingFile;
+        this.stackTrace      = stackTrace;
+    }
+
+
+    // Implementation for Runnable.
+
+    public void run()
+    {
+        consoleTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+        consoleTextArea.setText("");
+
+        // Redirect the stack trace string to the System's in stream, and the
+        // out and err streams to the console text area.
+        InputStream oldIn  = System.in;
+        PrintStream oldOut = System.out;
+        PrintStream oldErr = System.err;
+
+        ByteArrayInputStream inputStream =
+           new ByteArrayInputStream(stackTrace.getBytes());
+
+        PrintStream printStream =
+            new PrintStream(new TextAreaOutputStream(consoleTextArea), true);
+
+        System.setIn(inputStream);
+        System.setOut(printStream);
+        System.setErr(printStream);
+
+        try
+        {
+            // Create a new ProGuard object with the GUI's configuration.
+            ReTrace reTrace = new ReTrace(ReTrace.STACK_TRACE_EXPRESSION,
+                                          verbose,
+                                          mappingFile);
+
+            // Run it.
+            reTrace.execute();
+        }
+        catch (Exception ex)
+        {
+            // Print out the exception message.
+            System.out.println(ex.getMessage());
+
+            // Show a dialog as well.
+            MessageDialogRunnable.showMessageDialog(consoleTextArea,
+                                                    ex.getMessage(),
+                                                    msg("errorReTracing"),
+                                                    JOptionPane.ERROR_MESSAGE);
+        }
+        catch (OutOfMemoryError er)
+        {
+            // Forget about the ProGuard object as quickly as possible.
+            System.gc();
+
+            // Print out a message suggesting what to do next.
+            System.out.println(msg("outOfMemory"));
+
+            // Show a dialog as well.
+            MessageDialogRunnable.showMessageDialog(consoleTextArea,
+                                                    msg("outOfMemory"),
+                                                    msg("errorReTracing"),
+                                                    JOptionPane.ERROR_MESSAGE);
+        }
+
+        // Make sure all output has been sent to the console text area.
+        printStream.flush();
+
+        // Restore the old System's in, out, and err streams.
+        System.setIn(oldIn);
+        System.setOut(oldOut);
+        System.setErr(oldErr);
+
+        consoleTextArea.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        consoleTextArea.setCaretPosition(0);
+
+        // Reset the global static redirection lock.
+        ProGuardGUI.systemOutRedirected = false;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the message from the GUI resources that corresponds to the given
+     * key.
+     */
+    private String msg(String messageKey)
+    {
+         return GUIResources.getMessage(messageKey);
+    }
+}
diff --git a/src/proguard/gui/SwingUtil.java b/src/proguard/gui/SwingUtil.java
new file mode 100644
index 0000000..75d2f02
--- /dev/null
+++ b/src/proguard/gui/SwingUtil.java
@@ -0,0 +1,82 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import javax.swing.*;
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * This utility class provides variants of the invocation method from the
+ * <code>SwingUtilities</code> class.
+ *
+ * @see SwingUtilities
+ * @author Eric Lafortune
+ */
+public class SwingUtil
+{
+    /**
+     * Invokes the given Runnable in the AWT event dispatching thread,
+     * and waits for it to finish. This method may be called from any thread,
+     * including the event dispatching thread itself.
+     * @see SwingUtilities#invokeAndWait(Runnable)
+     * @param runnable the Runnable to be executed.
+     */
+    public static void invokeAndWait(Runnable runnable)
+    throws InterruptedException, InvocationTargetException
+    {
+        try
+        {
+            if (SwingUtilities.isEventDispatchThread())
+            {
+                runnable.run();
+            }
+            else
+            {
+                SwingUtilities.invokeAndWait(runnable);
+            }
+        }
+        catch (Exception ex)
+        {
+            // Ignore any exceptions.
+        }
+    }
+
+
+    /**
+     * Invokes the given Runnable in the AWT event dispatching thread, not
+     * necessarily right away. This method may be called from any thread,
+     * including the event dispatching thread itself.
+     * @see SwingUtilities#invokeLater(Runnable)
+     * @param runnable the Runnable to be executed.
+     */
+    public static void invokeLater(Runnable runnable)
+    {
+        if (SwingUtilities.isEventDispatchThread())
+        {
+            runnable.run();
+        }
+        else
+        {
+            SwingUtilities.invokeLater(runnable);
+        }
+    }
+}
diff --git a/src/proguard/gui/TabbedPane.java b/src/proguard/gui/TabbedPane.java
new file mode 100644
index 0000000..a6460f5
--- /dev/null
+++ b/src/proguard/gui/TabbedPane.java
@@ -0,0 +1,229 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+
+
+/**
+ * This <code>Jpanel</code> is similar to a <code>JTabbedPane</code>.
+ * It uses buttons on the left-hand side to switch between panels.
+ * An image can be added below these buttons.
+ * Some methods are provided to switch between tabs.
+ *
+ * @author Eric Lafortune
+ */
+public class TabbedPane
+     extends JPanel
+{
+    private final CardLayout  cardLayout  = new CardLayout();
+    private final JPanel      cardPanel   = new JPanel(cardLayout);
+    private final ButtonGroup buttonGroup = new ButtonGroup();
+
+
+    /**
+     * Creates a new TabbedPane.
+     */
+    public TabbedPane()
+    {
+        GridBagLayout layout = new GridBagLayout();
+        setLayout(layout);
+
+        GridBagConstraints cardConstraints = new GridBagConstraints();
+        cardConstraints.gridx      = 1;
+        cardConstraints.gridy      = 0;
+        cardConstraints.gridheight = GridBagConstraints.REMAINDER;
+        cardConstraints.fill       = GridBagConstraints.BOTH;
+        cardConstraints.weightx    = 1.0;
+        cardConstraints.weighty    = 1.0;
+        cardConstraints.anchor     = GridBagConstraints.NORTHWEST;
+
+        add(cardPanel, cardConstraints);
+    }
+
+
+    /**
+     * Adds a component with a given title to the tabbed pane.
+     *
+     * @param title     the title that will be used in the tab button.
+     * @param component the component that will be added as a tab.
+     */
+    public Component add(final String title, Component component)
+    {
+        GridBagConstraints buttonConstraints = new GridBagConstraints();
+        buttonConstraints.gridx  = 0;
+        buttonConstraints.fill   = GridBagConstraints.HORIZONTAL;
+        buttonConstraints.anchor = GridBagConstraints.NORTHWEST;
+        buttonConstraints.ipadx  = 10;
+        buttonConstraints.ipady  = 4;
+
+        JToggleButton button = new JToggleButton(title);
+
+        // Let the button react on the mouse press, instead of waiting for the
+        // mouse release.
+        button.setModel(new JToggleButton.ToggleButtonModel()
+        {
+            public void setPressed(boolean b)
+            {
+                if ((isPressed() == b) || !isEnabled())
+                {
+                    return;
+                }
+
+                if (!b && isArmed())
+                {
+                    setSelected(!this.isSelected());
+                }
+
+                if (b)
+                {
+                    stateMask |= PRESSED;
+                }
+                else
+                {
+                    stateMask &= ~PRESSED;
+                }
+
+                fireStateChanged();
+
+                if (isPressed())
+                {
+                    fireActionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand()));
+                }
+            }
+
+        });
+
+        // Switch to the tab on a button press.
+        button.addActionListener(new ActionListener()
+        {
+            public void actionPerformed(ActionEvent e)
+            {
+                cardLayout.show(cardPanel, title);
+            }
+        });
+
+        // Only one button can be selected at the same time.
+        buttonGroup.add(button);
+
+        // If this is the first tab, make sure its button is selected.
+        if (cardPanel.getComponentCount() == 0)
+        {
+            button.setSelected(true);
+        }
+
+        // Add the button and its panel.
+        add(button, buttonConstraints);
+        cardPanel.add(title, component);
+
+        return component;
+    }
+
+
+    /**
+     * Adds an image below the tab buttons, after all tabs have been added.
+     * The image will only be as visible as permitted by the available space.
+     *
+     * @param image the image.
+     * @return the component containing the image.
+     */
+    public Component addImage(final Image image)
+    {
+        GridBagConstraints imageConstraints = new GridBagConstraints();
+        imageConstraints.gridx   = 0;
+        imageConstraints.weighty = 1.0;
+        imageConstraints.fill    = GridBagConstraints.BOTH;
+        imageConstraints.anchor  = GridBagConstraints.SOUTHWEST;
+
+        JButton component = new JButton(new ImageIcon(image));
+        component.setFocusPainted(false);
+        component.setFocusable(false);
+        component.setRequestFocusEnabled(false);
+        component.setRolloverEnabled(false);
+        component.setMargin(new Insets(0, 0, 0, 0));
+        component.setHorizontalAlignment(JButton.LEFT);
+        component.setVerticalAlignment(JButton.BOTTOM);
+        component.setPreferredSize(new Dimension(0, 0));
+
+        add(component, imageConstraints);
+
+        return component;
+    }
+
+
+    /**
+     * Selects the first tab.
+     */
+    public void first()
+    {
+        cardLayout.first(cardPanel);
+        updateButtonSelection();
+    }
+
+
+    /**
+     * Selects the last tab.
+     */
+    public void last()
+    {
+        cardLayout.last(cardPanel);
+        updateButtonSelection();
+    }
+
+
+    /**
+     * Selects the previous tab.
+     */
+    public void previous()
+    {
+        cardLayout.previous(cardPanel);
+        updateButtonSelection();
+    }
+
+
+    /**
+     * Selects the next tab.
+     */
+    public void next()
+    {
+        cardLayout.next(cardPanel);
+        updateButtonSelection();
+    }
+
+
+    /**
+     * Lets the button selection reflect the currently visible panel.
+     */
+    private void updateButtonSelection()
+    {
+        int count = cardPanel.getComponentCount();
+        for (int index = 0 ; index < count ; index++) {
+            Component card = cardPanel.getComponent(index);
+            if (card.isShowing())
+            {
+                JToggleButton button = (JToggleButton)getComponent(index+1);
+                button.setSelected(true);
+            }
+        }
+    }
+}
diff --git a/src/proguard/gui/TextAreaOutputStream.java b/src/proguard/gui/TextAreaOutputStream.java
new file mode 100644
index 0000000..57f983d
--- /dev/null
+++ b/src/proguard/gui/TextAreaOutputStream.java
@@ -0,0 +1,81 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui;
+
+import javax.swing.*;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * This <code>PrintStream</code> appends its output to a given text area.
+ *
+ * @author Eric Lafortune
+ */
+final class TextAreaOutputStream extends FilterOutputStream implements Runnable
+{
+    private final JTextArea textArea;
+
+
+    public TextAreaOutputStream(JTextArea textArea)
+    {
+        super(new ByteArrayOutputStream());
+
+        this.textArea = textArea;
+    }
+
+
+    // Implementation for FilterOutputStream.
+
+    public void flush() throws IOException
+    {
+        super.flush();
+
+        try
+        {
+            // Append the accumulated buffer contents to the text area.
+            SwingUtil.invokeAndWait(this);
+        }
+        catch (Exception e)
+        {
+            // Nothing.
+        }
+    }
+
+
+    // Implementation for Runnable.
+
+    public void run()
+    {
+        ByteArrayOutputStream out = (ByteArrayOutputStream)super.out;
+
+        // Has any new text been written?
+        String text = out.toString();
+        if (text.length() > 0)
+        {
+            // Append the accumulated text to the text area.
+            textArea.append(text);
+
+            // Clear the buffer.
+            out.reset();
+        }
+    }
+}
diff --git a/src/proguard/gui/arrow.gif b/src/proguard/gui/arrow.gif
new file mode 100644
index 0000000..c58e34e
--- /dev/null
+++ b/src/proguard/gui/arrow.gif
Binary files differ
diff --git a/src/proguard/gui/boilerplate.pro b/src/proguard/gui/boilerplate.pro
new file mode 100644
index 0000000..70efb82
--- /dev/null
+++ b/src/proguard/gui/boilerplate.pro
@@ -0,0 +1,410 @@
+# Keep - Applications. Keep all application classes, along with their 'main'
+# methods.
+-keepclasseswithmembers public class * {
+    public static void main(java.lang.String[]);
+}
+
+# Keep - Applets. Keep all extensions of java.applet.Applet.
+-keep public class * extends java.applet.Applet
+
+# Keep - Servlets. Keep all extensions of javax.servlet.Servlet.
+-keep public class * extends javax.servlet.Servlet
+
+# Keep - Midlets. Keep all extensions of javax.microedition.midlet.MIDlet.
+-keep public class * extends javax.microedition.midlet.MIDlet
+
+# Keep - Xlets. Keep all extensions of javax.tv.xlet.Xlet.
+-keep public class * extends javax.tv.xlet.Xlet
+
+# Keep - Library. Keep all public and protected classes, fields, and methods.
+-keep public class * {
+    public protected <fields>;
+    public protected <methods>;
+}
+
+# Also keep - Enumerations. Keep the special static methods that are required in
+# enumeration classes.
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+# Also keep - Serialization code. Keep all fields and methods that are used for
+# serialization.
+-keepclassmembers class * extends java.io.Serializable {
+    static final long serialVersionUID;
+    static final java.io.ObjectStreamField[] serialPersistentFields;
+    private void writeObject(java.io.ObjectOutputStream);
+    private void readObject(java.io.ObjectInputStream);
+    java.lang.Object writeReplace();
+    java.lang.Object readResolve();
+}
+
+# Also keep - BeanInfo classes. Keep all implementations of java.beans.BeanInfo.
+-keep class * implements java.beans.BeanInfo
+
+# Also keep - Bean classes. Keep all specified classes, along with their getters
+# and setters.
+-keep class * {
+    void set*(***);
+    void set*(int,***);
+
+    boolean is*();
+    boolean is*(int);
+
+    *** get*();
+    *** get*(int);
+}
+
+# Also keep - Database drivers. Keep all implementations of java.sql.Driver.
+-keep class * implements java.sql.Driver
+
+# Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI,
+# along with the special 'createUI' method.
+-keep class * extends javax.swing.plaf.ComponentUI {
+    public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);
+}
+
+# Also keep - RMI interfaces. Keep all interfaces that extend the
+# java.rmi.Remote interface, and their methods.
+-keep interface * extends java.rmi.Remote {
+    <methods>;
+}
+
+# Also keep - RMI implementations. Keep all implementations of java.rmi.Remote,
+# including any explicit or implicit implementations of Activatable, with their
+# two-argument constructors.
+-keep class * implements java.rmi.Remote {
+    <init>(java.rmi.activation.ActivationID,java.rmi.MarshalledObject);
+}
+
+# Keep names - Native method names. Keep all native class/method names.
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# Keep names - _class method names. Keep all .class method names. This may be
+# useful for libraries that will be obfuscated again with different obfuscators.
+-keepclassmembernames class * {
+    java.lang.Class class$(java.lang.String);
+    java.lang.Class class$(java.lang.String,boolean);
+}
+
+# Remove - System method calls. Remove all invocations of System
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.System {
+    public static long currentTimeMillis();
+    static java.lang.Class getCallerClass();
+    public static int identityHashCode(java.lang.Object);
+    public static java.lang.SecurityManager getSecurityManager();
+    public static java.util.Properties getProperties();
+    public static java.lang.String getProperty(java.lang.String);
+    public static java.lang.String getenv(java.lang.String);
+    public static java.lang.String mapLibraryName(java.lang.String);
+    public static java.lang.String getProperty(java.lang.String,java.lang.String);
+}
+
+# Remove - Math method calls. Remove all invocations of Math
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.Math {
+    public static double sin(double);
+    public static double cos(double);
+    public static double tan(double);
+    public static double asin(double);
+    public static double acos(double);
+    public static double atan(double);
+    public static double toRadians(double);
+    public static double toDegrees(double);
+    public static double exp(double);
+    public static double log(double);
+    public static double log10(double);
+    public static double sqrt(double);
+    public static double cbrt(double);
+    public static double IEEEremainder(double,double);
+    public static double ceil(double);
+    public static double floor(double);
+    public static double rint(double);
+    public static double atan2(double,double);
+    public static double pow(double,double);
+    public static int round(float);
+    public static long round(double);
+    public static double random();
+    public static int abs(int);
+    public static long abs(long);
+    public static float abs(float);
+    public static double abs(double);
+    public static int max(int,int);
+    public static long max(long,long);
+    public static float max(float,float);
+    public static double max(double,double);
+    public static int min(int,int);
+    public static long min(long,long);
+    public static float min(float,float);
+    public static double min(double,double);
+    public static double ulp(double);
+    public static float ulp(float);
+    public static double signum(double);
+    public static float signum(float);
+    public static double sinh(double);
+    public static double cosh(double);
+    public static double tanh(double);
+    public static double hypot(double,double);
+    public static double expm1(double);
+    public static double log1p(double);
+}
+
+# Remove - Number method calls. Remove all invocations of Number
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.* extends java.lang.Number {
+    public static java.lang.String toString(byte);
+    public static java.lang.Byte valueOf(byte);
+    public static byte parseByte(java.lang.String);
+    public static byte parseByte(java.lang.String,int);
+    public static java.lang.Byte valueOf(java.lang.String,int);
+    public static java.lang.Byte valueOf(java.lang.String);
+    public static java.lang.Byte decode(java.lang.String);
+    public int compareTo(java.lang.Byte);
+
+    public static java.lang.String toString(short);
+    public static short parseShort(java.lang.String);
+    public static short parseShort(java.lang.String,int);
+    public static java.lang.Short valueOf(java.lang.String,int);
+    public static java.lang.Short valueOf(java.lang.String);
+    public static java.lang.Short valueOf(short);
+    public static java.lang.Short decode(java.lang.String);
+    public static short reverseBytes(short);
+    public int compareTo(java.lang.Short);
+
+    public static java.lang.String toString(int,int);
+    public static java.lang.String toHexString(int);
+    public static java.lang.String toOctalString(int);
+    public static java.lang.String toBinaryString(int);
+    public static java.lang.String toString(int);
+    public static int parseInt(java.lang.String,int);
+    public static int parseInt(java.lang.String);
+    public static java.lang.Integer valueOf(java.lang.String,int);
+    public static java.lang.Integer valueOf(java.lang.String);
+    public static java.lang.Integer valueOf(int);
+    public static java.lang.Integer getInteger(java.lang.String);
+    public static java.lang.Integer getInteger(java.lang.String,int);
+    public static java.lang.Integer getInteger(java.lang.String,java.lang.Integer);
+    public static java.lang.Integer decode(java.lang.String);
+    public static int highestOneBit(int);
+    public static int lowestOneBit(int);
+    public static int numberOfLeadingZeros(int);
+    public static int numberOfTrailingZeros(int);
+    public static int bitCount(int);
+    public static int rotateLeft(int,int);
+    public static int rotateRight(int,int);
+    public static int reverse(int);
+    public static int signum(int);
+    public static int reverseBytes(int);
+    public int compareTo(java.lang.Integer);
+
+    public static java.lang.String toString(long,int);
+    public static java.lang.String toHexString(long);
+    public static java.lang.String toOctalString(long);
+    public static java.lang.String toBinaryString(long);
+    public static java.lang.String toString(long);
+    public static long parseLong(java.lang.String,int);
+    public static long parseLong(java.lang.String);
+    public static java.lang.Long valueOf(java.lang.String,int);
+    public static java.lang.Long valueOf(java.lang.String);
+    public static java.lang.Long valueOf(long);
+    public static java.lang.Long decode(java.lang.String);
+    public static java.lang.Long getLong(java.lang.String);
+    public static java.lang.Long getLong(java.lang.String,long);
+    public static java.lang.Long getLong(java.lang.String,java.lang.Long);
+    public static long highestOneBit(long);
+    public static long lowestOneBit(long);
+    public static int numberOfLeadingZeros(long);
+    public static int numberOfTrailingZeros(long);
+    public static int bitCount(long);
+    public static long rotateLeft(long,int);
+    public static long rotateRight(long,int);
+    public static long reverse(long);
+    public static int signum(long);
+    public static long reverseBytes(long);
+    public int compareTo(java.lang.Long);
+
+    public static java.lang.String toString(float);
+    public static java.lang.String toHexString(float);
+    public static java.lang.Float valueOf(java.lang.String);
+    public static java.lang.Float valueOf(float);
+    public static float parseFloat(java.lang.String);
+    public static boolean isNaN(float);
+    public static boolean isInfinite(float);
+    public static int floatToIntBits(float);
+    public static int floatToRawIntBits(float);
+    public static float intBitsToFloat(int);
+    public static int compare(float,float);
+    public boolean isNaN();
+    public boolean isInfinite();
+    public int compareTo(java.lang.Float);
+
+    public static java.lang.String toString(double);
+    public static java.lang.String toHexString(double);
+    public static java.lang.Double valueOf(java.lang.String);
+    public static java.lang.Double valueOf(double);
+    public static double parseDouble(java.lang.String);
+    public static boolean isNaN(double);
+    public static boolean isInfinite(double);
+    public static long doubleToLongBits(double);
+    public static long doubleToRawLongBits(double);
+    public static double longBitsToDouble(long);
+    public static int compare(double,double);
+    public boolean isNaN();
+    public boolean isInfinite();
+    public int compareTo(java.lang.Double);
+
+    public <init>(byte);
+    public <init>(short);
+    public <init>(int);
+    public <init>(long);
+    public <init>(float);
+    public <init>(double);
+    public <init>(java.lang.String);
+    public byte byteValue();
+    public short shortValue();
+    public int intValue();
+    public long longValue();
+    public float floatValue();
+    public double doubleValue();
+
+    public int compareTo(java.lang.Object);
+    public boolean equals(java.lang.Object);
+    public int hashCode();
+    public java.lang.String toString();
+}
+
+# Remove - String method calls. Remove all invocations of String
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.String {
+    public java.lang.String();
+    public java.lang.String(byte[]);
+    public java.lang.String(byte[],int);
+    public java.lang.String(byte[],int,int);
+    public java.lang.String(byte[],int,int,int);
+    public java.lang.String(byte[],int,int,java.lang.String);
+    public java.lang.String(byte[],java.lang.String);
+    public java.lang.String(char[]);
+    public java.lang.String(char[],int,int);
+    public java.lang.String(java.lang.String);
+    public java.lang.String(java.lang.StringBuffer);
+    public static java.lang.String copyValueOf(char[]);
+    public static java.lang.String copyValueOf(char[],int,int);
+    public static java.lang.String valueOf(boolean);
+    public static java.lang.String valueOf(char);
+    public static java.lang.String valueOf(char[]);
+    public static java.lang.String valueOf(char[],int,int);
+    public static java.lang.String valueOf(double);
+    public static java.lang.String valueOf(float);
+    public static java.lang.String valueOf(int);
+    public static java.lang.String valueOf(java.lang.Object);
+    public static java.lang.String valueOf(long);
+    public boolean contentEquals(java.lang.StringBuffer);
+    public boolean endsWith(java.lang.String);
+    public boolean equalsIgnoreCase(java.lang.String);
+    public boolean equals(java.lang.Object);
+    public boolean matches(java.lang.String);
+    public boolean regionMatches(boolean,int,java.lang.String,int,int);
+    public boolean regionMatches(int,java.lang.String,int,int);
+    public boolean startsWith(java.lang.String);
+    public boolean startsWith(java.lang.String,int);
+    public byte[] getBytes();
+    public byte[] getBytes(java.lang.String);
+    public char charAt(int);
+    public char[] toCharArray();
+    public int compareToIgnoreCase(java.lang.String);
+    public int compareTo(java.lang.Object);
+    public int compareTo(java.lang.String);
+    public int hashCode();
+    public int indexOf(int);
+    public int indexOf(int,int);
+    public int indexOf(java.lang.String);
+    public int indexOf(java.lang.String,int);
+    public int lastIndexOf(int);
+    public int lastIndexOf(int,int);
+    public int lastIndexOf(java.lang.String);
+    public int lastIndexOf(java.lang.String,int);
+    public int length();
+    public java.lang.CharSequence subSequence(int,int);
+    public java.lang.String concat(java.lang.String);
+    public java.lang.String replaceAll(java.lang.String,java.lang.String);
+    public java.lang.String replace(char,char);
+    public java.lang.String replaceFirst(java.lang.String,java.lang.String);
+    public java.lang.String[] split(java.lang.String);
+    public java.lang.String[] split(java.lang.String,int);
+    public java.lang.String substring(int);
+    public java.lang.String substring(int,int);
+    public java.lang.String toLowerCase();
+    public java.lang.String toLowerCase(java.util.Locale);
+    public java.lang.String toString();
+    public java.lang.String toUpperCase();
+    public java.lang.String toUpperCase(java.util.Locale);
+    public java.lang.String trim();
+}
+
+# Remove - StringBuffer method calls. Remove all invocations of StringBuffer
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.StringBuffer {
+    public java.lang.StringBuffer();
+    public java.lang.StringBuffer(int);
+    public java.lang.StringBuffer(java.lang.String);
+    public java.lang.StringBuffer(java.lang.CharSequence);
+    public java.lang.String toString();
+    public char charAt(int);
+    public int capacity();
+    public int codePointAt(int);
+    public int codePointBefore(int);
+    public int indexOf(java.lang.String,int);
+    public int lastIndexOf(java.lang.String);
+    public int lastIndexOf(java.lang.String,int);
+    public int length();
+    public java.lang.String substring(int);
+    public java.lang.String substring(int,int);
+}
+
+# Remove - StringBuilder method calls. Remove all invocations of StringBuilder
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.StringBuilder {
+    public java.lang.StringBuilder();
+    public java.lang.StringBuilder(int);
+    public java.lang.StringBuilder(java.lang.String);
+    public java.lang.StringBuilder(java.lang.CharSequence);
+    public java.lang.String toString();
+    public char charAt(int);
+    public int capacity();
+    public int codePointAt(int);
+    public int codePointBefore(int);
+    public int indexOf(java.lang.String,int);
+    public int lastIndexOf(java.lang.String);
+    public int lastIndexOf(java.lang.String,int);
+    public int length();
+    public java.lang.String substring(int);
+    public java.lang.String substring(int,int);
+}
+
+# Remove debugging - Throwable_printStackTrace calls. Remove all invocations of
+# Throwable.printStackTrace().
+-assumenosideeffects public class java.lang.Throwable {
+    public void printStackTrace();
+}
+
+# Remove debugging - Thread_dumpStack calls. Remove all invocations of
+# Thread.dumpStack().
+-assumenosideeffects public class java.lang.Thread {
+    public static void dumpStack();
+}
+
+# Remove debugging - All logging API calls. Remove all invocations of the
+# logging API whose return values are not used.
+-assumenosideeffects public class java.util.logging.* {
+    <methods>;
+}
+
+# Remove debugging - All Log4j API calls. Remove all invocations of the
+# Log4j API whose return values are not used.
+-assumenosideeffects public class org.apache.log4j.** {
+    <methods>;
+}
diff --git a/src/proguard/gui/default.pro b/src/proguard/gui/default.pro
new file mode 100644
index 0000000..3bfe2d6
--- /dev/null
+++ b/src/proguard/gui/default.pro
@@ -0,0 +1,318 @@
+# The default configuration when starting up the GUI.
+
+-libraryjars <java.home>/lib/rt.jar
+
+# Keep - Applications. Keep all application classes, along with their 'main'
+# methods.
+-keepclasseswithmembers public class * {
+    public static void main(java.lang.String[]);
+}
+
+# Also keep - Enumerations. Keep the special static methods that are required in
+# enumeration classes.
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+# Also keep - Database drivers. Keep all implementations of java.sql.Driver.
+-keep class * extends java.sql.Driver
+
+# Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI,
+# along with the special 'createUI' method.
+-keep class * extends javax.swing.plaf.ComponentUI {
+    public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);
+}
+
+# Keep names - Native method names. Keep all native class/method names.
+-keepclasseswithmembers,allowshrinking class * {
+    native <methods>;
+}
+
+# Remove - System method calls. Remove all invocations of System
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.System {
+    public static long currentTimeMillis();
+    static java.lang.Class getCallerClass();
+    public static int identityHashCode(java.lang.Object);
+    public static java.lang.SecurityManager getSecurityManager();
+    public static java.util.Properties getProperties();
+    public static java.lang.String getProperty(java.lang.String);
+    public static java.lang.String getenv(java.lang.String);
+    public static java.lang.String mapLibraryName(java.lang.String);
+    public static java.lang.String getProperty(java.lang.String,java.lang.String);
+}
+
+# Remove - Math method calls. Remove all invocations of Math
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.Math {
+    public static double sin(double);
+    public static double cos(double);
+    public static double tan(double);
+    public static double asin(double);
+    public static double acos(double);
+    public static double atan(double);
+    public static double toRadians(double);
+    public static double toDegrees(double);
+    public static double exp(double);
+    public static double log(double);
+    public static double log10(double);
+    public static double sqrt(double);
+    public static double cbrt(double);
+    public static double IEEEremainder(double,double);
+    public static double ceil(double);
+    public static double floor(double);
+    public static double rint(double);
+    public static double atan2(double,double);
+    public static double pow(double,double);
+    public static int round(float);
+    public static long round(double);
+    public static double random();
+    public static int abs(int);
+    public static long abs(long);
+    public static float abs(float);
+    public static double abs(double);
+    public static int max(int,int);
+    public static long max(long,long);
+    public static float max(float,float);
+    public static double max(double,double);
+    public static int min(int,int);
+    public static long min(long,long);
+    public static float min(float,float);
+    public static double min(double,double);
+    public static double ulp(double);
+    public static float ulp(float);
+    public static double signum(double);
+    public static float signum(float);
+    public static double sinh(double);
+    public static double cosh(double);
+    public static double tanh(double);
+    public static double hypot(double,double);
+    public static double expm1(double);
+    public static double log1p(double);
+}
+
+# Remove - Number method calls. Remove all invocations of Number
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.* extends java.lang.Number {
+    public static java.lang.String toString(byte);
+    public static java.lang.Byte valueOf(byte);
+    public static byte parseByte(java.lang.String);
+    public static byte parseByte(java.lang.String,int);
+    public static java.lang.Byte valueOf(java.lang.String,int);
+    public static java.lang.Byte valueOf(java.lang.String);
+    public static java.lang.Byte decode(java.lang.String);
+    public int compareTo(java.lang.Byte);
+    public static java.lang.String toString(short);
+    public static short parseShort(java.lang.String);
+    public static short parseShort(java.lang.String,int);
+    public static java.lang.Short valueOf(java.lang.String,int);
+    public static java.lang.Short valueOf(java.lang.String);
+    public static java.lang.Short valueOf(short);
+    public static java.lang.Short decode(java.lang.String);
+    public static short reverseBytes(short);
+    public int compareTo(java.lang.Short);
+    public static java.lang.String toString(int,int);
+    public static java.lang.String toHexString(int);
+    public static java.lang.String toOctalString(int);
+    public static java.lang.String toBinaryString(int);
+    public static java.lang.String toString(int);
+    public static int parseInt(java.lang.String,int);
+    public static int parseInt(java.lang.String);
+    public static java.lang.Integer valueOf(java.lang.String,int);
+    public static java.lang.Integer valueOf(java.lang.String);
+    public static java.lang.Integer valueOf(int);
+    public static java.lang.Integer getInteger(java.lang.String);
+    public static java.lang.Integer getInteger(java.lang.String,int);
+    public static java.lang.Integer getInteger(java.lang.String,java.lang.Integer);
+    public static java.lang.Integer decode(java.lang.String);
+    public static int highestOneBit(int);
+    public static int lowestOneBit(int);
+    public static int numberOfLeadingZeros(int);
+    public static int numberOfTrailingZeros(int);
+    public static int bitCount(int);
+    public static int rotateLeft(int,int);
+    public static int rotateRight(int,int);
+    public static int reverse(int);
+    public static int signum(int);
+    public static int reverseBytes(int);
+    public int compareTo(java.lang.Integer);
+    public static java.lang.String toString(long,int);
+    public static java.lang.String toHexString(long);
+    public static java.lang.String toOctalString(long);
+    public static java.lang.String toBinaryString(long);
+    public static java.lang.String toString(long);
+    public static long parseLong(java.lang.String,int);
+    public static long parseLong(java.lang.String);
+    public static java.lang.Long valueOf(java.lang.String,int);
+    public static java.lang.Long valueOf(java.lang.String);
+    public static java.lang.Long valueOf(long);
+    public static java.lang.Long decode(java.lang.String);
+    public static java.lang.Long getLong(java.lang.String);
+    public static java.lang.Long getLong(java.lang.String,long);
+    public static java.lang.Long getLong(java.lang.String,java.lang.Long);
+    public static long highestOneBit(long);
+    public static long lowestOneBit(long);
+    public static int numberOfLeadingZeros(long);
+    public static int numberOfTrailingZeros(long);
+    public static int bitCount(long);
+    public static long rotateLeft(long,int);
+    public static long rotateRight(long,int);
+    public static long reverse(long);
+    public static int signum(long);
+    public static long reverseBytes(long);
+    public int compareTo(java.lang.Long);
+    public static java.lang.String toString(float);
+    public static java.lang.String toHexString(float);
+    public static java.lang.Float valueOf(java.lang.String);
+    public static java.lang.Float valueOf(float);
+    public static float parseFloat(java.lang.String);
+    public static boolean isNaN(float);
+    public static boolean isInfinite(float);
+    public static int floatToIntBits(float);
+    public static int floatToRawIntBits(float);
+    public static float intBitsToFloat(int);
+    public static int compare(float,float);
+    public boolean isNaN();
+    public boolean isInfinite();
+    public int compareTo(java.lang.Float);
+    public static java.lang.String toString(double);
+    public static java.lang.String toHexString(double);
+    public static java.lang.Double valueOf(java.lang.String);
+    public static java.lang.Double valueOf(double);
+    public static double parseDouble(java.lang.String);
+    public static boolean isNaN(double);
+    public static boolean isInfinite(double);
+    public static long doubleToLongBits(double);
+    public static long doubleToRawLongBits(double);
+    public static double longBitsToDouble(long);
+    public static int compare(double,double);
+    public boolean isNaN();
+    public boolean isInfinite();
+    public int compareTo(java.lang.Double);
+    public <init>(byte);
+    public <init>(short);
+    public <init>(int);
+    public <init>(long);
+    public <init>(float);
+    public <init>(double);
+    public <init>(java.lang.String);
+    public byte byteValue();
+    public short shortValue();
+    public int intValue();
+    public long longValue();
+    public float floatValue();
+    public double doubleValue();
+    public int compareTo(java.lang.Object);
+    public boolean equals(java.lang.Object);
+    public int hashCode();
+    public java.lang.String toString();
+}
+
+# Remove - String method calls. Remove all invocations of String
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.String {
+    public <init>();
+    public <init>(byte[]);
+    public <init>(byte[],int);
+    public <init>(byte[],int,int);
+    public <init>(byte[],int,int,int);
+    public <init>(byte[],int,int,java.lang.String);
+    public <init>(byte[],java.lang.String);
+    public <init>(char[]);
+    public <init>(char[],int,int);
+    public <init>(java.lang.String);
+    public <init>(java.lang.StringBuffer);
+    public static java.lang.String copyValueOf(char[]);
+    public static java.lang.String copyValueOf(char[],int,int);
+    public static java.lang.String valueOf(boolean);
+    public static java.lang.String valueOf(char);
+    public static java.lang.String valueOf(char[]);
+    public static java.lang.String valueOf(char[],int,int);
+    public static java.lang.String valueOf(double);
+    public static java.lang.String valueOf(float);
+    public static java.lang.String valueOf(int);
+    public static java.lang.String valueOf(java.lang.Object);
+    public static java.lang.String valueOf(long);
+    public boolean contentEquals(java.lang.StringBuffer);
+    public boolean endsWith(java.lang.String);
+    public boolean equalsIgnoreCase(java.lang.String);
+    public boolean equals(java.lang.Object);
+    public boolean matches(java.lang.String);
+    public boolean regionMatches(boolean,int,java.lang.String,int,int);
+    public boolean regionMatches(int,java.lang.String,int,int);
+    public boolean startsWith(java.lang.String);
+    public boolean startsWith(java.lang.String,int);
+    public byte[] getBytes();
+    public byte[] getBytes(java.lang.String);
+    public char charAt(int);
+    public char[] toCharArray();
+    public int compareToIgnoreCase(java.lang.String);
+    public int compareTo(java.lang.Object);
+    public int compareTo(java.lang.String);
+    public int hashCode();
+    public int indexOf(int);
+    public int indexOf(int,int);
+    public int indexOf(java.lang.String);
+    public int indexOf(java.lang.String,int);
+    public int lastIndexOf(int);
+    public int lastIndexOf(int,int);
+    public int lastIndexOf(java.lang.String);
+    public int lastIndexOf(java.lang.String,int);
+    public int length();
+    public java.lang.CharSequence subSequence(int,int);
+    public java.lang.String concat(java.lang.String);
+    public java.lang.String replaceAll(java.lang.String,java.lang.String);
+    public java.lang.String replace(char,char);
+    public java.lang.String replaceFirst(java.lang.String,java.lang.String);
+    public java.lang.String[] split(java.lang.String);
+    public java.lang.String[] split(java.lang.String,int);
+    public java.lang.String substring(int);
+    public java.lang.String substring(int,int);
+    public java.lang.String toLowerCase();
+    public java.lang.String toLowerCase(java.util.Locale);
+    public java.lang.String toString();
+    public java.lang.String toUpperCase();
+    public java.lang.String toUpperCase(java.util.Locale);
+    public java.lang.String trim();
+}
+
+# Remove - StringBuffer method calls. Remove all invocations of StringBuffer
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.StringBuffer {
+    public <init>();
+    public <init>(int);
+    public <init>(java.lang.String);
+    public <init>(java.lang.CharSequence);
+    public java.lang.String toString();
+    public char charAt(int);
+    public int capacity();
+    public int codePointAt(int);
+    public int codePointBefore(int);
+    public int indexOf(java.lang.String,int);
+    public int lastIndexOf(java.lang.String);
+    public int lastIndexOf(java.lang.String,int);
+    public int length();
+    public java.lang.String substring(int);
+    public java.lang.String substring(int,int);
+}
+
+# Remove - StringBuilder method calls. Remove all invocations of StringBuilder
+# methods without side effects whose return values are not used.
+-assumenosideeffects public class java.lang.StringBuilder {
+    public <init>();
+    public <init>(int);
+    public <init>(java.lang.String);
+    public <init>(java.lang.CharSequence);
+    public java.lang.String toString();
+    public char charAt(int);
+    public int capacity();
+    public int codePointAt(int);
+    public int codePointBefore(int);
+    public int indexOf(java.lang.String,int);
+    public int lastIndexOf(java.lang.String);
+    public int lastIndexOf(java.lang.String,int);
+    public int length();
+    public java.lang.String substring(int);
+    public java.lang.String substring(int,int);
+}
diff --git a/src/proguard/gui/package.html b/src/proguard/gui/package.html
new file mode 100644
index 0000000..4eedcc2
--- /dev/null
+++ b/src/proguard/gui/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains a GUI for ProGuard and ReTrace.
+</body>
diff --git a/src/proguard/gui/splash/BufferedSprite.java b/src/proguard/gui/splash/BufferedSprite.java
new file mode 100644
index 0000000..8427832
--- /dev/null
+++ b/src/proguard/gui/splash/BufferedSprite.java
@@ -0,0 +1,145 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+/**
+ * This Sprite encapsulates another Sprite, which is then buffered in an Image.
+ *
+ * @author Eric Lafortune
+ */
+public class BufferedSprite implements Sprite
+{
+    private final int         bufferX;
+    private final int         bufferY;
+    private final Image       bufferImage;
+    private final Color       backgroundColor;
+    private final Sprite      sprite;
+    private final VariableInt x;
+    private final VariableInt y;
+
+    private long cachedTime = -1;
+
+
+    /**
+     * Creates a new BufferedSprite with an ABGR image.
+     * @param bufferX the x offset of the buffer image.
+     * @param bufferY the y offset of the buffer image.
+     * @param width   the width of the buffer image.
+     * @param height  the height of the buffer image.
+     * @param sprite  the Sprite that is painted in the buffer.
+     * @param x       the variable x ordinate of the image buffer for painting.
+     * @param y       the variable y ordinate of the image buffer for painting.
+     *
+     */
+    public BufferedSprite(int         bufferX,
+                          int         bufferY,
+                          int         width,
+                          int         height,
+                          Sprite      sprite,
+                          VariableInt x,
+                          VariableInt y)
+    {
+
+        this(bufferX,
+             bufferY,
+             new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR),
+             null,
+             sprite,
+             x,
+             y);
+    }
+
+
+    /**
+     * Creates a new BufferedSprite with the given image.
+     * @param bufferX         the x offset of the buffer image.
+     * @param bufferY         the y offset of the buffer image.
+     * @param bufferImage     the Image that is used for the buffering.
+     * @param backgroundColor the background color that is used for the buffer.
+     * @param sprite          the Sprite that is painted in the buffer.
+     * @param x               the variable x ordinate of the image buffer for
+     *                        painting.
+     * @param y               the variable y ordinate of the image buffer for
+     *                        painting.
+     */
+    public BufferedSprite(int         bufferX,
+                          int         bufferY,
+                          Image       bufferImage,
+                          Color       backgroundColor,
+                          Sprite      sprite,
+                          VariableInt x,
+                          VariableInt y)
+    {
+        this.bufferX         = bufferX;
+        this.bufferY         = bufferY;
+        this.bufferImage     = bufferImage;
+        this.backgroundColor = backgroundColor;
+        this.sprite          = sprite;
+        this.x               = x;
+        this.y               = y;
+    }
+
+
+   // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        if (time != cachedTime)
+        {
+            Graphics bufferGraphics = bufferImage.getGraphics();
+
+            // Clear the background.
+            if (backgroundColor != null)
+            {
+                Graphics2D bufferGraphics2D = (Graphics2D)bufferGraphics;
+                bufferGraphics2D.setComposite(AlphaComposite.Clear);
+                bufferGraphics.fillRect(0, 0, bufferImage.getWidth(null), bufferImage.getHeight(null));
+                bufferGraphics2D.setComposite(AlphaComposite.Src);
+            }
+            else
+            {
+                bufferGraphics.setColor(backgroundColor);
+                bufferGraphics.fillRect(0, 0, bufferImage.getWidth(null), bufferImage.getHeight(null));
+            }
+
+            // Set up the buffer graphics.
+            bufferGraphics.translate(-bufferX, -bufferY);
+            bufferGraphics.setColor(graphics.getColor());
+            bufferGraphics.setFont(graphics.getFont());
+
+            // Draw the sprite.
+            sprite.paint(bufferGraphics, time);
+
+            bufferGraphics.dispose();
+
+            cachedTime = time;
+        }
+
+        // Draw the buffer image.
+        graphics.drawImage(bufferImage,
+                           bufferX + x.getInt(time),
+                           bufferY + y.getInt(time),
+                           null);
+    }
+}
diff --git a/src/proguard/gui/splash/CircleSprite.java b/src/proguard/gui/splash/CircleSprite.java
new file mode 100644
index 0000000..5dc65eb
--- /dev/null
+++ b/src/proguard/gui/splash/CircleSprite.java
@@ -0,0 +1,74 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite represents an animated circle. It can optionally be filled.
+ *
+ * @author Eric Lafortune
+ */
+public class CircleSprite implements Sprite
+{
+    private final boolean     filled;
+    private final VariableInt x;
+    private final VariableInt y;
+    private final VariableInt radius;
+
+
+    /**
+     * Creates a new CircleSprite.
+     * @param filled specifies whether the rectangle should be filled.
+     * @param x      the variable x-coordinate of the center of the circle.
+     * @param y      the variable y-coordinate of the center of the circle.
+     * @param radius the variable radius of the circle.
+     */
+    public CircleSprite(boolean     filled,
+                        VariableInt x,
+                        VariableInt y,
+                        VariableInt radius)
+    {
+        this.filled = filled;
+        this.x      = x;
+        this.y      = y;
+        this.radius = radius;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        int xt = x.getInt(time);
+        int yt = y.getInt(time);
+        int r  = radius.getInt(time);
+
+        if (filled)
+        {
+            graphics.fillOval(xt - r, yt - r, 2 * r, 2 * r);
+        }
+        else
+        {
+            graphics.drawOval(xt - r, yt - r, 2 * r, 2 * r);
+        }
+    }
+}
diff --git a/src/proguard/gui/splash/ClipSprite.java b/src/proguard/gui/splash/ClipSprite.java
new file mode 100644
index 0000000..55f9eac
--- /dev/null
+++ b/src/proguard/gui/splash/ClipSprite.java
@@ -0,0 +1,85 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite encapsulates another Sprite, which is clipped by a clip Sprite.
+ *
+ * @author Eric Lafortune
+ */
+public class ClipSprite implements Sprite
+{
+    private final VariableColor insideClipColor;
+    private final VariableColor outsideClipColor;
+    private final Sprite        clipSprite;
+    private final Sprite        sprite;
+
+
+    /**
+     * Creates a new ClipSprite.
+     * @param insideClipColor  the background color inside the clip sprite.
+     * @param outsideClipColor the background color outside the clip sprite.
+     * @param clipSprite       the clip Sprite.
+     * @param sprite           the clipped Sprite.
+     */
+    public ClipSprite(VariableColor insideClipColor,
+                      VariableColor outsideClipColor,
+                      Sprite        clipSprite,
+                      Sprite        sprite)
+    {
+        this.insideClipColor  = insideClipColor;
+        this.outsideClipColor = outsideClipColor;
+        this.clipSprite       = clipSprite;
+        this.sprite           = sprite;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        // Clear the background.
+        Color outsideColor = outsideClipColor.getColor(time);
+        Rectangle clip = graphics.getClipBounds();
+        graphics.setPaintMode();
+        graphics.setColor(outsideColor);
+        graphics.fillRect(0, 0, clip.width, clip.height);
+
+        // Draw the sprite in XOR mode.
+        OverrideGraphics2D g = new OverrideGraphics2D((Graphics2D)graphics);
+        Color insideColor = insideClipColor.getColor(time);
+        g.setOverrideXORMode(insideColor);
+        sprite.paint(g, time);
+        g.setOverrideXORMode(null);
+
+        // Clear the clip area.
+        g.setOverrideColor(insideColor);
+        clipSprite.paint(g, time);
+        g.setOverrideColor(null);
+
+        // Draw the sprite in XOR mode.
+        g.setOverrideXORMode(insideColor);
+        sprite.paint(g, time);
+        g.setOverrideXORMode(null);
+    }
+}
diff --git a/src/proguard/gui/splash/ColorSprite.java b/src/proguard/gui/splash/ColorSprite.java
new file mode 100644
index 0000000..3f9bc3b
--- /dev/null
+++ b/src/proguard/gui/splash/ColorSprite.java
@@ -0,0 +1,65 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite colors another given sprite.
+ *
+ * @author Eric Lafortune
+ */
+public class ColorSprite implements Sprite
+{
+    private final VariableColor color;
+    private final Sprite        sprite;
+
+
+    /**
+     * Creates a new ColorSprite.
+     * @param color  the variable color of the given sprite.
+     * @param sprite the sprite that will be colored and painted.
+     */
+    public ColorSprite(VariableColor color,
+                       Sprite        sprite)
+    {
+        this.color  = color;
+        this.sprite = sprite;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        // Save the old color.
+        Color oldColor = graphics.getColor();
+
+        // Set the new color.
+        graphics.setColor(color.getColor(time));
+
+        // Paint the actual sprite.
+        sprite.paint(graphics, time);
+
+        // Restore the old color.
+        graphics.setColor(oldColor);
+    }
+}
diff --git a/src/proguard/gui/splash/CompositeSprite.java b/src/proguard/gui/splash/CompositeSprite.java
new file mode 100644
index 0000000..2480ead
--- /dev/null
+++ b/src/proguard/gui/splash/CompositeSprite.java
@@ -0,0 +1,56 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite is the composition of a list of Sprite objects.
+ *
+ * @author Eric Lafortune
+ */
+public class CompositeSprite implements Sprite
+{
+    private final Sprite[] sprites;
+
+
+    /**
+     * Creates a new CompositeSprite.
+     * @param sprites the array of Sprite objects to which the painting will
+     *                be delegated, starting with the first element.
+     */
+    public CompositeSprite(Sprite[] sprites)
+    {
+        this.sprites = sprites;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        // Draw the sprites.
+        for (int index = 0; index < sprites.length; index++)
+        {
+            sprites[index].paint(graphics, time);
+        }
+    }
+}
diff --git a/src/proguard/gui/splash/ConstantColor.java b/src/proguard/gui/splash/ConstantColor.java
new file mode 100644
index 0000000..94c78df
--- /dev/null
+++ b/src/proguard/gui/splash/ConstantColor.java
@@ -0,0 +1,51 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This VariableColor is constant over time.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantColor implements VariableColor
+{
+    private final Color value;
+
+
+    /**
+     * Creates a new ConstantColor.
+     * @param value the constant value.
+     */
+    public ConstantColor(Color value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementation for VariableColor.
+
+    public Color getColor(long time)
+    {
+        return value;
+    }
+}
diff --git a/src/proguard/gui/splash/ConstantDouble.java b/src/proguard/gui/splash/ConstantDouble.java
new file mode 100644
index 0000000..0874d6d
--- /dev/null
+++ b/src/proguard/gui/splash/ConstantDouble.java
@@ -0,0 +1,49 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This VariableDouble is constant over time.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantDouble implements VariableDouble
+{
+    private final double value;
+
+
+    /**
+     * Creates a new ConstantDouble.
+     * @param value the constant value.
+     */
+    public ConstantDouble(double value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementation for VariableDouble.
+
+    public double getDouble(long time)
+    {
+        return value;
+    }
+}
diff --git a/src/proguard/gui/splash/ConstantFont.java b/src/proguard/gui/splash/ConstantFont.java
new file mode 100644
index 0000000..3f1ac03
--- /dev/null
+++ b/src/proguard/gui/splash/ConstantFont.java
@@ -0,0 +1,46 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This VariableFont is constant over time.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantFont implements VariableFont
+{
+    private final Font value;
+
+    public ConstantFont(Font value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementation for VariableFont.
+
+    public Font getFont(long time)
+    {
+        return value;
+    }
+}
diff --git a/src/proguard/gui/splash/ConstantInt.java b/src/proguard/gui/splash/ConstantInt.java
new file mode 100644
index 0000000..537196d
--- /dev/null
+++ b/src/proguard/gui/splash/ConstantInt.java
@@ -0,0 +1,49 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This VariableInt is constant over time.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantInt implements VariableInt
+{
+    private final int value;
+
+
+    /**
+     * Creates a new ConstantInt.
+     * @param value the constant value.
+     */
+    public ConstantInt(int value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementation for VariableInt.
+
+    public int getInt(long time)
+    {
+        return value;
+    }
+}
diff --git a/src/proguard/gui/splash/ConstantString.java b/src/proguard/gui/splash/ConstantString.java
new file mode 100644
index 0000000..7617c3f
--- /dev/null
+++ b/src/proguard/gui/splash/ConstantString.java
@@ -0,0 +1,49 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This VariableString is constant over time.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantString implements VariableString
+{
+    private final String value;
+
+
+    /**
+     * Creates a new ConstantString.
+     * @param value the constant value.
+     */
+    public ConstantString(String value)
+    {
+        this.value = value;
+    }
+
+
+    // Implementation for VariableString.
+
+    public String getString(long time)
+    {
+        return value;
+    }
+}
diff --git a/src/proguard/gui/splash/ConstantTiming.java b/src/proguard/gui/splash/ConstantTiming.java
new file mode 100644
index 0000000..dfde644
--- /dev/null
+++ b/src/proguard/gui/splash/ConstantTiming.java
@@ -0,0 +1,57 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This Timing is constant over time.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantTiming implements Timing
+{
+    private final double timing;
+
+
+    /**
+     * Creates a new ConstantTiming with a value of 0.
+     */
+    public ConstantTiming()
+    {
+        this(0.0);
+    }
+
+    /**
+     * Creates a new ConstantTiming with a given value.
+     * @param timing the constant value of the timing.
+     */
+    public ConstantTiming(double timing)
+    {
+        this.timing = timing;
+    }
+
+
+    // Implementation for Timing.
+
+    public double getTiming(long time)
+    {
+        return timing;
+    }
+}
diff --git a/src/proguard/gui/splash/FontSprite.java b/src/proguard/gui/splash/FontSprite.java
new file mode 100644
index 0000000..9a554ba
--- /dev/null
+++ b/src/proguard/gui/splash/FontSprite.java
@@ -0,0 +1,65 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite sets the font for another given sprite.
+ *
+ * @author Eric Lafortune
+ */
+public class FontSprite implements Sprite
+{
+    private final VariableFont font;
+    private final Sprite       sprite;
+
+
+    /**
+     * Creates a new FontSprite.
+     * @param font   the variable Font of the given sprite.
+     * @param sprite the sprite that will be provided of a font and painted.
+     */
+    public FontSprite(VariableFont font,
+                      Sprite       sprite)
+    {
+        this.font   = font;
+        this.sprite = sprite;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        // Save the old font.
+        Font oldFont = graphics.getFont();
+
+        // Set the new font.
+        graphics.setFont(font.getFont(time));
+
+        // Paint the actual sprite.
+        sprite.paint(graphics, time);
+
+        // Restore the old font.
+        graphics.setFont(oldFont);
+    }
+}
diff --git a/src/proguard/gui/splash/ImageSprite.java b/src/proguard/gui/splash/ImageSprite.java
new file mode 100644
index 0000000..6e7c189
--- /dev/null
+++ b/src/proguard/gui/splash/ImageSprite.java
@@ -0,0 +1,76 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite represents an animated image.
+ *
+ * @author Eric Lafortune
+ */
+public class ImageSprite implements Sprite
+{
+    private final Image          image;
+    private final VariableInt    x;
+    private final VariableInt    y;
+    private final VariableDouble scaleX;
+    private final VariableDouble scaleY;
+
+
+    /**
+     * Creates a new ImageSprite.
+     * @param image  the Image to be painted.
+     * @param x      the variable x-coordinate of the upper-left corner of the image.
+     * @param y      the variable y-coordinate of the upper-left corner of the image.
+     * @param scaleX the variable x-scale of the image.
+     * @param scaleY the variable y-scale of the image.
+     */
+    public ImageSprite(Image          image,
+                       VariableInt    x,
+                       VariableInt    y,
+                       VariableDouble scaleX,
+                       VariableDouble scaleY)
+    {
+        this.image  = image;
+        this.x      = x;
+        this.y      = y;
+        this.scaleX = scaleX;
+        this.scaleY = scaleY;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        int xt = x.getInt(time);
+        int yt = y.getInt(time);
+
+        double scale_x = scaleX.getDouble(time);
+        double scale_y = scaleY.getDouble(time);
+
+        int width  = (int)(image.getWidth(null)  * scale_x);
+        int height = (int)(image.getHeight(null) * scale_y);
+
+        graphics.drawImage(image, xt, yt, width, height, null);
+    }
+}
diff --git a/src/proguard/gui/splash/LinearColor.java b/src/proguard/gui/splash/LinearColor.java
new file mode 100644
index 0000000..3a7674d
--- /dev/null
+++ b/src/proguard/gui/splash/LinearColor.java
@@ -0,0 +1,72 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This VariableColor varies linearly with respect to its Timing.
+ *
+ * @author Eric Lafortune
+ */
+public class LinearColor implements VariableColor
+{
+    private final Color  fromValue;
+    private final Color  toValue;
+    private final Timing timing;
+
+    private double cachedTiming = -1.0;
+    private Color  cachedColor;
+
+
+    /**
+     * Creates a new LinearColor.
+     * @param fromValue the value that corresponds to a timing of 0.
+     * @param toValue   the value that corresponds to a timing of 1.
+     * @param timing    the applied timing.
+     */
+    public LinearColor(Color fromValue, Color toValue, Timing timing)
+    {
+        this.fromValue = fromValue;
+        this.toValue   = toValue;
+        this.timing    = timing;
+    }
+
+
+    // Implementation for VariableColor.
+
+    public Color getColor(long time)
+    {
+        double t = timing.getTiming(time);
+        if (t != cachedTiming)
+        {
+            cachedTiming = t;
+            cachedColor =
+                t == 0.0 ? fromValue :
+                t == 1.0 ? toValue   :
+                           new Color((int)(fromValue.getRed()   + t * (toValue.getRed()   - fromValue.getRed())),
+                                     (int)(fromValue.getGreen() + t * (toValue.getGreen() - fromValue.getGreen())),
+                                     (int)(fromValue.getBlue()  + t * (toValue.getBlue()  - fromValue.getBlue())));
+        }
+
+        return cachedColor;
+    }
+}
diff --git a/src/proguard/gui/splash/LinearDouble.java b/src/proguard/gui/splash/LinearDouble.java
new file mode 100644
index 0000000..046ae84
--- /dev/null
+++ b/src/proguard/gui/splash/LinearDouble.java
@@ -0,0 +1,55 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This VariableDouble varies linearly with respect to its Timing.
+ *
+ * @author Eric Lafortune
+ */
+public class LinearDouble implements VariableDouble
+{
+    private final double fromValue;
+    private final double toValue;
+    private final Timing timing;
+
+
+    /**
+     * Creates a new LinearDouble.
+     * @param fromValue the value that corresponds to a timing of 0.
+     * @param toValue   the value that corresponds to a timing of 1.
+     * @param timing    the applied timing.
+     */
+    public LinearDouble(double fromValue, double toValue, Timing timing)
+    {
+        this.fromValue = fromValue;
+        this.toValue   = toValue;
+        this.timing    = timing;
+    }
+
+
+    // Implementation for VariableDouble.
+
+    public double getDouble(long time)
+    {
+        return fromValue + timing.getTiming(time) * (toValue - fromValue);
+    }
+}
diff --git a/src/proguard/gui/splash/LinearInt.java b/src/proguard/gui/splash/LinearInt.java
new file mode 100644
index 0000000..8d299bc
--- /dev/null
+++ b/src/proguard/gui/splash/LinearInt.java
@@ -0,0 +1,55 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This VariableColor varies linearly with respect to its Timing.
+ *
+ * @author Eric Lafortune
+ */
+public class LinearInt implements VariableInt
+{
+    private final int    fromValue;
+    private final int    toValue;
+    private final Timing timing;
+
+
+    /**
+     * Creates a new LinearInt.
+     * @param fromValue the value that corresponds to a timing of 0.
+     * @param toValue   the value that corresponds to a timing of 1.
+     * @param timing    the applied timing.
+     */
+    public LinearInt(int fromValue, int toValue, Timing timing)
+    {
+        this.fromValue = fromValue;
+        this.toValue   = toValue;
+        this.timing    = timing;
+    }
+
+
+    // Implementation for VariableInt.
+
+    public int getInt(long time)
+    {
+        return (int) (fromValue + timing.getTiming(time) * (toValue - fromValue));
+    }
+}
diff --git a/src/proguard/gui/splash/LinearTiming.java b/src/proguard/gui/splash/LinearTiming.java
new file mode 100644
index 0000000..9b26644
--- /dev/null
+++ b/src/proguard/gui/splash/LinearTiming.java
@@ -0,0 +1,55 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This Timing ramps up linearly from 0 to 1 in a given time interval.
+ *
+ * @author Eric Lafortune
+ */
+public class LinearTiming implements Timing
+{
+    private final long fromTime;
+    private final long toTime;
+
+
+    /**
+     * Creates a new LinearTiming.
+     * @param fromTime the time at which the timing starts ramping up from 0.
+     * @param toTime   the time at which the timing stops ramping up at 1.
+     */
+    public LinearTiming(long fromTime, long toTime)
+    {
+        this.fromTime = fromTime;
+        this.toTime   = toTime;
+    }
+
+
+    // Implementation for Timing.
+
+    public double getTiming(long time)
+    {
+        // Compute the clamped linear interpolation.
+        return time <= fromTime ? 0.0 :
+               time >= toTime   ? 1.0 :
+                                  (double)(time - fromTime) / (double)(toTime - fromTime);
+    }
+}
diff --git a/src/proguard/gui/splash/OverrideGraphics2D.java b/src/proguard/gui/splash/OverrideGraphics2D.java
new file mode 100644
index 0000000..4333459
--- /dev/null
+++ b/src/proguard/gui/splash/OverrideGraphics2D.java
@@ -0,0 +1,598 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+import java.awt.RenderingHints.Key;
+import java.awt.font.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.*;
+import java.awt.image.renderable.RenderableImage;
+import java.text.AttributedCharacterIterator;
+import java.util.Map;
+
+/**
+ * This Graphics2D allows to fix some basic settings (Color, Font, Paint, Stroke,
+ * XORMode) of a delegate Graphics2D, overriding any subsequent attempts to
+ * change those settings.
+ *
+ * @author Eric Lafortune
+ * @noinspection deprecation
+ */
+final class OverrideGraphics2D extends Graphics2D
+{
+    private final Graphics2D graphics;
+
+    private Color  overrideColor;
+    private Font   overrideFont;
+    private Paint  overridePaint;
+    private Stroke overrideStroke;
+    private Color  overrideXORMode;
+
+    private Color  color;
+    private Font   font;
+    private Paint  paint;
+    private Stroke stroke;
+
+
+    /**
+     * Creates a new OverrideGraphics2D.
+     * @param graphics the delegate Graphics2D.
+     */
+    public OverrideGraphics2D(Graphics2D graphics)
+    {
+        this.graphics = graphics;
+        this.color    = graphics.getColor();
+        this.font     = graphics.getFont();
+        this.paint    = graphics.getPaint();
+        this.stroke   = graphics.getStroke();
+    }
+
+
+    /**
+     * Fixes the Color of the Graphics2D.
+     *
+     * @param color the fixed Color, or <code>null</code> to undo the fixing.
+     */
+    public void setOverrideColor(Color color)
+    {
+        this.overrideColor = color;
+        graphics.setColor(color != null ? color : this.color);
+    }
+
+    /**
+     * Fixes the Font of the Graphics2D.
+     *
+     * @param font the fixed Font, or <code>null</code> to undo the fixing.
+     */
+    public void setOverrideFont(Font font)
+    {
+        this.overrideFont = font;
+        graphics.setFont(font != null ? font : this.font);
+    }
+
+    /**
+     * Fixes the Paint of the Graphics2D.
+     *
+     * @param paint the fixed Paint, or <code>null</code> to undo the fixing.
+     */
+    public void setOverridePaint(Paint paint)
+    {
+        this.overridePaint = paint;
+        graphics.setPaint(paint != null ? paint : this.paint);
+    }
+
+    /**
+     * Fixes the Stroke of the Graphics2D.
+     *
+     * @param stroke the fixed Stroke, or <code>null</code> to undo the fixing.
+     */
+    public void setOverrideStroke(Stroke stroke)
+    {
+        this.overrideStroke = stroke;
+        graphics.setStroke(stroke != null ? stroke : this.stroke);
+    }
+
+    /**
+     * Fixes the XORMode of the Graphics2D.
+     *
+     * @param color the fixed XORMode Color, or <code>null</code> to undo the fixing.
+     */
+    public void setOverrideXORMode(Color color)
+    {
+        this.overrideXORMode = color;
+        if (color != null)
+        {
+            graphics.setXORMode(color);
+        }
+        else
+        {
+            graphics.setPaintMode();
+        }
+    }
+
+
+    // Implementations for Graphics2D.
+
+    public void setColor(Color color)
+    {
+        this.color = color;
+        if (overrideColor == null)
+        {
+            graphics.setColor(color);
+        }
+    }
+
+    public void setFont(Font font)
+    {
+        this.font = font;
+        if (overrideFont == null)
+        {
+            graphics.setFont(font);
+        }
+    }
+
+    public void setPaint(Paint paint)
+    {
+        this.paint = paint;
+        if (overridePaint == null)
+        {
+            graphics.setPaint(paint);
+        }
+    }
+
+    public void setStroke(Stroke stroke)
+    {
+        this.stroke = stroke;
+        if (overrideStroke == null)
+        {
+            graphics.setStroke(stroke);
+        }
+    }
+
+    public void setXORMode(Color color)
+    {
+        if (overrideXORMode == null)
+        {
+            graphics.setXORMode(color);
+        }
+    }
+
+    public void setPaintMode()
+    {
+        if (overrideXORMode == null)
+        {
+            graphics.setPaintMode();
+        }
+    }
+
+
+    public Color getColor()
+    {
+        return overrideColor != null ? color : graphics.getColor();
+    }
+
+    public Font getFont()
+    {
+        return overrideFont != null ? font : graphics.getFont();
+    }
+
+    public Paint getPaint()
+    {
+        return overridePaint != null ? paint : graphics.getPaint();
+    }
+
+    public Stroke getStroke()
+    {
+        return overrideStroke != null ? stroke : graphics.getStroke();
+    }
+
+
+    public Graphics create()
+    {
+        OverrideGraphics2D g = new OverrideGraphics2D((Graphics2D)graphics.create());
+        g.setOverrideColor(overrideColor);
+        g.setOverrideFont(overrideFont);
+        g.setOverridePaint(overridePaint);
+        g.setOverrideStroke(overrideStroke);
+
+        return g;
+    }
+
+    public Graphics create(int x, int y, int width, int height)
+    {
+        OverrideGraphics2D g = new OverrideGraphics2D((Graphics2D)graphics.create(x, y, width, height));
+        g.setOverrideColor(overrideColor);
+        g.setOverrideFont(overrideFont);
+        g.setOverridePaint(overridePaint);
+        g.setOverrideStroke(overrideStroke);
+
+        return g;
+    }
+
+
+    // Delegation for Graphics2D
+
+    public void addRenderingHints(Map hints)
+    {
+        graphics.addRenderingHints(hints);
+    }
+
+    public void clearRect(int x, int y, int width, int height)
+    {
+        graphics.clearRect(x, y, width, height);
+    }
+
+    public void clip(Shape s)
+    {
+        graphics.clip(s);
+    }
+
+    public void clipRect(int x, int y, int width, int height)
+    {
+        graphics.clipRect(x, y, width, height);
+    }
+
+    public void copyArea(int x, int y, int width, int height, int dx, int dy)
+    {
+        graphics.copyArea(x, y, width, height, dx, dy);
+    }
+
+    public void dispose()
+    {
+        graphics.dispose();
+    }
+
+    public void draw(Shape s)
+    {
+        graphics.draw(s);
+    }
+
+    public void draw3DRect(int x, int y, int width, int height, boolean raised)
+    {
+        graphics.draw3DRect(x, y, width, height, raised);
+    }
+
+    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
+    {
+        graphics.drawArc(x, y, width, height, startAngle, arcAngle);
+    }
+
+    public void drawBytes(byte[] data, int offset, int length, int x, int y)
+    {
+        graphics.drawBytes(data, offset, length, x, y);
+    }
+
+    public void drawChars(char[] data, int offset, int length, int x, int y)
+    {
+        graphics.drawChars(data, offset, length, x, y);
+    }
+
+    public void drawGlyphVector(GlyphVector g, float x, float y)
+    {
+        graphics.drawGlyphVector(g, x, y);
+    }
+
+    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)
+    {
+        return graphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer);
+    }
+
+    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)
+    {
+        return graphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
+    }
+
+    public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)
+    {
+        return graphics.drawImage(img, x, y, width, height, bgcolor, observer);
+    }
+
+    public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
+    {
+        return graphics.drawImage(img, x, y, width, height, observer);
+    }
+
+    public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)
+    {
+        return graphics.drawImage(img, x, y, bgcolor, observer);
+    }
+
+    public boolean drawImage(Image img, int x, int y, ImageObserver observer)
+    {
+        return graphics.drawImage(img, x, y, observer);
+    }
+
+    public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
+    {
+        return graphics.drawImage(img, xform, obs);
+    }
+
+    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y)
+    {
+        graphics.drawImage(img, op, x, y);
+    }
+
+    public void drawLine(int x1, int y1, int x2, int y2)
+    {
+        graphics.drawLine(x1, y1, x2, y2);
+    }
+
+    public void drawOval(int x, int y, int width, int height)
+    {
+        graphics.drawOval(x, y, width, height);
+    }
+
+    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
+    {
+        graphics.drawPolygon(xPoints, yPoints, nPoints);
+    }
+
+    public void drawPolygon(Polygon p)
+    {
+        graphics.drawPolygon(p);
+    }
+
+    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
+    {
+        graphics.drawPolyline(xPoints, yPoints, nPoints);
+    }
+
+    public void drawRect(int x, int y, int width, int height)
+    {
+        graphics.drawRect(x, y, width, height);
+    }
+
+    public void drawRenderableImage(RenderableImage img, AffineTransform xform)
+    {
+        graphics.drawRenderableImage(img, xform);
+    }
+
+    public void drawRenderedImage(RenderedImage img, AffineTransform xform)
+    {
+        graphics.drawRenderedImage(img, xform);
+    }
+
+    public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
+    {
+        graphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight);
+    }
+
+    public void drawString(String s, float x, float y)
+    {
+        graphics.drawString(s, x, y);
+    }
+
+    public void drawString(String str, int x, int y)
+    {
+        graphics.drawString(str, x, y);
+    }
+
+    public void drawString(AttributedCharacterIterator iterator, float x, float y)
+    {
+        graphics.drawString(iterator, x, y);
+    }
+
+    public void drawString(AttributedCharacterIterator iterator, int x, int y)
+    {
+        graphics.drawString(iterator, x, y);
+    }
+
+    public boolean equals(Object obj)
+    {
+        return graphics.equals(obj);
+    }
+
+    public void fill(Shape s)
+    {
+        graphics.fill(s);
+    }
+
+    public void fill3DRect(int x, int y, int width, int height, boolean raised)
+    {
+        graphics.fill3DRect(x, y, width, height, raised);
+    }
+
+    public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)
+    {
+        graphics.fillArc(x, y, width, height, startAngle, arcAngle);
+    }
+
+    public void fillOval(int x, int y, int width, int height)
+    {
+        graphics.fillOval(x, y, width, height);
+    }
+
+    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
+    {
+        graphics.fillPolygon(xPoints, yPoints, nPoints);
+    }
+
+    public void fillPolygon(Polygon p)
+    {
+        graphics.fillPolygon(p);
+    }
+
+    public void fillRect(int x, int y, int width, int height)
+    {
+        graphics.fillRect(x, y, width, height);
+    }
+
+    public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
+    {
+        graphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
+    }
+
+    public Color getBackground()
+    {
+        return graphics.getBackground();
+    }
+
+    public Shape getClip()
+    {
+        return graphics.getClip();
+    }
+
+    public Rectangle getClipBounds()
+    {
+        return graphics.getClipBounds();
+    }
+
+    public Rectangle getClipBounds(Rectangle r)
+    {
+        return graphics.getClipBounds(r);
+    }
+
+    public Rectangle getClipRect()
+    {
+        return graphics.getClipRect();
+    }
+
+    public Composite getComposite()
+    {
+        return graphics.getComposite();
+    }
+
+    public GraphicsConfiguration getDeviceConfiguration()
+    {
+        return graphics.getDeviceConfiguration();
+    }
+
+    public FontMetrics getFontMetrics()
+    {
+        return graphics.getFontMetrics();
+    }
+
+    public FontMetrics getFontMetrics(Font f)
+    {
+        return graphics.getFontMetrics(f);
+    }
+
+    public FontRenderContext getFontRenderContext()
+    {
+        return graphics.getFontRenderContext();
+    }
+
+    public Object getRenderingHint(Key hintKey)
+    {
+        return graphics.getRenderingHint(hintKey);
+    }
+
+    public RenderingHints getRenderingHints()
+    {
+        return graphics.getRenderingHints();
+    }
+
+    public AffineTransform getTransform()
+    {
+        return graphics.getTransform();
+    }
+
+    public int hashCode()
+    {
+        return graphics.hashCode();
+    }
+
+    public boolean hit(Rectangle rect, Shape s, boolean onStroke)
+    {
+        return graphics.hit(rect, s, onStroke);
+    }
+
+    public boolean hitClip(int x, int y, int width, int height)
+    {
+        return graphics.hitClip(x, y, width, height);
+    }
+
+    public void rotate(double theta)
+    {
+        graphics.rotate(theta);
+    }
+
+    public void rotate(double theta, double x, double y)
+    {
+        graphics.rotate(theta, x, y);
+    }
+
+    public void scale(double sx, double sy)
+    {
+        graphics.scale(sx, sy);
+    }
+
+    public void setBackground(Color color)
+    {
+        graphics.setBackground(color);
+    }
+
+    public void setClip(int x, int y, int width, int height)
+    {
+        graphics.setClip(x, y, width, height);
+    }
+
+    public void setClip(Shape clip)
+    {
+        graphics.setClip(clip);
+    }
+
+    public void setComposite(Composite comp)
+    {
+        graphics.setComposite(comp);
+    }
+
+    public void setRenderingHint(Key hintKey, Object hintValue)
+    {
+        graphics.setRenderingHint(hintKey, hintValue);
+    }
+
+    public void setRenderingHints(Map hints)
+    {
+        graphics.setRenderingHints(hints);
+    }
+
+    public void setTransform(AffineTransform Tx)
+    {
+        graphics.setTransform(Tx);
+    }
+
+    public void shear(double shx, double shy)
+    {
+        graphics.shear(shx, shy);
+    }
+
+    public String toString()
+    {
+        return graphics.toString();
+    }
+
+    public void transform(AffineTransform Tx)
+    {
+        graphics.transform(Tx);
+    }
+
+    public void translate(double tx, double ty)
+    {
+        graphics.translate(tx, ty);
+    }
+
+    public void translate(int x, int y)
+    {
+        graphics.translate(x, y);
+    }
+}
diff --git a/src/proguard/gui/splash/RectangleSprite.java b/src/proguard/gui/splash/RectangleSprite.java
new file mode 100644
index 0000000..d204831
--- /dev/null
+++ b/src/proguard/gui/splash/RectangleSprite.java
@@ -0,0 +1,114 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite represents an animated rounded rectangle. It can optionally be filled.
+ *
+ * @author Eric Lafortune
+ */
+public class RectangleSprite implements Sprite
+{
+    private final boolean       filled;
+    private final VariableColor color;
+    private final VariableInt   x;
+    private final VariableInt   y;
+    private final VariableInt   width;
+    private final VariableInt   height;
+    private final VariableInt   arcWidth;
+    private final VariableInt   arcHeight;
+
+
+    /**
+     * Creates a new rectangular RectangleSprite.
+     * @param filled specifies whether the rectangle should be filled.
+     * @param color  the variable color of the rectangle.
+     * @param x      the variable x-ordinate of the upper-left corner of the rectangle.
+     * @param y      the variable y-ordinate of the upper-left corner of the rectangle.
+     * @param width  the variable width of the rectangle.
+     * @param height the variable height of the rectangle.
+     */
+    public RectangleSprite(boolean       filled,
+                           VariableColor color,
+                           VariableInt   x,
+                           VariableInt   y,
+                           VariableInt   width,
+                           VariableInt   height)
+    {
+        this(filled, color, x, y, width, height, new ConstantInt(0), new ConstantInt(0));
+    }
+
+
+    /**
+     * Creates a new RectangleSprite with rounded corners.
+     * @param filled specifies whether the rectangle should be filled.
+     * @param color     the variable color of the rectangle.
+     * @param x         the variable x-ordinate of the upper-left corner of the rectangle.
+     * @param y         the variable y-ordinate of the upper-left corner of the rectangle.
+     * @param width     the variable width of the rectangle.
+     * @param height    the variable height of the rectangle.
+     * @param arcWidth  the variable width of the corner arcs.
+     * @param arcHeight the variable height of the corner arcs.
+     */
+    public RectangleSprite(boolean       filled,
+                           VariableColor color,
+                           VariableInt   x,
+                           VariableInt   y,
+                           VariableInt   width,
+                           VariableInt   height,
+                           VariableInt   arcWidth,
+                           VariableInt   arcHeight)
+    {
+        this.filled    = filled;
+        this.color     = color;
+        this.x         = x;
+        this.y         = y;
+        this.width     = width;
+        this.height    = height;
+        this.arcWidth  = arcWidth;
+        this.arcHeight = arcHeight;
+    }
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        graphics.setColor(color.getColor(time));
+
+        int xt = x.getInt(time);
+        int yt = y.getInt(time);
+        int w  = width.getInt(time);
+        int h  = height.getInt(time);
+        int aw = arcWidth.getInt(time);
+        int ah = arcHeight.getInt(time);
+
+        if (filled)
+        {
+            graphics.fillRoundRect(xt, yt, w, h, aw, ah);
+        }
+        else
+        {
+            graphics.drawRoundRect(xt, yt, w, h, aw, ah);
+        }
+    }
+}
diff --git a/src/proguard/gui/splash/SawToothTiming.java b/src/proguard/gui/splash/SawToothTiming.java
new file mode 100644
index 0000000..076d5e2
--- /dev/null
+++ b/src/proguard/gui/splash/SawToothTiming.java
@@ -0,0 +1,53 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This Timing ramps up linearly from 0 to 1 in a given repeated time interval.
+ *
+ * @author Eric Lafortune
+ */
+public class SawToothTiming implements Timing
+{
+    private final long period;
+    private final long phase;
+
+
+    /**
+     * Creates a new SawToothTiming.
+     * @param period the time period for a full cycle.
+     * @param phase  the phase of the cycle, which is added to the actual time.
+     */
+    public SawToothTiming(long period, long phase)
+    {
+        this.period = period;
+        this.phase  = phase;
+    }
+
+
+    // Implementation for Timing.
+
+    public double getTiming(long time)
+    {
+        // Compute the translated and scaled saw-tooth function.
+        return (double)((time + phase) % period) / (double)period;
+    }
+}
diff --git a/src/proguard/gui/splash/ShadowedSprite.java b/src/proguard/gui/splash/ShadowedSprite.java
new file mode 100644
index 0000000..c3504f3
--- /dev/null
+++ b/src/proguard/gui/splash/ShadowedSprite.java
@@ -0,0 +1,109 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite adds a drop shadow to another Sprite.
+ *
+ * @author Eric Lafortune
+ */
+public class ShadowedSprite implements Sprite
+{
+    private final VariableInt    xOffset;
+    private final VariableInt    yOffset;
+    private final VariableDouble alpha;
+    private final VariableInt    blur;
+    private final Sprite         sprite;
+
+    private float cachedAlpha = -1.0f;
+    private Color cachedColor;
+
+
+    /**
+     * Creates a new ShadowedSprite.
+     * @param xOffset the variable x-offset of the shadow, relative to the sprite itself.
+     * @param yOffset the variable y-offset of the shadow, relative to the sprite itself.
+     * @param alpha   the variable darkness of the shadow (between 0 and 1).
+     * @param blur    the variable blur of the shadow (0 for sharp shadows, 1 or
+     *                more for increasingly blurry shadows).
+     * @param sprite  the Sprite to be painted with its shadow.
+     */
+    public ShadowedSprite(VariableInt    xOffset,
+                          VariableInt    yOffset,
+                          VariableDouble alpha,
+                          VariableInt    blur,
+                          Sprite         sprite)
+    {
+        this.xOffset = xOffset;
+        this.yOffset = yOffset;
+        this.alpha   = alpha;
+        this.blur    = blur;
+        this.sprite  = sprite;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        double l = alpha.getDouble(time);
+        int    b = blur.getInt(time) + 1;
+
+        float a = 1.0f - (float)Math.pow(1.0 - l, 1.0/(b*b));
+        if (a != cachedAlpha)
+        {
+            cachedAlpha = a;
+            cachedColor = new Color(0f, 0f, 0f, a);
+        }
+
+        // Set up the shadow graphics.
+        //OverrideGraphics2D g = new OverrideGraphics2D((Graphics2D)graphics);
+        //g.setOverrideColor(cachedColor);
+
+        // Set the shadow color.
+        Color actualColor = graphics.getColor();
+        graphics.setColor(cachedColor);
+
+        int xo = xOffset.getInt(time) - b/2;
+        int yo = yOffset.getInt(time) - b/2;
+
+        // Draw the sprite's shadow.
+        for (int x = 0; x < b; x++)
+        {
+            for (int y = 0; y < b; y++)
+            {
+                int xt = xo + x;
+                int yt = yo + y;
+                graphics.translate(xt, yt);
+                sprite.paint(graphics, time);
+                graphics.translate(-xt, -yt);
+            }
+        }
+
+        // Restore the actual sprite color.
+        graphics.setColor(actualColor);
+
+        // Draw the sprite itself in the ordinary graphics.
+        sprite.paint(graphics, time);
+    }
+}
diff --git a/src/proguard/gui/splash/SineTiming.java b/src/proguard/gui/splash/SineTiming.java
new file mode 100644
index 0000000..eb0a7cc
--- /dev/null
+++ b/src/proguard/gui/splash/SineTiming.java
@@ -0,0 +1,53 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This Timing varies between 0 and 1, as a sine wave over time.
+ *
+ * @author Eric Lafortune
+ */
+public class SineTiming implements Timing
+{
+    private final long period;
+    private final long phase;
+
+
+    /**
+     * Creates a new SineTiming.
+     * @param period the time period for a full cycle.
+     * @param phase  the phase of the cycle, which is added to the actual time.
+     */
+    public SineTiming(long period, long phase)
+    {
+        this.period = period;
+        this.phase  = phase;
+    }
+
+
+    // Implementation for Timing.
+
+    public double getTiming(long time)
+    {
+        // Compute the translated and scaled sine function.
+        return 0.5 + 0.5 * Math.sin(2.0 * Math.PI * (time + phase) / period);
+    }
+}
diff --git a/src/proguard/gui/splash/SmoothTiming.java b/src/proguard/gui/splash/SmoothTiming.java
new file mode 100644
index 0000000..a985712
--- /dev/null
+++ b/src/proguard/gui/splash/SmoothTiming.java
@@ -0,0 +1,66 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This Timing ramps up smoothly from 0 to 1 in a given time interval.
+ *
+ * @author Eric Lafortune
+ */
+public class SmoothTiming implements Timing
+{
+    private final long fromTime;
+    private final long toTime;
+
+
+    /**
+     * Creates a new SmoothTiming.
+     * @param fromTime the time at which the timing starts ramping up from 0.
+     * @param toTime   the time at which the timing stops ramping up at 1.
+     */
+    public SmoothTiming(long fromTime, long toTime)
+    {
+        this.fromTime = fromTime;
+        this.toTime   = toTime;
+    }
+
+
+    // Implementation for Timing.
+
+    public double getTiming(long time)
+    {
+        if (time <= fromTime)
+        {
+            return 0.0;
+        }
+
+        if (time >= toTime)
+        {
+            return 1.0;
+        }
+
+        // Compute the linear interpolation.
+        double timing = (double) (time - fromTime) / (double) (toTime - fromTime);
+
+        // Smooth the interpolation at the ends.
+        return timing * timing * (3.0 - 2.0 * timing);
+    }
+}
diff --git a/src/proguard/gui/splash/SplashPanel.java b/src/proguard/gui/splash/SplashPanel.java
new file mode 100644
index 0000000..23a9ce4
--- /dev/null
+++ b/src/proguard/gui/splash/SplashPanel.java
@@ -0,0 +1,235 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import proguard.gui.SwingUtil;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * This JPanel renders an animated Sprite.
+ *
+ * @author Eric Lafortune
+ */
+public class SplashPanel extends JPanel
+{
+    private final MyAnimator  animator  = new MyAnimator();
+    private final MyRepainter repainter = new MyRepainter();
+
+    private final Sprite sprite;
+    private final double sleepFactor;
+
+    private long   startTime = Long.MAX_VALUE;
+    private final long   stopTime;
+
+    private volatile Thread animationThread;
+
+
+    /**
+     * Creates a new SplashPanel with the given Sprite, which will be animated
+     * indefinitely.
+     * @param sprite        the Sprite that will be animated.
+     * @param processorLoad the fraction of processing time to be spend on
+     *                      animating the Sprite (between 0 and 1).
+     */
+    public SplashPanel(Sprite sprite, double processorLoad)
+    {
+        this(sprite, processorLoad, (long)Integer.MAX_VALUE);
+    }
+
+
+    /**
+     * Creates a new SplashPanel with the given Sprite, which will be animated
+     * for a limited period of time.
+     * @param sprite        the Sprite that will be animated.
+     * @param processorLoad the fraction of processing time to be spend on
+     *                      animating the Sprite (between 0 and 1).
+     * @param stopTime      the number of milliseconds after which the
+     *                      animation will be stopped automatically.
+     */
+    public SplashPanel(Sprite sprite, double processorLoad, long stopTime)
+    {
+        this.sprite      = sprite;
+        this.sleepFactor = (1.0-processorLoad) / processorLoad;
+        this.stopTime    = stopTime;
+
+        // Restart the animation on a mouse click.
+        addMouseListener(new MouseAdapter()
+        {
+            public void mouseClicked(MouseEvent e)
+            {
+                SplashPanel.this.start();
+            }
+        });
+    }
+
+
+    /**
+     * Starts the animation.
+     */
+    public void start()
+    {
+        // Go to the beginning of the animation.
+        startTime = System.currentTimeMillis();
+
+        // Make sure we have an animation thread running.
+        if (animationThread == null)
+        {
+            animationThread = new Thread(animator);
+            animationThread.start();
+        }
+    }
+
+
+    /**
+     * Stops the animation.
+     */
+    public void stop()
+    {
+        // Go to the end of the animation.
+        startTime = 0L;
+
+        // Let the animation thread stop itself.
+        animationThread = null;
+
+        // Repaint the SplashPanel one last time.
+        try
+        {
+            SwingUtil.invokeAndWait(repainter);
+        }
+        catch (InterruptedException ex)
+        {
+            // Nothing.
+        }
+        catch (InvocationTargetException ex)
+        {
+            // Nothing.
+        }
+    }
+
+
+    // Implementation for JPanel.
+
+    public void paintComponent(Graphics graphics)
+    {
+        super.paintComponent(graphics);
+
+        sprite.paint(graphics, System.currentTimeMillis() - startTime);
+    }
+
+
+    /**
+     * This Runnable makes sure its SplashPanel gets repainted regularly,
+     * depending on the targeted processor load.
+     */
+    private class MyAnimator implements Runnable
+    {
+        public void run()
+        {
+            try
+            {
+                while (animationThread != null)
+                {
+                    // Check if we should stop the animation.
+                    long time = System.currentTimeMillis();
+                    if (time > startTime + stopTime)
+                    {
+                        animationThread = null;
+                    }
+
+                    // Do a repaint and time it.
+                    SwingUtil.invokeAndWait(repainter);
+
+                    // Sleep for a proportional while.
+                    long repaintTime = System.currentTimeMillis() - time;
+                    long sleepTime   = (long)(sleepFactor * repaintTime);
+                    if (sleepTime < 10L)
+                    {
+                        sleepTime = 10L;
+                    }
+
+                    Thread.sleep(sleepTime);
+                }
+            }
+            catch (InterruptedException ex)
+            {
+                // Nothing.
+            }
+            catch (InvocationTargetException ex)
+            {
+                // Nothing.
+            }
+        }
+    }
+
+
+    /**
+     * This Runnable repaints its SplashPanel.
+     */
+    private class MyRepainter implements Runnable
+    {
+        public void run()
+        {
+            SplashPanel.this.repaint();
+        }
+    }
+
+
+    /**
+     * A main method for testing the splash panel.
+     */
+    public static void main(String[] args)
+    {
+        JFrame frame = new JFrame();
+        frame.setTitle("Animation");
+        frame.setSize(800, 600);
+        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+        Dimension frameSize  = frame.getSize();
+        frame.setLocation((screenSize.width - frameSize.width)   / 2,
+                          (screenSize.height - frameSize.height) / 2);
+
+        Sprite sprite =
+            new ClipSprite(
+            new ConstantColor(Color.white),
+            new ConstantColor(Color.lightGray),
+            new CircleSprite(true,
+                             new LinearInt(200, 600, new SineTiming(2345L, 0L)),
+                             new LinearInt(200, 400, new SineTiming(3210L, 0L)),
+                             new ConstantInt(150)),
+            new ColorSprite(new ConstantColor(Color.gray),
+            new FontSprite(new ConstantFont(new Font("sansserif", Font.BOLD, 90)),
+            new TextSprite(new ConstantString("ProGuard"),
+                           new ConstantInt(200),
+                           new ConstantInt(300)))));
+
+        SplashPanel panel = new SplashPanel(sprite, 0.5);
+        panel.setBackground(Color.white);
+
+        frame.getContentPane().add(panel);
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        frame.setVisible(true);
+
+        panel.start();
+    }
+}
diff --git a/src/proguard/gui/splash/Sprite.java b/src/proguard/gui/splash/Sprite.java
new file mode 100644
index 0000000..ada7a81
--- /dev/null
+++ b/src/proguard/gui/splash/Sprite.java
@@ -0,0 +1,41 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This interface describes objects that can paint themselves, possibly varying
+ * as a function of time.
+ *
+ * @author Eric Lafortune
+ */
+public interface Sprite
+{
+    /**
+     * Paints the object.
+     *
+     * @param graphics the Graphics to paint on.
+     * @param time     the time since the start of the animation, expressed in
+     *                  milliseconds.
+     */
+    public void paint(Graphics graphics, long time);
+}
diff --git a/src/proguard/gui/splash/TextSprite.java b/src/proguard/gui/splash/TextSprite.java
new file mode 100644
index 0000000..bbf37d4
--- /dev/null
+++ b/src/proguard/gui/splash/TextSprite.java
@@ -0,0 +1,89 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite represents a text.
+ *
+ * @author Eric Lafortune
+ */
+public class TextSprite implements Sprite
+{
+    private final VariableString[] text;
+    private final VariableInt      spacing;
+    private final VariableInt      x;
+    private final VariableInt      y;
+
+
+    /**
+     * Creates a new TextSprite containing a single line of text.
+     * @param text  the variable text string.
+     * @param x     the variable x-coordinate of the lower-left corner of the text.
+     * @param y     the variable y-coordinate of the lower-left corner of the text.
+     */
+    public TextSprite(VariableString text,
+                      VariableInt    x,
+                      VariableInt    y)
+    {
+        this(new VariableString[] { text }, new ConstantInt(0), x, y);
+    }
+
+
+    /**
+     * Creates a new TextSprite containing a multiple lines of text.
+     * @param text    the variable text strings.
+     * @param spacing the variable spacing between the lines of text.
+     * @param x       the variable x-coordinate of the lower-left corner of the
+     *                first line of text.
+     * @param y       the variable y-coordinate of the lower-left corner of the
+     *                first line of text.
+     */
+    public TextSprite(VariableString[] text,
+                      VariableInt      spacing,
+                      VariableInt      x,
+                      VariableInt      y)
+    {
+
+        this.text    = text;
+        this.spacing = spacing;
+        this.x       = x;
+        this.y       = y;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+
+        int xt = x.getInt(time);
+        int yt = y.getInt(time);
+
+        int spacingt = spacing.getInt(time);
+
+        for (int index = 0; index < text.length; index++)
+        {
+            graphics.drawString(text[index].getString(time), xt, yt + index * spacingt);
+        }
+    }
+}
diff --git a/src/proguard/gui/splash/TimeSwitchSprite.java b/src/proguard/gui/splash/TimeSwitchSprite.java
new file mode 100644
index 0000000..921bef2
--- /dev/null
+++ b/src/proguard/gui/splash/TimeSwitchSprite.java
@@ -0,0 +1,75 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This Sprite displays another Sprite in a given time interval.
+ * The time of the encapsulated Sprite is shifted by the start time.
+ *
+ * @author Eric Lafortune
+ */
+public class TimeSwitchSprite implements Sprite
+{
+    private final long   onTime;
+    private final long offTime;
+    private final Sprite sprite;
+
+
+    /**
+     * Creates a new TimeSwitchSprite for displaying a given Sprite starting at
+     * a given time.
+     * @param onTime the start time.
+     * @param sprite the toggled Sprite.
+     */
+    public TimeSwitchSprite(long onTime, Sprite sprite)
+    {
+        this(onTime, 0L, sprite);
+    }
+
+
+    /**
+     * Creates a new TimeSwitchSprite for displaying a given Sprite in a given
+     * time interval.
+     * @param onTime  the start time.
+     * @param offTime the stop time.
+     * @param sprite  the toggled Sprite.
+     */
+    public TimeSwitchSprite(long onTime, long offTime, Sprite sprite)
+    {
+        this.onTime  = onTime;
+        this.offTime = offTime;
+        this.sprite  = sprite;
+    }
+
+
+    // Implementation for Sprite.
+
+    public void paint(Graphics graphics, long time)
+    {
+        if (time >= onTime && (offTime <= 0 || time <= offTime))
+        {
+            sprite.paint(graphics, time - onTime);
+        }
+
+    }
+}
diff --git a/src/proguard/gui/splash/Timing.java b/src/proguard/gui/splash/Timing.java
new file mode 100644
index 0000000..887d737
--- /dev/null
+++ b/src/proguard/gui/splash/Timing.java
@@ -0,0 +1,34 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This interface maps a time to a normalized timing between 0 and 1.
+ *
+ * @author Eric Lafortune
+ */
+interface Timing
+{
+    /**
+     * Returns the timing for the given time.
+     */
+    public double getTiming(long time);
+}
diff --git a/src/proguard/gui/splash/TypeWriterString.java b/src/proguard/gui/splash/TypeWriterString.java
new file mode 100644
index 0000000..9f1441e
--- /dev/null
+++ b/src/proguard/gui/splash/TypeWriterString.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This VariableString produces a String that grows linearly with respect to its
+ * Timing, as if it is being written on a typewriter. A cursor at the end
+ * precedes the typed characters.
+ *
+ * @author Eric Lafortune
+ */
+public class TypeWriterString implements VariableString
+{
+    private final String string;
+    private final Timing timing;
+
+    private int    cachedLength = -1;
+    private String cachedString;
+
+
+    /**
+     * Creates a new TypeWriterString.
+     * @param string the basic String.
+     * @param timing the applied timing.
+     */
+    public TypeWriterString(String string, Timing timing)
+    {
+        this.string = string;
+        this.timing = timing;
+    }
+
+
+    // Implementation for VariableString.
+
+    public String getString(long time)
+    {
+        double t = timing.getTiming(time);
+
+        int stringLength = string.length();
+        int length = (int)(stringLength * t + 0.5);
+        if (length != cachedLength)
+        {
+            cachedLength = length;
+            cachedString = string.substring(0, length);
+            if (t > 0.0 && length < stringLength)
+            {
+                cachedString += "_";
+            }
+        }
+
+        return cachedString;
+    }
+}
diff --git a/src/proguard/gui/splash/VariableColor.java b/src/proguard/gui/splash/VariableColor.java
new file mode 100644
index 0000000..6a30062
--- /dev/null
+++ b/src/proguard/gui/splash/VariableColor.java
@@ -0,0 +1,36 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This interface represents a Color that varies with time.
+ *
+ * @author Eric Lafortune
+ */
+interface VariableColor
+{
+    /**
+     * Returns the Color for the given time.
+     */
+    public Color getColor(long time);
+}
diff --git a/src/proguard/gui/splash/VariableDouble.java b/src/proguard/gui/splash/VariableDouble.java
new file mode 100644
index 0000000..39302dd
--- /dev/null
+++ b/src/proguard/gui/splash/VariableDouble.java
@@ -0,0 +1,34 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This interface represents a double that varies with time.
+ *
+ * @author Eric Lafortune
+ */
+interface VariableDouble
+{
+    /**
+     * Returns the double for the given time.
+     */
+    public double getDouble(long time);
+}
diff --git a/src/proguard/gui/splash/VariableFont.java b/src/proguard/gui/splash/VariableFont.java
new file mode 100644
index 0000000..a7de8d7
--- /dev/null
+++ b/src/proguard/gui/splash/VariableFont.java
@@ -0,0 +1,36 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This interface represents a Font that varies with time.
+ *
+ * @author Eric Lafortune
+ */
+interface VariableFont
+{
+    /**
+     * Returns the Font for the given time.
+     */
+    public Font getFont(long time);
+}
diff --git a/src/proguard/gui/splash/VariableInt.java b/src/proguard/gui/splash/VariableInt.java
new file mode 100644
index 0000000..68a33af
--- /dev/null
+++ b/src/proguard/gui/splash/VariableInt.java
@@ -0,0 +1,34 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This interface represents an integer that varies with time.
+ *
+ * @author Eric Lafortune
+ */
+interface VariableInt
+{
+    /**
+     * Returns the integer for the given time.
+     */
+    public int getInt(long time);
+}
diff --git a/src/proguard/gui/splash/VariableSizeFont.java b/src/proguard/gui/splash/VariableSizeFont.java
new file mode 100644
index 0000000..e36d28c
--- /dev/null
+++ b/src/proguard/gui/splash/VariableSizeFont.java
@@ -0,0 +1,65 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+import java.awt.*;
+
+/**
+ * This VariableFont varies in size with respect to its Timing.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableSizeFont implements VariableFont
+{
+    private final Font           font;
+    private final VariableDouble size;
+
+    private float cachedSize = -1.0f;
+    private Font  cachedFont;
+
+
+    /**
+     * Creates a new VariableSizeFont
+     * @param font the base font.
+     * @param size the variable size of the font.
+     */
+    public VariableSizeFont(Font font, VariableDouble size)
+    {
+        this.font = font;
+        this.size = size;
+    }
+
+
+    // Implementation for VariableFont.
+
+    public Font getFont(long time)
+    {
+        float s = (float)size.getDouble(time);
+
+        if (s != cachedSize)
+        {
+            cachedSize = s;
+            cachedFont = font.deriveFont((float)s);
+        }
+
+        return cachedFont;
+    }
+}
diff --git a/src/proguard/gui/splash/VariableString.java b/src/proguard/gui/splash/VariableString.java
new file mode 100644
index 0000000..1dec23b
--- /dev/null
+++ b/src/proguard/gui/splash/VariableString.java
@@ -0,0 +1,34 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.gui.splash;
+
+/**
+ * This interface represents a String that varies with time.
+ *
+ * @author Eric Lafortune
+ */
+interface VariableString
+{
+    /**
+     * Returns the String for the given time.
+     */
+    public String getString(long time);
+}
diff --git a/src/proguard/gui/splash/package.html b/src/proguard/gui/splash/package.html
new file mode 100644
index 0000000..209fad7
--- /dev/null
+++ b/src/proguard/gui/splash/package.html
@@ -0,0 +1,4 @@
+<body>
+This package contains a library for creating splash screens and animations
+with text, graphical elements, and some special effects.
+</body>
diff --git a/src/proguard/gui/vtitle.png b/src/proguard/gui/vtitle.png
new file mode 100644
index 0000000..218f716
--- /dev/null
+++ b/src/proguard/gui/vtitle.png
Binary files differ
diff --git a/src/proguard/io/CascadingDataEntryWriter.java b/src/proguard/io/CascadingDataEntryWriter.java
new file mode 100644
index 0000000..15719e6
--- /dev/null
+++ b/src/proguard/io/CascadingDataEntryWriter.java
@@ -0,0 +1,94 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.*;
+
+/**
+ * This DataEntryWriter delegates to a given DataEntryWriter, or failing that,
+ * to another given DataEntryWriter.
+ *
+ * @author Eric Lafortune
+ */
+public class CascadingDataEntryWriter implements DataEntryWriter
+{
+    private DataEntryWriter dataEntryWriter1;
+    private DataEntryWriter dataEntryWriter2;
+
+
+    /**
+     * Creates a new CascadingDataEntryWriter.
+     * @param dataEntryWriter1 the DataEntryWriter to which the writing will be
+     *                         delegated first.
+     * @param dataEntryWriter2 the DataEntryWriter to which the writing will be
+     *                         delegated, if the first one can't provide an
+     *                         output stream.
+     */
+    public CascadingDataEntryWriter(DataEntryWriter dataEntryWriter1,
+                                    DataEntryWriter dataEntryWriter2)
+    {
+        this.dataEntryWriter1 = dataEntryWriter1;
+        this.dataEntryWriter2 = dataEntryWriter2;
+    }
+
+
+    // Implementations for DataEntryWriter.
+
+
+    public boolean createDirectory(DataEntry dataEntry) throws IOException
+    {
+        // Try to create a directory with the first data entry writer, or
+        // otherwise with the second data entry writer.
+        return dataEntryWriter1.createDirectory(dataEntry) ||
+               dataEntryWriter2.createDirectory(dataEntry);
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry) throws IOException
+    {
+        return getOutputStream(dataEntry,  null);
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry,
+                                        Finisher  finisher) throws IOException
+    {
+        // Try to get an output stream from the first data entry writer.
+        OutputStream outputStream =
+            dataEntryWriter1.getOutputStream(dataEntry, finisher);
+
+        // Return it, if it's not null. Otherwise try to get an output stream
+        // from the second data entry writer.
+        return outputStream != null ?
+            outputStream :
+            dataEntryWriter2.getOutputStream(dataEntry, finisher);
+    }
+
+
+    public void close() throws IOException
+    {
+        dataEntryWriter1.close();
+        dataEntryWriter2.close();
+
+        dataEntryWriter1 = null;
+        dataEntryWriter2 = null;
+    }
+}
diff --git a/src/proguard/io/ClassFilter.java b/src/proguard/io/ClassFilter.java
new file mode 100644
index 0000000..bf591ab
--- /dev/null
+++ b/src/proguard/io/ClassFilter.java
@@ -0,0 +1,59 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.ClassConstants;
+import proguard.util.ExtensionMatcher;
+
+import java.io.IOException;
+
+
+/**
+ * This DataEntryReader delegates to one of two other DataEntryReader instances,
+ * depending on the extension of the data entry.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassFilter extends FilteredDataEntryReader
+{
+    /**
+     * Creates a new ClassFilter that delegates reading classes to the
+     * given reader.
+     */
+    public ClassFilter(DataEntryReader classReader)
+    {
+        this(classReader, null);
+    }
+
+
+    /**
+     * Creates a new ClassFilter that delegates to either of the two given
+     * readers.
+     */
+    public ClassFilter(DataEntryReader classReader,
+                       DataEntryReader dataEntryReader)
+    {
+        super(new DataEntryNameFilter(
+              new ExtensionMatcher(ClassConstants.CLASS_FILE_EXTENSION)),
+              classReader,
+              dataEntryReader);
+    }
+}
diff --git a/src/proguard/io/ClassReader.java b/src/proguard/io/ClassReader.java
new file mode 100644
index 0000000..e21968c
--- /dev/null
+++ b/src/proguard/io/ClassReader.java
@@ -0,0 +1,115 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.*;
+import proguard.classfile.io.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+import java.io.*;
+
+/**
+ * This DataEntryReader applies a given ClassVisitor to the class
+ * definitions that it reads.
+ * <p>
+ * Class files are read as ProgramClass objects or LibraryClass objects,
+ * depending on the <code>isLibrary</code> flag.
+ * <p>
+ * In case of libraries, only public classes are considered, if the
+ * <code>skipNonPublicLibraryClasses</code> flag is set.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassReader implements DataEntryReader
+{
+    private final boolean        isLibrary;
+    private final boolean        skipNonPublicLibraryClasses;
+    private final boolean        skipNonPublicLibraryClassMembers;
+    private final WarningPrinter warningPrinter;
+    private final ClassVisitor   classVisitor;
+
+
+    /**
+     * Creates a new DataEntryClassFilter for reading the specified
+     * Clazz objects.
+     */
+    public ClassReader(boolean        isLibrary,
+                       boolean        skipNonPublicLibraryClasses,
+                       boolean        skipNonPublicLibraryClassMembers,
+                       WarningPrinter warningPrinter,
+                       ClassVisitor   classVisitor)
+    {
+        this.isLibrary                        = isLibrary;
+        this.skipNonPublicLibraryClasses      = skipNonPublicLibraryClasses;
+        this.skipNonPublicLibraryClassMembers = skipNonPublicLibraryClassMembers;
+        this.warningPrinter                   = warningPrinter;
+        this.classVisitor                     = classVisitor;
+    }
+
+
+    // Implementations for DataEntryReader.
+
+    public void read(DataEntry dataEntry) throws IOException
+    {
+        try
+        {
+            // Get the input stream.
+            InputStream inputStream = dataEntry.getInputStream();
+
+            // Wrap it into a data input stream.
+            DataInputStream dataInputStream = new DataInputStream(inputStream);
+
+            // Create a Clazz representation.
+            Clazz clazz;
+            if (isLibrary)
+            {
+                clazz = new LibraryClass();
+                clazz.accept(new LibraryClassReader(dataInputStream, skipNonPublicLibraryClasses, skipNonPublicLibraryClassMembers));
+            }
+            else
+            {
+                clazz = new ProgramClass();
+                clazz.accept(new ProgramClassReader(dataInputStream));
+            }
+
+            // Apply the visitor, if we have a real class.
+            String className = clazz.getName();
+            if (className != null)
+            {
+                if (!dataEntry.getName().replace(File.pathSeparatorChar, ClassConstants.INTERNAL_PACKAGE_SEPARATOR).equals(className+ClassConstants.CLASS_FILE_EXTENSION) &&
+                    warningPrinter != null)
+                {
+                    warningPrinter.print(className,
+                                         "Warning: class [" + dataEntry.getName() + "] unexpectedly contains class [" + ClassUtil.externalClassName(className) + "]");
+                }
+
+                clazz.accept(classVisitor);
+            }
+
+            dataEntry.closeInputStream();
+        }
+        catch (Exception ex)
+        {
+            throw new IOException("Can't process class ["+dataEntry.getName()+"] ("+ex.getMessage()+")");
+        }
+    }
+}
diff --git a/src/proguard/io/ClassRewriter.java b/src/proguard/io/ClassRewriter.java
new file mode 100644
index 0000000..bd19e1d
--- /dev/null
+++ b/src/proguard/io/ClassRewriter.java
@@ -0,0 +1,80 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.*;
+import proguard.classfile.io.ProgramClassWriter;
+
+import java.io.*;
+
+
+/**
+ * This DataEntryReader reads class entries and writes their corresponding
+ * versions from the ClassPool to a given DataEntryWriter.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassRewriter implements DataEntryReader
+{
+    private final ClassPool       classPool;
+    private final DataEntryWriter dataEntryWriter;
+
+
+    public ClassRewriter(ClassPool       classPool,
+                         DataEntryWriter dataEntryWriter)
+    {
+        this.classPool       = classPool;
+        this.dataEntryWriter = dataEntryWriter;
+    }
+
+
+    // Implementations for DataEntryReader.
+
+    public void read(DataEntry dataEntry) throws IOException
+    {
+        String inputName = dataEntry.getName();
+        String className = inputName.substring(0, inputName.length() - ClassConstants.CLASS_FILE_EXTENSION.length());
+
+        // Find the modified class corrsponding to the input entry.
+        ProgramClass programClass = (ProgramClass)classPool.getClass(className);
+        if (programClass != null)
+        {
+            // Rename the data entry if necessary.
+            String newClassName = programClass.getName();
+            if (!className.equals(newClassName))
+            {
+                dataEntry = new RenamedDataEntry(dataEntry, newClassName + ClassConstants.CLASS_FILE_EXTENSION);
+            }
+
+            // Get the output entry corresponding to this input entry.
+            OutputStream outputStream = dataEntryWriter.getOutputStream(dataEntry);
+            if (outputStream != null)
+            {
+                // Write the class to the output entry.
+                DataOutputStream classOutputStream = new DataOutputStream(outputStream);
+
+                new ProgramClassWriter(classOutputStream).visitProgramClass(programClass);
+
+                classOutputStream.flush();
+            }
+        }
+    }
+}
diff --git a/src/proguard/io/DataEntry.java b/src/proguard/io/DataEntry.java
new file mode 100644
index 0000000..af0e373
--- /dev/null
+++ b/src/proguard/io/DataEntry.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.*;
+
+/**
+ * This interface describes a data entry, e.g. a ZIP entry, a file, or a
+ * directory.
+ *
+ * @author Eric Lafortune
+ */
+public interface DataEntry
+{
+    /**
+     * Returns the name of this data entry.
+     */
+    public String getName();
+
+    /**
+     * Returns whether the data entry represents a directory.
+     */
+    public boolean isDirectory();
+
+
+    /**
+     * Returns an input stream for reading the content of this data entry.
+     * The data entry may not represent a directory.
+     */
+    public InputStream getInputStream() throws IOException;
+
+
+    /**
+     * Closes the previously retrieved InputStream.
+     */
+    public void closeInputStream() throws IOException;
+
+
+    /**
+     * Returns the parent of this data entry, or <code>null</null> if it doesn't
+     * have one.
+     */
+    public DataEntry getParent();
+}
diff --git a/src/proguard/io/DataEntryCopier.java b/src/proguard/io/DataEntryCopier.java
new file mode 100644
index 0000000..faaa555
--- /dev/null
+++ b/src/proguard/io/DataEntryCopier.java
@@ -0,0 +1,247 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.util.ExtensionMatcher;
+
+import java.io.*;
+
+
+/**
+ * This DataEntryReader writes the ZIP entries and files that it reads to a
+ * given DataEntryWriter.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryCopier implements DataEntryReader
+{
+    private static final int BUFFER_SIZE = 1024;
+
+    private final DataEntryWriter dataEntryWriter;
+    private final byte[]          buffer = new byte[BUFFER_SIZE];
+
+
+
+    public DataEntryCopier(DataEntryWriter dataEntryWriter)
+    {
+        this.dataEntryWriter = dataEntryWriter;
+    }
+
+
+    // Implementations for DataEntryReader.
+
+    public void read(DataEntry dataEntry) throws IOException
+    {
+        try
+        {
+            if (dataEntry.isDirectory())
+            {
+                dataEntryWriter.createDirectory(dataEntry);
+            }
+            else
+            {
+                // Get the output entry corresponding to this input entry.
+                OutputStream outputStream = dataEntryWriter.getOutputStream(dataEntry);
+                if (outputStream != null)
+                {
+                    InputStream inputStream = dataEntry.getInputStream();
+
+                    // Copy the data from the input entry to the output entry.
+                    copyData(inputStream, outputStream);
+
+                    // Close the data entries.
+                    dataEntry.closeInputStream();
+                }
+            }
+        }
+        catch (IOException ex)
+        {
+            System.err.println("Warning: can't write resource [" + dataEntry.getName() + "] (" + ex.getMessage() + ")");
+        }
+    }
+
+
+    /**
+     * Copies all data that it can read from the given input stream to the
+     * given output stream.
+     */
+    protected void copyData(InputStream  inputStream,
+                            OutputStream outputStream)
+    throws IOException
+    {
+        while (true)
+        {
+            int count = inputStream.read(buffer);
+            if (count < 0)
+            {
+                break;
+            }
+            outputStream.write(buffer, 0, count);
+        }
+
+        outputStream.flush();
+    }
+
+
+    /**
+     * A main method for testing file/jar/war/directory copying.
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            String input  = args[0];
+            String output = args[1];
+
+            boolean outputIsJar = output.endsWith(".jar");
+            boolean outputIsWar = output.endsWith(".war");
+            boolean outputIsEar = output.endsWith(".ear");
+            boolean outputIsZip = output.endsWith(".zip");
+
+            DataEntryWriter writer = new DirectoryWriter(new File(output),
+                                                         outputIsJar ||
+                                                         outputIsWar ||
+                                                         outputIsEar ||
+                                                         outputIsZip);
+
+        if (!outputIsJar)
+        {
+            // Zip up any zips, if necessary.
+            DataEntryWriter zipWriter = new JarWriter(writer);
+            if (outputIsZip)
+            {
+                // Always zip.
+                writer = zipWriter;
+            }
+            else
+            {
+                // Only zip up zips.
+                writer = new FilteredDataEntryWriter(new DataEntryParentFilter(
+                                                     new DataEntryNameFilter(
+                                                     new ExtensionMatcher(".zip"))),
+                                                     zipWriter,
+                                                     writer);
+            }
+
+            // Zip up any wars, if necessary.
+            DataEntryWriter warWriter = new JarWriter(writer);
+            if (outputIsWar)
+            {
+                // Always zip.
+                writer = warWriter;
+            }
+            else
+            {
+                // Only zip up wars.
+                writer = new FilteredDataEntryWriter(new DataEntryParentFilter(
+                                                     new DataEntryNameFilter(
+                                                     new ExtensionMatcher(".war"))),
+                                                     warWriter,
+                                                     writer);
+            }
+        }
+
+        // Zip up any jars, if necessary.
+        DataEntryWriter jarWriter = new JarWriter(writer);
+        if (outputIsJar)
+        {
+            // Always zip.
+            writer = jarWriter;
+        }
+        else
+        {
+            // Only zip up jars.
+            writer = new FilteredDataEntryWriter(new DataEntryParentFilter(
+                                                 new DataEntryNameFilter(
+                                                 new ExtensionMatcher(".jar"))),
+                                                 jarWriter,
+                                                 writer);
+        }
+
+
+            // Create the copying DataEntryReader.
+            DataEntryReader reader = new DataEntryCopier(writer);
+
+
+            boolean inputIsJar = input.endsWith(".jar");
+            boolean inputIsWar = input.endsWith(".war");
+            boolean inputIsZip = input.endsWith(".zip");
+
+            // Unzip any jars, if necessary.
+            DataEntryReader jarReader = new JarReader(reader);
+            if (inputIsJar)
+            {
+                // Always unzip.
+                reader = jarReader;
+            }
+            else
+            {
+                // Only unzip jar entries.
+                reader = new FilteredDataEntryReader(new DataEntryNameFilter(
+                                                     new ExtensionMatcher(".jar")),
+                                                     jarReader,
+                                                     reader);
+
+                // Unzip any wars, if necessary.
+                DataEntryReader warReader = new JarReader(reader);
+                if (inputIsWar)
+                {
+                    // Always unzip.
+                    reader = warReader;
+                }
+                else
+                {
+                    // Only unzip war entries.
+                    reader = new FilteredDataEntryReader(new DataEntryNameFilter(
+                                                         new ExtensionMatcher(".war")),
+                                                         warReader,
+                                                         reader);
+                }
+
+                // Unzip any zips, if necessary.
+                DataEntryReader zipReader = new JarReader(reader);
+                if (inputIsZip)
+                {
+                    // Always unzip.
+                    reader = zipReader;
+                }
+                else
+                {
+                    // Only unzip zip entries.
+                    reader = new FilteredDataEntryReader(new DataEntryNameFilter(
+                                                         new ExtensionMatcher(".zip")),
+                                                         zipReader,
+                                                         reader);
+                }
+            }
+
+            DirectoryPump directoryReader = new DirectoryPump(new File(input));
+
+            directoryReader.pumpDataEntries(reader);
+
+            writer.close();
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/io/DataEntryDirectoryFilter.java b/src/proguard/io/DataEntryDirectoryFilter.java
new file mode 100644
index 0000000..bb36f3e
--- /dev/null
+++ b/src/proguard/io/DataEntryDirectoryFilter.java
@@ -0,0 +1,40 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.util.StringMatcher;
+
+/**
+ * This DataEntryFilter filters data entries based on whether they represent
+ * directories.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryDirectoryFilter
+implements   DataEntryFilter
+{
+    // Implementations for DataEntryFilter.
+
+    public boolean accepts(DataEntry dataEntry)
+    {
+        return dataEntry != null && dataEntry.isDirectory();
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/io/DataEntryFilter.java b/src/proguard/io/DataEntryFilter.java
new file mode 100644
index 0000000..ddcd0be
--- /dev/null
+++ b/src/proguard/io/DataEntryFilter.java
@@ -0,0 +1,38 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+
+/**
+ * This interface provides a method to filter data entries.
+ *
+ * @author Eric Lafortune
+ */
+public interface DataEntryFilter
+{
+    /**
+     * Checks whether the filter accepts the given data entry.
+     * @param dataEntry the data entry to filter.
+     * @return a boolean indicating whether the filter accepts the given data
+     *         entry.
+     */
+    public boolean accepts(DataEntry dataEntry);
+}
diff --git a/src/proguard/io/DataEntryNameFilter.java b/src/proguard/io/DataEntryNameFilter.java
new file mode 100644
index 0000000..d6afd2e
--- /dev/null
+++ b/src/proguard/io/DataEntryNameFilter.java
@@ -0,0 +1,54 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.util.StringMatcher;
+
+/**
+ * This DataEntryFilter filters data entries based on whether their names match
+ * a given StringMatcher.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryNameFilter
+implements   DataEntryFilter
+{
+    private final StringMatcher stringMatcher;
+
+
+    /**
+     * Creates a new DataEntryNameFilter.
+     * @param stringMatcher the string matcher that will be applied to the names
+     *                      of the filtered data entries.
+     */
+    public DataEntryNameFilter(StringMatcher stringMatcher)
+    {
+        this.stringMatcher = stringMatcher;
+    }
+
+
+    // Implementations for DataEntryFilter.
+
+    public boolean accepts(DataEntry dataEntry)
+    {
+        return dataEntry != null && stringMatcher.matches(dataEntry.getName());
+    }
+}
diff --git a/src/proguard/io/DataEntryObfuscator.java b/src/proguard/io/DataEntryObfuscator.java
new file mode 100644
index 0000000..aa63af1
--- /dev/null
+++ b/src/proguard/io/DataEntryObfuscator.java
@@ -0,0 +1,131 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.*;
+import proguard.classfile.util.ClassUtil;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * This DataEntryReader delegates to another DataEntryReader, renaming the
+ * data entries based on the renamed classes in the given ClassPool.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryObfuscator implements DataEntryReader
+{
+    private final ClassPool       classPool;
+    private final Map             packagePrefixMap;
+    private final DataEntryReader dataEntryReader;
+
+
+    /**
+     * Creates a new DataEntryObfuscator.
+     * @param classPool        the class pool that maps from old names to new
+     *                         names.
+     * @param packagePrefixMap the map from old package prefixes to new package
+     *                         prefixes.
+     * @param dataEntryReader  the DataEntryReader to which calls will be
+     *                         delegated.
+     */
+    public DataEntryObfuscator(ClassPool       classPool,
+                               Map             packagePrefixMap,
+                               DataEntryReader dataEntryReader)
+    {
+        this.classPool        = classPool;
+        this.packagePrefixMap = packagePrefixMap;
+        this.dataEntryReader  = dataEntryReader;
+    }
+
+
+    // Implementations for DataEntryReader.
+
+    public void read(DataEntry dataEntry) throws IOException
+    {
+        // Delegate to the actual data entry reader.
+        dataEntryReader.read(renamedDataEntry(dataEntry));
+    }
+
+
+    /**
+     * Create a renamed data entry, if possible.
+     */
+    private DataEntry renamedDataEntry(DataEntry dataEntry)
+    {
+        String dataEntryName = dataEntry.getName();
+
+        // Try to find a corresponding class name by removing increasingly
+        // long suffixes,
+        for (int suffixIndex = dataEntryName.length() - 1;
+             suffixIndex > 0;
+             suffixIndex--)
+        {
+            char c = dataEntryName.charAt(suffixIndex);
+            if (!Character.isLetterOrDigit(c))
+            {
+                // Chop off the suffix.
+                String className = dataEntryName.substring(0, suffixIndex);
+
+                // Did we get to the package separator?
+                if (c == ClassConstants.INTERNAL_PACKAGE_SEPARATOR)
+                {
+                    break;
+                }
+
+                // Is there a class corresponding to the data entry?
+                Clazz clazz = classPool.getClass(className);
+                if (clazz != null)
+                {
+                    // Did the class get a new name?
+                    String newClassName = clazz.getName();
+                    if (!className.equals(newClassName))
+                    {
+                        // Return a renamed data entry.
+                        String newDataEntryName =
+                            newClassName + dataEntryName.substring(suffixIndex);
+
+                        return new RenamedDataEntry(dataEntry, newDataEntryName);
+                    }
+
+                    // Otherwise stop looking.
+                    break;
+                }
+            }
+        }
+
+        // Did the package get a new name?
+        String packagePrefix    = ClassUtil.internalPackagePrefix(dataEntryName);
+        String newPackagePrefix = (String)packagePrefixMap.get(packagePrefix);
+        if (newPackagePrefix != null &&
+            !packagePrefix.equals(newPackagePrefix))
+        {
+            // Return a renamed data entry.
+            String newDataEntryName =
+                newPackagePrefix + dataEntryName.substring(packagePrefix.length());
+
+            return new RenamedDataEntry(dataEntry, newDataEntryName);
+        }
+
+        return dataEntry;
+    }
+}
diff --git a/src/proguard/io/DataEntryParentFilter.java b/src/proguard/io/DataEntryParentFilter.java
new file mode 100644
index 0000000..fbeac4f
--- /dev/null
+++ b/src/proguard/io/DataEntryParentFilter.java
@@ -0,0 +1,51 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+/**
+ * This DataEntryFilter delegates filtering to a DataEntryFilter for its parent.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryParentFilter
+implements   DataEntryFilter
+{
+    private final DataEntryFilter dataEntryFilter;
+
+
+    /**
+     * Creates a new ParentFilter.
+     * @param dataEntryFilter the filter that will be applied to the data
+     *                        entry's parent.
+     */
+    public DataEntryParentFilter(DataEntryFilter dataEntryFilter)
+    {
+        this.dataEntryFilter = dataEntryFilter;
+    }
+
+
+    // Implementations for DataEntryFilter.
+
+    public boolean accepts(DataEntry dataEntry)
+    {
+        return dataEntry != null && dataEntryFilter.accepts(dataEntry.getParent());
+    }
+}
diff --git a/src/proguard/io/DataEntryPump.java b/src/proguard/io/DataEntryPump.java
new file mode 100644
index 0000000..bfe22a3
--- /dev/null
+++ b/src/proguard/io/DataEntryPump.java
@@ -0,0 +1,43 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.IOException;
+
+
+/**
+ * This interface provides a method to pump data entries. The implementation
+ * determines the source and the type of the data entries. Typical examples
+ * are zip entries coming from a zip file of file entries coming from a
+ * directory structure. The reader can for instance collect the classes,
+ * or copy the resource files that are presented.
+ *
+ * @author Eric Lafortune
+ */
+public interface DataEntryPump
+{
+    /**
+     * Applies the given DataEntryReader to all data entries that the
+     * implementation can provide.
+     */
+    public void pumpDataEntries(DataEntryReader dataEntryReader)
+    throws IOException;
+}
diff --git a/src/proguard/io/DataEntryReader.java b/src/proguard/io/DataEntryReader.java
new file mode 100644
index 0000000..e77f7bf
--- /dev/null
+++ b/src/proguard/io/DataEntryReader.java
@@ -0,0 +1,38 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.IOException;
+
+
+/**
+ * This interface provides methods for reading data entries. The implementation
+ * determines what to do with the read data, if anything.
+ *
+ * @author Eric Lafortune
+ */
+public interface DataEntryReader
+{
+    /**
+     * Reads the given data entry.
+     */
+    public void read(DataEntry dataEntry) throws IOException;
+}
diff --git a/src/proguard/io/DataEntryRenamer.java b/src/proguard/io/DataEntryRenamer.java
new file mode 100644
index 0000000..45c61ee
--- /dev/null
+++ b/src/proguard/io/DataEntryRenamer.java
@@ -0,0 +1,104 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.ClassConstants;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * This DataEntryReader delegates to another DataEntryReader, renaming the
+ * data entries based on the given map. Entries whose name does not appear
+ * in the map may be passed to an alternative DataEntryReader.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryRenamer implements DataEntryReader
+{
+    private final Map             nameMap;
+    private final DataEntryReader renamedDataEntryReader;
+    private final DataEntryReader missingDataEntryReader;
+
+
+    /**
+     * Creates a new DataEntryRenamer.
+     * @param nameMap                the map from old names to new names.
+     * @param renamedDataEntryReader the DataEntryReader to which renamed data
+     *                               entries will be passed.
+     */
+    public DataEntryRenamer(Map             nameMap,
+                            DataEntryReader renamedDataEntryReader)
+    {
+        this(nameMap, renamedDataEntryReader, null);
+    }
+
+
+    /**
+     * Creates a new DataEntryRenamer.
+     * @param nameMap                the map from old names to new names.
+     * @param renamedDataEntryReader the DataEntryReader to which renamed data
+     *                               entries will be passed.
+     * @param missingDataEntryReader the optional DataEntryReader to which data
+     *                               entries that can't be renamed will be
+     *                               passed.
+     */
+    public DataEntryRenamer(Map             nameMap,
+                            DataEntryReader renamedDataEntryReader,
+                            DataEntryReader missingDataEntryReader)
+    {
+        this.nameMap                = nameMap;
+        this.renamedDataEntryReader = renamedDataEntryReader;
+        this.missingDataEntryReader = missingDataEntryReader;
+    }
+
+
+    // Implementations for DataEntryReader.
+
+    public void read(DataEntry dataEntry) throws IOException
+    {
+        String name = dataEntry.getName();
+
+        // Add a directory separator if necessary.
+        if (dataEntry.isDirectory() &&
+            name.length() > 0)
+        {
+            name += ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
+        }
+
+        String newName = (String)nameMap.get(name);
+        if (newName != null)
+        {
+            // Add remove the directory separator if necessary.
+            if (dataEntry.isDirectory() &&
+                newName.length() > 0)
+            {
+                newName = newName.substring(0, newName.length() -  1);
+            }
+
+            renamedDataEntryReader.read(new RenamedDataEntry(dataEntry, newName));
+        }
+        else if (missingDataEntryReader != null)
+        {
+            missingDataEntryReader.read(dataEntry);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/io/DataEntryRewriter.java b/src/proguard/io/DataEntryRewriter.java
new file mode 100644
index 0000000..eefced4
--- /dev/null
+++ b/src/proguard/io/DataEntryRewriter.java
@@ -0,0 +1,148 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.*;
+
+import java.io.*;
+
+/**
+ * This DataEntryReader writes the resource data entries that it reads to a
+ * given DataEntryWriter, updating their contents based on the renamed classes
+ * in the given ClassPool.
+ *
+ * @author Eric Lafortune
+ */
+public class DataEntryRewriter extends DataEntryCopier
+{
+    private final ClassPool classPool;
+
+
+    /**
+     * Creates a new DataEntryRewriter.
+     */
+    public DataEntryRewriter(ClassPool       classPool,
+                             DataEntryWriter dataEntryWriter)
+    {
+        super(dataEntryWriter);
+
+        this.classPool = classPool;
+    }
+
+
+    // Implementations for DataEntryCopier.
+
+    protected void copyData(InputStream  inputStream,
+                            OutputStream outputStream)
+    throws IOException
+    {
+        Reader reader = new BufferedReader(new InputStreamReader(inputStream));
+        Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream));
+
+        copyData(reader, writer);
+
+        writer.flush();
+        outputStream.flush();
+    }
+
+
+    /**
+     * Copies all data that it can read from the given reader to the given
+     * writer.
+     */
+    protected void copyData(Reader reader,
+                            Writer writer)
+    throws IOException
+    {
+        StringBuffer word = new StringBuffer();
+
+        while (true)
+        {
+            int i = reader.read();
+            if (i < 0)
+            {
+                break;
+            }
+
+            // Is the character part of a word?
+            char c = (char)i;
+            if (Character.isJavaIdentifierPart(c) ||
+                c == '.' ||
+                c == '-')
+            {
+                // Collect the characters in this word.
+                word.append(c);
+            }
+            else
+            {
+                // Write out the updated word, if any.
+                writeUpdatedWord(writer, word.toString());
+                word.setLength(0);
+
+                // Write out the character that terminated it.
+                writer.write(c);
+            }
+        }
+
+        // Write out the final word.
+        writeUpdatedWord(writer, word.toString());
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Writes the given word to the given writer, after having adapted it,
+     * based on the renamed class names.
+     */
+    private void writeUpdatedWord(Writer writer, String word)
+    throws IOException
+    {
+        if (word.length() > 0)
+        {
+            String newWord = word;
+
+            boolean containsDots = word.indexOf('.') >= 0;
+
+            // Replace dots by forward slashes.
+            String className = containsDots ?
+                word.replace('.', ClassConstants.INTERNAL_PACKAGE_SEPARATOR) :
+                word;
+
+            // Find the class corrsponding to the word.
+            Clazz clazz = classPool.getClass(className);
+            if (clazz != null)
+            {
+                // Update the word if necessary.
+                String newClassName = clazz.getName();
+                if (!className.equals(newClassName))
+                {
+                    // Replace forward slashes by dots.
+                    newWord = containsDots ?
+                        newClassName.replace(ClassConstants.INTERNAL_PACKAGE_SEPARATOR, '.') :
+                        newClassName;
+                }
+            }
+
+            writer.write(newWord);
+        }
+    }
+}
diff --git a/src/proguard/io/DataEntryWriter.java b/src/proguard/io/DataEntryWriter.java
new file mode 100644
index 0000000..9ecf79b
--- /dev/null
+++ b/src/proguard/io/DataEntryWriter.java
@@ -0,0 +1,73 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.*;
+
+
+/**
+ * This interface provides methods for writing data entries, such as ZIP entries
+ * or files. The implementation determines to which type of data entry the
+ * data will be written.
+ *
+ * @author Eric Lafortune
+ */
+public interface DataEntryWriter
+{
+    /**
+     * Creates a directory.
+     * @param dataEntry the data entry for which the directory is to be created.
+     * @return whether the directory has been created.
+     */
+    public boolean createDirectory(DataEntry dataEntry) throws IOException;
+
+
+    /**
+     * Returns an output stream for writing data. The caller must not close
+     * the output stream; closing the output stream is the responsibility of
+     * the implementation of this interface.
+     * @param dataEntry the data entry for which the output stream is to be created.
+     * @return the output stream. The stream may be <code>null</code> to indicate
+     *         that the data entry should not be written.
+     */
+    public OutputStream getOutputStream(DataEntry dataEntry) throws IOException;
+
+
+    /**
+     * Returns an output stream for writing data. The caller must not close
+     * the output stream; closing the output stream is the responsibility of
+     * the implementation of this interface.
+     * @param dataEntry the data entry for which the output stream is to be created.
+     * @param finisher  the optional finisher that will be called before this
+     *                  class closes the output stream (at some later point in
+     *                  time) that will be returned (now).
+     * @return the output stream. The stream may be <code>null</code> to indicate
+     *         that the data entry should not be written.
+     */
+    public OutputStream getOutputStream(DataEntry dataEntry,
+                                        Finisher  finisher) throws IOException;
+
+
+    /**
+     * Finishes writing all data entries.
+     */
+    public void close() throws IOException;
+}
diff --git a/src/proguard/io/DirectoryFilter.java b/src/proguard/io/DirectoryFilter.java
new file mode 100644
index 0000000..a45d1aa
--- /dev/null
+++ b/src/proguard/io/DirectoryFilter.java
@@ -0,0 +1,58 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.ClassConstants;
+import proguard.util.ExtensionMatcher;
+
+import java.io.IOException;
+
+
+/**
+ * This DataEntryReader delegates to one of two other DataEntryReader instances,
+ * depending on whether the data entry represents a directory or not.
+ *
+ * @author Eric Lafortune
+ */
+public class DirectoryFilter extends FilteredDataEntryReader
+{
+    /**
+     * Creates a new ClassFilter that delegates reading directories to the
+     * given reader.
+     */
+    public DirectoryFilter(DataEntryReader directoryReader)
+    {
+        this (directoryReader, null);
+    }
+
+
+    /**
+     * Creates a new ClassFilter that delegates to either of the two given
+     * readers.
+     */
+    public DirectoryFilter(DataEntryReader directoryReader,
+                           DataEntryReader otherReader)
+    {
+        super(new DataEntryDirectoryFilter(),
+              directoryReader,
+              otherReader);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/io/DirectoryPump.java b/src/proguard/io/DirectoryPump.java
new file mode 100644
index 0000000..cab2ff3
--- /dev/null
+++ b/src/proguard/io/DirectoryPump.java
@@ -0,0 +1,78 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.*;
+
+
+/**
+ * This class can read a given file or directory, recursively, applying a given
+ * DataEntryReader to all files it comes across.
+ *
+ * @author Eric Lafortune
+ */
+public class DirectoryPump implements DataEntryPump
+{
+    private final File directory;
+
+
+    public DirectoryPump(File directory)
+    {
+        this.directory = directory;
+    }
+
+
+    // Implementations for DataEntryPump.
+
+    public void pumpDataEntries(DataEntryReader dataEntryReader)
+    throws IOException
+    {
+        if (!directory.exists())
+        {
+            throw new IOException("No such file or directory");
+        }
+
+        readFiles(directory, dataEntryReader);
+    }
+
+
+    /**
+     * Reads the given subdirectory recursively, applying the given DataEntryReader
+     * to all files that are encountered.
+     */
+    private void readFiles(File file, DataEntryReader dataEntryReader)
+    throws IOException
+    {
+        // Pass the file data entry to the reader.
+        dataEntryReader.read(new FileDataEntry(directory, file));
+
+        if (file.isDirectory())
+        {
+            // Recurse into the subdirectory.
+            File[] files = file.listFiles();
+
+            for (int index = 0; index < files.length; index++)
+            {
+                readFiles(files[index], dataEntryReader);
+            }
+        }
+    }
+}
diff --git a/src/proguard/io/DirectoryWriter.java b/src/proguard/io/DirectoryWriter.java
new file mode 100644
index 0000000..c6605df
--- /dev/null
+++ b/src/proguard/io/DirectoryWriter.java
@@ -0,0 +1,165 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.ClassConstants;
+
+import java.io.*;
+
+
+/**
+ * This DataEntryWriter writes data entries to individual files in a given
+ * directory.
+ *
+ * @author Eric Lafortune
+ */
+public class DirectoryWriter implements DataEntryWriter
+{
+    private final File    baseFile;
+    private final boolean isFile;
+
+    private File         currentFile;
+    private OutputStream currentOutputStream;
+    private Finisher     currentFinisher;
+
+
+    /**
+     * Creates a new DirectoryWriter.
+     * @param baseFile the base directory to which all files will be written.
+     */
+    public DirectoryWriter(File    baseFile,
+                           boolean isFile)
+    {
+        this.baseFile = baseFile;
+        this.isFile   = isFile;
+    }
+
+
+    // Implementations for DataEntryWriter.
+
+    public boolean createDirectory(DataEntry dataEntry) throws IOException
+    {
+        // Should we close the current file?
+        if (!isFile &&
+            currentFile != null)
+        {
+            closeEntry();
+        }
+
+        File directory = getFile(dataEntry);
+        if (!directory.exists() &&
+            !directory.mkdirs())
+        {
+            throw new IOException("Can't create directory [" + directory.getPath() + "]");
+        }
+
+        return true;
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry) throws IOException
+    {
+        return getOutputStream(dataEntry,  null);
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry,
+                                        Finisher  finisher) throws IOException
+    {
+        File file = getFile(dataEntry);
+
+        // Should we close the current file?
+        if (!isFile             &&
+            currentFile != null &&
+            !currentFile.equals(file))
+        {
+            closeEntry();
+        }
+
+        // Do we need a new stream?
+        if (currentOutputStream == null)
+        {
+            // Make sure the parent directories exist.
+            File parentDirectory = file.getParentFile();
+            if (parentDirectory != null   &&
+                !parentDirectory.exists() &&
+                !parentDirectory.mkdirs())
+            {
+                throw new IOException("Can't create directory [" + parentDirectory.getPath() + "]");
+            }
+
+            // Open a new output stream for writing to the file.
+            currentOutputStream =
+                new BufferedOutputStream(
+                new FileOutputStream(file));
+
+            currentFinisher = finisher;
+            currentFile     = file;
+        }
+
+        return currentOutputStream;
+    }
+
+
+    public void close() throws IOException
+    {
+        // Close the file stream, if any.
+        closeEntry();
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the file for the given data entry.
+     */
+    private File getFile(DataEntry dataEntry)
+    {
+        // Use the specified file, or construct a new file.
+        return isFile ?
+            baseFile :
+            new File(baseFile,
+                     dataEntry.getName().replace(ClassConstants.INTERNAL_PACKAGE_SEPARATOR,
+                                                 File.separatorChar));
+    }
+
+
+    /**
+     * Closes the previous file, if any.
+     */
+    private void closeEntry() throws IOException
+    {
+        // Close the file stream, if any.
+        if (currentOutputStream != null)
+        {
+            // Let any finisher finish up first.
+            if (currentFinisher != null)
+            {
+                currentFinisher.finish();
+                currentFinisher = null;
+            }
+
+            currentOutputStream.close();
+            currentOutputStream = null;
+            currentFile         = null;
+        }
+    }
+}
diff --git a/src/proguard/io/FileDataEntry.java b/src/proguard/io/FileDataEntry.java
new file mode 100644
index 0000000..d0449ee
--- /dev/null
+++ b/src/proguard/io/FileDataEntry.java
@@ -0,0 +1,96 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.ClassConstants;
+
+import java.io.*;
+
+/**
+ * This <code>DataEntry</code> represents a file.
+ *
+ * @author Eric Lafortune
+ */
+public class FileDataEntry implements DataEntry
+{
+    private final File        directory;
+    private final File        file;
+    private InputStream inputStream;
+
+
+    public FileDataEntry(File directory,
+                         File file)
+    {
+        this.directory = directory;
+        this.file      = file;
+    }
+
+
+    // Implementations for DataEntry.
+
+    public String getName()
+    {
+        // Chop the directory name from the file name and get the right separators.
+        return file.equals(directory) ?
+            file.getName() :
+            file.getPath()
+            .substring(directory.getPath().length() + File.separator.length())
+            .replace(File.separatorChar, ClassConstants.INTERNAL_PACKAGE_SEPARATOR);
+    }
+
+
+    public boolean isDirectory()
+    {
+        return file.isDirectory();
+    }
+
+
+    public InputStream getInputStream() throws IOException
+    {
+        if (inputStream == null)
+        {
+            inputStream = new BufferedInputStream(new FileInputStream(file));
+        }
+
+        return inputStream;
+    }
+
+
+    public void closeInputStream() throws IOException
+    {
+        inputStream.close();
+        inputStream = null;
+    }
+
+
+    public DataEntry getParent()
+    {
+        return null;
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return getName();
+    }
+}
diff --git a/src/proguard/io/FilteredDataEntryReader.java b/src/proguard/io/FilteredDataEntryReader.java
new file mode 100644
index 0000000..11da0d4
--- /dev/null
+++ b/src/proguard/io/FilteredDataEntryReader.java
@@ -0,0 +1,90 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.IOException;
+
+
+/**
+ * This DataEntryReader delegates to one of two other DataEntryReader instances,
+ * depending on whether the data entry passes through a given data entry filter
+ * or not.
+ *
+ * @author Eric Lafortune
+ */
+public class FilteredDataEntryReader implements DataEntryReader
+{
+    private final DataEntryFilter dataEntryFilter;
+    private final DataEntryReader acceptedDataEntryReader;
+    private final DataEntryReader rejectedDataEntryReader;
+
+
+    /**
+     * Creates a new FilteredDataEntryReader with only a reader for accepted
+     * data entries.
+     * @param dataEntryFilter         the data entry filter.
+     * @param acceptedDataEntryReader the DataEntryReader to which the reading
+     *                                will be delegated if the filter accepts
+     *                                the data entry. May be <code>null</code>.
+     */
+    public FilteredDataEntryReader(DataEntryFilter dataEntryFilter,
+                                   DataEntryReader acceptedDataEntryReader)
+    {
+        this(dataEntryFilter, acceptedDataEntryReader, null);
+    }
+
+
+    /**
+     * Creates a new FilteredDataEntryReader.
+     * @param dataEntryFilter         the data entry filter.
+     * @param acceptedDataEntryReader the DataEntryReader to which the reading
+     *                                will be delegated if the filter accepts
+     *                                the data entry. May be <code>null</code>.
+     * @param rejectedDataEntryReader the DataEntryReader to which the reading
+     *                                will be delegated if the filter does not
+     *                                accept the data entry. May be
+     *                                <code>null</code>.
+     */
+    public FilteredDataEntryReader(DataEntryFilter dataEntryFilter,
+                                   DataEntryReader acceptedDataEntryReader,
+                                   DataEntryReader rejectedDataEntryReader)
+    {
+        this.dataEntryFilter         = dataEntryFilter;
+        this.acceptedDataEntryReader = acceptedDataEntryReader;
+        this.rejectedDataEntryReader = rejectedDataEntryReader;
+    }
+
+
+    // Implementations for DataEntryReader.
+
+    public void read(DataEntry dataEntry)
+    throws IOException
+    {
+        DataEntryReader dataEntryReader = dataEntryFilter.accepts(dataEntry) ?
+            acceptedDataEntryReader :
+            rejectedDataEntryReader;
+
+        if (dataEntryReader != null)
+        {
+            dataEntryReader.read(dataEntry);
+        }
+    }
+}
diff --git a/src/proguard/io/FilteredDataEntryWriter.java b/src/proguard/io/FilteredDataEntryWriter.java
new file mode 100644
index 0000000..40a8c64
--- /dev/null
+++ b/src/proguard/io/FilteredDataEntryWriter.java
@@ -0,0 +1,125 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.*;
+
+/**
+ * This DataEntryWriter delegates to one of two other DataEntryWriter instances,
+ * depending on whether the data entry passes through a given data entry filter
+ * or not.
+ *
+ * @author Eric Lafortune
+ */
+public class FilteredDataEntryWriter implements DataEntryWriter
+{
+    private final DataEntryFilter dataEntryFilter;
+    private DataEntryWriter acceptedDataEntryWriter;
+    private DataEntryWriter rejectedDataEntryWriter;
+
+
+    /**
+     * Creates a new FilteredDataEntryWriter with only a writer for accepted
+     * data entries.
+     * @param dataEntryFilter         the data entry filter.
+     * @param acceptedDataEntryWriter the DataEntryWriter to which the writing
+     *                                will be delegated if the filter accepts
+     *                                the data entry. May be <code>null</code>.
+     */
+    public FilteredDataEntryWriter(DataEntryFilter dataEntryFilter,
+                                   DataEntryWriter acceptedDataEntryWriter)
+    {
+        this(dataEntryFilter, acceptedDataEntryWriter, null);
+    }
+
+
+    /**
+     * Creates a new FilteredDataEntryWriter.
+     * @param dataEntryFilter         the data entry filter.
+     * @param acceptedDataEntryWriter the DataEntryWriter to which the writing
+     *                                will be delegated if the filter accepts
+     *                                the data entry. May be <code>null</code>.
+     * @param rejectedDataEntryWriter the DataEntryWriter to which the writing
+     *                                will be delegated if the filter does not
+     *                                accept the data entry. May be
+     *                                <code>null</code>.
+     */
+    public FilteredDataEntryWriter(DataEntryFilter dataEntryFilter,
+                                   DataEntryWriter acceptedDataEntryWriter,
+                                   DataEntryWriter rejectedDataEntryWriter)
+    {
+        this.dataEntryFilter         = dataEntryFilter;
+        this.acceptedDataEntryWriter = acceptedDataEntryWriter;
+        this.rejectedDataEntryWriter = rejectedDataEntryWriter;
+    }
+
+
+    // Implementations for DataEntryWriter.
+
+    public boolean createDirectory(DataEntry dataEntry) throws IOException
+    {
+        // Get the right data entry writer.
+        DataEntryWriter dataEntryWriter = dataEntryFilter.accepts(dataEntry) ?
+            acceptedDataEntryWriter :
+            rejectedDataEntryWriter;
+
+        // Delegate to it, if it's not null.
+        return dataEntryWriter != null &&
+               dataEntryWriter.createDirectory(dataEntry);
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry) throws IOException
+    {
+        return getOutputStream(dataEntry,  null);
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry,
+                                        Finisher  finisher) throws IOException
+    {
+        // Get the right data entry writer.
+        DataEntryWriter dataEntryWriter = dataEntryFilter.accepts(dataEntry) ?
+            acceptedDataEntryWriter :
+            rejectedDataEntryWriter;
+
+        // Delegate to it, if it's not null.
+        return dataEntryWriter != null ?
+            dataEntryWriter.getOutputStream(dataEntry, finisher) :
+            null;
+    }
+
+
+    public void close() throws IOException
+    {
+        if (acceptedDataEntryWriter != null)
+        {
+            acceptedDataEntryWriter.close();
+            acceptedDataEntryWriter = null;
+        }
+
+        if (rejectedDataEntryWriter != null)
+        {
+            rejectedDataEntryWriter.close();
+            rejectedDataEntryWriter = null;
+        }
+    }
+}
diff --git a/src/proguard/io/Finisher.java b/src/proguard/io/Finisher.java
new file mode 100644
index 0000000..8c4cc1e
--- /dev/null
+++ b/src/proguard/io/Finisher.java
@@ -0,0 +1,37 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.IOException;
+
+/**
+ * This interface specifies a listener that is called to finish an output stream
+ * before it is closed.
+ *
+ * @author Eric Lafortune
+ */
+public interface Finisher
+{
+    /**
+     * Finishes an output stream right before it is closed.
+     */
+    public void finish() throws IOException;
+}
diff --git a/src/proguard/io/JarReader.java b/src/proguard/io/JarReader.java
new file mode 100644
index 0000000..f96b4aa
--- /dev/null
+++ b/src/proguard/io/JarReader.java
@@ -0,0 +1,75 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.IOException;
+import java.util.zip.*;
+
+/**
+ * This DataEntryReader lets a given DataEntryReader read all data entries of
+ * the read jar/war/zip data entries.
+ *
+ * @author Eric Lafortune
+ */
+public class JarReader implements DataEntryReader
+{
+    private final DataEntryReader dataEntryReader;
+
+
+    /**
+     * Creates a new JarReader.
+     */
+    public JarReader(DataEntryReader dataEntryReader)
+    {
+        this.dataEntryReader = dataEntryReader;
+    }
+
+
+    // Implementation for DataEntryReader.
+
+    public void read(DataEntry dataEntry) throws IOException
+    {
+        ZipInputStream zipInputStream = new ZipInputStream(dataEntry.getInputStream());
+
+        try
+        {
+            // Get all entries from the input jar.
+            while (true)
+            {
+                // Can we get another entry?
+                ZipEntry zipEntry = zipInputStream.getNextEntry();
+                if (zipEntry == null)
+                {
+                    break;
+                }
+
+                // Delegate the actual reading to the data entry reader.
+                dataEntryReader.read(new ZipDataEntry(dataEntry,
+                                                      zipEntry,
+                                                      zipInputStream));
+            }
+        }
+        finally
+        {
+            dataEntry.closeInputStream();
+        }
+    }
+}
diff --git a/src/proguard/io/JarWriter.java b/src/proguard/io/JarWriter.java
new file mode 100644
index 0000000..3e40cdf
--- /dev/null
+++ b/src/proguard/io/JarWriter.java
@@ -0,0 +1,235 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.ClassConstants;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.zip.*;
+
+
+/**
+ * This DataEntryWriter sends data entries to a given jar/zip file.
+ * The manifest and comment properties can optionally be set.
+ *
+ * @author Eric Lafortune
+ */
+public class JarWriter implements DataEntryWriter, Finisher
+{
+    private final DataEntryWriter dataEntryWriter;
+    private final Manifest        manifest;
+    private final String          comment;
+
+    private OutputStream    currentParentOutputStream;
+    private ZipOutputStream currentJarOutputStream;
+    private Finisher        currentFinisher;
+    private DataEntry       currentDataEntry;
+
+    // The names of the jar entries that are already in the jar.
+    private final Set jarEntryNames = new HashSet();
+
+
+    /**
+     * Creates a new JarWriter without manifest or comment.
+     */
+    public JarWriter(DataEntryWriter dataEntryWriter)
+    {
+        this(dataEntryWriter, null, null);
+    }
+
+
+    /**
+     * Creates a new JarWriter.
+     */
+    public JarWriter(DataEntryWriter dataEntryWriter,
+                     Manifest        manifest,
+                     String          comment)
+    {
+        this.dataEntryWriter = dataEntryWriter;
+        this.manifest        = manifest;
+        this.comment         = comment;
+    }
+
+
+    // Implementations for DataEntryWriter.
+
+    public boolean createDirectory(DataEntry dataEntry) throws IOException
+    {
+        //Make sure we can start with a new entry.
+        if (!prepareEntry(dataEntry))
+        {
+            return false;
+        }
+
+        // Close the previous ZIP entry, if any.
+        closeEntry();
+
+        // Get the directory entry name.
+        String name = dataEntry.getName() + ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
+
+        // We have to check if the name is already used, because
+        // ZipOutputStream doesn't handle this case properly (it throws
+        // an exception which can be caught, but the ZipDataEntry is
+        // remembered anyway).
+        if (jarEntryNames.add(name))
+        {
+            // Create a new directory entry.
+            currentJarOutputStream.putNextEntry(new ZipEntry(name));
+            currentJarOutputStream.closeEntry();
+        }
+
+        // Clear the finisher.
+        currentFinisher  = null;
+        currentDataEntry = null;
+
+        return true;
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry) throws IOException
+    {
+        return getOutputStream(dataEntry,  null);
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry,
+                                        Finisher  finisher) throws IOException
+    {
+        //Make sure we can start with a new entry.
+        if (!prepareEntry(dataEntry))
+        {
+            return null;
+        }
+
+        // Do we need a new entry?
+        if (!dataEntry.equals(currentDataEntry))
+        {
+            // Close the previous ZIP entry, if any.
+            closeEntry();
+
+            // Get the entry name.
+            String name = dataEntry.getName();
+
+            // We have to check if the name is already used, because
+            // ZipOutputStream doesn't handle this case properly (it throws
+            // an exception which can be caught, but the ZipDataEntry is
+            // remembered anyway).
+            if (!jarEntryNames.add(name))
+            {
+                throw new IOException("Duplicate zip entry ["+dataEntry+"]");
+            }
+
+            // Create a new entry.
+            currentJarOutputStream.putNextEntry(new ZipEntry(name));
+
+            // Set up the finisher for the entry.
+            currentFinisher  = finisher;
+            currentDataEntry = dataEntry;
+        }
+
+        return currentJarOutputStream;
+    }
+
+
+    public void finish() throws IOException
+    {
+        // Finish the entire ZIP stream, if any.
+        if (currentJarOutputStream != null)
+        {
+            // Close the previous ZIP entry, if any.
+            closeEntry();
+
+            // Finish the entire ZIP stream.
+            currentJarOutputStream.finish();
+            currentJarOutputStream    = null;
+            currentParentOutputStream = null;
+            jarEntryNames.clear();
+        }
+    }
+
+
+    public void close() throws IOException
+    {
+        // Close the parent stream.
+        dataEntryWriter.close();
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Makes sure the current output stream is set up for the given entry.
+     */
+    private boolean prepareEntry(DataEntry dataEntry) throws IOException
+    {
+        // Get the parent stream, new or exisiting.
+        // This may finish our own jar output stream.
+        OutputStream parentOutputStream =
+            dataEntryWriter.getOutputStream(dataEntry.getParent(), this);
+
+        // Did we get a stream?
+        if (parentOutputStream == null)
+        {
+            return false;
+        }
+
+        // Do we need a new stream?
+        if (currentParentOutputStream == null)
+        {
+            currentParentOutputStream = parentOutputStream;
+
+            // Create a new jar stream, with a manifest, if set.
+            currentJarOutputStream = manifest != null ?
+                new JarOutputStream(parentOutputStream, manifest) :
+                new ZipOutputStream(parentOutputStream);
+
+            // Add a comment, if set.
+            if (comment != null)
+            {
+                currentJarOutputStream.setComment(comment);
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Closes the previous ZIP entry, if any.
+     */
+    private void closeEntry() throws IOException
+    {
+        if (currentDataEntry != null)
+        {
+            // Let any finisher finish up first.
+            if (currentFinisher != null)
+            {
+                currentFinisher.finish();
+                currentFinisher = null;
+            }
+
+            currentJarOutputStream.closeEntry();
+            currentDataEntry = null;
+        }
+    }
+}
diff --git a/src/proguard/io/ManifestRewriter.java b/src/proguard/io/ManifestRewriter.java
new file mode 100644
index 0000000..f10307e
--- /dev/null
+++ b/src/proguard/io/ManifestRewriter.java
@@ -0,0 +1,216 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.*;
+
+import java.io.*;
+
+/**
+ * This DataEntryReader writes the manifest data entries that it reads to a
+ * given DataEntryWriter, updating their contents based on the renamed classes
+ * in the given ClassPool.
+ *
+ * @author Eric Lafortune
+ */
+public class ManifestRewriter extends DataEntryRewriter
+{
+    /**
+     * Creates a new ManifestRewriter.
+     */
+    public ManifestRewriter(ClassPool       classPool,
+                            DataEntryWriter dataEntryWriter)
+    {
+        super(classPool, dataEntryWriter);
+    }
+
+
+    // Implementations for DataEntryRewriter.
+
+    protected void copyData(Reader reader,
+                            Writer writer)
+    throws IOException
+    {
+        super.copyData(new SplitLineReader(reader),
+                       new SplitLineWriter(writer));
+    }
+
+
+    /**
+     * This Reader reads manifest files, joining any split lines.
+     */
+    private static class SplitLineReader extends FilterReader
+    {
+        private char[] buffer      = new char[2];
+        private int    bufferIndex = 0;
+        private int    bufferSize  = 0;
+
+
+        public SplitLineReader(Reader reader)
+        {
+            super(reader);
+        }
+
+
+        // Implementations for Reader.
+
+        public int read() throws IOException
+        {
+            while (true)
+            {
+                if (bufferIndex < bufferSize)
+                {
+                    return buffer[bufferIndex++];
+                }
+
+                // Read the first character.
+                int c1 = super.read();
+                if (c1 != '\n' && c1 != '\r')
+                {
+                    return c1;
+                }
+
+                bufferIndex = 0;
+                bufferSize  = 0;
+                buffer[bufferSize++] = '\n';
+
+                // Read the second character.
+                int c2 = super.read();
+                if (c2 == ' ')
+                {
+                    bufferSize = 0;
+                    continue;
+                }
+
+                if (c1 != '\r' || c2 != '\n')
+                {
+                    buffer[bufferSize++] = (char)c2;
+                    continue;
+                }
+
+                // Read the third character.
+                int c3 = super.read();
+                if (c3 == ' ')
+                {
+                    bufferSize = 0;
+                    continue;
+                }
+
+                buffer[bufferSize++] = (char)c3;
+            }
+        }
+
+
+        public int read(char[] cbuf, int off, int len) throws IOException
+        {
+            // Delegate to reading a single character at a time.
+            int count = 0;
+            while (count < len)
+            {
+                int c = read();
+                if (c == -1)
+                {
+                    break;
+                }
+
+                cbuf[off + count++] = (char)c;
+            }
+
+            return count;
+        }
+
+
+        public long skip(long n) throws IOException
+        {
+            // Delegate to reading a single character at a time.
+            int count = 0;
+            while (count < n)
+            {
+                int c = read();
+                if (c == -1)
+                {
+                    break;
+                }
+
+                count++;
+            }
+
+            return count;
+        }
+    }
+
+
+    /**
+     * This Writer writes manifest files, splitting any long lines.
+     */
+    private static class SplitLineWriter extends FilterWriter
+    {
+        private int counter = 0;
+
+
+        public SplitLineWriter(Writer writer)
+        {
+            super(writer);
+        }
+
+
+        // Implementations for Reader.
+
+        public void write(int c) throws IOException
+        {
+            // TODO: We should actually count the Utf-8 bytes, not the characters.
+            if (c == '\n')
+            {
+                // Reset the character count.
+                counter = 0;
+            }
+            else if (counter == 70)
+            {
+                // Insert are newline and space.
+                super.write('\n');
+                super.write(' ');
+
+                counter = 2;
+            }
+            else
+            {
+                counter++;
+            }
+
+            super.write(c);
+        }
+
+
+        public void write(char[] cbuf, int off, int len) throws IOException
+        {
+            for (int count = 0; count < len; count++)
+            {
+                write(cbuf[off + count]);
+            }
+        }
+
+
+        public void write(String str, int off, int len) throws IOException
+        {
+            write(str.toCharArray(), off, len);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/io/NameFilter.java b/src/proguard/io/NameFilter.java
new file mode 100644
index 0000000..2a9fbc3
--- /dev/null
+++ b/src/proguard/io/NameFilter.java
@@ -0,0 +1,83 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.util.*;
+
+import java.util.List;
+
+/**
+ * This DataEntryReader delegates to one of two other DataEntryReader instances,
+ * depending on the name of the data entry.
+ *
+ * @author Eric Lafortune
+ */
+public class NameFilter extends FilteredDataEntryReader
+{
+    /**
+     * Creates a new NameFilter that delegates to the given reader, depending
+     * on the given list of filters.
+     */
+    public NameFilter(String          regularExpression,
+                      DataEntryReader acceptedDataEntryReader)
+    {
+        this(regularExpression, acceptedDataEntryReader, null);
+    }
+
+
+    /**
+     * Creates a new NameFilter that delegates to either of the two given
+     * readers, depending on the given list of filters.
+     */
+    public NameFilter(String          regularExpression,
+                      DataEntryReader acceptedDataEntryReader,
+                      DataEntryReader rejectedDataEntryReader)
+    {
+        super(new DataEntryNameFilter(new ListParser(new FileNameParser()).parse(regularExpression)),
+              acceptedDataEntryReader,
+              rejectedDataEntryReader);
+    }
+
+
+    /**
+     * Creates a new NameFilter that delegates to the given reader, depending
+     * on the given list of filters.
+     */
+    public NameFilter(List            regularExpressions,
+                      DataEntryReader acceptedDataEntryReader)
+    {
+        this(regularExpressions, acceptedDataEntryReader, null);
+    }
+
+
+    /**
+     * Creates a new NameFilter that delegates to either of the two given
+     * readers, depending on the given list of filters.
+     */
+    public NameFilter(List            regularExpressions,
+                      DataEntryReader acceptedDataEntryReader,
+                      DataEntryReader rejectedDataEntryReader)
+    {
+        super(new DataEntryNameFilter(new ListParser(new FileNameParser()).parse(regularExpressions)),
+              acceptedDataEntryReader,
+              rejectedDataEntryReader);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/io/ParentDataEntryWriter.java b/src/proguard/io/ParentDataEntryWriter.java
new file mode 100644
index 0000000..4f16d12
--- /dev/null
+++ b/src/proguard/io/ParentDataEntryWriter.java
@@ -0,0 +1,75 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.*;
+
+/**
+ * This DataEntryWriter lets another DataEntryWriter write the parent data
+ * entries.
+ *
+ * @author Eric Lafortune
+ */
+public class ParentDataEntryWriter implements DataEntryWriter
+{
+    private DataEntryWriter dataEntryWriter;
+
+
+    /**
+     * Creates a new ParentDataEntryWriter.
+     * @param dataEntryWriter the DataEntryWriter to which the writing will be
+     *                        delegated, passing the data entries' parents.
+     */
+    public ParentDataEntryWriter(DataEntryWriter dataEntryWriter)
+    {
+        this.dataEntryWriter = dataEntryWriter;
+    }
+
+
+    // Implementations for DataEntryWriter.
+
+
+    public boolean createDirectory(DataEntry dataEntry) throws IOException
+    {
+        return getOutputStream(dataEntry) != null;
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry) throws IOException
+    {
+        return getOutputStream(dataEntry, null);
+    }
+
+
+    public OutputStream getOutputStream(DataEntry dataEntry,
+                                        Finisher  finisher) throws IOException
+    {
+        return dataEntryWriter.getOutputStream(dataEntry.getParent(),
+                                               finisher);
+    }
+
+
+    public void close() throws IOException
+    {
+        dataEntryWriter.close();
+        dataEntryWriter = null;
+    }
+}
diff --git a/src/proguard/io/RenamedDataEntry.java b/src/proguard/io/RenamedDataEntry.java
new file mode 100644
index 0000000..ae2d267
--- /dev/null
+++ b/src/proguard/io/RenamedDataEntry.java
@@ -0,0 +1,83 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import java.io.*;
+
+/**
+ * This DataEntry wraps another data entry, returning a different name instead
+ * of the wrapped data entry's name.
+ *
+ * @author Eric Lafortune
+ */
+public class RenamedDataEntry implements DataEntry
+{
+    private final DataEntry dataEntry;
+    private final String    name;
+
+
+    public RenamedDataEntry(DataEntry dataEntry,
+                            String    name)
+    {
+        this.dataEntry = dataEntry;
+        this.name      = name;
+    }
+
+
+    // Implementations for DataEntry.
+
+    public String getName()
+    {
+        return name;
+    }
+
+
+    public boolean isDirectory()
+    {
+        return dataEntry.isDirectory();
+    }
+
+
+    public InputStream getInputStream() throws IOException
+    {
+        return dataEntry.getInputStream();
+    }
+
+
+    public void closeInputStream() throws IOException
+    {
+        dataEntry.closeInputStream();
+    }
+
+
+    public DataEntry getParent()
+    {
+        return dataEntry.getParent();
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return name + " == " + dataEntry;
+    }
+}
diff --git a/src/proguard/io/ZipDataEntry.java b/src/proguard/io/ZipDataEntry.java
new file mode 100644
index 0000000..5779fd8
--- /dev/null
+++ b/src/proguard/io/ZipDataEntry.java
@@ -0,0 +1,98 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.io;
+
+import proguard.classfile.ClassConstants;
+
+import java.io.*;
+import java.util.zip.*;
+
+/**
+ * This <code>DataEntry</code> represents a ZIP entry.
+ *
+ * @author Eric Lafortune
+ */
+public class ZipDataEntry implements DataEntry
+{
+    private final DataEntry      parent;
+    private final ZipEntry       zipEntry;
+    private       ZipInputStream zipInputStream;
+
+
+    public ZipDataEntry(DataEntry      parent,
+                        ZipEntry       zipEntry,
+                        ZipInputStream zipInputStream)
+    {
+        this.parent         = parent;
+        this.zipEntry       = zipEntry;
+        this.zipInputStream = zipInputStream;
+    }
+
+
+    // Implementations for DataEntry.
+
+    public String getName()
+    {
+        // Get the right separators.
+        String name = zipEntry.getName()
+            .replace(File.separatorChar, ClassConstants.INTERNAL_PACKAGE_SEPARATOR);
+
+        // Chop the trailing directory slash, if any.
+        int length = name.length();
+        return length > 0 &&
+               name.charAt(length-1) == ClassConstants.INTERNAL_PACKAGE_SEPARATOR ?
+                   name.substring(0, length -1) :
+                   name;
+    }
+
+
+    public boolean isDirectory()
+    {
+        return zipEntry.isDirectory();
+    }
+
+
+    public InputStream getInputStream() throws IOException
+    {
+        return zipInputStream;
+    }
+
+
+    public void closeInputStream() throws IOException
+    {
+        zipInputStream.closeEntry();
+        zipInputStream = null;
+    }
+
+
+    public DataEntry getParent()
+    {
+        return parent;
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return parent.toString() + ':' + getName();
+    }
+}
diff --git a/src/proguard/io/package.html b/src/proguard/io/package.html
new file mode 100644
index 0000000..4ad9f41
--- /dev/null
+++ b/src/proguard/io/package.html
@@ -0,0 +1,4 @@
+<body>
+This package contains classes to read and write files, optionally wrapped in
+jars, wars, ears, zips, directories,...
+</body>
diff --git a/src/proguard/obfuscate/AttributeShrinker.java b/src/proguard/obfuscate/AttributeShrinker.java
new file mode 100644
index 0000000..a8bc36b
--- /dev/null
+++ b/src/proguard/obfuscate/AttributeShrinker.java
@@ -0,0 +1,121 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor removes attributes that are not marked as being used or
+ * required.
+ *
+ * @see AttributeUsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class AttributeShrinker
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             AttributeVisitor
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Compact the array for class attributes.
+        programClass.u2attributesCount =
+            shrinkArray(programClass.attributes,
+                        programClass.u2attributesCount);
+
+        // Compact the attributes in fields, methods, and class attributes,
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Library classes are left unchanged.
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        // Compact the attributes array.
+        programMember.u2attributesCount =
+            shrinkArray(programMember.attributes,
+                        programMember.u2attributesCount);
+
+        // Compact any attributes of the remaining attributes.
+        programMember.attributesAccept(programClass, this);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Compact the attributes array.
+        codeAttribute.u2attributesCount =
+            shrinkArray(codeAttribute.attributes,
+                        codeAttribute.u2attributesCount);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Removes all VisitorAccepter objects that are not marked as being used
+     * from the given array.
+     * @return the new number of VisitorAccepter objects.
+     */
+    private static int shrinkArray(VisitorAccepter[] array, int length)
+    {
+        int counter = 0;
+
+        // Shift the used objects together.
+        for (int index = 0; index < length; index++)
+        {
+            if (AttributeUsageMarker.isUsed(array[index]))
+            {
+                array[counter++] = array[index];
+            }
+        }
+
+        // Clear the remaining array elements.
+        for (int index = counter; index < length; index++)
+        {
+            array[index] = null;
+        }
+
+        return counter;
+    }
+}
diff --git a/src/proguard/obfuscate/AttributeUsageMarker.java b/src/proguard/obfuscate/AttributeUsageMarker.java
new file mode 100644
index 0000000..e772324
--- /dev/null
+++ b/src/proguard/obfuscate/AttributeUsageMarker.java
@@ -0,0 +1,71 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This ClassVisitor marks all attributes that it visits.
+ *
+ * @see AttributeShrinker
+ *
+ * @author Eric Lafortune
+ */
+public class AttributeUsageMarker
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    // A visitor info flag to indicate the attribute is being used.
+    private static final Object USED = new Object();
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute)
+    {
+        markAsUsed(attribute);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the given VisitorAccepter as being used (or useful).
+     * In this context, the VisitorAccepter will be an Attribute object.
+     */
+    private static void markAsUsed(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(USED);
+    }
+
+
+    /**
+     * Returns whether the given VisitorAccepter has been marked as being used.
+     * In this context, the VisitorAccepter will be an Attribute object.
+     */
+    static boolean isUsed(VisitorAccepter visitorAccepter)
+    {
+        return visitorAccepter.getVisitorInfo() == USED;
+    }
+}
diff --git a/src/proguard/obfuscate/ClassObfuscator.java b/src/proguard/obfuscate/ClassObfuscator.java
new file mode 100644
index 0000000..9af0c82
--- /dev/null
+++ b/src/proguard/obfuscate/ClassObfuscator.java
@@ -0,0 +1,521 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.ClassVisitor;
+import proguard.util.*;
+
+import java.util.*;
+
+/**
+ * This <code>ClassVisitor</code> comes up with obfuscated names for the
+ * classes it visits, and for their class members. The actual renaming is
+ * done afterward.
+ *
+ * @see ClassRenamer
+ *
+ * @author Eric Lafortune
+ */
+public class ClassObfuscator
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             AttributeVisitor,
+             InnerClassesInfoVisitor,
+             ConstantVisitor
+{
+    private final DictionaryNameFactory classNameFactory;
+    private final DictionaryNameFactory packageNameFactory;
+    private final boolean               useMixedCaseClassNames;
+    private final StringMatcher         keepPackageNamesMatcher;
+    private final String                flattenPackageHierarchy;
+    private final String                repackageClasses;
+    private final boolean               allowAccessModification;
+
+    private final Set classNamesToAvoid                       = new HashSet();
+
+    // Map: [package prefix - new package prefix]
+    private final Map packagePrefixMap                        = new HashMap();
+
+    // Map: [package prefix - package name factory]
+    private final Map packagePrefixPackageNameFactoryMap      = new HashMap();
+
+    // Map: [package prefix - numeric class name factory]
+    private final Map packagePrefixClassNameFactoryMap        = new HashMap();
+
+    // Map: [package prefix - numeric class name factory]
+    private final Map packagePrefixNumericClassNameFactoryMap = new HashMap();
+
+    // Field acting as temporary variables and as return values for names
+    // of outer classes and types of inner classes.
+    private String  newClassName;
+    private boolean numericClassName;
+
+
+    /**
+     * Creates a new ClassObfuscator.
+     * @param programClassPool        the class pool in which class names
+     *                                have to be unique.
+     * @param classNameFactory        the optional class obfuscation dictionary.
+     * @param packageNameFactory      the optional package obfuscation
+     *                                dictionary.
+     * @param useMixedCaseClassNames  specifies whether obfuscated packages and
+     *                                classes can get mixed-case names.
+     * @param keepPackageNames        the optional filter for which matching
+     *                                package names are kept.
+     * @param flattenPackageHierarchy the base package if the obfuscated package
+     *                                hierarchy is to be flattened.
+     * @param repackageClasses        the base package if the obfuscated classes
+     *                                are to be repackaged.
+     * @param allowAccessModification specifies whether obfuscated classes can
+     *                                be freely moved between packages.
+     */
+    public ClassObfuscator(ClassPool             programClassPool,
+                           DictionaryNameFactory classNameFactory,
+                           DictionaryNameFactory packageNameFactory,
+                           boolean               useMixedCaseClassNames,
+                           List                  keepPackageNames,
+                           String                flattenPackageHierarchy,
+                           String                repackageClasses,
+                           boolean               allowAccessModification)
+    {
+        this.classNameFactory   = classNameFactory;
+        this.packageNameFactory = packageNameFactory;
+
+        // First append the package separator if necessary.
+        if (flattenPackageHierarchy != null &&
+            flattenPackageHierarchy.length() > 0)
+        {
+            flattenPackageHierarchy += ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
+        }
+
+        // First append the package separator if necessary.
+        if (repackageClasses != null &&
+            repackageClasses.length() > 0)
+        {
+            repackageClasses += ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
+        }
+
+        this.useMixedCaseClassNames  = useMixedCaseClassNames;
+        this.keepPackageNamesMatcher = keepPackageNames == null ? null :
+            new ListParser(new FileNameParser()).parse(keepPackageNames);
+        this.flattenPackageHierarchy = flattenPackageHierarchy;
+        this.repackageClasses        = repackageClasses;
+        this.allowAccessModification = allowAccessModification;
+
+        // Map the root package onto the root package.
+        packagePrefixMap.put("", "");
+
+        // Collect all names that have been taken already.
+        programClassPool.classesAccept(new MyKeepCollector());
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Does this class still need a new name?
+        newClassName = newClassName(programClass);
+        if (newClassName == null)
+        {
+            // Make sure the outer class has a name, if it exists. The name will
+            // be stored as the new class name, as a side effect, so we'll be
+            // able to use it as a prefix.
+            programClass.attributesAccept(this);
+
+            // Figure out a package prefix. The package prefix may actually be
+            // the an outer class prefix, if any, or it may be the fixed base
+            // package, if classes are to be repackaged.
+            String newPackagePrefix = newClassName != null ?
+                newClassName + ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR :
+                newPackagePrefix(ClassUtil.internalPackagePrefix(programClass.getName()));
+
+            // Come up with a new class name, numeric or ordinary.
+            newClassName = newClassName != null && numericClassName ?
+                generateUniqueNumericClassName(newPackagePrefix) :
+                generateUniqueClassName(newPackagePrefix);
+
+            setNewClassName(programClass, newClassName);
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // Make sure the outer classes have a name, if they exist.
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        // Make sure the enclosing class has a name.
+        enclosingMethodAttribute.referencedClassAccept(this);
+
+        String innerClassName = clazz.getName();
+        String outerClassName = clazz.getClassName(enclosingMethodAttribute.u2classIndex);
+
+        numericClassName = isNumericClassName(innerClassName,
+                                              outerClassName);
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        // Make sure the outer class has a name, if it exists.
+        int innerClassIndex = innerClassesInfo.u2innerClassIndex;
+        int outerClassIndex = innerClassesInfo.u2outerClassIndex;
+        if (innerClassIndex != 0 &&
+            outerClassIndex != 0)
+        {
+            String innerClassName = clazz.getClassName(innerClassIndex);
+            if (innerClassName.equals(clazz.getName()))
+            {
+                clazz.constantPoolEntryAccept(outerClassIndex, this);
+
+                String outerClassName = clazz.getClassName(outerClassIndex);
+
+                numericClassName = isNumericClassName(innerClassName,
+                                                      outerClassName);
+            }
+        }
+    }
+
+
+    /**
+     * Returns whether the given inner class name is a numeric name.
+     */
+    private boolean isNumericClassName(String innerClassName,
+                                       String outerClassName)
+    {
+        int innerClassNameStart  = outerClassName.length() + 1;
+        int innerClassNameLength = innerClassName.length();
+
+        if (innerClassNameStart >= innerClassNameLength)
+        {
+            return false;
+        }
+
+        for (int index = innerClassNameStart; index < innerClassNameLength; index++)
+        {
+            if (!Character.isDigit(innerClassName.charAt(index)))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Make sure the outer class has a name.
+        classConstant.referencedClassAccept(this);
+    }
+
+
+    /**
+     * This ClassVisitor collects package names and class names that have to
+     * be kept.
+     */
+    private class MyKeepCollector implements ClassVisitor
+    {
+        public void visitProgramClass(ProgramClass programClass)
+        {
+            // Does the class already have a new name?
+            String newClassName = newClassName(programClass);
+            if (newClassName != null)
+            {
+                // Remember not to use this name.
+                classNamesToAvoid.add(mixedCaseClassName(newClassName));
+
+                // Are we not aggressively repackaging all obfuscated classes?
+                if (repackageClasses == null ||
+                    !allowAccessModification)
+                {
+                    String className = programClass.getName();
+
+                    // Keep the package name for all other classes in the same
+                    // package. Do this recursively if we're not doing any
+                    // repackaging.
+                    mapPackageName(className,
+                                   newClassName,
+                                   repackageClasses        == null &&
+                                   flattenPackageHierarchy == null);
+                }
+            }
+        }
+
+
+        public void visitLibraryClass(LibraryClass libraryClass)
+        {
+        }
+
+
+        /**
+         * Makes sure the package name of the given class will always be mapped
+         * consistently with its new name.
+         */
+        private void mapPackageName(String  className,
+                                    String  newClassName,
+                                    boolean recursively)
+        {
+            String packagePrefix    = ClassUtil.internalPackagePrefix(className);
+            String newPackagePrefix = ClassUtil.internalPackagePrefix(newClassName);
+
+            // Put the mapping of this package prefix, and possibly of its
+            // entire hierarchy, into the package prefix map.
+            do
+            {
+                packagePrefixMap.put(packagePrefix, newPackagePrefix);
+
+                if (!recursively)
+                {
+                    break;
+                }
+
+                packagePrefix    = ClassUtil.internalPackagePrefix(packagePrefix);
+                newPackagePrefix = ClassUtil.internalPackagePrefix(newPackagePrefix);
+            }
+            while (packagePrefix.length()    > 0 &&
+                   newPackagePrefix.length() > 0);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Finds or creates the new package prefix for the given package.
+     */
+    private String newPackagePrefix(String packagePrefix)
+    {
+        // Doesn't the package prefix have a new package prefix yet?
+        String newPackagePrefix = (String)packagePrefixMap.get(packagePrefix);
+        if (newPackagePrefix == null)
+        {
+            // Are we keeping the package name?
+            if (keepPackageNamesMatcher != null &&
+                keepPackageNamesMatcher.matches(packagePrefix.length() > 0 ?
+                    packagePrefix.substring(0, packagePrefix.length()-1) :
+                    packagePrefix))
+            {
+                return packagePrefix;
+            }
+
+            // Are we forcing a new package prefix?
+            if (repackageClasses != null)
+            {
+                return repackageClasses;
+            }
+
+            // Are we forcing a new superpackage prefix?
+            // Otherwise figure out the new superpackage prefix, recursively.
+            String newSuperPackagePrefix = flattenPackageHierarchy != null ?
+                flattenPackageHierarchy :
+                newPackagePrefix(ClassUtil.internalPackagePrefix(packagePrefix));
+
+            // Come up with a new package prefix.
+            newPackagePrefix = generateUniquePackagePrefix(newSuperPackagePrefix);
+
+            // Remember to use this mapping in the future.
+            packagePrefixMap.put(packagePrefix, newPackagePrefix);
+        }
+
+        return newPackagePrefix;
+    }
+
+
+    /**
+     * Creates a new package prefix in the given new superpackage.
+     */
+    private String generateUniquePackagePrefix(String newSuperPackagePrefix)
+    {
+        // Find the right name factory for this package.
+        NameFactory packageNameFactory =
+            (NameFactory)packagePrefixPackageNameFactoryMap.get(newSuperPackagePrefix);
+        if (packageNameFactory == null)
+        {
+            // We haven't seen packages in this superpackage before. Create
+            // a new name factory for them.
+            packageNameFactory = new SimpleNameFactory(useMixedCaseClassNames);
+            if (this.packageNameFactory != null)
+            {
+                packageNameFactory =
+                    new DictionaryNameFactory(this.packageNameFactory,
+                                              packageNameFactory);
+            }
+
+            packagePrefixPackageNameFactoryMap.put(newSuperPackagePrefix,
+                                                   packageNameFactory);
+        }
+
+        return generateUniquePackagePrefix(newSuperPackagePrefix, packageNameFactory);
+    }
+
+
+    /**
+     * Creates a new package prefix in the given new superpackage, with the
+     * given package name factory.
+     */
+    private String generateUniquePackagePrefix(String      newSuperPackagePrefix,
+                                               NameFactory packageNameFactory)
+    {
+        // Come up with package names until we get an original one.
+        String newPackagePrefix;
+        do
+        {
+            // Let the factory produce a package name.
+            newPackagePrefix = newSuperPackagePrefix +
+                               packageNameFactory.nextName() +
+                               ClassConstants.INTERNAL_PACKAGE_SEPARATOR;
+        }
+        while (packagePrefixMap.containsValue(newPackagePrefix));
+
+        return newPackagePrefix;
+    }
+
+
+    /**
+     * Creates a new class name in the given new package.
+     */
+    private String generateUniqueClassName(String newPackagePrefix)
+    {
+        // Find the right name factory for this package.
+        NameFactory classNameFactory =
+            (NameFactory)packagePrefixClassNameFactoryMap.get(newPackagePrefix);
+        if (classNameFactory == null)
+        {
+            // We haven't seen classes in this package before.
+            // Create a new name factory for them.
+            classNameFactory = new SimpleNameFactory(useMixedCaseClassNames);
+            if (this.classNameFactory != null)
+            {
+                classNameFactory =
+                    new DictionaryNameFactory(this.classNameFactory,
+                                              classNameFactory);
+            }
+
+            packagePrefixClassNameFactoryMap.put(newPackagePrefix,
+                                                 classNameFactory);
+        }
+
+        return generateUniqueClassName(newPackagePrefix, classNameFactory);
+    }
+
+
+    /**
+     * Creates a new class name in the given new package.
+     */
+    private String generateUniqueNumericClassName(String newPackagePrefix)
+    {
+        // Find the right name factory for this package.
+        NameFactory classNameFactory =
+            (NameFactory)packagePrefixNumericClassNameFactoryMap.get(newPackagePrefix);
+        if (classNameFactory == null)
+        {
+            // We haven't seen classes in this package before.
+            // Create a new name factory for them.
+            classNameFactory = new NumericNameFactory();
+
+            packagePrefixNumericClassNameFactoryMap.put(newPackagePrefix,
+                                                        classNameFactory);
+        }
+
+        return generateUniqueClassName(newPackagePrefix, classNameFactory);
+    }
+
+
+    /**
+     * Creates a new class name in the given new package, with the given
+     * class name factory.
+     */
+    private String generateUniqueClassName(String      newPackagePrefix,
+                                           NameFactory classNameFactory)
+    {
+        // Come up with class names until we get an original one.
+        String newClassName;
+        do
+        {
+            // Let the factory produce a class name.
+            newClassName = newPackagePrefix +
+                           classNameFactory.nextName();
+        }
+        while (classNamesToAvoid.contains(mixedCaseClassName(newClassName)));
+
+        return newClassName;
+    }
+
+
+    /**
+     * Returns the given class name, unchanged if mixed-case class names are
+     * allowed, or the lower-case version otherwise.
+     */
+    private String mixedCaseClassName(String className)
+    {
+        return useMixedCaseClassNames ?
+            className :
+            className.toLowerCase();
+    }
+
+
+    /**
+     * Assigns a new name to the given class.
+     * @param clazz the given class.
+     * @param name  the new name.
+     */
+    static void setNewClassName(Clazz clazz, String name)
+    {
+        clazz.setVisitorInfo(name);
+    }
+
+
+    /**
+     * Retrieves the new name of the given class.
+     * @param clazz the given class.
+     * @return the class's new name, or <code>null</code> if it doesn't
+     *         have one yet.
+     */
+    static String newClassName(Clazz clazz)
+    {
+        Object visitorInfo = clazz.getVisitorInfo();
+
+        return visitorInfo instanceof String ?
+            (String)visitorInfo :
+            null;
+    }
+}
diff --git a/src/proguard/obfuscate/ClassRenamer.java b/src/proguard/obfuscate/ClassRenamer.java
new file mode 100644
index 0000000..143e3fb
--- /dev/null
+++ b/src/proguard/obfuscate/ClassRenamer.java
@@ -0,0 +1,109 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.editor.ConstantPoolEditor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This <code>ClassVisitor</code> renames the class names and class member
+ * names of the classes it visits, using names previously determined by the
+ * obfuscator.
+ *
+ * @see ClassObfuscator
+ * @see MemberObfuscator
+ *
+ * @author Eric Lafortune
+ */
+public class ClassRenamer
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             ConstantVisitor
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Rename this class.
+        programClass.thisClassConstantAccept(this);
+
+        // Rename the class members.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        libraryClass.thisClassName = ClassObfuscator.newClassName(libraryClass);
+
+        // Rename the class members.
+        libraryClass.fieldsAccept(this);
+        libraryClass.methodsAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass  programClass,
+                                     ProgramMember programMember)
+    {
+        // Has the class member name changed?
+        String name    = programMember.getName(programClass);
+        String newName = MemberObfuscator.newMemberName(programMember);
+        if (newName != null &&
+            !newName.equals(name))
+        {
+            programMember.u2nameIndex =
+                new ConstantPoolEditor(programClass).addUtf8Constant(newName);
+        }
+    }
+
+    public void visitLibraryMember(LibraryClass  libraryClass,
+                                   LibraryMember libraryMember)
+    {
+        String newName = MemberObfuscator.newMemberName(libraryMember);
+        if (newName != null)
+        {
+            libraryMember.name = newName;
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Update the Class entry if required.
+        String newName = ClassObfuscator.newClassName(clazz);
+        if (newName != null)
+        {
+            // Refer to a new Utf8 entry.
+            classConstant.u2nameIndex =
+                new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newName);
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/DictionaryNameFactory.java b/src/proguard/obfuscate/DictionaryNameFactory.java
new file mode 100644
index 0000000..f262664
--- /dev/null
+++ b/src/proguard/obfuscate/DictionaryNameFactory.java
@@ -0,0 +1,189 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * This <code>NameFactory</code> generates names that are read from a
+ * specified input file.
+ * Comments (everything starting with '#' on a single line) are ignored.
+ *
+ * @author Eric Lafortune
+ */
+public class DictionaryNameFactory implements NameFactory
+{
+    private static final char COMMENT_CHARACTER = '#';
+
+
+    private final List        names;
+    private final NameFactory nameFactory;
+
+    private int index = 0;
+
+
+    /**
+     * Creates a new <code>DictionaryNameFactory</code>.
+     * @param file        the file from which the names can be read.
+     * @param nameFactory the name factory from which names will be retrieved
+     *                    if the list of read names has been exhausted.
+     */
+    public DictionaryNameFactory(File        file,
+                                 NameFactory nameFactory) throws IOException
+    {
+        this.names       = new ArrayList();
+        this.nameFactory = nameFactory;
+
+        Reader reader = new FileReader(file);
+
+        try
+        {
+            StringBuffer buffer = new StringBuffer();
+
+            while (true)
+            {
+                // Read the next character.
+                int c = reader.read();
+
+                // Is it a valid identifier character?
+                if (c != -1 &&
+                    (buffer.length() == 0 ?
+                         Character.isJavaIdentifierStart((char)c) :
+                         Character.isJavaIdentifierPart((char)c)))
+                {
+                    // Append it to the current identifier.
+                    buffer.append((char)c);
+                }
+                else
+                {
+                    // Did we collect a new identifier?
+                    if (buffer.length() > 0)
+                    {
+                        // Add the completed name to the list of names, if it's
+                        // not in it yet.
+                        String name = buffer.toString();
+                        if (!names.contains(name))
+                        {
+                            names.add(name);
+                        }
+
+                        // Clear the buffer.
+                        buffer.setLength(0);
+                    }
+
+                    // Is this the beginning of a comment line?
+                    if (c == COMMENT_CHARACTER)
+                    {
+                        // Skip all characters till the end of the line.
+                        do
+                        {
+                            c = reader.read();
+                        }
+                        while (c != -1 &&
+                               c != '\n' &&
+                               c != '\r');
+                    }
+
+                    // Is this the end of the file?
+                    if (c == -1)
+                    {
+                        // Just return.
+                        return;
+                    }
+                }
+            }
+        }
+        finally
+        {
+            reader.close();
+        }
+    }
+
+
+    /**
+     * Creates a new <code>DictionaryNameFactory</code>.
+     * @param dictionaryNameFactory the dictionary name factory whose dictionary
+     *                              will be used.
+     * @param nameFactory           the name factory from which names will be
+     *                              retrieved if the list of read names has been
+     *                              exhausted.
+     */
+    public DictionaryNameFactory(DictionaryNameFactory dictionaryNameFactory,
+                                 NameFactory           nameFactory)
+    {
+        this.names       = dictionaryNameFactory.names;
+        this.nameFactory = nameFactory;
+    }
+
+
+    // Implementations for NameFactory.
+
+    public void reset()
+    {
+        index = 0;
+
+        nameFactory.reset();
+    }
+
+
+    public String nextName()
+    {
+        String name;
+
+        // Do we still have names?
+        if (index < names.size())
+        {
+            // Return the next name.
+            name = (String)names.get(index++);
+        }
+        else
+        {
+            // Return the next different name from the other name factory.
+            do
+            {
+                name = nameFactory.nextName();
+            }
+            while (names.contains(name));
+        }
+
+        return name;
+    }
+
+
+    public static void main(String[] args)
+    {
+        try
+        {
+            DictionaryNameFactory factory =
+                new DictionaryNameFactory(new File(args[0]), new SimpleNameFactory());
+
+            for (int counter = 0; counter < 50; counter++)
+            {
+                System.out.println("["+factory.nextName()+"]");
+            }
+        }
+        catch (IOException ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/MapCleaner.java b/src/proguard/obfuscate/MapCleaner.java
new file mode 100644
index 0000000..fdefec5
--- /dev/null
+++ b/src/proguard/obfuscate/MapCleaner.java
@@ -0,0 +1,57 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+import java.util.Map;
+
+/**
+ * This ClassVisitor clears a given map whenever it visits a class.
+ *
+ * @author Eric Lafortune
+ */
+public class MapCleaner
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private final Map map;
+
+
+    /**
+     * Creates a new MapCleaner.
+     * @param map the map to be cleared.
+     */
+    public MapCleaner(Map map)
+    {
+        this.map = map;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitAnyClass(Clazz clazz)
+    {
+        map.clear();
+    }
+}
diff --git a/src/proguard/obfuscate/MappingKeeper.java b/src/proguard/obfuscate/MappingKeeper.java
new file mode 100644
index 0000000..c9d6aa6
--- /dev/null
+++ b/src/proguard/obfuscate/MappingKeeper.java
@@ -0,0 +1,177 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.util.ListUtil;
+
+
+/**
+ * This MappingKeeper applies the mappings that it receives to its class pool,
+ * so these mappings are ensured in a subsequent obfuscation step.
+ *
+ * @author Eric Lafortune
+ */
+public class MappingKeeper implements MappingProcessor
+{
+    private final ClassPool      classPool;
+    private final WarningPrinter warningPrinter;
+
+    // A field acting as a parameter.
+    private Clazz clazz;
+
+
+    /**
+     * Creates a new MappingKeeper.
+     * @param classPool      the class pool in which class names and class
+     *                       member names have to be mapped.
+     * @param warningPrinter the optional warning printer to which warnings
+     *                       can be printed.
+     */
+    public MappingKeeper(ClassPool      classPool,
+                         WarningPrinter warningPrinter)
+    {
+        this.classPool      = classPool;
+        this.warningPrinter = warningPrinter;
+    }
+
+
+    // Implementations for MappingProcessor.
+
+    public boolean processClassMapping(String className,
+                                       String newClassName)
+    {
+        // Find the class.
+        String name = ClassUtil.internalClassName(className);
+
+        clazz = classPool.getClass(name);
+        if (clazz != null)
+        {
+            String newName = ClassUtil.internalClassName(newClassName);
+
+            // Print out a warning if the mapping conflicts with a name that
+            // was set before.
+            if (warningPrinter != null)
+            {
+                String currentNewName = ClassObfuscator.newClassName(clazz);
+                if (currentNewName != null &&
+                    !currentNewName.equals(newName))
+                {
+                    warningPrinter.print(name,
+                                         currentNewName,
+                                         "Warning: " +
+                                         className +
+                                         " is not being kept as '" +
+                                         ClassUtil.externalClassName(currentNewName) +
+                                         "', but remapped to '" +
+                                         newClassName + "'");
+                }
+            }
+
+            ClassObfuscator.setNewClassName(clazz, newName);
+
+            // The class members have to be kept as well.
+            return true;
+        }
+
+        return false;
+    }
+
+
+    public void processFieldMapping(String className,
+                                    String fieldType,
+                                    String fieldName,
+                                    String newFieldName)
+    {
+        if (clazz != null)
+        {
+            // Find the field.
+            String name       = fieldName;
+            String descriptor = ClassUtil.internalType(fieldType);
+
+            Field field = clazz.findField(name, descriptor);
+            if (field != null)
+            {
+                // Print out a warning if the mapping conflicts with a name that
+                // was set before.
+                if (warningPrinter != null)
+                {
+                    String currentNewName = MemberObfuscator.newMemberName(field);
+                    if (currentNewName != null &&
+                        !currentNewName.equals(newFieldName))
+                    {
+                        warningPrinter.print(ClassUtil.internalClassName(className),
+                                             "Warning: " +
+                                             className +
+                                             ": field '" + fieldType + " " + fieldName +
+                                             "' is not being kept as '" + currentNewName +
+                                             "', but remapped to '" + newFieldName + "'");
+                    }
+                }
+
+                // Make sure the mapping name will be kept.
+                MemberObfuscator.setFixedNewMemberName(field, newFieldName);
+            }
+        }
+    }
+
+
+    public void processMethodMapping(String className,
+                                     int    firstLineNumber,
+                                     int    lastLineNumber,
+                                     String methodReturnType,
+                                     String methodName,
+                                     String methodArguments,
+                                     String newMethodName)
+    {
+        if (clazz != null)
+        {
+            // Find the method.
+            String descriptor = ClassUtil.internalMethodDescriptor(methodReturnType,
+                                                                   ListUtil.commaSeparatedList(methodArguments));
+
+            Method method = clazz.findMethod(methodName, descriptor);
+            if (method != null)
+            {
+                // Print out a warning if the mapping conflicts with a name that
+                // was set before.
+                if (warningPrinter != null)
+                {
+                    String currentNewName = MemberObfuscator.newMemberName(method);
+                    if (currentNewName != null &&
+                        !currentNewName.equals(newMethodName))
+                    {
+                        warningPrinter.print(ClassUtil.internalClassName(className),
+                                             "Warning: " +
+                                             className +
+                                             ": method '" + methodReturnType + " " + methodName + ClassConstants.EXTERNAL_METHOD_ARGUMENTS_OPEN + methodArguments + ClassConstants.EXTERNAL_METHOD_ARGUMENTS_CLOSE +
+                                             "' is not being kept as '" + currentNewName +
+                                             "', but remapped to '" + newMethodName + "'");
+                    }
+                }
+
+                // Make sure the mapping name will be kept.
+                MemberObfuscator.setFixedNewMemberName(method, newMethodName);
+            }
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/MappingPrinter.java b/src/proguard/obfuscate/MappingPrinter.java
new file mode 100644
index 0000000..aa8b13e
--- /dev/null
+++ b/src/proguard/obfuscate/MappingPrinter.java
@@ -0,0 +1,147 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+import java.io.PrintStream;
+
+
+/**
+ * This ClassVisitor prints out the renamed classes and class members with
+ * their old names and new names.
+ *
+ * @see ClassRenamer
+ *
+ * @author Eric Lafortune
+ */
+public class MappingPrinter
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor
+{
+    private final PrintStream ps;
+
+
+    /**
+     * Creates a new MappingPrinter that prints to <code>System.out</code>.
+     */
+    public MappingPrinter()
+    {
+        this(System.out);
+    }
+
+
+    /**
+     * Creates a new MappingPrinter that prints to the given stream.
+     * @param printStream the stream to which to print
+     */
+    public MappingPrinter(PrintStream printStream)
+    {
+        this.ps = printStream;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        String name    = programClass.getName();
+        String newName = ClassObfuscator.newClassName(programClass);
+
+        ps.println(ClassUtil.externalClassName(name) +
+                   " -> " +
+                   ClassUtil.externalClassName(newName) +
+                   ":");
+
+        // Print out the class members.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        String newName = MemberObfuscator.newMemberName(programField);
+        if (newName != null)
+        {
+            ps.println("    " +
+                       //lineNumberRange(programClass, programField) +
+                       ClassUtil.externalFullFieldDescription(
+                           0,
+                           programField.getName(programClass),
+                           programField.getDescriptor(programClass)) +
+                       " -> " +
+                       newName);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Special cases: <clinit> and <init> are always kept unchanged.
+        // We can ignore them here.
+        String name = programMethod.getName(programClass);
+        if (name.equals(ClassConstants.INTERNAL_METHOD_NAME_CLINIT) ||
+            name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            return;
+        }
+
+        String newName = MemberObfuscator.newMemberName(programMethod);
+        if (newName != null)
+        {
+            ps.println("    " +
+                       lineNumberRange(programClass, programMethod) +
+                       ClassUtil.externalFullMethodDescription(
+                           programClass.getName(),
+                           0,
+                           programMethod.getName(programClass),
+                           programMethod.getDescriptor(programClass)) +
+                       " -> " +
+                       newName);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the line number range of the given class member, followed by a
+     * colon, or just an empty String if no range is available.
+     */
+    private static String lineNumberRange(ProgramClass programClass, ProgramMember programMember)
+    {
+        String range = programMember.getLineNumberRange(programClass);
+        return range != null ?
+            (range + ":") :
+            "";
+    }
+}
diff --git a/src/proguard/obfuscate/MappingProcessor.java b/src/proguard/obfuscate/MappingProcessor.java
new file mode 100644
index 0000000..01c1809
--- /dev/null
+++ b/src/proguard/obfuscate/MappingProcessor.java
@@ -0,0 +1,79 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+
+/**
+ * This interface specifies methods to process name mappings between original
+ * classes and their obfuscated versions. The mappings are typically read
+ * from a mapping file.
+ *
+ * @see MappingReader
+ *
+ * @author Eric Lafortune
+ */
+public interface MappingProcessor
+{
+    /**
+     * Processes the given class name mapping.
+     *
+     * @param className    the original class name.
+     * @param newClassName the new class name.
+     * @return whether the processor is interested in receiving mappings of the
+     *         class members of this class.
+     */
+    public boolean processClassMapping(String className,
+                                       String newClassName);
+
+    /**
+     * Processes the given field name mapping.
+     *
+     * @param className    the original class name.
+     * @param fieldType    the original external field type.
+     * @param fieldName    the original field name.
+     * @param newFieldName the new field name.
+     */
+    public void processFieldMapping(String className,
+                                    String fieldType,
+                                    String fieldName,
+                                    String newFieldName);
+
+    /**
+     * Processes the given method name mapping.
+     *
+     * @param className        the original class name.
+     * @param firstLineNumber  the first line number of the method, or 0 if it
+     *                         is not known.
+     * @param lastLineNumber   the last line number of the method, or 0 if it
+     *                         is not known.
+     * @param methodReturnType the original external method return type.
+     * @param methodName       the original external method name.
+     * @param methodArguments  the original external method arguments.
+     * @param newMethodName    the new method name.
+     */
+    public void processMethodMapping(String className,
+                                     int    firstLineNumber,
+                                     int    lastLineNumber,
+                                     String methodReturnType,
+                                     String methodName,
+                                     String methodArguments,
+                                     String newMethodName);
+}
diff --git a/src/proguard/obfuscate/MappingReader.java b/src/proguard/obfuscate/MappingReader.java
new file mode 100644
index 0000000..24fd26c
--- /dev/null
+++ b/src/proguard/obfuscate/MappingReader.java
@@ -0,0 +1,199 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import java.io.*;
+
+
+/**
+ * This class can parse mapping files and invoke a processor for each of the
+ * mapping entries.
+ *
+ * @author Eric Lafortune
+ */
+public class MappingReader
+{
+    private final File mappingFile;
+
+
+    public MappingReader(File mappingFile)
+    {
+        this.mappingFile = mappingFile;
+    }
+
+
+    /**
+     * Reads the mapping file, presenting all of the encountered mapping entries
+     * to the given processor.
+     */
+    public void pump(MappingProcessor mappingProcessor) throws IOException
+    {
+        LineNumberReader reader = new LineNumberReader(
+                                  new BufferedReader(
+                                  new FileReader(mappingFile)));
+        try
+        {
+            String className = null;
+
+            // Read the subsequent class mappings and class member mappings.
+            while (true)
+            {
+                String line = reader.readLine();
+
+                if (line == null)
+                {
+                    break;
+                }
+
+                line = line.trim();
+
+                // The distinction between a class mapping and a class
+                // member mapping is the initial whitespace.
+                if (line.endsWith(":"))
+                {
+                    // Process the class mapping and remember the class's
+                    // old name.
+                    className = processClassMapping(line, mappingProcessor);
+                }
+                else if (className != null)
+                {
+                    // Process the class member mapping, in the context of the
+                    // current old class name.
+                    processClassMemberMapping(className, line, mappingProcessor);
+                }
+            }
+        }
+        catch (IOException ex)
+        {
+            throw new IOException("Can't process mapping file (" + ex.getMessage() + ")");
+        }
+        finally
+        {
+            try
+            {
+                reader.close();
+            }
+            catch (IOException ex)
+            {
+                // This shouldn't happen.
+            }
+        }
+    }
+
+
+    /**
+     * Parses the given line with a class mapping and processes the
+     * results with the given mapping processor. Returns the old class name,
+     * or null if any subsequent class member lines can be ignored.
+     */
+    private String processClassMapping(String           line,
+                                       MappingProcessor mappingProcessor)
+    {
+        // See if we can parse "___ -> ___:", containing the original
+        // class name and the new class name.
+
+        int arrowIndex = line.indexOf("->");
+        if (arrowIndex < 0)
+        {
+            return null;
+        }
+
+        int colonIndex = line.indexOf(':', arrowIndex + 2);
+        if (colonIndex < 0)
+        {
+            return null;
+        }
+
+        // Extract the elements.
+        String className    = line.substring(0, arrowIndex).trim();
+        String newClassName = line.substring(arrowIndex + 2, colonIndex).trim();
+
+        // Process this class name mapping.
+        boolean interested = mappingProcessor.processClassMapping(className, newClassName);
+
+        return interested ? className : null;
+    }
+
+
+    /**
+     * Parses the given line with a class member mapping and processes the
+     * results with the given mapping processor.
+     */
+    private void processClassMemberMapping(String           className,
+                                           String           line,
+                                           MappingProcessor mappingProcessor)
+    {
+        // See if we can parse "___:___:___ ___(___) -> ___",
+        // containing the optional line numbers, the return type, the original
+        // field/method name, optional arguments, and the new field/method name.
+
+        int colonIndex1    =                           line.indexOf(':');
+        int colonIndex2    = colonIndex1    < 0 ? -1 : line.indexOf(':', colonIndex1    + 1);
+        int spaceIndex     =                           line.indexOf(' ', colonIndex2    + 2);
+        int argumentIndex1 =                           line.indexOf('(', spaceIndex     + 1);
+        int argumentIndex2 = argumentIndex1 < 0 ? -1 : line.indexOf(')', argumentIndex1 + 1);
+        int arrowIndex     =                           line.indexOf("->", Math.max(spaceIndex, argumentIndex2) + 1);
+
+        if (spaceIndex < 0 ||
+            arrowIndex < 0)
+        {
+            return;
+        }
+
+        // Extract the elements.
+        String type    = line.substring(colonIndex2 + 1, spaceIndex).trim();
+        String name    = line.substring(spaceIndex + 1, argumentIndex1 >= 0 ? argumentIndex1 : arrowIndex).trim();
+        String newName = line.substring(arrowIndex + 2).trim();
+
+        // Process this class member mapping.
+        if (type.length()    > 0 &&
+            name.length()    > 0 &&
+            newName.length() > 0)
+        {
+            // Is it a field or a method?
+            if (argumentIndex2 < 0)
+            {
+                mappingProcessor.processFieldMapping(className, type, name, newName);
+            }
+            else
+            {
+                int firstLineNumber = 0;
+                int lastLineNumber  = 0;
+
+                if (colonIndex2 > 0)
+                {
+                    firstLineNumber = Integer.parseInt(line.substring(0, colonIndex1).trim());
+                    lastLineNumber  = Integer.parseInt(line.substring(colonIndex1 + 1, colonIndex2).trim());
+                }
+
+                String arguments = line.substring(argumentIndex1 + 1, argumentIndex2).trim();
+
+                mappingProcessor.processMethodMapping(className,
+                                                      firstLineNumber,
+                                                      lastLineNumber,
+                                                      type,
+                                                      name,
+                                                      arguments,
+                                                      newName);
+            }
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/MemberNameCleaner.java b/src/proguard/obfuscate/MemberNameCleaner.java
new file mode 100644
index 0000000..c41c59d
--- /dev/null
+++ b/src/proguard/obfuscate/MemberNameCleaner.java
@@ -0,0 +1,60 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This <code>MemberVisitor</code> clears the new names of the class members
+ * that it visits.
+ *
+ * @see MemberObfuscator
+ *
+ * @author Eric Lafortune
+ */
+public class MemberNameCleaner implements MemberVisitor
+{
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        MemberObfuscator.setNewMemberName(programField, null);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        MemberObfuscator.setNewMemberName(programMethod, null);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        MemberObfuscator.setNewMemberName(libraryField, null);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        MemberObfuscator.setNewMemberName(libraryMethod, null);
+    }
+}
diff --git a/src/proguard/obfuscate/MemberNameCollector.java b/src/proguard/obfuscate/MemberNameCollector.java
new file mode 100644
index 0000000..c248820
--- /dev/null
+++ b/src/proguard/obfuscate/MemberNameCollector.java
@@ -0,0 +1,106 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+import java.util.Map;
+
+/**
+ * This MemberVisitor collects all new (obfuscation) names of the members
+ * that it visits.
+ *
+ * @see MemberObfuscator
+ *
+ * @author Eric Lafortune
+ */
+public class MemberNameCollector
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final boolean allowAggressiveOverloading;
+    private final Map     descriptorMap;
+
+
+    /**
+     * Creates a new MemberNameCollector.
+     * @param allowAggressiveOverloading a flag that specifies whether class
+     *                                   members can be overloaded aggressively.
+     * @param descriptorMap              the map of descriptors to
+     *                                   [new name - old name] maps.
+     */
+    public MemberNameCollector(boolean allowAggressiveOverloading,
+                               Map     descriptorMap)
+    {
+        this.allowAggressiveOverloading = allowAggressiveOverloading;
+        this.descriptorMap              = descriptorMap;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        // Special cases: <clinit> and <init> are always kept unchanged.
+        // We can ignore them here.
+        String name = member.getName(clazz);
+        if (name.equals(ClassConstants.INTERNAL_METHOD_NAME_CLINIT) ||
+            name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            return;
+        }
+
+        // Get the member's new name.
+        String newName = MemberObfuscator.newMemberName(member);
+
+        // Remember it, if it has already been set.
+        if (newName != null)
+        {
+            // Get the member's descriptor.
+            String descriptor = member.getDescriptor(clazz);
+
+            // Check whether we're allowed to do aggressive overloading
+            if (!allowAggressiveOverloading)
+            {
+                // Trim the return argument from the descriptor if not.
+                // Works for fields and methods alike.
+                descriptor = descriptor.substring(0, descriptor.indexOf(')')+1);
+            }
+
+            // Put the [descriptor - new name] in the map,
+            // creating a new [new name - old name] map if necessary.
+            Map nameMap = MemberObfuscator.retrieveNameMap(descriptorMap, descriptor);
+
+            // Isn't there another original name for this new name, or should
+            // this original name get priority?
+            String otherName = (String)nameMap.get(newName);
+            if (otherName == null                              ||
+                MemberObfuscator.hasFixedNewMemberName(member) ||
+                name.compareTo(otherName) < 0)
+            {
+                // Remember not to use the new name again in this name space.
+                nameMap.put(newName, name);
+            }
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/MemberNameConflictFixer.java b/src/proguard/obfuscate/MemberNameConflictFixer.java
new file mode 100644
index 0000000..b9093a6
--- /dev/null
+++ b/src/proguard/obfuscate/MemberNameConflictFixer.java
@@ -0,0 +1,159 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+
+import java.util.Map;
+
+/**
+ * This MemberInfoVisitor solves obfuscation naming conflicts in all class
+ * members that it visits. It avoids names from the given descriptor map,
+ * delegating to the given obfuscator in order to get a new name if necessary.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberNameConflictFixer implements MemberVisitor
+{
+    private final boolean          allowAggressiveOverloading;
+    private final Map              descriptorMap;
+    private final WarningPrinter   warningPrinter;
+    private final MemberObfuscator memberObfuscator;
+
+
+    /**
+     * Creates a new MemberNameConflictFixer.
+     * @param allowAggressiveOverloading a flag that specifies whether class
+     *                                   members can be overloaded aggressively.
+     * @param descriptorMap              the map of descriptors to
+     *                                   [new name - old name] maps.
+     * @param warningPrinter             an optional warning printer to which
+     *                                   warnings about conflicting name
+     *                                   mappings can be printed.
+     * @param memberObfuscator           the obfuscator that can assign new
+     *                                   names to members with conflicting
+     *                                   names.
+     */
+    public MemberNameConflictFixer(boolean          allowAggressiveOverloading,
+                                   Map              descriptorMap,
+                                   WarningPrinter   warningPrinter,
+                                   MemberObfuscator memberObfuscator)
+    {
+        this.allowAggressiveOverloading = allowAggressiveOverloading;
+        this.descriptorMap              = descriptorMap;
+        this.warningPrinter             = warningPrinter;
+        this.memberObfuscator           = memberObfuscator;
+    }
+
+
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        visitMember(programClass, programField, true);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Special cases: <clinit> and <init> are always kept unchanged.
+        // We can ignore them here.
+        String name = programMethod.getName(programClass);
+        if (name.equals(ClassConstants.INTERNAL_METHOD_NAME_CLINIT) ||
+            name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            return;
+        }
+
+        visitMember(programClass, programMethod, false);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField) {}
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {}
+
+
+    /**
+     * Obfuscates the given class member.
+     * @param clazz   the class  of the given member.
+     * @param member  the class member to be obfuscated.
+     * @param isField specifies whether the class member is a field.
+     */
+    private void visitMember(Clazz   clazz,
+                             Member  member,
+                             boolean isField)
+    {
+        // Get the member's name and descriptor.
+        String name       = member.getName(clazz);
+        String descriptor = member.getDescriptor(clazz);
+
+        // Check whether we're allowed to overload aggressively.
+        if (!allowAggressiveOverloading)
+        {
+            // Trim the return argument from the descriptor if not.
+            // Works for fields and methods alike.
+            descriptor = descriptor.substring(0, descriptor.indexOf(')')+1);
+        }
+
+        // Get the name map.
+        Map nameMap = MemberObfuscator.retrieveNameMap(descriptorMap, descriptor);
+
+        // Get the member's new name.
+        String newName = MemberObfuscator.newMemberName(member);
+
+        // Get the expected old name for this new name.
+        String previousName = (String)nameMap.get(newName);
+        if (previousName != null &&
+            !name.equals(previousName))
+        {
+            // There's a conflict! A member (with a given old name) in a
+            // first namespace has received the same new name as this
+            // member (with a different old name) in a second name space,
+            // and now these two have to live together in this name space.
+            if (MemberObfuscator.hasFixedNewMemberName(member) &&
+                warningPrinter != null)
+            {
+                descriptor = member.getDescriptor(clazz);
+                warningPrinter.print(clazz.getName(),
+                                     "Warning: " + ClassUtil.externalClassName(clazz.getName()) +
+                                                   (isField ?
+                                                       ": field '" + ClassUtil.externalFullFieldDescription(0, name, descriptor) :
+                                                       ": method '" + ClassUtil.externalFullMethodDescription(clazz.getName(), 0, name, descriptor)) +
+                                     "' can't be mapped to '" + newName +
+                                     "' because it would conflict with " +
+                                     (isField ?
+                                         "field '" :
+                                         "method '" ) + previousName +
+                                     "', which is already being mapped to '" + newName + "'");
+            }
+
+            // Clear the conflicting name.
+            MemberObfuscator.setNewMemberName(member, null);
+
+            // Assign a new name.
+            member.accept(clazz, memberObfuscator);
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/MemberObfuscator.java b/src/proguard/obfuscate/MemberObfuscator.java
new file mode 100644
index 0000000..332b849
--- /dev/null
+++ b/src/proguard/obfuscate/MemberObfuscator.java
@@ -0,0 +1,230 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+
+import java.util.*;
+
+/**
+ * This MemberVisitor obfuscates all class members that it visits.
+ * It uses names from the given name factory. At the same time, it avoids names
+ * from the given descriptor map.
+ * <p>
+ * The class members must have been linked before applying this visitor.
+ *
+ * @see MethodLinker
+ *
+ * @author Eric Lafortune
+ */
+public class MemberObfuscator
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final boolean        allowAggressiveOverloading;
+    private final NameFactory    nameFactory;
+    private final Map            descriptorMap;
+
+
+    /**
+     * Creates a new MemberObfuscator.
+     * @param allowAggressiveOverloading a flag that specifies whether class
+     *                                   members can be overloaded aggressively.
+     * @param nameFactory                the factory that can produce
+     *                                   obfuscated member names.
+     * @param descriptorMap              the map of descriptors to
+     *                                   [new name - old name] maps.
+     */
+    public MemberObfuscator(boolean        allowAggressiveOverloading,
+                            NameFactory    nameFactory,
+                            Map            descriptorMap)
+    {
+        this.allowAggressiveOverloading = allowAggressiveOverloading;
+        this.nameFactory                = nameFactory;
+        this.descriptorMap              = descriptorMap;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        // Special cases: <clinit> and <init> are always kept unchanged.
+        // We can ignore them here.
+        String name = member.getName(clazz);
+        if (name.equals(ClassConstants.INTERNAL_METHOD_NAME_CLINIT) ||
+            name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            return;
+        }
+
+        // Get the member's descriptor.
+        String descriptor = member.getDescriptor(clazz);
+
+        // Check whether we're allowed to do aggressive overloading
+        if (!allowAggressiveOverloading)
+        {
+            // Trim the return argument from the descriptor if not.
+            // Works for fields and methods alike.
+            descriptor = descriptor.substring(0, descriptor.indexOf(')')+1);
+        }
+
+        // Get the name map, creating a new one if necessary.
+        Map nameMap = retrieveNameMap(descriptorMap, descriptor);
+
+        // Get the member's new name.
+        String newName = newMemberName(member);
+
+        // Assign a new one, if necessary.
+        if (newName == null)
+        {
+            // Find an acceptable new name.
+            nameFactory.reset();
+
+            do
+            {
+                newName = nameFactory.nextName();
+            }
+            while (nameMap.containsKey(newName));
+
+            // Remember not to use the new name again in this name space.
+            nameMap.put(newName, name);
+
+            // Assign the new name.
+            setNewMemberName(member, newName);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Gets the name map, based on the given map and a given descriptor.
+     * A new empty map is created if necessary.
+     * @param descriptorMap the map of descriptors to [new name - old name] maps.
+     * @param descriptor    the class member descriptor.
+     * @return the corresponding name map.
+     */
+    static Map retrieveNameMap(Map descriptorMap, String descriptor)
+    {
+        // See if we can find the nested map with this descriptor key.
+        Map nameMap = (Map)descriptorMap.get(descriptor);
+
+        // Create a new one if not.
+        if (nameMap == null)
+        {
+            nameMap = new HashMap();
+            descriptorMap.put(descriptor, nameMap);
+        }
+
+        return nameMap;
+    }
+
+
+    /**
+     * Assigns a fixed new name to the given class member.
+     * @param member the class member.
+     * @param name   the new name.
+     */
+    static void setFixedNewMemberName(Member member, String name)
+    {
+        VisitorAccepter lastVisitorAccepter = MethodLinker.lastVisitorAccepter(member);
+
+        if (!(lastVisitorAccepter instanceof LibraryMember) &&
+            !(lastVisitorAccepter instanceof MyFixedName))
+        {
+            lastVisitorAccepter.setVisitorInfo(new MyFixedName(name));
+        }
+        else
+        {
+            lastVisitorAccepter.setVisitorInfo(name);
+        }
+    }
+
+
+    /**
+     * Assigns a new name to the given class member.
+     * @param member the class member.
+     * @param name   the new name.
+     */
+    static void setNewMemberName(Member member, String name)
+    {
+        MethodLinker.lastVisitorAccepter(member).setVisitorInfo(name);
+    }
+
+
+    /**
+     * Returns whether the new name of the given class member is fixed.
+     * @param member the class member.
+     * @return whether its new name is fixed.
+     */
+    static boolean hasFixedNewMemberName(Member member)
+    {
+        VisitorAccepter lastVisitorAccepter = MethodLinker.lastVisitorAccepter(member);
+
+        return lastVisitorAccepter instanceof LibraryMember ||
+               lastVisitorAccepter instanceof MyFixedName;
+    }
+
+
+    /**
+     * Retrieves the new name of the given class member.
+     * @param member the class member.
+     * @return the class member's new name, or <code>null</code> if it doesn't
+     *         have one yet.
+     */
+    static String newMemberName(Member member)
+    {
+        return (String)MethodLinker.lastVisitorAccepter(member).getVisitorInfo();
+    }
+
+
+    /**
+     * This VisitorAccepter can be used to wrap a name string, to indicate that
+     * the name is fixed.
+     */
+    private static class MyFixedName implements VisitorAccepter
+    {
+        private String newName;
+
+
+        public MyFixedName(String newName)
+        {
+            this.newName = newName;
+        }
+
+
+        // Implementations for VisitorAccepter.
+
+        public Object getVisitorInfo()
+        {
+            return newName;
+        }
+
+
+        public void setVisitorInfo(Object visitorInfo)
+        {
+            newName = (String)visitorInfo;
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/MemberSpecialNameFilter.java b/src/proguard/obfuscate/MemberSpecialNameFilter.java
new file mode 100644
index 0000000..f83374b
--- /dev/null
+++ b/src/proguard/obfuscate/MemberSpecialNameFilter.java
@@ -0,0 +1,101 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to another given
+ * <code>MemberVisitor</code>, but only when the visited member has a
+ * special new name. A special name is a name that might have been produced by
+ * a <code>SpecialNameFactory</code>.
+ *
+ * @see MemberObfuscator
+ * @see SpecialNameFactory
+ *
+ * @author Eric Lafortune
+ */
+public class MemberSpecialNameFilter implements MemberVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new MemberSpecialNameFilter.
+     * @param memberVisitor the <code>MemberVisitor</code> to which
+     *                      visits will be delegated.
+     */
+    public MemberSpecialNameFilter(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (isSpecialName(programField))
+        {
+            memberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (isSpecialName(programMethod))
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (isSpecialName(libraryField))
+        {
+            memberVisitor.visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (isSpecialName(libraryMethod))
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the given class member has a special new name.
+     * @param member the class member.
+     */
+    private static boolean isSpecialName(Member member)
+    {
+        return SpecialNameFactory.isSpecialName(MemberObfuscator.newMemberName(member));
+    }
+}
diff --git a/src/proguard/obfuscate/MultiMappingProcessor.java b/src/proguard/obfuscate/MultiMappingProcessor.java
new file mode 100644
index 0000000..4074ff8
--- /dev/null
+++ b/src/proguard/obfuscate/MultiMappingProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+/**
+ * This MappingKeeper delegates all method calls to each MappingProcessor
+ * in a given list.
+ *
+ * @author Eric Lafortune
+ */
+public class MultiMappingProcessor implements MappingProcessor
+{
+    private final MappingProcessor[] mappingProcessors;
+
+
+    /**
+     * Creates a new MultiMappingProcessor.
+     * @param mappingProcessors the mapping processors to which method calls
+     *                          will be delegated.
+     */
+    public MultiMappingProcessor(MappingProcessor[] mappingProcessors)
+    {
+        this.mappingProcessors = mappingProcessors;
+    }
+
+
+    // Implementations for MappingProcessor.
+
+    public boolean processClassMapping(String className,
+                                       String newClassName)
+    {
+        boolean result = false;
+
+        for (int index = 0; index < mappingProcessors.length; index++)
+        {
+            result |= mappingProcessors[index].processClassMapping(className,
+                                                                   newClassName);
+        }
+
+        return result;
+    }
+
+
+    public void processFieldMapping(String className,
+                                    String fieldType,
+                                    String fieldName,
+                                    String newFieldName)
+    {
+        for (int index = 0; index < mappingProcessors.length; index++)
+        {
+            mappingProcessors[index].processFieldMapping(className,
+                                                         fieldType,
+                                                         fieldName,
+                                                         newFieldName);
+        }
+    }
+
+
+    public void processMethodMapping(String className,
+                                     int    firstLineNumber,
+                                     int    lastLineNumber,
+                                     String methodReturnType,
+                                     String methodName,
+                                     String methodArguments,
+                                     String newMethodName)
+    {
+        for (int index = 0; index < mappingProcessors.length; index++)
+        {
+            mappingProcessors[index].processMethodMapping(className,
+                                                          firstLineNumber,
+                                                          lastLineNumber,
+                                                          methodReturnType,
+                                                          methodName,
+                                                          methodArguments,
+                                                          newMethodName);
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/NameAndTypeShrinker.java b/src/proguard/obfuscate/NameAndTypeShrinker.java
new file mode 100644
index 0000000..1284c82
--- /dev/null
+++ b/src/proguard/obfuscate/NameAndTypeShrinker.java
@@ -0,0 +1,112 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.Constant;
+import proguard.classfile.editor.ConstantPoolRemapper;
+import proguard.classfile.visitor.ClassVisitor;
+
+
+/**
+ * This ClassVisitor removes NameAndType constant pool entries
+ * that are not marked as being used.
+ *
+ * @see NameAndTypeUsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class NameAndTypeShrinker implements ClassVisitor
+{
+    private int[]                constantIndexMap;
+    private final ConstantPoolRemapper constantPoolRemapper = new ConstantPoolRemapper();
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Shift the used constant pool entries together, filling out the
+        // index map.
+        programClass.u2constantPoolCount =
+            shrinkConstantPool(programClass.constantPool,
+                               programClass.u2constantPoolCount);
+
+
+        // Remap all constant pool references.
+        constantPoolRemapper.setConstantIndexMap(constantIndexMap);
+        constantPoolRemapper.visitProgramClass(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Removes all NameAndType entries that are not marked as being used
+     * from the given constant pool.
+     * @return the new number of entries.
+     */
+    private int shrinkConstantPool(Constant[] constantPool, int length)
+    {
+        // Create a new index map, if necessary.
+        if (constantIndexMap == null ||
+            constantIndexMap.length < length)
+        {
+            constantIndexMap = new int[length];
+        }
+
+        int     counter = 1;
+        boolean isUsed  = false;
+
+        // Shift the used constant pool entries together.
+        for (int index = 1; index < length; index++)
+        {
+            constantIndexMap[index] = counter;
+
+            Constant constant = constantPool[index];
+
+            // Don't update the flag if this is the second half of a long entry.
+            if (constant != null)
+            {
+                isUsed = constant.getTag() != ClassConstants.CONSTANT_NameAndType ||
+                         NameAndTypeUsageMarker.isUsed(constant);
+            }
+
+            if (isUsed)
+            {
+                constantPool[counter++] = constant;
+            }
+        }
+
+        // Clear the remaining constant pool elements.
+        for (int index = counter; index < length; index++)
+        {
+            constantPool[index] = null;
+        }
+
+        return counter;
+    }
+}
diff --git a/src/proguard/obfuscate/NameAndTypeUsageMarker.java b/src/proguard/obfuscate/NameAndTypeUsageMarker.java
new file mode 100644
index 0000000..cc779f0
--- /dev/null
+++ b/src/proguard/obfuscate/NameAndTypeUsageMarker.java
@@ -0,0 +1,135 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor marks all NameAndType constant pool entries that are
+ * being used in the program classes it visits.
+ *
+ * @see NameAndTypeShrinker
+ *
+ * @author Eric Lafortune
+ */
+public class NameAndTypeUsageMarker
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             AttributeVisitor
+{
+    // A visitor info flag to indicate the NameAndType constant pool entry is being used.
+    private static final Object USED = new Object();
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Mark the NameAndType entries referenced by all other constant pool
+        // entries.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Mark the NameAndType entries referenced by all EnclosingMethod
+        // attributes.
+        programClass.attributesAccept(this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        visitRefConstant(clazz, fieldrefConstant);
+    }
+
+
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant)
+    {
+        visitRefConstant(clazz, interfaceMethodrefConstant);
+    }
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        visitRefConstant(clazz, methodrefConstant);
+    }
+
+
+    private void visitRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        markNameAndTypeConstant(clazz, refConstant.u2nameAndTypeIndex);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        if (enclosingMethodAttribute.u2nameAndTypeIndex != 0)
+        {
+            markNameAndTypeConstant(clazz, enclosingMethodAttribute.u2nameAndTypeIndex);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the given UTF-8 constant pool entry of the given class.
+     */
+    private void markNameAndTypeConstant(Clazz clazz, int index)
+    {
+         markAsUsed((NameAndTypeConstant)((ProgramClass)clazz).getConstant(index));
+    }
+
+
+    /**
+     * Marks the given VisitorAccepter as being used.
+     * In this context, the VisitorAccepter will be a NameAndTypeConstant object.
+     */
+    private static void markAsUsed(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(USED);
+    }
+
+
+    /**
+     * Returns whether the given VisitorAccepter has been marked as being used.
+     * In this context, the VisitorAccepter will be a NameAndTypeConstant object.
+     */
+    static boolean isUsed(VisitorAccepter visitorAccepter)
+    {
+        return visitorAccepter.getVisitorInfo() == USED;
+    }
+}
diff --git a/src/proguard/obfuscate/NameFactory.java b/src/proguard/obfuscate/NameFactory.java
new file mode 100644
index 0000000..c64d1ad
--- /dev/null
+++ b/src/proguard/obfuscate/NameFactory.java
@@ -0,0 +1,34 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+/**
+ * This interfaces provides methods to generate unique sequences of names.
+ * The names must be valid Java identifiers.
+ *
+ * @author Eric Lafortune
+ */
+public interface NameFactory
+{
+    public void reset();
+
+    public String nextName();
+}
diff --git a/src/proguard/obfuscate/NameFactoryResetter.java b/src/proguard/obfuscate/NameFactoryResetter.java
new file mode 100644
index 0000000..b6ba6ad
--- /dev/null
+++ b/src/proguard/obfuscate/NameFactoryResetter.java
@@ -0,0 +1,59 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor resets a given name factory whenever it visits a class
+ * file.
+ *
+ * @author Eric Lafortune
+ */
+public class NameFactoryResetter implements ClassVisitor
+{
+    private final NameFactory nameFactory;
+
+
+    /**
+     * Creates a new NameFactoryResetter.
+     * @param nameFactory the name factory to be reset.
+     */
+    public NameFactoryResetter(NameFactory nameFactory)
+    {
+        this.nameFactory = nameFactory;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        nameFactory.reset();
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        nameFactory.reset();
+    }
+}
diff --git a/src/proguard/obfuscate/NameMarker.java b/src/proguard/obfuscate/NameMarker.java
new file mode 100644
index 0000000..2ce0ee9
--- /dev/null
+++ b/src/proguard/obfuscate/NameMarker.java
@@ -0,0 +1,166 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.visitor.*;
+
+
+/**
+ * This <code>ClassVisitor</code> and <code>MemberVisitor</code>
+ * marks names of the classes and class members it visits. The marked names
+ * will remain unchanged in the obfuscation step.
+ *
+ * @see ClassObfuscator
+ * @see MemberObfuscator
+ *
+ * @author Eric Lafortune
+ */
+class      NameMarker
+extends    SimplifiedVisitor
+implements ClassVisitor,
+           MemberVisitor,
+           AttributeVisitor,
+           InnerClassesInfoVisitor,
+           ConstantVisitor
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        keepClassName(programClass);
+
+        // Make sure any outer class names are kept as well.
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        keepClassName(libraryClass);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        keepFieldName(programClass, programField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        keepMethodName(programClass, programMethod);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        keepFieldName(libraryClass, libraryField);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        keepMethodName(libraryClass, libraryMethod);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // Make sure the outer class names are kept as well.
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        // Make sure the outer class name is kept as well.
+        int innerClassIndex = innerClassesInfo.u2innerClassIndex;
+        int outerClassIndex = innerClassesInfo.u2outerClassIndex;
+        if (innerClassIndex != 0 &&
+            outerClassIndex != 0 &&
+            clazz.getClassName(innerClassIndex).equals(clazz.getName()))
+        {
+            clazz.constantPoolEntryAccept(outerClassIndex, this);
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Make sure the outer class name is kept as well.
+        classConstant.referencedClassAccept(this);
+    }
+
+
+    // Small utility method.
+
+    /**
+     * Ensures the name of the given class name will be kept.
+     */
+    public void keepClassName(Clazz clazz)
+    {
+        ClassObfuscator.setNewClassName(clazz,
+                                        clazz.getName());
+    }
+
+
+    /**
+     * Ensures the name of the given field name will be kept.
+     */
+    private void keepFieldName(Clazz clazz, Field field)
+    {
+        MemberObfuscator.setFixedNewMemberName(field,
+                                               field.getName(clazz));
+    }
+
+
+    /**
+     * Ensures the name of the given method name will be kept.
+     */
+    private void keepMethodName(Clazz clazz, Method method)
+    {
+        String name = method.getName(clazz);
+
+        if (!name.equals(ClassConstants.INTERNAL_METHOD_NAME_CLINIT) &&
+            !name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            MemberObfuscator.setFixedNewMemberName(method,
+                                                   method.getName(clazz));
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/NumericNameFactory.java b/src/proguard/obfuscate/NumericNameFactory.java
new file mode 100644
index 0000000..cc21c4b
--- /dev/null
+++ b/src/proguard/obfuscate/NumericNameFactory.java
@@ -0,0 +1,49 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import java.util.*;
+
+
+/**
+ * This <code>NameFactory</code> generates unique numeric names, starting at
+ * "1".
+ *
+ * @author Eric Lafortune
+ */
+public class NumericNameFactory implements NameFactory
+{
+    private int index;
+
+
+    // Implementations for NameFactory.
+
+    public void reset()
+    {
+        index = 0;
+    }
+
+
+    public String nextName()
+    {
+        return Integer.toString(++index);
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/obfuscate/Obfuscator.java b/src/proguard/obfuscate/Obfuscator.java
new file mode 100644
index 0000000..dce563a
--- /dev/null
+++ b/src/proguard/obfuscate/Obfuscator.java
@@ -0,0 +1,429 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.*;
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.visitor.AllConstantVisitor;
+import proguard.classfile.editor.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.util.*;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * This class can perform obfuscation of class pools according to a given
+ * specification.
+ *
+ * @author Eric Lafortune
+ */
+public class Obfuscator
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new Obfuscator.
+     */
+    public Obfuscator(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Performs obfuscation of the given program class pool.
+     */
+    public void execute(ClassPool programClassPool,
+                        ClassPool libraryClassPool) throws IOException
+    {
+        // Check if we have at least some keep commands.
+        if (configuration.keep         == null &&
+            configuration.applyMapping == null &&
+            configuration.printMapping == null)
+        {
+            throw new IOException("You have to specify '-keep' options for the obfuscation step.");
+        }
+
+        // Clean up any old visitor info.
+        programClassPool.classesAccept(new ClassCleaner());
+        libraryClassPool.classesAccept(new ClassCleaner());
+
+        // If the class member names have to correspond globally,
+        // link all class members in all classes, otherwise
+        // link all non-private methods in all class hierarchies.
+        ClassVisitor memberInfoLinker =
+            configuration.useUniqueClassMemberNames ?
+                (ClassVisitor)new AllMemberVisitor(new MethodLinker()) :
+                (ClassVisitor)new BottomClassFilter(new MethodLinker());
+
+        programClassPool.classesAccept(memberInfoLinker);
+        libraryClassPool.classesAccept(memberInfoLinker);
+
+        // Create a visitor for marking the seeds.
+        NameMarker nameMarker = new NameMarker();
+        ClassPoolVisitor classPoolvisitor =
+            ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
+                                                                    nameMarker,
+                                                                    nameMarker,
+                                                                    false,
+                                                                    false,
+                                                                    true);
+        // Mark the seeds.
+        programClassPool.accept(classPoolvisitor);
+        libraryClassPool.accept(classPoolvisitor);
+
+        // All library classes and library class members keep their names.
+        libraryClassPool.classesAccept(nameMarker);
+        libraryClassPool.classesAccept(new AllMemberVisitor(nameMarker));
+
+        // Mark attributes that have to be kept.
+        AttributeUsageMarker requiredAttributeUsageMarker =
+            new AttributeUsageMarker();
+
+        AttributeVisitor optionalAttributeUsageMarker =
+            configuration.keepAttributes == null ? null :
+                new AttributeNameFilter(new ListParser(new NameParser()).parse(configuration.keepAttributes),
+                                        requiredAttributeUsageMarker);
+
+        programClassPool.classesAccept(
+            new AllAttributeVisitor(true,
+            new RequiredAttributeFilter(requiredAttributeUsageMarker,
+                                        optionalAttributeUsageMarker)));
+
+        // Remove the attributes that can be discarded. Note that the attributes
+        // may only be discarded after the seeds have been marked, since the
+        // configuration may rely on annotations.
+        programClassPool.classesAccept(new AttributeShrinker());
+
+        // Apply the mapping, if one has been specified. The mapping can
+        // override the names of library classes and of library class members.
+        if (configuration.applyMapping != null)
+        {
+            WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
+
+            MappingReader reader = new MappingReader(configuration.applyMapping);
+
+            MappingProcessor keeper =
+                new MultiMappingProcessor(new MappingProcessor[]
+                {
+                    new MappingKeeper(programClassPool, warningPrinter),
+                    new MappingKeeper(libraryClassPool, null),
+                });
+
+            reader.pump(keeper);
+
+            // Print out a summary of the warnings if necessary.
+            int mappingWarningCount = warningPrinter.getWarningCount();
+            if (mappingWarningCount > 0)
+            {
+                System.err.println("Warning: there were " + mappingWarningCount +
+                                                            " kept classes and class members that were remapped anyway.");
+                System.err.println("         You should adapt your configuration or edit the mapping file.");
+
+                if (!configuration.ignoreWarnings)
+                {
+                    System.err.println("         If you are sure this remapping won't hurt,");
+                    System.err.println("         you could try your luck using the '-ignorewarnings' option.");
+                    throw new IOException("Please correct the above warnings first.");
+                }
+            }
+        }
+
+        // Come up with new names for all classes.
+        DictionaryNameFactory classNameFactory = configuration.classObfuscationDictionary != null ?
+            new DictionaryNameFactory(configuration.classObfuscationDictionary, null) :
+            null;
+
+        DictionaryNameFactory packageNameFactory = configuration.packageObfuscationDictionary != null ?
+            new DictionaryNameFactory(configuration.packageObfuscationDictionary, null) :
+            null;
+
+        programClassPool.classesAccept(
+            new ClassObfuscator(programClassPool,
+                                classNameFactory,
+                                packageNameFactory,
+                                configuration.useMixedCaseClassNames,
+                                configuration.keepPackageNames,
+                                configuration.flattenPackageHierarchy,
+                                configuration.repackageClasses,
+                                configuration.allowAccessModification));
+
+        // Come up with new names for all class members.
+        NameFactory nameFactory = new SimpleNameFactory();
+
+        if (configuration.obfuscationDictionary != null)
+        {
+            nameFactory = new DictionaryNameFactory(configuration.obfuscationDictionary,
+                                                    nameFactory);
+        }
+
+        WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);
+
+        // Maintain a map of names to avoid [descriptor - new name - old name].
+        Map descriptorMap = new HashMap();
+
+        // Do the class member names have to be globally unique?
+        if (configuration.useUniqueClassMemberNames)
+        {
+            // Collect all member names in all classes.
+            programClassPool.classesAccept(
+                new AllMemberVisitor(
+                new MemberNameCollector(configuration.overloadAggressively,
+                                        descriptorMap)));
+
+            // Assign new names to all members in all classes.
+            programClassPool.classesAccept(
+                new AllMemberVisitor(
+                new MemberObfuscator(configuration.overloadAggressively,
+                                     nameFactory,
+                                     descriptorMap)));
+        }
+        else
+        {
+            // Come up with new names for all non-private class members.
+            programClassPool.classesAccept(
+                new MultiClassVisitor(new ClassVisitor[]
+                {
+                    // Collect all private member names in this class and down
+                    // the hierarchy.
+                    new ClassHierarchyTraveler(true, false, false, true,
+                    new AllMemberVisitor(
+                    new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
+                    new MemberNameCollector(configuration.overloadAggressively,
+                                            descriptorMap)))),
+
+                    // Collect all non-private member names anywhere in the hierarchy.
+                    new ClassHierarchyTraveler(true, true, true, true,
+                    new AllMemberVisitor(
+                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                    new MemberNameCollector(configuration.overloadAggressively,
+                                            descriptorMap)))),
+
+                    // Assign new names to all non-private members in this class.
+                    new AllMemberVisitor(
+                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                    new MemberObfuscator(configuration.overloadAggressively,
+                                         nameFactory,
+                                         descriptorMap))),
+
+                    // Clear the collected names.
+                    new MapCleaner(descriptorMap)
+                }));
+
+            // Come up with new names for all private class members.
+            programClassPool.classesAccept(
+                new MultiClassVisitor(new ClassVisitor[]
+                {
+                    // Collect all member names in this class.
+                    new AllMemberVisitor(
+                    new MemberNameCollector(configuration.overloadAggressively,
+                                            descriptorMap)),
+
+                    // Collect all non-private member names higher up the hierarchy.
+                    new ClassHierarchyTraveler(false, true, true, false,
+                    new AllMemberVisitor(
+                    new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                    new MemberNameCollector(configuration.overloadAggressively,
+                                            descriptorMap)))),
+
+                    // Assign new names to all private members in this class.
+                    new AllMemberVisitor(
+                    new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
+                    new MemberObfuscator(configuration.overloadAggressively,
+                                         nameFactory,
+                                         descriptorMap))),
+
+                    // Clear the collected names.
+                    new MapCleaner(descriptorMap)
+                }));
+        }
+
+        // Some class members may have ended up with conflicting names.
+        // Come up with new, globally unique names for them.
+        NameFactory specialNameFactory =
+            new SpecialNameFactory(new SimpleNameFactory());
+
+        // Collect a map of special names to avoid
+        // [descriptor - new name - old name].
+        Map specialDescriptorMap = new HashMap();
+
+        programClassPool.classesAccept(
+            new AllMemberVisitor(
+            new MemberSpecialNameFilter(
+            new MemberNameCollector(configuration.overloadAggressively,
+                                    specialDescriptorMap))));
+
+        libraryClassPool.classesAccept(
+            new AllMemberVisitor(
+            new MemberSpecialNameFilter(
+            new MemberNameCollector(configuration.overloadAggressively,
+                                    specialDescriptorMap))));
+
+        // Replace conflicting non-private member names with special names.
+        programClassPool.classesAccept(
+            new MultiClassVisitor(new ClassVisitor[]
+            {
+                // Collect all private member names in this class and down
+                // the hierarchy.
+                new ClassHierarchyTraveler(true, false, false, true,
+                new AllMemberVisitor(
+                new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
+                new MemberNameCollector(configuration.overloadAggressively,
+                                        descriptorMap)))),
+
+                // Collect all non-private member names in this class and
+                // higher up the hierarchy.
+                new ClassHierarchyTraveler(true, true, true, false,
+                new AllMemberVisitor(
+                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                new MemberNameCollector(configuration.overloadAggressively,
+                                        descriptorMap)))),
+
+                // Assign new names to all conflicting non-private members
+                // in this class and higher up the hierarchy.
+                new ClassHierarchyTraveler(true, true, true, false,
+                new AllMemberVisitor(
+                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                new MemberNameConflictFixer(configuration.overloadAggressively,
+                                            descriptorMap,
+                                            warningPrinter,
+                new MemberObfuscator(configuration.overloadAggressively,
+                                     specialNameFactory,
+                                     specialDescriptorMap))))),
+
+                // Clear the collected names.
+                new MapCleaner(descriptorMap)
+            }));
+
+        // Replace conflicting private member names with special names.
+        // This is only possible if those names were kept or mapped.
+        programClassPool.classesAccept(
+            new MultiClassVisitor(new ClassVisitor[]
+            {
+                // Collect all member names in this class.
+                new AllMemberVisitor(
+                new MemberNameCollector(configuration.overloadAggressively,
+                                        descriptorMap)),
+
+                // Collect all non-private member names higher up the hierarchy.
+                new ClassHierarchyTraveler(false, true, true, false,
+                new AllMemberVisitor(
+                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                new MemberNameCollector(configuration.overloadAggressively,
+                                        descriptorMap)))),
+
+                // Assign new names to all conflicting private members in this
+                // class.
+                new AllMemberVisitor(
+                new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
+                new MemberNameConflictFixer(configuration.overloadAggressively,
+                                            descriptorMap,
+                                            warningPrinter,
+                new MemberObfuscator(configuration.overloadAggressively,
+                                     specialNameFactory,
+                                     specialDescriptorMap)))),
+
+                // Clear the collected names.
+                new MapCleaner(descriptorMap)
+            }));
+
+        // Print out any warnings about member name conflicts.
+        int warningCount = warningPrinter.getWarningCount();
+        if (warningCount > 0)
+        {
+            System.err.println("Warning: there were " + warningCount +
+                               " conflicting class member name mappings.");
+            System.err.println("         Your configuration may be inconsistent.");
+
+            if (!configuration.ignoreWarnings)
+            {
+                System.err.println("         If you are sure the conflicts are harmless,");
+                System.err.println("         you could try your luck using the '-ignorewarnings' option.");
+                throw new IOException("Please correct the above warnings first.");
+            }
+        }
+
+        // Print out the mapping, if requested.
+        if (configuration.printMapping != null)
+        {
+            PrintStream ps = isFile(configuration.printMapping) ?
+                new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printMapping))) :
+                System.out;
+
+            // Print out items that will be removed.
+            programClassPool.classesAcceptAlphabetically(new MappingPrinter(ps));
+
+            if (ps != System.out)
+            {
+                ps.close();
+            }
+        }
+
+        // Actually apply the new names.
+        programClassPool.classesAccept(new ClassRenamer());
+        libraryClassPool.classesAccept(new ClassRenamer());
+
+        // Update all references to these new names.
+        programClassPool.classesAccept(new ClassReferenceFixer(false));
+        libraryClassPool.classesAccept(new ClassReferenceFixer(false));
+        programClassPool.classesAccept(new MemberReferenceFixer());
+
+        // Make package visible elements public or protected, if obfuscated
+        // classes are being repackaged aggressively.
+        if (configuration.repackageClasses != null &&
+            configuration.allowAccessModification)
+        {
+            programClassPool.classesAccept(
+                new AllConstantVisitor(
+                new AccessFixer()));
+        }
+
+        // Rename the source file attributes, if requested.
+        if (configuration.newSourceFileAttribute != null)
+        {
+            programClassPool.classesAccept(new SourceFileRenamer(configuration.newSourceFileAttribute));
+        }
+
+        // Mark NameAndType constant pool entries that have to be kept
+        // and remove the other ones.
+        programClassPool.classesAccept(new NameAndTypeUsageMarker());
+        programClassPool.classesAccept(new NameAndTypeShrinker());
+
+        // Mark Utf8 constant pool entries that have to be kept
+        // and remove the other ones.
+        programClassPool.classesAccept(new Utf8UsageMarker());
+        programClassPool.classesAccept(new Utf8Shrinker());
+    }
+
+
+    /**
+     * Returns whether the given file is actually a file, or just a placeholder
+     * for the standard output.
+     */
+    private boolean isFile(File file)
+    {
+        return file.getPath().length() > 0;
+    }
+}
diff --git a/src/proguard/obfuscate/SimpleNameFactory.java b/src/proguard/obfuscate/SimpleNameFactory.java
new file mode 100644
index 0000000..bce22de
--- /dev/null
+++ b/src/proguard/obfuscate/SimpleNameFactory.java
@@ -0,0 +1,156 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import java.util.*;
+
+
+/**
+ * This <code>NameFactory</code> generates unique short names, using mixed-case
+ * characters or lower-case characters only.
+ *
+ * @author Eric Lafortune
+ */
+public class SimpleNameFactory implements NameFactory
+{
+    private static final int CHARACTER_COUNT = 26;
+
+    private static final List cachedMixedCaseNames = new ArrayList();
+    private static final List cachedLowerCaseNames = new ArrayList();
+
+    private final boolean generateMixedCaseNames;
+    private int     index = 0;
+
+
+    /**
+     * Creates a new <code>SimpleNameFactory</code> that generates mixed-case names.
+     */
+    public SimpleNameFactory()
+    {
+        this(true);
+    }
+
+
+    /**
+     * Creates a new <code>SimpleNameFactory</code>.
+     * @param generateMixedCaseNames a flag to indicate whether the generated
+     *                               names will be mixed-case, or lower-case only.
+     */
+    public SimpleNameFactory(boolean generateMixedCaseNames)
+    {
+        this.generateMixedCaseNames = generateMixedCaseNames;
+    }
+
+
+    // Implementations for NameFactory.
+
+    public void reset()
+    {
+        index = 0;
+    }
+
+
+    public String nextName()
+    {
+        return name(index++);
+    }
+
+
+    /**
+     * Returns the name at the given index.
+     */
+    private String name(int index)
+    {
+        // Which cache do we need?
+        List cachedNames = generateMixedCaseNames ?
+            cachedMixedCaseNames :
+            cachedLowerCaseNames;
+
+        // Do we have the name in the cache?
+        if (index < cachedNames.size())
+        {
+            return (String)cachedNames.get(index);
+        }
+
+        // Create a new name and cache it.
+        String name = newName(index);
+        cachedNames.add(index, name);
+
+        return name;
+    }
+
+
+    /**
+     * Creates and returns the name at the given index.
+     */
+    private String newName(int index)
+    {
+        // If we're allowed to generate mixed-case names, we can use twice as
+        // many characters.
+        int totalCharacterCount = generateMixedCaseNames ?
+            2 * CHARACTER_COUNT :
+            CHARACTER_COUNT;
+
+        int baseIndex = index / totalCharacterCount;
+        int offset    = index % totalCharacterCount;
+
+        char newChar = charAt(offset);
+
+        String newName = baseIndex == 0 ?
+            new String(new char[] { newChar }) :
+            (name(baseIndex-1) + newChar);
+
+        return newName;
+    }
+
+
+    /**
+     * Returns the character with the given index, between 0 and the number of
+     * acceptable characters.
+     */
+    private char charAt(int index)
+    {
+        return (char)((index < CHARACTER_COUNT ? 'a' - 0               :
+                                                 'A' - CHARACTER_COUNT) + index);
+    }
+
+
+    public static void main(String[] args)
+    {
+        System.out.println("Some mixed-case names:");
+        printNameSamples(new SimpleNameFactory(true), 60);
+        System.out.println("Some lower-case names:");
+        printNameSamples(new SimpleNameFactory(false), 60);
+        System.out.println("Some more mixed-case names:");
+        printNameSamples(new SimpleNameFactory(true), 80);
+        System.out.println("Some more lower-case names:");
+        printNameSamples(new SimpleNameFactory(false), 80);
+    }
+
+
+    private static void printNameSamples(SimpleNameFactory factory, int count)
+    {
+        for (int counter = 0; counter < count; counter++)
+        {
+            System.out.println("  ["+factory.nextName()+"]");
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/SourceFileRenamer.java b/src/proguard/obfuscate/SourceFileRenamer.java
new file mode 100644
index 0000000..cbf1b63
--- /dev/null
+++ b/src/proguard/obfuscate/SourceFileRenamer.java
@@ -0,0 +1,84 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.ConstantPoolEditor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor changes the name stored in the source file attributes
+ * and source dir attributes of the classes that it visits, if the
+ * attributes are present.
+ *
+ * @author Eric Lafortune
+ */
+public class SourceFileRenamer
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             AttributeVisitor
+{
+    private final String newSourceFileAttribute;
+
+
+    /**
+     * Creates a new SourceFileRenamer.
+     * @param newSourceFileAttribute the new string to be put in the source file
+     *                               attributes.
+     */
+    public SourceFileRenamer(String newSourceFileAttribute)
+    {
+        this.newSourceFileAttribute = newSourceFileAttribute;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Only visit the class attributes.
+        programClass.attributesAccept(this);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        // Fix the source file attribute.
+        sourceFileAttribute.u2sourceFileIndex =
+            new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSourceFileAttribute);
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        // Fix the source file attribute.
+        sourceDirAttribute.u2sourceDirIndex =
+            new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSourceFileAttribute);
+    }
+}
diff --git a/src/proguard/obfuscate/SpecialNameFactory.java b/src/proguard/obfuscate/SpecialNameFactory.java
new file mode 100644
index 0000000..a5431ca
--- /dev/null
+++ b/src/proguard/obfuscate/SpecialNameFactory.java
@@ -0,0 +1,83 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+/**
+ * This <code>NameFactory</code> generates names that are special, by appending
+ * a suffix.
+ *
+ * @author Eric Lafortune
+ */
+public class SpecialNameFactory implements NameFactory
+{
+    private static final char SPECIAL_SUFFIX = '_';
+
+
+    private final NameFactory nameFactory;
+
+
+    /**
+     * Creates a new <code>SpecialNameFactory</code>.
+     * @param nameFactory the name factory from which original names will be
+     *                    retrieved.
+     */
+    public SpecialNameFactory(NameFactory nameFactory)
+    {
+        this.nameFactory = nameFactory;
+    }
+
+
+    // Implementations for NameFactory.
+
+    public void reset()
+    {
+        nameFactory.reset();
+    }
+
+
+    public String nextName()
+    {
+        return nameFactory.nextName() + SPECIAL_SUFFIX;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the given name is special.
+     */
+    static boolean isSpecialName(String name)
+    {
+        return name != null &&
+               name.charAt(name.length()-1) == SPECIAL_SUFFIX;
+    }
+
+
+    public static void main(String[] args)
+    {
+        SpecialNameFactory factory = new SpecialNameFactory(new SimpleNameFactory());
+
+        for (int counter = 0; counter < 50; counter++)
+        {
+            System.out.println("["+factory.nextName()+"]");
+        }
+    }
+}
diff --git a/src/proguard/obfuscate/Utf8Shrinker.java b/src/proguard/obfuscate/Utf8Shrinker.java
new file mode 100644
index 0000000..87ada80
--- /dev/null
+++ b/src/proguard/obfuscate/Utf8Shrinker.java
@@ -0,0 +1,110 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.Constant;
+import proguard.classfile.editor.ConstantPoolRemapper;
+import proguard.classfile.visitor.ClassVisitor;
+
+
+/**
+ * This ClassVisitor removes UTF-8 constant pool entries that are not marked
+ * as being used.
+ *
+ * @see Utf8UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class Utf8Shrinker implements ClassVisitor
+{
+    private int[]                constantIndexMap     = new int[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE];
+    private final ConstantPoolRemapper constantPoolRemapper = new ConstantPoolRemapper();
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Shift the used constant pool entries together, filling out the
+        // index map.
+        programClass.u2constantPoolCount =
+            shrinkConstantPool(programClass.constantPool,
+                               programClass.u2constantPoolCount);
+
+        // Remap all constant pool references.
+        constantPoolRemapper.setConstantIndexMap(constantIndexMap);
+        constantPoolRemapper.visitProgramClass(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Removes all UTF-8 entries that are not marked as being used
+     * from the given constant pool.
+     * @return the new number of entries.
+     */
+    private int shrinkConstantPool(Constant[] constantPool, int length)
+    {
+        // Create a new index map, if necessary.
+        if (constantIndexMap.length < length)
+        {
+            constantIndexMap = new int[length];
+        }
+
+        int     counter = 1;
+        boolean isUsed  = false;
+
+        // Shift the used constant pool entries together.
+        for (int index = 1; index < length; index++)
+        {
+            constantIndexMap[index] = counter;
+
+            Constant constant = constantPool[index];
+
+            // Don't update the flag if this is the second half of a long entry.
+            if (constant != null)
+            {
+                isUsed = constant.getTag() != ClassConstants.CONSTANT_Utf8 ||
+                         Utf8UsageMarker.isUsed(constant);
+            }
+
+            if (isUsed)
+            {
+                constantPool[counter++] = constant;
+            }
+        }
+
+        // Clear the remaining constant pool elements.
+        for (int index = counter; index < length; index++)
+        {
+            constantPool[index] = null;
+        }
+
+        return counter;
+    }
+}
diff --git a/src/proguard/obfuscate/Utf8UsageMarker.java b/src/proguard/obfuscate/Utf8UsageMarker.java
new file mode 100644
index 0000000..c59ebb8
--- /dev/null
+++ b/src/proguard/obfuscate/Utf8UsageMarker.java
@@ -0,0 +1,392 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.obfuscate;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor marks all UTF-8 constant pool entries that are
+ * being used in the program classes it visits.
+ *
+ * @see Utf8Shrinker
+ *
+ * @author Eric Lafortune
+ */
+public class Utf8UsageMarker
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             ConstantVisitor,
+             AttributeVisitor,
+             InnerClassesInfoVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    // A visitor info flag to indicate the UTF-8 constant pool entry is being used.
+    private static final Object USED = new Object();
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Mark the UTF-8 entries referenced by the other constant pool entries.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Mark the UTF-8 entries referenced by the fields and methods.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+
+        // Mark the UTF-8 entries referenced by the attributes.
+        programClass.attributesAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        // Mark the name and descriptor UTF-8 entries.
+        markCpUtf8Entry(programClass, programMember.u2nameIndex);
+        markCpUtf8Entry(programClass, programMember.u2descriptorIndex);
+
+        // Mark the UTF-8 entries referenced by the attributes.
+        programMember.attributesAccept(programClass, this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        markCpUtf8Entry(clazz, stringConstant.u2stringIndex);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        markCpUtf8Entry(clazz, classConstant.u2nameIndex);
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        markCpUtf8Entry(clazz, nameAndTypeConstant.u2nameIndex);
+        markCpUtf8Entry(clazz, nameAndTypeConstant.u2descriptorIndex);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        // This is the best we can do for unknown attributes.
+        markCpUtf8Entry(clazz, unknownAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        markCpUtf8Entry(clazz, sourceFileAttribute.u2attributeNameIndex);
+
+        markCpUtf8Entry(clazz, sourceFileAttribute.u2sourceFileIndex);
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        markCpUtf8Entry(clazz, sourceDirAttribute.u2attributeNameIndex);
+
+        markCpUtf8Entry(clazz, sourceDirAttribute.u2sourceDirIndex);
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        markCpUtf8Entry(clazz, innerClassesAttribute.u2attributeNameIndex);
+
+        // Mark the UTF-8 entries referenced by the inner classes.
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        markCpUtf8Entry(clazz, enclosingMethodAttribute.u2attributeNameIndex);
+
+        // These entries have already been marked in the constant pool.
+        //clazz.constantPoolEntryAccept(this, enclosingMethodAttribute.u2classIndex);
+        //clazz.constantPoolEntryAccept(this, enclosingMethodAttribute.u2nameAndTypeIndex);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        markCpUtf8Entry(clazz, deprecatedAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        markCpUtf8Entry(clazz, syntheticAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        markCpUtf8Entry(clazz, signatureAttribute.u2attributeNameIndex);
+
+        markCpUtf8Entry(clazz, signatureAttribute.u2signatureIndex);
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        markCpUtf8Entry(clazz, constantValueAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        markCpUtf8Entry(clazz, exceptionsAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        markCpUtf8Entry(clazz, codeAttribute.u2attributeNameIndex);
+
+        // Mark the UTF-8 entries referenced by the attributes.
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        markCpUtf8Entry(clazz, stackMapAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        markCpUtf8Entry(clazz, stackMapTableAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        markCpUtf8Entry(clazz, lineNumberTableAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        markCpUtf8Entry(clazz, localVariableTableAttribute.u2attributeNameIndex);
+
+        // Mark the UTF-8 entries referenced by the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        markCpUtf8Entry(clazz, localVariableTypeTableAttribute.u2attributeNameIndex);
+
+        // Mark the UTF-8 entries referenced by the local variable types.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        markCpUtf8Entry(clazz, annotationsAttribute.u2attributeNameIndex);
+
+        // Mark the UTF-8 entries referenced by the annotations.
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        markCpUtf8Entry(clazz, parameterAnnotationsAttribute.u2attributeNameIndex);
+
+        // Mark the UTF-8 entries referenced by the annotations.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        markCpUtf8Entry(clazz, annotationDefaultAttribute.u2attributeNameIndex);
+
+        // Mark the UTF-8 entries referenced by the element value.
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        if (innerClassesInfo.u2innerNameIndex != 0)
+        {
+            markCpUtf8Entry(clazz, innerClassesInfo.u2innerNameIndex);
+        }
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        markCpUtf8Entry(clazz, localVariableInfo.u2nameIndex);
+        markCpUtf8Entry(clazz, localVariableInfo.u2descriptorIndex);
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        markCpUtf8Entry(clazz, localVariableTypeInfo.u2nameIndex);
+        markCpUtf8Entry(clazz, localVariableTypeInfo.u2signatureIndex);
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        markCpUtf8Entry(clazz, annotation.u2typeIndex);
+
+        // Mark the UTF-8 entries referenced by the element values.
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        if (constantElementValue.u2elementNameIndex != 0)
+        {
+            markCpUtf8Entry(clazz, constantElementValue.u2elementNameIndex);
+        }
+
+        // Only the string constant element value refers to a UTF-8 entry.
+        if (constantElementValue.u1tag == ClassConstants.ELEMENT_VALUE_STRING_CONSTANT)
+        {
+            markCpUtf8Entry(clazz, constantElementValue.u2constantValueIndex);
+        }
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        if (enumConstantElementValue.u2elementNameIndex != 0)
+        {
+            markCpUtf8Entry(clazz, enumConstantElementValue.u2elementNameIndex);
+        }
+
+        markCpUtf8Entry(clazz, enumConstantElementValue.u2typeNameIndex);
+        markCpUtf8Entry(clazz, enumConstantElementValue.u2constantNameIndex);
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        if (classElementValue.u2elementNameIndex != 0)
+        {
+            markCpUtf8Entry(clazz, classElementValue.u2elementNameIndex);
+        }
+
+        markCpUtf8Entry(clazz, classElementValue.u2classInfoIndex);
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        if (annotationElementValue.u2elementNameIndex != 0)
+        {
+            markCpUtf8Entry(clazz, annotationElementValue.u2elementNameIndex);
+        }
+
+        // Mark the UTF-8 entries referenced by the annotation.
+        annotationElementValue.annotationAccept(clazz, this);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        if (arrayElementValue.u2elementNameIndex != 0)
+        {
+            markCpUtf8Entry(clazz, arrayElementValue.u2elementNameIndex);
+        }
+
+        // Mark the UTF-8 entries referenced by the element values.
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the given UTF-8 constant pool entry of the given class.
+     */
+    private void markCpUtf8Entry(Clazz clazz, int index)
+    {
+         markAsUsed((Utf8Constant)((ProgramClass)clazz).getConstant(index));
+    }
+
+
+    /**
+     * Marks the given VisitorAccepter as being used.
+     * In this context, the VisitorAccepter will be a Utf8Constant object.
+     */
+    private static void markAsUsed(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(USED);
+    }
+
+
+    /**
+     * Returns whether the given VisitorAccepter has been marked as being used.
+     * In this context, the VisitorAccepter will be a Utf8Constant object.
+     */
+    static boolean isUsed(VisitorAccepter visitorAccepter)
+    {
+        return visitorAccepter.getVisitorInfo() == USED;
+    }
+}
diff --git a/src/proguard/obfuscate/package.html b/src/proguard/obfuscate/package.html
new file mode 100644
index 0000000..a82d266
--- /dev/null
+++ b/src/proguard/obfuscate/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains classes to perform obfuscation of class files.
+</body>
diff --git a/src/proguard/optimize/ChangedCodePrinter.java b/src/proguard/optimize/ChangedCodePrinter.java
new file mode 100644
index 0000000..67a79ab
--- /dev/null
+++ b/src/proguard/optimize/ChangedCodePrinter.java
@@ -0,0 +1,291 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.ClassUtil;
+
+/**
+ * This AttributeVisitor delegates its call to another AttributeVisitor, and
+ * prints out the code if the other visitor has changed it.
+ *
+ * @author Eric Lafortune
+ */
+public class ChangedCodePrinter
+implements   AttributeVisitor
+{
+    private final AttributeVisitor attributeVisitor;
+
+
+    public ChangedCodePrinter(AttributeVisitor attributeVisitor)
+    {
+        this.attributeVisitor = attributeVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        attributeVisitor.visitUnknownAttribute(clazz, unknownAttribute);
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        attributeVisitor.visitSourceFileAttribute(clazz, sourceFileAttribute);
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        attributeVisitor.visitSourceDirAttribute(clazz, sourceDirAttribute);
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        attributeVisitor.visitInnerClassesAttribute(clazz, innerClassesAttribute);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        attributeVisitor.visitEnclosingMethodAttribute(clazz, enclosingMethodAttribute);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        attributeVisitor.visitDeprecatedAttribute(clazz, deprecatedAttribute);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        attributeVisitor.visitSyntheticAttribute(clazz, syntheticAttribute);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute syntheticAttribute)
+    {
+        attributeVisitor.visitSignatureAttribute(clazz, syntheticAttribute);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Field field, DeprecatedAttribute deprecatedAttribute)
+    {
+        attributeVisitor.visitDeprecatedAttribute(clazz, field, deprecatedAttribute);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Field field, SyntheticAttribute syntheticAttribute)
+    {
+        attributeVisitor.visitSyntheticAttribute(clazz, field, syntheticAttribute);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Field field, SignatureAttribute syntheticAttribute)
+    {
+        attributeVisitor.visitSignatureAttribute(clazz, field, syntheticAttribute);
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, Method method, DeprecatedAttribute deprecatedAttribute)
+    {
+        attributeVisitor.visitDeprecatedAttribute(clazz, method, deprecatedAttribute);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, Method method, SyntheticAttribute syntheticAttribute)
+    {
+        attributeVisitor.visitSyntheticAttribute(clazz, method, syntheticAttribute);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute syntheticAttribute)
+    {
+        attributeVisitor.visitSignatureAttribute(clazz, method, syntheticAttribute);
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        attributeVisitor.visitConstantValueAttribute(clazz, field, constantValueAttribute);
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        attributeVisitor.visitExceptionsAttribute(clazz, method, exceptionsAttribute);
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        attributeVisitor.visitStackMapAttribute(clazz, method, codeAttribute, stackMapAttribute);
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        attributeVisitor.visitStackMapTableAttribute(clazz, method, codeAttribute, stackMapTableAttribute);
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        attributeVisitor.visitLineNumberTableAttribute(clazz, method, codeAttribute, lineNumberTableAttribute);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        attributeVisitor.visitLocalVariableTableAttribute(clazz, method, codeAttribute, localVariableTableAttribute);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        attributeVisitor.visitLocalVariableTypeTableAttribute(clazz, method, codeAttribute, localVariableTypeTableAttribute);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, runtimeVisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, runtimeInvisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, field, runtimeVisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Field field, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, field, runtimeInvisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeVisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute)
+    {
+        attributeVisitor.visitRuntimeVisibleAnnotationsAttribute(clazz, method, runtimeVisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute)
+    {
+        attributeVisitor.visitRuntimeInvisibleAnnotationsAttribute(clazz, method, runtimeInvisibleAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeVisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeVisibleParameterAnnotationsAttribute runtimeVisibleParameterAnnotationsAttribute)
+    {
+        attributeVisitor.visitRuntimeVisibleParameterAnnotationsAttribute(clazz, method, runtimeVisibleParameterAnnotationsAttribute);
+    }
+
+
+    public void visitRuntimeInvisibleParameterAnnotationsAttribute(Clazz clazz, Method method, RuntimeInvisibleParameterAnnotationsAttribute runtimeInvisibleParameterAnnotationsAttribute)
+    {
+        attributeVisitor.visitRuntimeInvisibleParameterAnnotationsAttribute(clazz, method, runtimeInvisibleParameterAnnotationsAttribute);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        attributeVisitor.visitAnnotationDefaultAttribute(clazz, method, annotationDefaultAttribute);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        byte[] code    = codeAttribute.code;
+        byte[] oldCode = new byte[code.length];
+
+        // Copy the current code.
+        System.arraycopy(code, 0, oldCode, 0, codeAttribute.u4codeLength);
+
+        // Delegate to the real visitor.
+        attributeVisitor.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Check if the code has changed.
+        if (codeHasChanged(codeAttribute, oldCode))
+        {
+            printChangedCode(clazz, method, codeAttribute, oldCode);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private boolean codeHasChanged(CodeAttribute codeAttribute, byte[] oldCode)
+    {
+        if (oldCode.length != codeAttribute.u4codeLength)
+        {
+            return true;
+        }
+
+        for (int index = 0; index < codeAttribute.u4codeLength; index++)
+        {
+            if (oldCode[index] != codeAttribute.code[index])
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    private void printChangedCode(Clazz         clazz,
+                                  Method        method,
+                                  CodeAttribute codeAttribute,
+                                  byte[]        oldCode)
+    {
+        System.out.println("Class "+ClassUtil.externalClassName(clazz.getName()));
+        System.out.println("Method "+ClassUtil.externalFullMethodDescription(clazz.getName(),
+                                                                             0,
+                                                                             method.getName(clazz),
+                                                                             method.getDescriptor(clazz)));
+
+        for (int index = 0; index < codeAttribute.u4codeLength; index++)
+        {
+            System.out.println(
+                (oldCode[index] == codeAttribute.code[index]? "  -- ":"  => ")+
+                index+": "+
+                Integer.toHexString(0x100|oldCode[index]           &0xff).substring(1)+" "+
+                Integer.toHexString(0x100|codeAttribute.code[index]&0xff).substring(1));
+        }
+    }
+}
diff --git a/src/proguard/optimize/ConstantMemberFilter.java b/src/proguard/optimize/ConstantMemberFilter.java
new file mode 100644
index 0000000..56437c3
--- /dev/null
+++ b/src/proguard/optimize/ConstantMemberFilter.java
@@ -0,0 +1,77 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.evaluation.value.Value;
+import proguard.optimize.evaluation.StoringInvocationUnit;
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to program class members
+ * to another given <code>MemberVisitor</code>, but only when the visited
+ * class member has been marked as a constant.
+ *
+ * @see StoringInvocationUnit
+ * @author Eric Lafortune
+ */
+public class ConstantMemberFilter
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final MemberVisitor constantMemberVisitor;
+
+
+    /**
+     * Creates a new ConstantMemberFilter.
+     * @param constantMemberVisitor the <code>MemberVisitor</code> to which
+     *                              visits to constant members will be delegated.
+     */
+    public ConstantMemberFilter(MemberVisitor constantMemberVisitor)
+    {
+        this.constantMemberVisitor = constantMemberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        Value value = StoringInvocationUnit.getFieldValue(programField);
+        if (value != null &&
+            value.isParticular())
+        {
+            constantMemberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        Value value = StoringInvocationUnit.getMethodReturnValue(programMethod);
+        if (value != null &&
+            value.isParticular())
+        {
+            constantMemberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+}
diff --git a/src/proguard/optimize/ConstantParameterFilter.java b/src/proguard/optimize/ConstantParameterFilter.java
new file mode 100644
index 0000000..24a7040
--- /dev/null
+++ b/src/proguard/optimize/ConstantParameterFilter.java
@@ -0,0 +1,79 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.evaluation.value.Value;
+import proguard.optimize.evaluation.StoringInvocationUnit;
+import proguard.optimize.info.ParameterUsageMarker;
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to program methods
+ * to another given <code>MemberVisitor</code>, for each method parameter
+ * that has been marked as constant.
+ *
+ * @see StoringInvocationUnit
+ * @author Eric Lafortune
+ */
+public class ConstantParameterFilter
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final MemberVisitor constantParameterVisitor;
+
+
+    /**
+     * Creates a new ConstantParameterFilter.
+     * @param constantParameterVisitor the <code>MemberVisitor</code> to which
+     *                                 visits will be delegated.
+     */
+    public ConstantParameterFilter(MemberVisitor constantParameterVisitor)
+    {
+        this.constantParameterVisitor = constantParameterVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // All parameters of non-static methods are shifted by one in the local
+        // variable frame.
+        int firstParameterIndex =
+            (programMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0 ?
+                0 : 1;
+
+        int parameterCount =
+            ClassUtil.internalMethodParameterCount(programMethod.getDescriptor(programClass));
+
+        for (int index = firstParameterIndex; index < parameterCount; index++)
+        {
+            Value value = StoringInvocationUnit.getMethodParameterValue(programMethod, index);
+            if (value != null &&
+                value.isParticular())
+            {
+                constantParameterVisitor.visitProgramMethod(programClass, programMethod);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/DuplicateInitializerFixer.java b/src/proguard/optimize/DuplicateInitializerFixer.java
new file mode 100644
index 0000000..746d182
--- /dev/null
+++ b/src/proguard/optimize/DuplicateInitializerFixer.java
@@ -0,0 +1,207 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.editor.ConstantPoolEditor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This MemberVisitor adds an additional parameter to the duplicate
+ * initialization methods that it visits.
+ */
+public class DuplicateInitializerFixer
+extends      SimplifiedVisitor
+implements   MemberVisitor,
+             AttributeVisitor
+{
+    private static final boolean DEBUG = false;
+
+    private static final char[] TYPES = new char[]
+    {
+        ClassConstants.INTERNAL_TYPE_BYTE,
+        ClassConstants.INTERNAL_TYPE_CHAR,
+        ClassConstants.INTERNAL_TYPE_SHORT,
+        ClassConstants.INTERNAL_TYPE_INT,
+        ClassConstants.INTERNAL_TYPE_BOOLEAN
+    };
+
+
+    private final MemberVisitor extraFixedInitializerVisitor;
+
+
+    /**
+     * Creates a new DuplicateInitializerFixer.
+     */
+    public DuplicateInitializerFixer()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new DuplicateInitializerFixer with an extra visitor.
+     * @param extraFixedInitializerVisitor an optional extra visitor for all
+     *                                     initializers that have been fixed.
+     */
+    public DuplicateInitializerFixer(MemberVisitor extraFixedInitializerVisitor)
+    {
+        this.extraFixedInitializerVisitor = extraFixedInitializerVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Is it a class instance initializer?
+        String name = programMethod.getName(programClass);
+        if (name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            // Is there already another initializer with the same descriptor?
+            String descriptor    = programMethod.getDescriptor(programClass);
+            Method similarMethod = programClass.findMethod(name, descriptor);
+            if (!programMethod.equals(similarMethod))
+            {
+                // Should this initializer be preserved?
+                if (!KeepMarker.isKept(programMethod))
+                {
+                    // Fix the other initializer.
+                    programMethod = (ProgramMethod)similarMethod;
+                }
+
+                int index = descriptor.indexOf(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
+
+                // Try to find a new, unique descriptor.
+                for (int typeIndex = 0; typeIndex < TYPES.length; typeIndex++)
+                {
+                    String newDescriptor =
+                        descriptor.substring(0, index) +
+                        TYPES[typeIndex] +
+                        descriptor.substring(index);
+
+                    // Is the new initializer descriptor unique?
+                    if (programClass.findMethod(name, newDescriptor) == null)
+                    {
+                        if (DEBUG)
+                        {
+                            System.out.println("DuplicateInitializerFixer:");
+                            System.out.println("  ["+programClass.getName()+"]: "+name+descriptor+" -> "+newDescriptor);
+                        }
+
+                        // Update the descriptor.
+                        programMethod.u2descriptorIndex =
+                            new ConstantPoolEditor(programClass).addUtf8Constant(newDescriptor);
+
+                        // Fix the local variable frame size, the method
+                        // signature, and the parameter annotations, if
+                        // necessary.
+                        programMethod.attributesAccept(programClass,
+                                                       this);
+
+                        // Visit the initializer, if required.
+                        if (extraFixedInitializerVisitor != null)
+                        {
+                            extraFixedInitializerVisitor.visitProgramMethod(programClass, programMethod);
+                        }
+
+                        // We're done with this constructor.
+                        return;
+                    }
+                }
+
+                throw new IllegalStateException("Can't find unique constructor descriptor for ["+
+                                                programClass.getName()+"."+
+                                                programMethod.getName(programClass)+
+                                                programMethod.getDescriptor(programClass)+"]");
+            }
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // The minimum variable size is determined by the arguments.
+        int maxLocals =
+            ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz),
+                                                  method.getAccessFlags());
+
+        if (codeAttribute.u2maxLocals < maxLocals)
+        {
+            codeAttribute.u2maxLocals = maxLocals;
+        }
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute signatureAttribute)
+    {
+        String descriptor      = method.getDescriptor(clazz);
+        int    descriptorIndex = descriptor.indexOf(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
+        String signature       = clazz.getString(signatureAttribute.u2signatureIndex);
+        int    signatureIndex  = signature.indexOf(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
+
+        String newSignature = signature.substring(0, signatureIndex) +
+                              descriptor.charAt(descriptorIndex - 1) +
+                              signature.substring(signatureIndex);
+
+        // Update the signature.
+        signatureAttribute.u2signatureIndex =
+            new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Update the number of parameters.
+        int oldParametersCount = parameterAnnotationsAttribute.u2parametersCount++;
+
+        if (parameterAnnotationsAttribute.u2parameterAnnotationsCount == null ||
+            parameterAnnotationsAttribute.u2parameterAnnotationsCount.length < parameterAnnotationsAttribute.u2parametersCount)
+        {
+            int[]          annotationsCounts = new int[parameterAnnotationsAttribute.u2parametersCount];
+            Annotation[][] annotations       = new Annotation[parameterAnnotationsAttribute.u2parametersCount][];
+
+            System.arraycopy(parameterAnnotationsAttribute.u2parameterAnnotationsCount,
+                             0,
+                             annotationsCounts,
+                             0,
+                             oldParametersCount);
+
+            System.arraycopy(parameterAnnotationsAttribute.parameterAnnotations,
+                             0,
+                             annotations,
+                             0,
+                             oldParametersCount);
+
+            parameterAnnotationsAttribute.u2parameterAnnotationsCount = annotationsCounts;
+            parameterAnnotationsAttribute.parameterAnnotations        = annotations;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/DuplicateInitializerInvocationFixer.java b/src/proguard/optimize/DuplicateInitializerInvocationFixer.java
new file mode 100644
index 0000000..ca24481
--- /dev/null
+++ b/src/proguard/optimize/DuplicateInitializerInvocationFixer.java
@@ -0,0 +1,150 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This AttributeVisitor adds an additional integer parameter to the tweaked
+ * initialization method invocations that it visits.
+ */
+public class DuplicateInitializerInvocationFixer
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ConstantVisitor,
+             MemberVisitor
+{
+    private static final boolean DEBUG = false;
+
+    private final InstructionVisitor extraAddedInstructionVisitor;
+
+    private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
+
+    private String  descriptor;
+    private boolean hasBeenFixed;
+
+
+    /**
+     * Creates a new EvaluationSimplifier.
+     */
+    public DuplicateInitializerInvocationFixer()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new EvaluationSimplifier.
+     * @param extraAddedInstructionVisitor an optional extra visitor for all
+     *                                     added instructions.
+     */
+    public DuplicateInitializerInvocationFixer(InstructionVisitor extraAddedInstructionVisitor)
+    {
+        this.extraAddedInstructionVisitor = extraAddedInstructionVisitor;
+    }
+
+
+   // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+
+        // Reset the code changes.
+        codeAttributeEditor.reset(codeAttribute.u4codeLength);
+
+        // Fix any duplicate constructor invocations.
+        codeAttribute.instructionsAccept(clazz,
+                                         method,
+                                         this);
+
+        // Apply all accumulated changes to the code.
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        if (constantInstruction.opcode == InstructionConstants.OP_INVOKESPECIAL)
+        {
+            hasBeenFixed = false;
+            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+
+            if (hasBeenFixed)
+            {
+                Instruction extraInstruction =
+                    new SimpleInstruction(InstructionConstants.OP_ICONST_0);
+
+                codeAttributeEditor.insertBeforeInstruction(offset,
+                                                            extraInstruction);
+
+                if (DEBUG)
+                {
+                    System.out.println("DuplicateInitializerInvocationFixer:");
+                    System.out.println("  Inserting "+extraInstruction.toString()+" before "+constantInstruction.toString(offset));
+                }
+
+                if (extraAddedInstructionVisitor != null)
+                {
+                    extraInstruction.accept(null, null, null, offset, extraAddedInstructionVisitor);
+                }
+            }
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        // Check the referenced constructor descriptor.
+        descriptor = methodrefConstant.getType(clazz);
+        methodrefConstant.referencedMemberAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod) {}
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        hasBeenFixed = !descriptor.equals(programMethod.getDescriptor(programClass));
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/KeepMarker.java b/src/proguard/optimize/KeepMarker.java
new file mode 100644
index 0000000..4297996
--- /dev/null
+++ b/src/proguard/optimize/KeepMarker.java
@@ -0,0 +1,95 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.util.MethodLinker;
+import proguard.classfile.visitor.*;
+
+
+/**
+ * This <code>ClassVisitor</code> and <code>MemberVisitor</code>
+ * marks classes and class members it visits. The marked elements
+ * will remain unchanged as necessary in the optimization step.
+ *
+ * @author Eric Lafortune
+ */
+public class KeepMarker
+implements   ClassVisitor,
+             MemberVisitor
+{
+    // A visitor info flag to indicate the visitor accepter is being kept.
+    private static final Object KEPT = new Object();
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        markAsKept(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        markAsKept(libraryClass);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        markAsKept(programField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        markAsKept(MethodLinker.lastMember(programMethod));
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        markAsKept(libraryField);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        markAsKept(MethodLinker.lastMember(libraryMethod));
+    }
+
+
+    // Small utility methods.
+
+    private static void markAsKept(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(KEPT);
+    }
+
+
+    public static boolean isKept(VisitorAccepter visitorAccepter)
+    {
+        return MethodLinker.lastVisitorAccepter(visitorAccepter).getVisitorInfo() == KEPT;
+    }
+}
diff --git a/src/proguard/optimize/MemberDescriptorSpecializer.java b/src/proguard/optimize/MemberDescriptorSpecializer.java
new file mode 100644
index 0000000..0d0b841
--- /dev/null
+++ b/src/proguard/optimize/MemberDescriptorSpecializer.java
@@ -0,0 +1,138 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.editor.ClassReferenceFixer;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.evaluation.value.Value;
+import proguard.optimize.evaluation.StoringInvocationUnit;
+
+/**
+ * This MemberVisitor specializes parameters in the descriptors of the
+ * methods that it visits.
+ *
+ * @see StoringInvocationUnit
+ * @see ClassReferenceFixer
+ * @author Eric Lafortune
+ */
+public class MemberDescriptorSpecializer
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private static final boolean DEBUG = true;
+
+
+    private final MemberVisitor extraParameterMemberVisitor;
+
+
+    /**
+     * Creates a new MethodDescriptorShrinker.
+     */
+    public MemberDescriptorSpecializer()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new MethodDescriptorShrinker with an extra visitor.
+     * @param extraParameterMemberVisitor an optional extra visitor for all
+     *                                    class members whose parameters have
+     *                                    been specialized.
+     */
+    public MemberDescriptorSpecializer(MemberVisitor extraParameterMemberVisitor)
+    {
+        this.extraParameterMemberVisitor = extraParameterMemberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        Value parameterValue = StoringInvocationUnit.getFieldValue(programField);
+        if (parameterValue.computationalType() == Value.TYPE_REFERENCE)
+        {
+            Clazz referencedClass = parameterValue.referenceValue().getReferencedClass();
+            if (programField.referencedClass != referencedClass)
+            {
+                if (DEBUG)
+                {
+                    System.out.println("MemberDescriptorSpecializer: "+programClass.getName()+"."+programField.getName(programClass)+" "+programField.getDescriptor(programClass));
+                    System.out.println("  "+programField.referencedClass.getName()+" -> "+referencedClass.getName());
+                }
+
+                programField.referencedClass = referencedClass;
+
+                // Visit the field, if required.
+                if (extraParameterMemberVisitor != null)
+                {
+                    extraParameterMemberVisitor.visitProgramField(programClass, programField);
+                }
+            }
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // All parameters of non-static methods are shifted by one in the local
+        // variable frame.
+        int firstParameterIndex =
+            (programMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0 ?
+                0 : 1;
+
+        int parameterCount =
+            ClassUtil.internalMethodParameterCount(programMethod.getDescriptor(programClass));
+
+        int classIndex = 0;
+
+        // Go over the parameters.
+        for (int parameterIndex = firstParameterIndex; parameterIndex < parameterCount; parameterIndex++)
+        {
+            Value parameterValue = StoringInvocationUnit.getMethodParameterValue(programMethod, parameterIndex);
+             if (parameterValue.computationalType() == Value.TYPE_REFERENCE)
+             {
+                 Clazz referencedClass = parameterValue.referenceValue().getReferencedClass();
+                 if (programMethod.referencedClasses[classIndex] != referencedClass)
+                 {
+                     if (DEBUG)
+                     {
+                         System.out.println("MemberDescriptorSpecializer: "+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass));
+                         System.out.println("  "+programMethod.referencedClasses[classIndex].getName()+" -> "+referencedClass.getName());
+                     }
+
+                     programMethod.referencedClasses[classIndex] = referencedClass;
+
+                     // Visit the method, if required.
+                     if (extraParameterMemberVisitor != null)
+                     {
+                         extraParameterMemberVisitor.visitProgramMethod(programClass, programMethod);
+                     }
+                 }
+
+                 classIndex++;
+             }
+        }
+    }
+}
diff --git a/src/proguard/optimize/MethodDescriptorShrinker.java b/src/proguard/optimize/MethodDescriptorShrinker.java
new file mode 100644
index 0000000..48374e7
--- /dev/null
+++ b/src/proguard/optimize/MethodDescriptorShrinker.java
@@ -0,0 +1,319 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.ConstantPoolEditor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.info.*;
+import proguard.optimize.peephole.VariableShrinker;
+
+/**
+ * This MemberVisitor removes unused parameters in the descriptors of the
+ * methods that it visits.
+ *
+ * @see ParameterUsageMarker
+ * @see VariableUsageMarker
+ * @see VariableShrinker
+ * @author Eric Lafortune
+ */
+public class MethodDescriptorShrinker
+extends      SimplifiedVisitor
+implements   MemberVisitor,
+             AttributeVisitor
+{
+    private static final boolean DEBUG = false;
+
+
+    private final MemberVisitor extraMemberVisitor;
+
+
+    /**
+     * Creates a new MethodDescriptorShrinker.
+     */
+    public MethodDescriptorShrinker()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new MethodDescriptorShrinker with an extra visitor.
+     * @param extraMemberVisitor an optional extra visitor for all methods whose
+     *                           parameters have been simplified.
+     */
+    public MethodDescriptorShrinker(MemberVisitor extraMemberVisitor)
+    {
+        this.extraMemberVisitor = extraMemberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Update the descriptor if it has any unused parameters.
+        String descriptor    = programMethod.getDescriptor(programClass);
+        String newDescriptor = shrinkDescriptor(programMethod, descriptor);
+
+        if (!descriptor.equals(newDescriptor))
+        {
+            // Shrink the signature and parameter annotations.
+            programMethod.attributesAccept(programClass, this);
+
+            String name    = programMethod.getName(programClass);
+            String newName = name;
+
+            // Append a code, if the method isn't a class instance initializer.
+            if (!name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+            {
+                newName += ClassConstants.SPECIAL_MEMBER_SEPARATOR + Long.toHexString(Math.abs((descriptor).hashCode()));
+            }
+
+            if (DEBUG)
+            {
+                System.out.println("MethodDescriptorShrinker:");
+                System.out.println("  Class file        = "+programClass.getName());
+                System.out.println("  Method name       = "+name);
+                System.out.println("                   -> "+newName);
+                System.out.println("  Method descriptor = "+descriptor);
+                System.out.println("                   -> "+newDescriptor);
+            }
+
+            ConstantPoolEditor constantPoolEditor =
+                new ConstantPoolEditor(programClass);
+
+            // Update the name, if necessary.
+            if (!newName.equals(name))
+            {
+                programMethod.u2nameIndex =
+                    constantPoolEditor.addUtf8Constant(newName);
+            }
+
+            // Update the referenced classes.
+            programMethod.referencedClasses =
+                shrinkReferencedClasses(programMethod,
+                                        descriptor,
+                                        programMethod.referencedClasses);
+
+            // Finally, update the descriptor itself.
+            programMethod.u2descriptorIndex =
+                constantPoolEditor.addUtf8Constant(newDescriptor);
+
+            // Visit the method, if required.
+            if (extraMemberVisitor != null)
+            {
+                extraMemberVisitor.visitProgramMethod(programClass, programMethod);
+            }
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitSignatureAttribute(Clazz clazz, Method method, SignatureAttribute signatureAttribute)
+    {
+        // Compute the new signature.
+        String signature    = clazz.getString(signatureAttribute.u2signatureIndex);
+        String newSignature = shrinkDescriptor(method, signature);
+
+        // Update the signature.
+        signatureAttribute.u2signatureIndex =
+            new ConstantPoolEditor((ProgramClass)clazz).addUtf8Constant(newSignature);
+
+        // Update the referenced classes.
+        signatureAttribute.referencedClasses =
+            shrinkReferencedClasses(method,
+                                    signature,
+                                    signatureAttribute.referencedClasses);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        int[]          annotationsCounts = parameterAnnotationsAttribute.u2parameterAnnotationsCount;
+        Annotation[][] annotations       = parameterAnnotationsAttribute.parameterAnnotations;
+
+        // All parameters of non-static methods are shifted by one in the local
+        // variable frame.
+        int parameterIndex =
+            (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0 ?
+                0 : 1;
+
+        int annotationIndex    = 0;
+        int newAnnotationIndex = 0;
+
+        // Go over the parameters.
+        String descriptor = method.getDescriptor(clazz);
+        InternalTypeEnumeration internalTypeEnumeration =
+            new InternalTypeEnumeration(descriptor);
+
+        while (internalTypeEnumeration.hasMoreTypes())
+        {
+            String type = internalTypeEnumeration.nextType();
+            if (ParameterUsageMarker.isParameterUsed(method, parameterIndex))
+            {
+                annotationsCounts[newAnnotationIndex] = annotationsCounts[annotationIndex];
+                annotations[newAnnotationIndex++]     = annotations[annotationIndex];
+            }
+
+            annotationIndex++;
+
+            parameterIndex += ClassUtil.isInternalCategory2Type(type) ? 2 : 1;
+        }
+
+        // Update the number of parameters.
+        parameterAnnotationsAttribute.u2parametersCount = newAnnotationIndex;
+
+        // Clear the unused entries.
+        while (newAnnotationIndex < annotationIndex)
+        {
+            annotationsCounts[newAnnotationIndex] = 0;
+            annotations[newAnnotationIndex++]     = null;
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns a shrunk descriptor or signature of the given method.
+     */
+    private String shrinkDescriptor(Method method, String descriptor)
+    {
+        // All parameters of non-static methods are shifted by one in the local
+        // variable frame.
+        int parameterIndex =
+            (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0 ?
+                0 : 1;
+
+        // Go over the parameters.
+        InternalTypeEnumeration internalTypeEnumeration =
+            new InternalTypeEnumeration(descriptor);
+
+        StringBuffer newDescriptorBuffer = new StringBuffer();
+
+        newDescriptorBuffer.append(internalTypeEnumeration.formalTypeParameters());
+        newDescriptorBuffer.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_OPEN);
+
+        while (internalTypeEnumeration.hasMoreTypes())
+        {
+            String type = internalTypeEnumeration.nextType();
+            if (ParameterUsageMarker.isParameterUsed(method, parameterIndex))
+            {
+                newDescriptorBuffer.append(type);
+            }
+            else if (DEBUG)
+            {
+                System.out.println("  Deleting parameter #"+parameterIndex+" ["+type+"]");
+            }
+
+            parameterIndex += ClassUtil.isInternalCategory2Type(type) ? 2 : 1;
+        }
+
+        newDescriptorBuffer.append(ClassConstants.INTERNAL_METHOD_ARGUMENTS_CLOSE);
+        newDescriptorBuffer.append(internalTypeEnumeration.returnType());
+
+        return newDescriptorBuffer.toString();
+    }
+
+
+    /**
+     * Shrinks the array of referenced classes of the given method.
+     */
+    private Clazz[] shrinkReferencedClasses(Method  method,
+                                            String  descriptor,
+                                            Clazz[] referencedClasses)
+    {
+        if (referencedClasses != null)
+        {
+            // All parameters of non-static methods are shifted by one in the local
+            // variable frame.
+            int parameterIndex =
+                (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0 ?
+                    0 : 1;
+
+            int referencedClassIndex    = 0;
+            int newReferencedClassIndex = 0;
+
+            // Go over the parameters.
+            InternalTypeEnumeration internalTypeEnumeration =
+                new InternalTypeEnumeration(descriptor);
+
+            // Also look at the formal type parameters.
+            String type  = internalTypeEnumeration.formalTypeParameters();
+            int    count = new DescriptorClassEnumeration(type).classCount();
+            for (int counter = 0; counter < count; counter++)
+            {
+                referencedClasses[newReferencedClassIndex++] =
+                    referencedClasses[referencedClassIndex++];
+            }
+
+            while (internalTypeEnumeration.hasMoreTypes())
+            {
+                // Consider the classes referenced by this parameter type.
+                type  = internalTypeEnumeration.nextType();
+                count = new DescriptorClassEnumeration(type).classCount();
+
+                if (ParameterUsageMarker.isParameterUsed(method, parameterIndex))
+                {
+                    // Copy the referenced classes.
+                    for (int counter = 0; counter < count; counter++)
+                    {
+                        referencedClasses[newReferencedClassIndex++] =
+                            referencedClasses[referencedClassIndex++];
+                    }
+                }
+                else
+                {
+                    // Skip the referenced classes.
+                    referencedClassIndex += count;
+                }
+
+                parameterIndex += ClassUtil.isInternalCategory2Type(type) ? 2 : 1;
+            }
+
+            // Also look at the return value.
+            type  = internalTypeEnumeration.returnType();
+            count = new DescriptorClassEnumeration(type).classCount();
+            for (int counter = 0; counter < count; counter++)
+            {
+                referencedClasses[newReferencedClassIndex++] =
+                    referencedClasses[referencedClassIndex++];
+            }
+
+            // Clear the unused entries.
+            while (newReferencedClassIndex < referencedClassIndex)
+            {
+                referencedClasses[newReferencedClassIndex++] = null;
+            }
+        }
+
+        return referencedClasses;
+    }
+}
diff --git a/src/proguard/optimize/MethodStaticizer.java b/src/proguard/optimize/MethodStaticizer.java
new file mode 100644
index 0000000..8dd11e1
--- /dev/null
+++ b/src/proguard/optimize/MethodStaticizer.java
@@ -0,0 +1,87 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.MethodInvocationFixer;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.info.ParameterUsageMarker;
+import proguard.optimize.peephole.VariableShrinker;
+
+/**
+ * This MemberVisitor makes all methods that it visits static, if their 'this'
+ * parameters are unused.
+ *
+ * @see ParameterUsageMarker
+ * @see MethodInvocationFixer
+ * @see VariableShrinker
+ * @author Eric Lafortune
+ */
+public class MethodStaticizer
+extends      SimplifiedVisitor
+implements   MemberVisitor,
+             AttributeVisitor
+{
+    private final MemberVisitor extraStaticMemberVisitor;
+
+
+    /**
+     * Creates a new MethodStaticizer.
+     */
+    public MethodStaticizer()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new MethodStaticizer with an extra visitor.
+     * @param extraStaticMemberVisitor an optional extra visitor for all
+     *                                 methods that have been made static.
+     */
+    public MethodStaticizer(MemberVisitor extraStaticMemberVisitor)
+    {
+        this.extraStaticMemberVisitor = extraStaticMemberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Is the 'this' parameter being used?
+        if (!ParameterUsageMarker.isParameterUsed(programMethod, 0))
+        {
+            // Make the method static.
+            programMethod.u2accessFlags =
+                (programMethod.getAccessFlags() & ~ClassConstants.INTERNAL_ACC_FINAL) |
+                ClassConstants.INTERNAL_ACC_STATIC;
+
+            // Visit the method, if required.
+            if (extraStaticMemberVisitor != null)
+            {
+                extraStaticMemberVisitor.visitProgramMethod(programClass, programMethod);
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/OptimizationInfoMemberFilter.java b/src/proguard/optimize/OptimizationInfoMemberFilter.java
new file mode 100644
index 0000000..8760aee
--- /dev/null
+++ b/src/proguard/optimize/OptimizationInfoMemberFilter.java
@@ -0,0 +1,94 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.info.*;
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to another given
+ * <code>MemberVisitor</code>, but only when the visited member has optimization
+ * info.
+ *
+ * @see FieldOptimizationInfo
+ * @see MethodOptimizationInfo
+ * @author Eric Lafortune
+ */
+public class OptimizationInfoMemberFilter
+implements   MemberVisitor
+{
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new OptimizationInfoMemberFilter.
+     * @param memberVisitor the <code>MemberVisitor</code> to which visits will
+     *                      be delegated.
+     */
+    public OptimizationInfoMemberFilter(MemberVisitor memberVisitor)
+    {
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Does the field have optimization info?
+        if (FieldOptimizationInfo.getFieldOptimizationInfo(programField) != null)
+        {
+            memberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        // Does the field have optimization info?
+        if (FieldOptimizationInfo.getFieldOptimizationInfo(libraryField) != null)
+        {
+            memberVisitor.visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Does the method have optimization info?
+        if (MethodOptimizationInfo.getMethodOptimizationInfo(programMethod) != null)
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        // Does the method have optimization info?
+        if (MethodOptimizationInfo.getMethodOptimizationInfo(libraryMethod) != null)
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+}
diff --git a/src/proguard/optimize/Optimizer.java b/src/proguard/optimize/Optimizer.java
new file mode 100644
index 0000000..a3e8a6e
--- /dev/null
+++ b/src/proguard/optimize/Optimizer.java
@@ -0,0 +1,876 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.*;
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.visitor.AllConstantVisitor;
+import proguard.classfile.editor.*;
+import proguard.classfile.instruction.visitor.*;
+import proguard.classfile.util.MethodLinker;
+import proguard.classfile.visitor.*;
+import proguard.evaluation.*;
+import proguard.evaluation.value.*;
+import proguard.optimize.evaluation.*;
+import proguard.optimize.info.*;
+import proguard.optimize.peephole.*;
+import proguard.util.*;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * This class optimizes class pools according to a given configuration.
+ *
+ * @author Eric Lafortune
+ */
+public class Optimizer
+{
+    private static final String CLASS_MARKING_FINAL            = "class/marking/final";
+    private static final String CLASS_MERGING_VERTICAL         = "class/merging/vertical";
+    private static final String CLASS_MERGING_HORIZONTAL       = "class/merging/horizontal";
+    private static final String FIELD_REMOVAL_WRITEONLY        = "field/removal/writeonly";
+    private static final String FIELD_MARKING_PRIVATE          = "field/marking/private";
+    private static final String FIELD_PROPAGATION_VALUE        = "field/propagation/value";
+    private static final String METHOD_MARKING_PRIVATE         = "method/marking/private";
+    private static final String METHOD_MARKING_STATIC          = "method/marking/static";
+    private static final String METHOD_MARKING_FINAL           = "method/marking/final";
+    private static final String METHOD_REMOVAL_PARAMETER       = "method/removal/parameter";
+    private static final String METHOD_PROPAGATION_PARAMETER   = "method/propagation/parameter";
+    private static final String METHOD_PROPAGATION_RETURNVALUE = "method/propagation/returnvalue";
+    private static final String METHOD_INLINING_SHORT          = "method/inlining/short";
+    private static final String METHOD_INLINING_UNIQUE         = "method/inlining/unique";
+    private static final String METHOD_INLINING_TAILRECURSION  = "method/inlining/tailrecursion";
+    private static final String CODE_MERGING                   = "code/merging";
+    private static final String CODE_SIMPLIFICATION_VARIABLE   = "code/simplification/variable";
+    private static final String CODE_SIMPLIFICATION_ARITHMETIC = "code/simplification/arithmetic";
+    private static final String CODE_SIMPLIFICATION_CAST       = "code/simplification/cast";
+    private static final String CODE_SIMPLIFICATION_FIELD      = "code/simplification/field";
+    private static final String CODE_SIMPLIFICATION_BRANCH     = "code/simplification/branch";
+    private static final String CODE_SIMPLIFICATION_ADVANCED   = "code/simplification/advanced";
+    private static final String CODE_REMOVAL_ADVANCED          = "code/removal/advanced";
+    private static final String CODE_REMOVAL_SIMPLE            = "code/removal/simple";
+    private static final String CODE_REMOVAL_VARIABLE          = "code/removal/variable";
+    private static final String CODE_REMOVAL_EXCEPTION         = "code/removal/exception";
+    private static final String CODE_ALLOCATION_VARIABLE       = "code/allocation/variable";
+
+
+    public static final String[] OPTIMIZATION_NAMES = new String[]
+    {
+        CLASS_MARKING_FINAL,
+        CLASS_MERGING_VERTICAL,
+        CLASS_MERGING_HORIZONTAL,
+        FIELD_REMOVAL_WRITEONLY,
+        FIELD_PROPAGATION_VALUE,
+        METHOD_MARKING_PRIVATE,
+        METHOD_MARKING_STATIC,
+        METHOD_MARKING_FINAL,
+        METHOD_REMOVAL_PARAMETER,
+        METHOD_PROPAGATION_PARAMETER,
+        METHOD_PROPAGATION_RETURNVALUE,
+        METHOD_INLINING_SHORT,
+        METHOD_INLINING_UNIQUE,
+        METHOD_INLINING_TAILRECURSION,
+        CODE_MERGING,
+        CODE_SIMPLIFICATION_VARIABLE,
+        CODE_SIMPLIFICATION_ARITHMETIC,
+        CODE_SIMPLIFICATION_CAST,
+        CODE_SIMPLIFICATION_FIELD,
+        CODE_SIMPLIFICATION_BRANCH,
+        CODE_SIMPLIFICATION_ADVANCED,
+        CODE_REMOVAL_ADVANCED,
+        CODE_REMOVAL_SIMPLE,
+        CODE_REMOVAL_VARIABLE,
+        CODE_REMOVAL_EXCEPTION,
+        CODE_ALLOCATION_VARIABLE,
+    };
+
+
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new Optimizer.
+     */
+    public Optimizer(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Performs optimization of the given program class pool.
+     */
+    public boolean execute(ClassPool programClassPool,
+                           ClassPool libraryClassPool) throws IOException
+    {
+        // Check if we have at least some keep commands.
+        if (configuration.keep         == null &&
+            configuration.applyMapping == null &&
+            configuration.printMapping == null)
+        {
+            throw new IOException("You have to specify '-keep' options for the optimization step.");
+        }
+
+        // Create a matcher for filtering optimizations.
+        StringMatcher filter = configuration.optimizations != null ?
+            new ListParser(new NameParser()).parse(configuration.optimizations) :
+            new ConstantMatcher(true);
+
+        boolean classMarkingFinal            = filter.matches(CLASS_MARKING_FINAL);
+        boolean classMergingVertical         = filter.matches(CLASS_MERGING_VERTICAL);
+        boolean classMergingHorizontal       = filter.matches(CLASS_MERGING_HORIZONTAL);
+        boolean fieldRemovalWriteonly        = filter.matches(FIELD_REMOVAL_WRITEONLY);
+        boolean fieldMarkingPrivate          = filter.matches(FIELD_MARKING_PRIVATE);
+        boolean fieldPropagationValue        = filter.matches(FIELD_PROPAGATION_VALUE);
+        boolean methodMarkingPrivate         = filter.matches(METHOD_MARKING_PRIVATE);
+        boolean methodMarkingStatic          = filter.matches(METHOD_MARKING_STATIC);
+        boolean methodMarkingFinal           = filter.matches(METHOD_MARKING_FINAL);
+        boolean methodRemovalParameter       = filter.matches(METHOD_REMOVAL_PARAMETER);
+        boolean methodPropagationParameter   = filter.matches(METHOD_PROPAGATION_PARAMETER);
+        boolean methodPropagationReturnvalue = filter.matches(METHOD_PROPAGATION_RETURNVALUE);
+        boolean methodInliningShort          = filter.matches(METHOD_INLINING_SHORT);
+        boolean methodInliningUnique         = filter.matches(METHOD_INLINING_UNIQUE);
+        boolean methodInliningTailrecursion  = filter.matches(METHOD_INLINING_TAILRECURSION);
+        boolean codeMerging                  = filter.matches(CODE_MERGING);
+        boolean codeSimplificationVariable   = filter.matches(CODE_SIMPLIFICATION_VARIABLE);
+        boolean codeSimplificationArithmetic = filter.matches(CODE_SIMPLIFICATION_ARITHMETIC);
+        boolean codeSimplificationCast       = filter.matches(CODE_SIMPLIFICATION_CAST);
+        boolean codeSimplificationField      = filter.matches(CODE_SIMPLIFICATION_FIELD);
+        boolean codeSimplificationBranch     = filter.matches(CODE_SIMPLIFICATION_BRANCH);
+        boolean codeSimplificationAdvanced   = filter.matches(CODE_SIMPLIFICATION_ADVANCED);
+        boolean codeRemovalAdvanced          = filter.matches(CODE_REMOVAL_ADVANCED);
+        boolean codeRemovalSimple            = filter.matches(CODE_REMOVAL_SIMPLE);
+        boolean codeRemovalVariable          = filter.matches(CODE_REMOVAL_VARIABLE);
+        boolean codeRemovalException         = filter.matches(CODE_REMOVAL_EXCEPTION);
+        boolean codeAllocationVariable       = filter.matches(CODE_ALLOCATION_VARIABLE);
+
+        // Create counters to count the numbers of optimizations.
+        ClassCounter       classMarkingFinalCounter            = new ClassCounter();
+        ClassCounter       classMergingVerticalCounter         = new ClassCounter();
+        ClassCounter       classMergingHorizontalCounter       = new ClassCounter();
+        MemberCounter      fieldRemovalWriteonlyCounter        = new MemberCounter();
+        MemberCounter      fieldMarkingPrivateCounter          = new MemberCounter();
+        MemberCounter      fieldPropagationValueCounter        = new MemberCounter();
+        MemberCounter      methodMarkingPrivateCounter         = new MemberCounter();
+        MemberCounter      methodMarkingStaticCounter          = new MemberCounter();
+        MemberCounter      methodMarkingFinalCounter           = new MemberCounter();
+        MemberCounter      methodRemovalParameterCounter       = new MemberCounter();
+        MemberCounter      methodPropagationParameterCounter   = new MemberCounter();
+        MemberCounter      methodPropagationReturnvalueCounter = new MemberCounter();
+        InstructionCounter methodInliningShortCounter          = new InstructionCounter();
+        InstructionCounter methodInliningUniqueCounter         = new InstructionCounter();
+        InstructionCounter methodInliningTailrecursionCounter  = new InstructionCounter();
+        InstructionCounter codeMergingCounter                  = new InstructionCounter();
+        InstructionCounter codeSimplificationVariableCounter   = new InstructionCounter();
+        InstructionCounter codeSimplificationArithmeticCounter = new InstructionCounter();
+        InstructionCounter codeSimplificationCastCounter       = new InstructionCounter();
+        InstructionCounter codeSimplificationFieldCounter      = new InstructionCounter();
+        InstructionCounter codeSimplificationBranchCounter     = new InstructionCounter();
+        InstructionCounter codeSimplificationAdvancedCounter   = new InstructionCounter();
+        InstructionCounter deletedCounter                      = new InstructionCounter();
+        InstructionCounter addedCounter                        = new InstructionCounter();
+        MemberCounter      codeRemovalVariableCounter          = new MemberCounter();
+        ExceptionCounter   codeRemovalExceptionCounter         = new ExceptionCounter();
+        MemberCounter      codeAllocationVariableCounter       = new MemberCounter();
+        MemberCounter      initializerFixCounter               = new MemberCounter();
+
+        // Some optimizations are required by other optimizations.
+        codeSimplificationAdvanced =
+            codeSimplificationAdvanced ||
+            fieldPropagationValue      ||
+            methodPropagationParameter ||
+            methodPropagationReturnvalue;
+
+        codeRemovalAdvanced =
+            codeRemovalAdvanced   ||
+            fieldRemovalWriteonly ||
+            methodMarkingStatic   ||
+            methodRemovalParameter;
+
+        codeRemovalSimple =
+            codeRemovalSimple ||
+            codeSimplificationBranch;
+
+        codeRemovalException =
+            codeRemovalException ||
+            codeRemovalAdvanced ||
+            codeRemovalSimple;
+
+        // Clean up any old visitor info.
+        programClassPool.classesAccept(new ClassCleaner());
+        libraryClassPool.classesAccept(new ClassCleaner());
+
+        // Link all methods that should get the same optimization info.
+        programClassPool.classesAccept(new BottomClassFilter(
+                                       new MethodLinker()));
+        libraryClassPool.classesAccept(new BottomClassFilter(
+                                       new MethodLinker()));
+
+        // Create a visitor for marking the seeds.
+        KeepMarker keepMarker = new KeepMarker();
+        ClassPoolVisitor classPoolvisitor =
+            ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
+                                                                    keepMarker,
+                                                                    keepMarker,
+                                                                    false,
+                                                                    true,
+                                                                    false);
+        // Mark the seeds.
+        programClassPool.accept(classPoolvisitor);
+        libraryClassPool.accept(classPoolvisitor);
+
+        // All library classes and library class members remain unchanged.
+        libraryClassPool.classesAccept(keepMarker);
+        libraryClassPool.classesAccept(new AllMemberVisitor(keepMarker));
+
+        // We also keep all classes that are involved in .class constructs.
+        programClassPool.classesAccept(
+            new AllMethodVisitor(
+            new AllAttributeVisitor(
+            new AllInstructionVisitor(
+            new DotClassClassVisitor(keepMarker)))));
+
+        // We also keep all classes that are involved in Class.forName constructs.
+        programClassPool.classesAccept(
+            new AllConstantVisitor(
+            new ClassForNameClassVisitor(keepMarker)));
+
+        // Attach some optimization info to all classes and class members, so
+        // it can be filled out later.
+        programClassPool.classesAccept(new ClassOptimizationInfoSetter());
+
+        programClassPool.classesAccept(new AllMemberVisitor(
+                                       new MemberOptimizationInfoSetter()));
+
+        if (configuration.assumeNoSideEffects != null)
+        {
+            // Create a visitor for marking methods that don't have any side effects.
+            NoSideEffectMethodMarker noSideEffectMethodMarker = new NoSideEffectMethodMarker();
+            ClassPoolVisitor noClassPoolvisitor =
+                ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.assumeNoSideEffects,
+                                                                        null,
+                                                                        noSideEffectMethodMarker);
+
+            // Mark the seeds.
+            programClassPool.accept(noClassPoolvisitor);
+            libraryClassPool.accept(noClassPoolvisitor);
+        }
+
+        if (classMarkingFinal)
+        {
+            // Make classes final, whereever possible.
+            programClassPool.classesAccept(
+                new ClassFinalizer(classMarkingFinalCounter));
+        }
+
+        if (methodMarkingFinal)
+        {
+            // Make methods final, whereever possible.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new MethodFinalizer(methodMarkingFinalCounter)));
+        }
+
+        if (fieldRemovalWriteonly)
+        {
+            // Mark all fields that are write-only.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new AllInstructionVisitor(
+                new ReadWriteFieldMarker()))));
+
+            // Count the write-only fields.
+            programClassPool.classesAccept(
+                new AllFieldVisitor(
+                new WriteOnlyFieldFilter(fieldRemovalWriteonlyCounter)));
+        }
+        else
+        {
+            // Mark all fields as read/write.
+            programClassPool.classesAccept(
+                new AllFieldVisitor(
+                new ReadWriteFieldMarker()));
+        }
+
+        // Mark all used parameters, including the 'this' parameters.
+        programClassPool.classesAccept(
+            new AllMethodVisitor(
+            new OptimizationInfoMemberFilter(
+            new ParameterUsageMarker(!methodMarkingStatic,
+                                     !methodRemovalParameter))));
+
+        // Mark all methods that have side effects.
+        programClassPool.accept(new SideEffectMethodMarker());
+
+//        System.out.println("Optimizer.execute: before evaluation simplification");
+//        programClassPool.classAccept("abc/Def", new NamedMethodVisitor("abc", null, new ClassPrinter()));
+
+        // Perform partial evaluation for filling out fields, method parameters,
+        // and method return values.
+        ValueFactory valueFactory = new IdentifiedValueFactory();
+
+        if (fieldPropagationValue      ||
+            methodPropagationParameter ||
+            methodPropagationReturnvalue)
+        {
+            // Fill out fields, method parameters, and return values, so they
+            // can be propagated.
+            InvocationUnit storingInvocationUnit =
+                new StoringInvocationUnit(valueFactory,
+                                          fieldPropagationValue,
+                                          methodPropagationParameter,
+                                          methodPropagationReturnvalue);
+
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new PartialEvaluator(valueFactory, storingInvocationUnit, false))));
+
+            // Count the constant fields and methods.
+            programClassPool.classesAccept(
+                new MultiClassVisitor(
+                new ClassVisitor[]
+                {
+                    new AllFieldVisitor(
+                    new ConstantMemberFilter(fieldPropagationValueCounter)),
+                    new AllMethodVisitor(
+                    new ConstantParameterFilter(methodPropagationParameterCounter)),
+                    new AllMethodVisitor(
+                    new ConstantMemberFilter(methodPropagationReturnvalueCounter)),
+                }));
+        }
+
+        InvocationUnit loadingInvocationUnit =
+            new LoadingInvocationUnit(valueFactory,
+                                      fieldPropagationValue,
+                                      methodPropagationParameter,
+                                      methodPropagationReturnvalue);
+
+        if (codeSimplificationAdvanced)
+        {
+            // Simplify based on partial evaluation, propagating constant
+            // field values, method parameter values, and return values.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new EvaluationSimplifier(
+                new PartialEvaluator(valueFactory, loadingInvocationUnit, false),
+                codeSimplificationAdvancedCounter))));
+        }
+
+        if (codeRemovalAdvanced)
+        {
+            // Remove code based on partial evaluation, also removing unused
+            // parameters from method invocations, and making methods static
+            // if possible.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new EvaluationShrinker(
+                new PartialEvaluator(valueFactory, loadingInvocationUnit, !codeSimplificationAdvanced),
+                deletedCounter, addedCounter))));
+        }
+
+        if (methodRemovalParameter)
+        {
+            // Shrink the parameters in the method descriptors.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new OptimizationInfoMemberFilter(
+                new MethodDescriptorShrinker())));
+        }
+
+        if (methodMarkingStatic)
+        {
+            // Make all non-static methods that don't require the 'this'
+            // parameter static.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new OptimizationInfoMemberFilter(
+                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_STATIC,
+                new MethodStaticizer(methodMarkingStaticCounter)))));
+        }
+
+        if (methodRemovalParameter)
+        {
+            // Fix all references to class members.
+            // This operation also updates the stack sizes.
+            programClassPool.classesAccept(
+                new MemberReferenceFixer());
+        }
+
+        if (methodRemovalParameter ||
+            methodMarkingPrivate   ||
+            methodMarkingStatic)
+        {
+            // Remove all unused parameters from the byte code, shifting all
+            // remaining variables.
+            // This operation also updates the local variable frame sizes.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new ParameterShrinker(methodRemovalParameterCounter))));
+        }
+        else if (codeRemovalAdvanced)
+        {
+            // Just update the local variable frame sizes.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new StackSizeUpdater())));
+        }
+
+//        // Specializing the class member descriptors seems to increase the
+//        // class file size, on average.
+//        // Specialize all class member descriptors.
+//        programClassPool.classesAccept(new AllMemberVisitor(
+//                                       new OptimizationInfoMemberFilter(
+//                                       new MemberDescriptorSpecializer())));
+//
+//        // Fix all references to classes, for MemberDescriptorSpecializer.
+//        programClassPool.classesAccept(new AllMemberVisitor(
+//                                       new OptimizationInfoMemberFilter(
+//                                       new ClassReferenceFixer(true))));
+
+        // Mark all classes with package visible members.
+        // Mark all exception catches of methods.
+        // Count all method invocations.
+        // Mark super invocations and other access of methods.
+        programClassPool.classesAccept(
+            new MultiClassVisitor(
+            new ClassVisitor[]
+            {
+                new AllConstantVisitor(
+                new PackageVisibleMemberInvokingClassMarker()),
+                new AllMethodVisitor(
+                new MultiMemberVisitor(
+                new MemberVisitor[]
+                {
+                    new PackageVisibleMemberContainingClassMarker(),
+                    new AllAttributeVisitor(
+                    new MultiAttributeVisitor(
+                    new AttributeVisitor[]
+                    {
+                        new CatchExceptionMarker(),
+                        new AllInstructionVisitor(
+                        new MultiInstructionVisitor(
+                        new InstructionVisitor[]
+                        {
+                            new InstantiationClassMarker(),
+                            new InstanceofClassMarker(),
+                            new DotClassMarker(),
+                            new MethodInvocationMarker(),
+                            new SuperInvocationMarker(),
+                            new BackwardBranchMarker(),
+                            new AccessMethodMarker(),
+                        })),
+                        new AllExceptionInfoVisitor(
+                        new ExceptionHandlerConstantVisitor(
+                        new ReferencedClassVisitor(
+                        new CaughtClassMarker()))),
+                    })),
+                })),
+            }));
+
+        if (classMergingVertical)
+        {
+            // Merge classes into their superclasses or interfaces.
+            programClassPool.classesAccept(
+                new VerticalClassMerger(configuration.allowAccessModification,
+                                        configuration.mergeInterfacesAggressively,
+                                        classMergingVerticalCounter));
+        }
+
+        if (classMergingHorizontal)
+        {
+            // Merge classes into their sibling classes.
+            programClassPool.classesAccept(
+                new HorizontalClassMerger(configuration.allowAccessModification,
+                                          configuration.mergeInterfacesAggressively,
+                                          classMergingHorizontalCounter));
+        }
+
+        if (classMergingVertical ||
+            classMergingHorizontal)
+        {
+            // Clean up inner class attributes to avoid loops.
+            programClassPool.classesAccept(new RetargetedInnerClassAttributeRemover());
+
+            // Update references to merged classes.
+            programClassPool.classesAccept(new TargetClassChanger());
+            programClassPool.classesAccept(new ClassReferenceFixer(true));
+            programClassPool.classesAccept(new MemberReferenceFixer());
+
+            if (configuration.allowAccessModification)
+            {
+                // Fix the access flags of referenced merged classes and their
+                // class members.
+                programClassPool.classesAccept(
+                    new AllConstantVisitor(
+                    new AccessFixer()));
+            }
+        }
+
+        if (methodRemovalParameter ||
+            classMergingVertical   ||
+            classMergingHorizontal)
+        {
+            // Tweak the descriptors of duplicate initializers.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new DuplicateInitializerFixer(initializerFixCounter)));
+
+            if (initializerFixCounter.getCount() > 0)
+            {
+                // Fix all invocations of tweaked initializers.
+                programClassPool.classesAccept(
+                    new AllMethodVisitor(
+                    new AllAttributeVisitor(
+                    new DuplicateInitializerInvocationFixer(addedCounter))));
+
+                // Fix all references to tweaked initializers.
+                programClassPool.classesAccept(new MemberReferenceFixer());
+            }
+        }
+
+        if (methodInliningUnique)
+        {
+            // Inline methods that are only invoked once.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new MethodInliner(configuration.microEdition,
+                                  configuration.allowAccessModification,
+                                  true,
+                                  methodInliningUniqueCounter))));
+        }
+
+        if (methodInliningShort)
+        {
+            // Inline short methods.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new MethodInliner(configuration.microEdition,
+                                  configuration.allowAccessModification,
+                                  false,
+                                  methodInliningShortCounter))));
+        }
+
+        if (methodInliningTailrecursion)
+        {
+            // Simplify tail recursion calls.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new TailRecursionSimplifier(methodInliningTailrecursionCounter))));
+        }
+
+        if (fieldMarkingPrivate ||
+            methodMarkingPrivate)
+        {
+            // Mark all class members that can not be made private.
+            programClassPool.classesAccept(
+                new NonPrivateMemberMarker());
+        }
+
+        if (fieldMarkingPrivate ||
+            methodMarkingPrivate)
+        {
+            // Make all non-private fields private, whereever possible.
+            programClassPool.classesAccept(
+                new AllFieldVisitor(
+                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                new MemberPrivatizer(fieldMarkingPrivateCounter))));
+        }
+
+        if (methodMarkingPrivate)
+        {
+            // Make all non-private methods private, whereever possible.
+            programClassPool.classesAccept(
+                new ClassAccessFilter(0, ClassConstants.INTERNAL_ACC_INTERFACE,
+                new AllMethodVisitor(
+                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                new MemberPrivatizer(methodMarkingPrivateCounter)))));
+        }
+
+        if ((methodInliningUnique ||
+             methodInliningShort  ||
+             methodInliningTailrecursion) &&
+            configuration.allowAccessModification)
+        {
+            // Fix the access flags of referenced classes and class members,
+            // for MethodInliner.
+            programClassPool.classesAccept(
+                new AllConstantVisitor(
+                new AccessFixer()));
+        }
+
+        if (methodRemovalParameter ||
+            classMergingVertical   ||
+            classMergingHorizontal ||
+            methodMarkingPrivate)
+        {
+            // Fix invocations of interface methods, of methods that have become
+            // non-abstract or private, and of methods that have moved to a
+            // different package.
+            programClassPool.classesAccept(
+                new AllMemberVisitor(
+                new AllAttributeVisitor(
+                new MethodInvocationFixer())));
+        }
+
+        if (codeMerging)
+        {
+            // Share common blocks of code at branches.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new GotoCommonCodeReplacer(codeMergingCounter))));
+        }
+
+        // Create a branch target marker and a code attribute editor that can
+        // be reused for all code attributes.
+        BranchTargetFinder  branchTargetFinder  = new BranchTargetFinder();
+        CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
+
+        List peepholeOptimizations = new ArrayList();
+        if (codeSimplificationVariable)
+        {
+            // Peephole optimizations involving local variables.
+            peepholeOptimizations.add(
+                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
+                                                 InstructionSequenceConstants.VARIABLE,
+                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationVariableCounter));
+        }
+
+        if (codeSimplificationArithmetic)
+        {
+            // Peephole optimizations involving arithmetic operations.
+            peepholeOptimizations.add(
+                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
+                                                 InstructionSequenceConstants.ARITHMETIC,
+                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationArithmeticCounter));
+        }
+
+        if (codeSimplificationCast)
+        {
+            // Peephole optimizations involving cast operations.
+            peepholeOptimizations.add(
+                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
+                                                 InstructionSequenceConstants.CAST,
+                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationCastCounter));
+        }
+
+        if (codeSimplificationField)
+        {
+            // Peephole optimizations involving fields.
+            peepholeOptimizations.add(
+                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
+                                                 InstructionSequenceConstants.FIELD,
+                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationFieldCounter));
+        }
+
+        if (codeSimplificationBranch)
+        {
+            // Peephole optimizations involving branches.
+            peepholeOptimizations.add(
+                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
+                                                 InstructionSequenceConstants.BRANCH,
+                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationBranchCounter));
+
+            // Include optimization of branches to branches and returns.
+            peepholeOptimizations.add(
+                new GotoGotoReplacer(codeAttributeEditor, codeSimplificationBranchCounter));
+            peepholeOptimizations.add(
+                new GotoReturnReplacer(codeAttributeEditor, codeSimplificationBranchCounter));
+        }
+
+        if (!peepholeOptimizations.isEmpty())
+        {
+            // Convert the list into an array.
+            InstructionVisitor[] peepholeOptimizationsArray =
+                new InstructionVisitor[peepholeOptimizations.size()];
+            peepholeOptimizations.toArray(peepholeOptimizationsArray);
+
+            // Perform the peephole optimisations.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new PeepholeOptimizer(branchTargetFinder, codeAttributeEditor,
+                new MultiInstructionVisitor(
+                peepholeOptimizationsArray)))));
+        }
+
+        if (codeRemovalException)
+        {
+            // Remove unnecessary exception handlers.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new UnreachableExceptionRemover(codeRemovalExceptionCounter))));
+        }
+
+        if (codeRemovalSimple)
+        {
+            // Remove unreachable code.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new UnreachableCodeRemover(deletedCounter))));
+        }
+
+        if (codeRemovalVariable)
+        {
+            // Remove all unused local variables.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new VariableShrinker(codeRemovalVariableCounter))));
+        }
+        else
+        {
+            // Clean up all unused local variables.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new VariableCleaner())));
+        }
+
+        if (codeAllocationVariable)
+        {
+            // Optimize the variables.
+            programClassPool.classesAccept(
+                new AllMethodVisitor(
+                new AllAttributeVisitor(
+                new VariableOptimizer(false, codeAllocationVariableCounter))));
+        }
+
+        int classMarkingFinalCount            = classMarkingFinalCounter           .getCount();
+        int classMergingVerticalCount         = classMergingVerticalCounter        .getCount();
+        int classMergingHorizontalCount       = classMergingHorizontalCounter      .getCount();
+        int fieldRemovalWriteonlyCount        = fieldRemovalWriteonlyCounter       .getCount();
+        int fieldMarkingPrivateCount          = fieldMarkingPrivateCounter         .getCount();
+        int fieldPropagationValueCount        = fieldPropagationValueCounter       .getCount();
+        int methodMarkingPrivateCount         = methodMarkingPrivateCounter        .getCount();
+        int methodMarkingStaticCount          = methodMarkingStaticCounter         .getCount();
+        int methodMarkingFinalCount           = methodMarkingFinalCounter          .getCount();
+        int methodRemovalParameterCount       = methodRemovalParameterCounter      .getCount() - methodMarkingStaticCounter.getCount() - initializerFixCounter.getCount();
+        int methodPropagationParameterCount   = methodPropagationParameterCounter  .getCount();
+        int methodPropagationReturnvalueCount = methodPropagationReturnvalueCounter.getCount();
+        int methodInliningShortCount          = methodInliningShortCounter         .getCount();
+        int methodInliningUniqueCount         = methodInliningUniqueCounter        .getCount();
+        int methodInliningTailrecursionCount  = methodInliningTailrecursionCounter .getCount();
+        int codeMergingCount                  = codeMergingCounter                 .getCount();
+        int codeSimplificationVariableCount   = codeSimplificationVariableCounter  .getCount();
+        int codeSimplificationArithmeticCount = codeSimplificationArithmeticCounter.getCount();
+        int codeSimplificationCastCount       = codeSimplificationCastCounter      .getCount();
+        int codeSimplificationFieldCount      = codeSimplificationFieldCounter     .getCount();
+        int codeSimplificationBranchCount     = codeSimplificationBranchCounter    .getCount();
+        int codeSimplificationAdvancedCount   = codeSimplificationAdvancedCounter  .getCount();
+        int codeRemovalCount                  = deletedCounter                     .getCount() - addedCounter.getCount();
+        int codeRemovalVariableCount          = codeRemovalVariableCounter         .getCount();
+        int codeRemovalExceptionCount         = codeRemovalExceptionCounter        .getCount();
+        int codeAllocationVariableCount       = codeAllocationVariableCounter      .getCount();
+
+        if (configuration.verbose)
+        {
+            System.out.println("  Number of finalized classes:                 " + classMarkingFinalCount            + disabled(classMarkingFinal));
+            System.out.println("  Number of vertically merged classes:         " + classMergingVerticalCount         + disabled(classMergingVertical));
+            System.out.println("  Number of horizontally merged classes:       " + classMergingHorizontalCount       + disabled(classMergingHorizontal));
+            System.out.println("  Number of removed write-only fields:         " + fieldRemovalWriteonlyCount        + disabled(fieldRemovalWriteonly));
+            System.out.println("  Number of privatized fields:                 " + fieldMarkingPrivateCount          + disabled(fieldMarkingPrivate));
+            System.out.println("  Number of inlined constant fields:           " + fieldPropagationValueCount        + disabled(fieldPropagationValue));
+            System.out.println("  Number of privatized methods:                " + methodMarkingPrivateCount         + disabled(methodMarkingPrivate));
+            System.out.println("  Number of staticized methods:                " + methodMarkingStaticCount          + disabled(methodMarkingStatic));
+            System.out.println("  Number of finalized methods:                 " + methodMarkingFinalCount           + disabled(methodMarkingFinal));
+            System.out.println("  Number of removed method parameters:         " + methodRemovalParameterCount       + disabled(methodRemovalParameter));
+            System.out.println("  Number of inlined constant parameters:       " + methodPropagationParameterCount   + disabled(methodPropagationParameter));
+            System.out.println("  Number of inlined constant return values:    " + methodPropagationReturnvalueCount + disabled(methodPropagationReturnvalue));
+            System.out.println("  Number of inlined short method calls:        " + methodInliningShortCount          + disabled(methodInliningShort));
+            System.out.println("  Number of inlined unique method calls:       " + methodInliningUniqueCount         + disabled(methodInliningUnique));
+            System.out.println("  Number of inlined tail recursion calls:      " + methodInliningTailrecursionCount  + disabled(methodInliningTailrecursion));
+            System.out.println("  Number of merged code blocks:                " + codeMergingCount                  + disabled(codeMerging));
+            System.out.println("  Number of variable peephole optimizations:   " + codeSimplificationVariableCount   + disabled(codeSimplificationVariable));
+            System.out.println("  Number of arithmetic peephole optimizations: " + codeSimplificationArithmeticCount + disabled(codeSimplificationArithmetic));
+            System.out.println("  Number of cast peephole optimizations:       " + codeSimplificationCastCount       + disabled(codeSimplificationCast));
+            System.out.println("  Number of field peephole optimizations:      " + codeSimplificationFieldCount      + disabled(codeSimplificationField));
+            System.out.println("  Number of branch peephole optimizations:     " + codeSimplificationBranchCount     + disabled(codeSimplificationBranch));
+            System.out.println("  Number of simplified instructions:           " + codeSimplificationAdvancedCount   + disabled(codeSimplificationAdvanced));
+            System.out.println("  Number of removed instructions:              " + codeRemovalCount                  + disabled(codeRemovalAdvanced));
+            System.out.println("  Number of removed local variables:           " + codeRemovalVariableCount          + disabled(codeRemovalVariable));
+            System.out.println("  Number of removed exception blocks:          " + codeRemovalExceptionCount         + disabled(codeRemovalException));
+            System.out.println("  Number of optimized local variable frames:   " + codeAllocationVariableCount       + disabled(codeAllocationVariable));
+        }
+
+        return classMarkingFinalCount            > 0 ||
+               classMergingVerticalCount         > 0 ||
+               classMergingHorizontalCount       > 0 ||
+               fieldRemovalWriteonlyCount        > 0 ||
+               fieldMarkingPrivateCount          > 0 ||
+               methodMarkingPrivateCount         > 0 ||
+               methodMarkingStaticCount          > 0 ||
+               methodMarkingFinalCount           > 0 ||
+               fieldPropagationValueCount        > 0 ||
+               methodRemovalParameterCount       > 0 ||
+               methodPropagationParameterCount   > 0 ||
+               methodPropagationReturnvalueCount > 0 ||
+               methodInliningShortCount          > 0 ||
+               methodInliningUniqueCount         > 0 ||
+               methodInliningTailrecursionCount  > 0 ||
+               codeMergingCount                  > 0 ||
+               codeSimplificationVariableCount   > 0 ||
+               codeSimplificationArithmeticCount > 0 ||
+               codeSimplificationCastCount       > 0 ||
+               codeSimplificationFieldCount      > 0 ||
+               codeSimplificationBranchCount     > 0 ||
+               codeSimplificationAdvancedCount   > 0 ||
+               codeRemovalCount                  > 0 ||
+               codeRemovalVariableCount          > 0 ||
+               codeRemovalExceptionCount         > 0 ||
+               codeAllocationVariableCount       > 0;
+    }
+
+
+    /**
+     * Returns a String indicating whether the given flag is enabled or
+     * disabled.
+     */
+    private String disabled(boolean flag)
+    {
+        return flag ? "" : "   (disabled)";
+    }
+
+
+    /**
+     * Returns a String indicating whether the given flags are enabled or
+     * disabled.
+     */
+    private String disabled(boolean flag1, boolean flag2)
+    {
+        return flag1 && flag2 ? "" :
+               flag1 || flag2 ? "   (partially disabled)" :
+                                "   (disabled)";
+    }
+}
diff --git a/src/proguard/optimize/ParameterShrinker.java b/src/proguard/optimize/ParameterShrinker.java
new file mode 100644
index 0000000..a2bc6d3
--- /dev/null
+++ b/src/proguard/optimize/ParameterShrinker.java
@@ -0,0 +1,146 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.info.ParameterUsageMarker;
+
+/**
+ * This MemberVisitor removes unused parameters from the code of the methods
+ * that it visits.
+ *
+ * @see ParameterUsageMarker
+ * @see MethodStaticizer
+ * @see MethodDescriptorShrinker
+ * @author Eric Lafortune
+ */
+public class ParameterShrinker
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private static final boolean DEBUG = false;
+
+
+    private final MemberVisitor extraVariableMemberVisitor;
+
+    private final VariableRemapper variableRemapper = new VariableRemapper();
+
+
+    /**
+     * Creates a new ParameterShrinker.
+     */
+    public ParameterShrinker()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new ParameterShrinker with an extra visitor.
+     * @param extraVariableMemberVisitor an optional extra visitor for all
+     *                                   removed parameters.
+     */
+    public ParameterShrinker(MemberVisitor extraVariableMemberVisitor)
+    {
+        this.extraVariableMemberVisitor = extraVariableMemberVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Get the original parameter size that was saved.
+        int oldParameterSize = ParameterUsageMarker.getParameterSize(method);
+
+        // Compute the new parameter size from the shrunk descriptor.
+        int newParameterSize =
+            ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz),
+                                                  method.getAccessFlags());
+
+        if (oldParameterSize > newParameterSize)
+        {
+            // Get the total size of the local variable frame.
+            int maxLocals = codeAttribute.u2maxLocals;
+
+            if (DEBUG)
+            {
+                System.out.println("ParameterShrinker: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
+                System.out.println("  Old parameter size = " + oldParameterSize);
+                System.out.println("  New parameter size = " + newParameterSize);
+                System.out.println("  Max locals         = " + maxLocals);
+            }
+
+            // Create a variable map.
+            int[] variableMap = new int[maxLocals];
+
+            // Move unused parameters right after the parameter block.
+            int usedParameterIndex   = 0;
+            int unusedParameterIndex = newParameterSize;
+            for (int parameterIndex = 0; parameterIndex < oldParameterSize; parameterIndex++)
+            {
+                // Is the variable required as a parameter?
+                if (ParameterUsageMarker.isParameterUsed(method, parameterIndex))
+                {
+                    // Keep the variable as a parameter.
+                    variableMap[parameterIndex] = usedParameterIndex++;
+                }
+                else
+                {
+                    if (DEBUG)
+                    {
+                        System.out.println("  Deleting parameter #"+parameterIndex);
+                    }
+
+                    // Shift the variable to the unused parameter block,
+                    // in case it is still used as a variable.
+                    variableMap[parameterIndex] = unusedParameterIndex++;
+
+                    // Visit the method, if required.
+                    if (extraVariableMemberVisitor != null)
+                    {
+                        method.accept(clazz, extraVariableMemberVisitor);
+                    }
+                }
+            }
+
+            // Fill out the remainder of the map.
+            for (int variableIndex = oldParameterSize; variableIndex < maxLocals; variableIndex++)
+            {
+                variableMap[variableIndex] = variableIndex;
+            }
+
+            // Set the map.
+            variableRemapper.setVariableMap(variableMap);
+
+            // Remap the variables.
+            variableRemapper.visitCodeAttribute(clazz, method, codeAttribute);
+        }
+    }
+}
diff --git a/src/proguard/optimize/TailRecursionSimplifier.java b/src/proguard/optimize/TailRecursionSimplifier.java
new file mode 100644
index 0000000..0946b6a
--- /dev/null
+++ b/src/proguard/optimize/TailRecursionSimplifier.java
@@ -0,0 +1,331 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.MethodrefConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.editor.CodeAttributeComposer;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.*;
+
+/**
+ * This MemberVisitor simplifies tail recursion calls in  all methods that it
+ * visits.
+ *
+ * @author Eric Lafortune
+ */
+public class TailRecursionSimplifier
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ConstantVisitor,
+             ExceptionInfoVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    private final InstructionVisitor extraTailRecursionVisitor;
+
+
+    private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer();
+
+    private Method  targetMethod;
+    private boolean recursive;
+    private boolean inlinedAny;
+
+
+
+    /**
+     * Creates a new TailRecursionSimplifier.
+     */
+    public TailRecursionSimplifier()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new TailRecursionSimplifier with an extra visitor.
+     * @param extraTailRecursionVisitor an optional extra visitor for all
+     *                                  simplified tail recursions.
+     */
+    public TailRecursionSimplifier(InstructionVisitor extraTailRecursionVisitor)
+    {
+        this.extraTailRecursionVisitor = extraTailRecursionVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        int accessFlags = method.getAccessFlags();
+
+        if (// Only check the method if it is private, static, or final.
+            (accessFlags & (ClassConstants.INTERNAL_ACC_PRIVATE |
+                            ClassConstants.INTERNAL_ACC_STATIC  |
+                            ClassConstants.INTERNAL_ACC_FINAL)) != 0 &&
+
+            // Only check the method if it is not synchronized, etc.
+            (accessFlags & (ClassConstants.INTERNAL_ACC_SYNCHRONIZED |
+                            ClassConstants.INTERNAL_ACC_NATIVE       |
+                            ClassConstants.INTERNAL_ACC_INTERFACE    |
+                            ClassConstants.INTERNAL_ACC_ABSTRACT)) == 0)
+        {
+//            codeAttributeComposer.DEBUG = DEBUG =
+//                clazz.getName().equals("abc/Def") &&
+//                method.getName(clazz).equals("abc");
+
+            targetMethod    = method;
+            inlinedAny      = false;
+            codeAttributeComposer.reset();
+
+            // Append the body of the code.
+            copyCode(clazz, method, codeAttribute);
+
+            // Update the code attribute if any code has been inlined.
+            if (inlinedAny)
+            {
+                codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);
+            }
+        }
+    }
+
+
+    /**
+     * Appends the code of the given code attribute.
+     */
+    private void copyCode(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // The code may expand, due to expanding constant and variable
+        // instructions.
+        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
+
+        // Copy the instructions.
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        // Append a label just after the code.
+        codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
+
+        codeAttributeComposer.endCodeFragment();
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        // Copy the instruction.
+        codeAttributeComposer.appendInstruction(offset, instruction.shrink());
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        // Is it a method invocation?
+        switch (constantInstruction.opcode)
+        {
+            case InstructionConstants.OP_INVOKEVIRTUAL:
+            case InstructionConstants.OP_INVOKESPECIAL:
+            case InstructionConstants.OP_INVOKESTATIC:
+            {
+                // Is it a recursive call?
+                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+
+                if (recursive)
+                {
+                    // Is the next instruction a return?
+                    int nextOffset =
+                        offset + constantInstruction.length(offset);
+
+                    Instruction nextInstruction =
+                        InstructionFactory.create(codeAttribute.code, nextOffset);
+
+                    switch (nextInstruction.opcode)
+                    {
+                        case InstructionConstants.OP_IRETURN:
+                        case InstructionConstants.OP_LRETURN:
+                        case InstructionConstants.OP_FRETURN:
+                        case InstructionConstants.OP_DRETURN:
+                        case InstructionConstants.OP_ARETURN:
+                        case InstructionConstants.OP_RETURN:
+                        {
+                            // Isn't the recursive call inside a try/catch block?
+                            codeAttribute.exceptionsAccept(clazz, method, offset, this);
+
+                            if (recursive)
+                            {
+                                if (DEBUG)
+                                {
+                                    System.out.println("TailRecursionSimplifier.visitConstantInstruction: ["+
+                                                       clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"], inlining "+constantInstruction.toString(offset));
+                                }
+
+                                // Append a label.
+                                codeAttributeComposer.appendLabel(offset);
+
+                                storeParameters(clazz, method);
+
+                                // Branch back to the start of the method.
+                                int gotoOffset = offset + 1;
+                                codeAttributeComposer.appendInstruction(gotoOffset,
+                                                                        new BranchInstruction(InstructionConstants.OP_GOTO, -gotoOffset));
+
+                                // The original return instruction will be
+                                // removed elsewhere, if possible.
+
+                                // Remember that the code has changed.
+                                inlinedAny = true;
+
+                                if (extraTailRecursionVisitor != null)
+                                {
+                                    extraTailRecursionVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction);
+                                }
+
+                                // The invocation itself is no longer necessary.
+                                return;
+                            }
+                        }
+                    }
+                }
+
+                break;
+            }
+        }
+
+        // Copy the instruction.
+        codeAttributeComposer.appendInstruction(offset, constantInstruction.shrink());
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        recursive = targetMethod.equals(methodrefConstant.referencedMember);
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        recursive = false;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Appends instructions to pop the parameters for the given method, storing
+     * them in new local variables.
+     */
+    private void storeParameters(Clazz clazz, Method method)
+    {
+        String descriptor = method.getDescriptor(clazz);
+
+        boolean isStatic =
+            (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0;
+
+        // Count the number of parameters, taking into account their categories.
+        int parameterCount  = ClassUtil.internalMethodParameterCount(descriptor);
+        int parameterSize   = ClassUtil.internalMethodParameterSize(descriptor);
+        int parameterOffset = isStatic ? 0 : 1;
+
+        // Store the parameter types.
+        String[] parameterTypes = new String[parameterSize];
+
+        InternalTypeEnumeration internalTypeEnumeration =
+            new InternalTypeEnumeration(descriptor);
+
+        for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++)
+        {
+            String parameterType = internalTypeEnumeration.nextType();
+            parameterTypes[parameterIndex] = parameterType;
+            if (ClassUtil.internalTypeSize(parameterType) == 2)
+            {
+                parameterIndex++;
+            }
+        }
+
+        codeAttributeComposer.beginCodeFragment(parameterSize + 1);
+
+        // Go over the parameter types backward, storing the stack entries
+        // in their corresponding variables.
+        for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--)
+        {
+            String parameterType = parameterTypes[parameterIndex];
+            if (parameterType != null)
+            {
+                byte opcode;
+                switch (parameterType.charAt(0))
+                {
+                    case ClassConstants.INTERNAL_TYPE_BOOLEAN:
+                    case ClassConstants.INTERNAL_TYPE_BYTE:
+                    case ClassConstants.INTERNAL_TYPE_CHAR:
+                    case ClassConstants.INTERNAL_TYPE_SHORT:
+                    case ClassConstants.INTERNAL_TYPE_INT:
+                        opcode = InstructionConstants.OP_ISTORE;
+                        break;
+
+                    case ClassConstants.INTERNAL_TYPE_LONG:
+                        opcode = InstructionConstants.OP_LSTORE;
+                        break;
+
+                    case ClassConstants.INTERNAL_TYPE_FLOAT:
+                        opcode = InstructionConstants.OP_FSTORE;
+                        break;
+
+                    case ClassConstants.INTERNAL_TYPE_DOUBLE:
+                        opcode = InstructionConstants.OP_DSTORE;
+                        break;
+
+                    default:
+                        opcode = InstructionConstants.OP_ASTORE;
+                        break;
+                }
+
+                codeAttributeComposer.appendInstruction(parameterSize-parameterIndex-1,
+                                                        new VariableInstruction(opcode, parameterOffset + parameterIndex).shrink());
+            }
+        }
+
+        // Put the 'this' reference in variable 0.
+        if (!isStatic)
+        {
+            codeAttributeComposer.appendInstruction(parameterSize,
+                                                    new VariableInstruction(InstructionConstants.OP_ASTORE, 0).shrink());
+        }
+
+        codeAttributeComposer.endCodeFragment();
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/WriteOnlyFieldFilter.java b/src/proguard/optimize/WriteOnlyFieldFilter.java
new file mode 100644
index 0000000..578beb2
--- /dev/null
+++ b/src/proguard/optimize/WriteOnlyFieldFilter.java
@@ -0,0 +1,65 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.info.ReadWriteFieldMarker;
+
+/**
+ * This <code>MemberVisitor</code> delegates its visits to program fields to
+ * other given <code>MemberVisitor</code> instances, but only when the visited
+ * field has been marked as write-only.
+ *
+ * @see ReadWriteFieldMarker
+ * @author Eric Lafortune
+ */
+public class WriteOnlyFieldFilter
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final MemberVisitor writeOnlyFieldVisitor;
+
+
+    /**
+     * Creates a new WriteOnlyFieldFilter.
+     * @param writeOnlyFieldVisitor the <code>MemberVisitor</code> to which
+     *                              visits to write-only fields will be delegated.
+     */
+    public WriteOnlyFieldFilter(MemberVisitor writeOnlyFieldVisitor)
+    {
+        this.writeOnlyFieldVisitor = writeOnlyFieldVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+
+        if (ReadWriteFieldMarker.isWritten(programField) &&
+            !ReadWriteFieldMarker.isRead(programField))
+        {
+            writeOnlyFieldVisitor.visitProgramField(programClass, programField);
+        }
+    }
+}
diff --git a/src/proguard/optimize/evaluation/EvaluationShrinker.java b/src/proguard/optimize/evaluation/EvaluationShrinker.java
new file mode 100644
index 0000000..1463feb
--- /dev/null
+++ b/src/proguard/optimize/evaluation/EvaluationShrinker.java
@@ -0,0 +1,1912 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.RefConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.evaluation.*;
+import proguard.evaluation.value.*;
+import proguard.optimize.info.*;
+
+/**
+ * This AttributeVisitor simplifies the code attributes that it visits, based
+ * on partial evaluation.
+ *
+ * @author Eric Lafortune
+ */
+public class EvaluationShrinker
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    //*
+    private static final boolean DEBUG_RESULTS  = false;
+    private static final boolean DEBUG          = false;
+    /*/
+    private static boolean DEBUG_RESULTS  = true;
+    private static boolean DEBUG          = true;
+    //*/
+
+    private final InstructionVisitor extraDeletedInstructionVisitor;
+    private final InstructionVisitor extraAddedInstructionVisitor;
+
+    private final PartialEvaluator             partialEvaluator;
+    private final PartialEvaluator             simplePartialEvaluator       = new PartialEvaluator();
+    private final SideEffectInstructionChecker sideEffectInstructionChecker = new SideEffectInstructionChecker(true);
+    private final MyUnusedParameterSimplifier  unusedParameterSimplifier    = new MyUnusedParameterSimplifier();
+    private final MyProducerMarker             producerMarker               = new MyProducerMarker();
+    private final MyStackConsistencyFixer      stackConsistencyFixer        = new MyStackConsistencyFixer();
+    private final CodeAttributeEditor          codeAttributeEditor          = new CodeAttributeEditor(false);
+
+    private boolean[][] variablesNecessaryAfter = new boolean[ClassConstants.TYPICAL_CODE_LENGTH][ClassConstants.TYPICAL_VARIABLES_SIZE];
+    private boolean[][] stacksNecessaryAfter    = new boolean[ClassConstants.TYPICAL_CODE_LENGTH][ClassConstants.TYPICAL_STACK_SIZE];
+    private boolean[][] stacksSimplifiedBefore  = new boolean[ClassConstants.TYPICAL_CODE_LENGTH][ClassConstants.TYPICAL_STACK_SIZE];
+    private boolean[]   instructionsNecessary   = new boolean[ClassConstants.TYPICAL_CODE_LENGTH];
+
+    private int maxMarkedOffset;
+
+
+    /**
+     * Creates a new EvaluationShrinker.
+     */
+    public EvaluationShrinker()
+    {
+        this(new PartialEvaluator(), null, null);
+    }
+
+
+    /**
+     * Creates a new EvaluationShrinker.
+     * @param partialEvaluator               the partial evaluator that will
+     *                                       execute the code and provide
+     *                                       information about the results.
+     * @param extraDeletedInstructionVisitor an optional extra visitor for all
+     *                                       deleted instructions.
+     * @param extraAddedInstructionVisitor   an optional extra visitor for all
+     *                                       added instructions.
+     */
+    public EvaluationShrinker(PartialEvaluator   partialEvaluator,
+                              InstructionVisitor extraDeletedInstructionVisitor,
+                              InstructionVisitor extraAddedInstructionVisitor)
+    {
+        this.partialEvaluator               = partialEvaluator;
+        this.extraDeletedInstructionVisitor = extraDeletedInstructionVisitor;
+        this.extraAddedInstructionVisitor   = extraAddedInstructionVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG = DEBUG_RESULTS =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // TODO: Remove this when the evaluation shrinker has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            // Process the code.
+            visitCodeAttribute0(clazz, method, codeAttribute);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while shrinking instructions after partial evaluation:");
+            System.err.println("  Class       = ["+clazz.getName()+"]");
+            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+            System.err.println("Not optimizing this method");
+
+            if (DEBUG)
+            {
+                method.accept(clazz, new ClassPrinter());
+
+                throw ex;
+            }
+        }
+    }
+
+
+    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (DEBUG_RESULTS)
+        {
+            System.out.println();
+            System.out.println("Class "+ClassUtil.externalClassName(clazz.getName()));
+            System.out.println("Method "+ClassUtil.externalFullMethodDescription(clazz.getName(),
+                                                                                 0,
+                                                                                 method.getName(clazz),
+                                                                                 method.getDescriptor(clazz)));
+        }
+
+        // Initialize the necessary array.
+        initializeNecessary(codeAttribute);
+
+        // Evaluate the method.
+        partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
+
+        int codeLength = codeAttribute.u4codeLength;
+
+        // Reset the code changes.
+        codeAttributeEditor.reset(codeLength);
+
+        // Mark any unused method parameters on the stack.
+        if (DEBUG) System.out.println("Invocation simplification:");
+
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            if (partialEvaluator.isTraced(offset))
+            {
+                Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                    offset);
+
+                instruction.accept(clazz, method, codeAttribute, offset, unusedParameterSimplifier);
+            }
+        }
+
+        // Mark all essential instructions that have been encountered as used.
+        if (DEBUG) System.out.println("Usage initialization: ");
+
+        maxMarkedOffset = -1;
+
+        // The invocation of the "super" or "this" <init> method inside a
+        // constructor is always necessary.
+        int superInitializationOffset = partialEvaluator.superInitializationOffset();
+        if (superInitializationOffset != PartialEvaluator.NONE)
+        {
+            if (DEBUG) System.out.print("(super.<init>)");
+
+            markInstruction(superInitializationOffset);
+        }
+
+        // Also mark infinite loops and instructions that cause side effects.
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            if (partialEvaluator.isTraced(offset))
+            {
+                Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                    offset);
+
+                // Mark that the instruction is necessary if it is an infinite loop.
+                if (instruction.opcode == InstructionConstants.OP_GOTO &&
+                    ((BranchInstruction)instruction).branchOffset == 0)
+                {
+                    if (DEBUG) System.out.print("(infinite loop)");
+                    markInstruction(offset);
+                }
+
+                // Mark that the instruction is necessary if it has side effects.
+                else if (sideEffectInstructionChecker.hasSideEffects(clazz,
+                                                                     method,
+                                                                     codeAttribute,
+                                                                     offset,
+                                                                     instruction))
+                {
+                    markInstruction(offset);
+                }
+            }
+        }
+        if (DEBUG) System.out.println();
+
+
+        // Globally mark instructions and their produced variables and stack
+        // entries on which necessary instructions depend.
+        // Instead of doing this recursively, we loop across all instructions,
+        // starting at the highest previously unmarked instruction that has
+        // been been marked.
+        if (DEBUG) System.out.println("Usage marking:");
+
+        while (maxMarkedOffset >= 0)
+        {
+            int offset = maxMarkedOffset;
+
+            maxMarkedOffset = offset - 1;
+
+            if (partialEvaluator.isTraced(offset))
+            {
+                if (isInstructionNecessary(offset))
+                {
+                    Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                        offset);
+
+                    instruction.accept(clazz, method, codeAttribute, offset, producerMarker);
+                }
+
+                // Check if this instruction is a branch origin from a branch
+                // that straddles some marked code.
+                markStraddlingBranches(offset,
+                                       partialEvaluator.branchTargets(offset),
+                                       true);
+
+                // Check if this instruction is a branch target from a branch
+                // that straddles some marked code.
+                markStraddlingBranches(offset,
+                                       partialEvaluator.branchOrigins(offset),
+                                       false);
+            }
+
+            if (DEBUG)
+            {
+                if (maxMarkedOffset > offset)
+                {
+                    System.out.println(" -> "+maxMarkedOffset);
+                }
+            }
+        }
+        if (DEBUG) System.out.println();
+
+
+        // Mark variable initializations, even if they aren't strictly necessary.
+        // The virtual machine's verification step is not smart enough to see
+        // this, and may complain otherwise.
+        if (DEBUG) System.out.println("Initialization marking: ");
+
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            // Is it a variable initialization that hasn't been marked yet?
+            if (partialEvaluator.isTraced(offset) &&
+                !isInstructionNecessary(offset))
+            {
+                // Is the corresponding variable necessary anywhere in the code,
+                // accoriding to a simple partial evaluation?
+                int variableIndex = partialEvaluator.initializedVariable(offset);
+                if (variableIndex >= 0 &&
+                    isVariableInitializationNecessary(clazz,
+                                                      method,
+                                                      codeAttribute,
+                                                      offset,
+                                                      variableIndex))
+                {
+                    markInstruction(offset);
+                }
+            }
+        }
+        if (DEBUG) System.out.println();
+
+
+        // Locally fix instructions, in order to keep the stack consistent.
+        if (DEBUG) System.out.println("Stack consistency fixing:");
+
+        maxMarkedOffset = codeLength - 1;
+
+        while (maxMarkedOffset >= 0)
+        {
+            int offset = maxMarkedOffset;
+
+            maxMarkedOffset = offset - 1;
+
+            if (partialEvaluator.isTraced(offset))
+            {
+                Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                    offset);
+
+                instruction.accept(clazz, method, codeAttribute, offset, stackConsistencyFixer);
+
+                // Check if this instruction is a branch origin from a branch
+                // that straddles some marked code.
+                markStraddlingBranches(offset,
+                                       partialEvaluator.branchTargets(offset),
+                                       true);
+
+                // Check if this instruction is a branch target from a branch
+                // that straddles some marked code.
+                markStraddlingBranches(offset,
+                                       partialEvaluator.branchOrigins(offset),
+                                       false);
+            }
+        }
+        if (DEBUG) System.out.println();
+
+
+        // Replace traced but unmarked backward branches by infinite loops.
+        // The virtual machine's verification step is not smart enough to see
+        // the code isn't reachable, and may complain otherwise.
+        // Any clearly unreachable code will still be removed elsewhere.
+        if (DEBUG) System.out.println("Infinite loop fixing:");
+
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            // Is it a traced but unmarked backward branch, without an unmarked
+            // straddling forward branch? Note that this is still a heuristic.
+            if (partialEvaluator.isTraced(offset) &&
+                !isInstructionNecessary(offset)   &&
+                isAllSmallerThanOrEqual(partialEvaluator.branchTargets(offset),
+                                        offset)   &&
+                !isAnyUnnecessaryInstructionBranchingOver(lastNecessaryInstructionOffset(offset),
+                                                          offset))
+            {
+                replaceByInfiniteLoop(clazz, offset);
+            }
+        }
+        if (DEBUG) System.out.println();
+
+
+        // Insert infinite loops after jumps to subroutines that don't return.
+        // The virtual machine's verification step is not smart enough to see
+        // the code isn't reachable, and may complain otherwise.
+        if (DEBUG) System.out.println("Non-returning subroutine fixing:");
+
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            // Is it a traced but unmarked backward branch, without an unmarked
+            // straddling forward branch? Note that this is still a heuristic.
+            if (isInstructionNecessary(offset) &&
+                partialEvaluator.isSubroutineInvocation(offset))
+            {
+                Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                    offset);
+
+                int nextOffset = offset + instruction.length(offset);
+                if (!isInstructionNecessary(nextOffset))
+                {
+                    replaceByInfiniteLoop(clazz, nextOffset);
+                }
+            }
+        }
+        if (DEBUG) System.out.println();
+
+
+        // Delete all instructions that are not used.
+        int offset = 0;
+        do
+        {
+            Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                offset);
+            if (!isInstructionNecessary(offset))
+            {
+                codeAttributeEditor.deleteInstruction(offset);
+
+                codeAttributeEditor.insertBeforeInstruction(offset, (Instruction)null);
+                codeAttributeEditor.replaceInstruction(offset,      (Instruction)null);
+                codeAttributeEditor.insertAfterInstruction(offset,  (Instruction)null);
+
+                // Visit the instruction, if required.
+                if (extraDeletedInstructionVisitor != null)
+                {
+                    instruction.accept(clazz, method, codeAttribute, offset, extraDeletedInstructionVisitor);
+                }
+            }
+
+            offset += instruction.length(offset);
+        }
+        while (offset < codeLength);
+
+
+        if (DEBUG_RESULTS)
+        {
+            System.out.println("Simplification results:");
+
+            offset = 0;
+            do
+            {
+                Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                    offset);
+                System.out.println((isInstructionNecessary(offset) ? " + " : " - ")+instruction.toString(offset));
+
+                if (partialEvaluator.isTraced(offset))
+                {
+                    int initializationOffset = partialEvaluator.initializationOffset(offset);
+                    if (initializationOffset != PartialEvaluator.NONE)
+                    {
+                        System.out.println("     is to be initialized at ["+initializationOffset+"]");
+                    }
+
+                    InstructionOffsetValue branchTargets = partialEvaluator.branchTargets(offset);
+                    if (branchTargets != null)
+                    {
+                        System.out.println("     has overall been branching to "+branchTargets);
+                    }
+
+                    boolean deleted = codeAttributeEditor.deleted[offset];
+                    if (isInstructionNecessary(offset) && deleted)
+                    {
+                        System.out.println("     is deleted");
+                    }
+
+                    Instruction preInsertion = codeAttributeEditor.preInsertions[offset];
+                    if (preInsertion != null)
+                    {
+                        System.out.println("     is preceded by: "+preInsertion);
+                    }
+
+                    Instruction replacement = codeAttributeEditor.replacements[offset];
+                    if (replacement != null)
+                    {
+                        System.out.println("     is replaced by: "+replacement);
+                    }
+
+                    Instruction postInsertion = codeAttributeEditor.postInsertions[offset];
+                    if (postInsertion != null)
+                    {
+                        System.out.println("     is followed by: "+postInsertion);
+                    }
+                }
+
+                offset += instruction.length(offset);
+            }
+            while (offset < codeLength);
+        }
+
+        // Apply all accumulated changes to the code.
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+    }
+
+
+    /**
+     * This MemberVisitor marks stack entries that aren't necessary because
+     * parameters aren't used in the methods that are visited.
+     */
+    private class MyUnusedParameterSimplifier
+    extends       SimplifiedVisitor
+    implements    InstructionVisitor, ConstantVisitor, MemberVisitor
+    {
+        private int                 invocationOffset;
+        private ConstantInstruction invocationInstruction;
+
+
+        // Implementations for InstructionVisitor.
+
+        public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+        public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+        {
+            switch (constantInstruction.opcode)
+            {
+                case InstructionConstants.OP_INVOKEVIRTUAL:
+                case InstructionConstants.OP_INVOKESPECIAL:
+                case InstructionConstants.OP_INVOKESTATIC:
+                case InstructionConstants.OP_INVOKEINTERFACE:
+                    this.invocationOffset      = offset;
+                    this.invocationInstruction = constantInstruction;
+                    clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+                    break;
+            }
+        }
+
+
+        // Implementations for ConstantVisitor.
+
+        public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+        {
+            refConstant.referencedMemberAccept(this);
+        }
+
+
+        // Implementations for MemberVisitor.
+
+        public void visitAnyMember(Clazz clazz, Member member) {}
+
+
+        public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+        {
+            // Get the total size of the parameters.
+            int parameterSize = ParameterUsageMarker.getParameterSize(programMethod);
+
+            // Make the method invocation static, if possible.
+            if ((programMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) == 0 &&
+                !ParameterUsageMarker.isParameterUsed(programMethod, 0))
+            {
+                replaceByStaticInvocation(programClass,
+                                          invocationOffset,
+                                          invocationInstruction);
+            }
+
+            // Remove unused parameters.
+            for (int index = 0; index < parameterSize; index++)
+            {
+                if (!ParameterUsageMarker.isParameterUsed(programMethod, index))
+                {
+                    TracedStack stack =
+                        partialEvaluator.getStackBefore(invocationOffset);
+
+                    int stackIndex = stack.size() - parameterSize + index;
+
+                    if (DEBUG)
+                    {
+                        System.out.println("  ["+invocationOffset+"] Ignoring parameter #"+index+" of "+programClass.getName()+"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"] (stack entry #"+stackIndex+" ["+stack.getBottom(stackIndex)+"])");
+                        System.out.println("    Full stack: "+stack);
+                    }
+
+                    markStackSimplificationBefore(invocationOffset, stackIndex);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * This InstructionVisitor marks the producing instructions and produced
+     * variables and stack entries of the instructions that it visits.
+     * Simplified method arguments are ignored.
+     */
+    private class MyProducerMarker
+    extends       SimplifiedVisitor
+    implements    InstructionVisitor
+    {
+        // Implementations for InstructionVisitor.
+
+        public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+        {
+            markStackProducers(clazz, offset, instruction);
+        }
+
+
+        public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+        {
+            switch (simpleInstruction.opcode)
+            {
+                case InstructionConstants.OP_DUP:
+                    conditionallyMarkStackEntryProducers(offset, 0, 0);
+                    conditionallyMarkStackEntryProducers(offset, 1, 0);
+                    break;
+                case InstructionConstants.OP_DUP_X1:
+                    conditionallyMarkStackEntryProducers(offset, 0, 0);
+                    conditionallyMarkStackEntryProducers(offset, 1, 1);
+                    conditionallyMarkStackEntryProducers(offset, 2, 0);
+                    break;
+                case InstructionConstants.OP_DUP_X2:
+                    conditionallyMarkStackEntryProducers(offset, 0, 0);
+                    conditionallyMarkStackEntryProducers(offset, 1, 1);
+                    conditionallyMarkStackEntryProducers(offset, 2, 2);
+                    conditionallyMarkStackEntryProducers(offset, 3, 0);
+                    break;
+                case InstructionConstants.OP_DUP2:
+                    conditionallyMarkStackEntryProducers(offset, 0, 0);
+                    conditionallyMarkStackEntryProducers(offset, 1, 1);
+                    conditionallyMarkStackEntryProducers(offset, 2, 0);
+                    conditionallyMarkStackEntryProducers(offset, 3, 1);
+                    break;
+                case InstructionConstants.OP_DUP2_X1:
+                    conditionallyMarkStackEntryProducers(offset, 0, 0);
+                    conditionallyMarkStackEntryProducers(offset, 1, 1);
+                    conditionallyMarkStackEntryProducers(offset, 2, 2);
+                    conditionallyMarkStackEntryProducers(offset, 3, 0);
+                    conditionallyMarkStackEntryProducers(offset, 4, 1);
+                    break;
+                case InstructionConstants.OP_DUP2_X2:
+                    conditionallyMarkStackEntryProducers(offset, 0, 0);
+                    conditionallyMarkStackEntryProducers(offset, 1, 1);
+                    conditionallyMarkStackEntryProducers(offset, 2, 2);
+                    conditionallyMarkStackEntryProducers(offset, 3, 3);
+                    conditionallyMarkStackEntryProducers(offset, 4, 0);
+                    conditionallyMarkStackEntryProducers(offset, 5, 1);
+                    break;
+                case InstructionConstants.OP_SWAP:
+                    conditionallyMarkStackEntryProducers(offset, 0, 1);
+                    conditionallyMarkStackEntryProducers(offset, 1, 0);
+                    break;
+                default:
+                    markStackProducers(clazz, offset, simpleInstruction);
+                    break;
+            }
+        }
+
+
+        public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+        {
+            // Is the variable being loaded (or incremented)?
+            if (variableInstruction.opcode < InstructionConstants.OP_ISTORE)
+            {
+                markVariableProducers(offset, variableInstruction.variableIndex);
+            }
+            else
+            {
+                markStackProducers(clazz, offset, variableInstruction);
+            }
+        }
+
+
+        public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+        {
+            // Mark the initializer invocation, if this is a 'new' instruction.
+            if (constantInstruction.opcode == InstructionConstants.OP_NEW)
+            {
+                markInitialization(offset);
+            }
+
+            markStackProducers(clazz, offset, constantInstruction);
+        }
+
+
+        public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+        {
+            // Explicitly mark the produced stack entry of a 'jsr' instruction,
+            // because the consuming 'astore' instruction of the subroutine is
+            // cleared every time it is traced.
+            if (branchInstruction.opcode == InstructionConstants.OP_JSR ||
+                branchInstruction.opcode == InstructionConstants.OP_JSR_W)
+            {
+                markStackEntryAfter(offset, 0);
+            }
+            else
+            {
+                markStackProducers(clazz, offset, branchInstruction);
+            }
+        }
+    }
+
+
+    /**
+     * This InstructionVisitor fixes instructions locally, popping any unused
+     * produced stack entries after marked instructions, and popping produced
+     * stack entries and pushing missing stack entries instead of unmarked
+     * instructions.
+     */
+    private class MyStackConsistencyFixer
+    extends       SimplifiedVisitor
+    implements    InstructionVisitor
+    {
+        // Implementations for InstructionVisitor.
+
+        public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+        {
+            // Has the instruction been marked?
+            if (isInstructionNecessary(offset))
+            {
+                // Check all stack entries that are popped.
+                // Typical case: a freshly marked variable initialization that
+                // requires some value on the stack.
+                int popCount = instruction.stackPopCount(clazz);
+                if (popCount > 0)
+                {
+                    TracedStack tracedStack =
+                        partialEvaluator.getStackBefore(offset);
+
+                    int top = tracedStack.size() - 1;
+
+                    int requiredPushCount = 0;
+                    for (int stackIndex = 0; stackIndex < popCount; stackIndex++)
+                    {
+                        // Is the stack entry required by other consumers?
+                        if (!isStackSimplifiedBefore(offset, top - stackIndex) &&
+                            !isAnyStackEntryNecessaryAfter(tracedStack.getTopProducerValue(stackIndex).instructionOffsetValue(), top - stackIndex))
+                        {
+                            // Remember to push it.
+                            requiredPushCount++;
+                        }
+                    }
+
+                    // Push some necessary stack entries.
+                    if (requiredPushCount > 0)
+                    {
+                        if (DEBUG) System.out.println("  Inserting before marked consumer "+instruction.toString(offset));
+
+                        if (requiredPushCount > (instruction.isCategory2() ? 2 : 1))
+                        {
+                            throw new IllegalArgumentException("Unsupported stack size increment ["+requiredPushCount+"]");
+                        }
+
+                        insertPushInstructions(offset, false, tracedStack.getTop(0).computationalType());
+                    }
+                }
+
+                // Check all stack entries that are pushed.
+                // Typical case: a return value that wasn't really required and
+                // that should be popped.
+                int pushCount = instruction.stackPushCount(clazz);
+                if (pushCount > 0)
+                {
+                    TracedStack tracedStack =
+                        partialEvaluator.getStackAfter(offset);
+
+                    int top = tracedStack.size() - 1;
+
+                    int requiredPopCount = 0;
+                    for (int stackIndex = 0; stackIndex < pushCount; stackIndex++)
+                    {
+                        // Is the stack entry required by consumers?
+                        if (!isStackEntryNecessaryAfter(offset, top - stackIndex))
+                        {
+                            // Remember to pop it.
+                            requiredPopCount++;
+                        }
+                    }
+
+                    // Pop the unnecessary stack entries.
+                    if (requiredPopCount > 0)
+                    {
+                        if (DEBUG) System.out.println("  Inserting after marked producer "+instruction.toString(offset));
+
+                        insertPopInstructions(offset, false, requiredPopCount);
+                    }
+                }
+            }
+            else
+            {
+                // Check all stack entries that would be popped.
+                // Typical case: a stack value that is required elsewhere and
+                // that still has to be popped.
+                int popCount = instruction.stackPopCount(clazz);
+                if (popCount > 0)
+                {
+                    TracedStack tracedStack =
+                        partialEvaluator.getStackBefore(offset);
+
+                    int top = tracedStack.size() - 1;
+
+                    int expectedPopCount = 0;
+                    for (int stackIndex = 0; stackIndex < popCount; stackIndex++)
+                    {
+                        // Is the stack entry required by other consumers?
+                        if (isAnyStackEntryNecessaryAfter(tracedStack.getTopProducerValue(stackIndex).instructionOffsetValue(), top - stackIndex))
+                        {
+                            // Remember to pop it.
+                            expectedPopCount++;
+                        }
+                    }
+
+                    // Pop the unnecessary stack entries.
+                    if (expectedPopCount > 0)
+                    {
+                        if (DEBUG) System.out.println("  Replacing unmarked consumer "+instruction.toString(offset));
+
+                        insertPopInstructions(offset, true, expectedPopCount);
+                    }
+                }
+
+                // Check all stack entries that would be pushed.
+                // Typical case: never.
+                int pushCount = instruction.stackPushCount(clazz);
+                if (pushCount > 0)
+                {
+                    TracedStack tracedStack =
+                        partialEvaluator.getStackAfter(offset);
+
+                    int top = tracedStack.size() - 1;
+
+                    int expectedPushCount = 0;
+                    for (int stackIndex = 0; stackIndex < pushCount; stackIndex++)
+                    {
+                        // Is the stack entry required by consumers?
+                        if (isStackEntryNecessaryAfter(offset, top - stackIndex))
+                        {
+                            // Remember to push it.
+                            expectedPushCount++;
+                        }
+                    }
+
+                    // Push some necessary stack entries.
+                    if (expectedPushCount > 0)
+                    {
+                        if (DEBUG) System.out.println("  Replacing unmarked producer "+instruction.toString(offset));
+
+                        insertPushInstructions(offset, true, tracedStack.getTop(0).computationalType());
+                    }
+                }
+            }
+        }
+
+
+        public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+        {
+            if (isInstructionNecessary(offset) &&
+                isDupOrSwap(simpleInstruction))
+            {
+                fixDupInstruction(clazz, codeAttribute, offset, simpleInstruction);
+            }
+            else
+            {
+                visitAnyInstruction(clazz, method, codeAttribute, offset, simpleInstruction);
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the variable and the corresponding producing instructions
+     * of the consumer at the given offset.
+     * @param consumerOffset the offset of the consumer.
+     * @param variableIndex  the index of the variable to be marked.
+     */
+    private void markVariableProducers(int consumerOffset,
+                                       int variableIndex)
+    {
+        TracedVariables tracedVariables =
+            partialEvaluator.getVariablesBefore(consumerOffset);
+
+        // Mark the producer of the loaded value.
+        markVariableProducers(tracedVariables.getProducerValue(variableIndex).instructionOffsetValue(),
+                              variableIndex);
+    }
+
+
+    /**
+     * Marks the variable and its producing instructions at the given offsets.
+     * @param producerOffsets the offsets of the producers to be marked.
+     * @param variableIndex   the index of the variable to be marked.
+     */
+    private void markVariableProducers(InstructionOffsetValue producerOffsets,
+                                       int                    variableIndex)
+    {
+        if (producerOffsets != null)
+        {
+            int offsetCount = producerOffsets.instructionOffsetCount();
+            for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++)
+            {
+                // Make sure the variable and the instruction are marked
+                // at the producing offset.
+                int offset = producerOffsets.instructionOffset(offsetIndex);
+
+                markVariableAfter(offset, variableIndex);
+                markInstruction(offset);
+            }
+        }
+    }
+
+
+    /**
+     * Marks the stack entries and their producing instructions of the
+     * consumer at the given offset.
+     * @param clazz          the containing class.
+     * @param consumerOffset the offset of the consumer.
+     * @param consumer       the consumer of the stack entries.
+     */
+    private void markStackProducers(Clazz       clazz,
+                                    int         consumerOffset,
+                                    Instruction consumer)
+    {
+        // Mark the producers of the popped values.
+        int popCount = consumer.stackPopCount(clazz);
+        for (int stackIndex = 0; stackIndex < popCount; stackIndex++)
+        {
+            markStackEntryProducers(consumerOffset, stackIndex);
+        }
+    }
+
+
+    /**
+     * Marks the stack entry and the corresponding producing instructions
+     * of the consumer at the given offset, if the stack entry of the
+     * consumer is marked.
+     * @param consumerOffset     the offset of the consumer.
+     * @param consumerStackIndex the index of the stack entry to be checked
+     *                           (counting from the top).
+     * @param producerStackIndex the index of the stack entry to be marked
+     *                           (counting from the top).
+     */
+    private void conditionallyMarkStackEntryProducers(int consumerOffset,
+                                                      int consumerStackIndex,
+                                                      int producerStackIndex)
+    {
+        int top = partialEvaluator.getStackAfter(consumerOffset).size() - 1;
+
+        if (isStackEntryNecessaryAfter(consumerOffset, top - consumerStackIndex))
+        {
+            markStackEntryProducers(consumerOffset, producerStackIndex);
+        }
+    }
+
+
+    /**
+     * Marks the stack entry and the corresponding producing instructions
+     * of the consumer at the given offset.
+     * @param consumerOffset the offset of the consumer.
+     * @param stackIndex     the index of the stack entry to be marked
+     *                        (counting from the top).
+     */
+    private void markStackEntryProducers(int consumerOffset,
+                                         int stackIndex)
+    {
+        TracedStack tracedStack =
+            partialEvaluator.getStackBefore(consumerOffset);
+
+        int stackBottomIndex = tracedStack.size() - 1 - stackIndex;
+
+        if (!isStackSimplifiedBefore(consumerOffset, stackBottomIndex))
+        {
+            markStackEntryProducers(tracedStack.getTopProducerValue(stackIndex).instructionOffsetValue(),
+                                    stackBottomIndex);
+        }
+    }
+
+
+    /**
+     * Marks the stack entry and its producing instructions at the given
+     * offsets.
+     * @param producerOffsets the offsets of the producers to be marked.
+     * @param stackIndex      the index of the stack entry to be marked
+     *                        (counting from the bottom).
+     */
+    private void markStackEntryProducers(InstructionOffsetValue producerOffsets,
+                                         int                    stackIndex)
+    {
+        if (producerOffsets != null)
+        {
+            int offsetCount = producerOffsets.instructionOffsetCount();
+            for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++)
+            {
+                // Make sure the stack entry and the instruction are marked
+                // at the producing offset.
+                int offset = producerOffsets.instructionOffset(offsetIndex);
+
+                markStackEntryAfter(offset, stackIndex);
+                markInstruction(offset);
+            }
+        }
+    }
+
+
+    /**
+     * Marks the stack entry and its initializing instruction
+     * ('invokespecial *.<init>') for the given 'new' instruction offset.
+     * @param newInstructionOffset the offset of the 'new' instruction.
+     */
+    private void markInitialization(int newInstructionOffset)
+    {
+        int initializationOffset =
+            partialEvaluator.initializationOffset(newInstructionOffset);
+
+        TracedStack tracedStack =
+            partialEvaluator.getStackAfter(newInstructionOffset);
+
+        markStackEntryAfter(initializationOffset, tracedStack.size() - 1);
+        markInstruction(initializationOffset);
+    }
+
+
+    /**
+     * Marks the branch instructions of straddling branches, if they straddle
+     * some code that has been marked.
+     * @param instructionOffset   the offset of the branch origin or branch target.
+     * @param branchOffsets       the offsets of the straddling branch targets
+     *                            or branch origins.
+     * @param isPointingToTargets <code>true</code> if the above offsets are
+     *                            branch targets, <code>false</code> if they
+     *                            are branch origins.
+     */
+    private void markStraddlingBranches(int                    instructionOffset,
+                                        InstructionOffsetValue branchOffsets,
+                                        boolean                isPointingToTargets)
+    {
+        if (branchOffsets != null)
+        {
+            // Loop over all branch offsets.
+            int branchCount = branchOffsets.instructionOffsetCount();
+            for (int branchIndex = 0; branchIndex < branchCount; branchIndex++)
+            {
+                // Is the branch straddling forward any necessary instructions?
+                int branchOffset = branchOffsets.instructionOffset(branchIndex);
+
+                // Is the offset pointing to a branch origin or to a branch target?
+                if (isPointingToTargets)
+                {
+                    markStraddlingBranch(instructionOffset,
+                                         branchOffset,
+                                         instructionOffset,
+                                         branchOffset);
+                }
+                else
+                {
+                    markStraddlingBranch(instructionOffset,
+                                         branchOffset,
+                                         branchOffset,
+                                         instructionOffset);
+                }
+            }
+        }
+    }
+
+
+    private void markStraddlingBranch(int instructionOffsetStart,
+                                      int instructionOffsetEnd,
+                                      int branchOrigin,
+                                      int branchTarget)
+    {
+        if (!isInstructionNecessary(branchOrigin) &&
+            isAnyInstructionNecessary(instructionOffsetStart, instructionOffsetEnd))
+        {
+            if (DEBUG) System.out.print("["+branchOrigin+"->"+branchTarget+"]");
+
+            // Mark the branch instruction.
+            markInstruction(branchOrigin);
+        }
+    }
+
+
+    /**
+     * Marks the specified instruction if it is a required dup/swap instruction,
+     * replacing it by an appropriate variant if necessary.
+     * @param clazz         the class that is being checked.
+     * @param codeAttribute the code that is being checked.
+     * @param dupOffset     the offset of the dup/swap instruction.
+     * @param instruction   the dup/swap instruction.
+     */
+    private void fixDupInstruction(Clazz         clazz,
+                                   CodeAttribute codeAttribute,
+                                   int           dupOffset,
+                                   Instruction   instruction)
+    {
+        int top = partialEvaluator.getStackAfter(dupOffset).size() - 1;
+
+        byte oldOpcode = instruction.opcode;
+        byte newOpcode = 0;
+
+        // Simplify the popping instruction if possible.
+        switch (oldOpcode)
+        {
+            case InstructionConstants.OP_DUP:
+            {
+                boolean stackEntryPresent0 = isStackEntryNecessaryAfter(dupOffset, top - 0);
+                boolean stackEntryPresent1 = isStackEntryNecessaryAfter(dupOffset, top - 1);
+
+                // Should either the original element or the copy be present?
+                if (stackEntryPresent0 ||
+                    stackEntryPresent1)
+                {
+                    // Should both the original element and the copy be present?
+                    if (stackEntryPresent0 &&
+                        stackEntryPresent1)
+                    {
+                        newOpcode = InstructionConstants.OP_DUP;
+                    }
+                }
+                break;
+            }
+            case InstructionConstants.OP_DUP_X1:
+            {
+                boolean stackEntryPresent0 = isStackEntryNecessaryAfter(dupOffset, top - 0);
+                boolean stackEntryPresent1 = isStackEntryNecessaryAfter(dupOffset, top - 1);
+                boolean stackEntryPresent2 = isStackEntryNecessaryAfter(dupOffset, top - 2);
+
+                // Should either the original element or the copy be present?
+                if (stackEntryPresent0 ||
+                    stackEntryPresent2)
+                {
+                    // Should the copy be present?
+                    if (stackEntryPresent2)
+                    {
+                        // Compute the number of elements to be skipped.
+                        int skipCount = stackEntryPresent1 ? 1 : 0;
+
+                        // Should the original element be present?
+                        if (stackEntryPresent0)
+                        {
+                            // Copy the original element.
+                            newOpcode = (byte)(InstructionConstants.OP_DUP + skipCount);
+                        }
+                        else if (skipCount == 1)
+                        {
+                            // Move the original element.
+                            newOpcode = InstructionConstants.OP_SWAP;
+                        }
+                    }
+                }
+                break;
+            }
+            case InstructionConstants.OP_DUP_X2:
+            {
+                boolean stackEntryPresent0 = isStackEntryNecessaryAfter(dupOffset, top - 0);
+                boolean stackEntryPresent1 = isStackEntryNecessaryAfter(dupOffset, top - 1);
+                boolean stackEntryPresent2 = isStackEntryNecessaryAfter(dupOffset, top - 2);
+                boolean stackEntryPresent3 = isStackEntryNecessaryAfter(dupOffset, top - 3);
+
+                // Should either the original element or the copy be present?
+                if (stackEntryPresent0 ||
+                    stackEntryPresent3)
+                {
+                    // Should the copy be present?
+                    if (stackEntryPresent3)
+                    {
+                        int skipCount = (stackEntryPresent1 ? 1 : 0) +
+                                        (stackEntryPresent2 ? 1 : 0);
+
+                        // Should the original element be present?
+                        if (stackEntryPresent0)
+                        {
+                            // Copy the original element.
+                            newOpcode = (byte)(InstructionConstants.OP_DUP + skipCount);
+                        }
+                        else if (skipCount == 1)
+                        {
+                            // Move the original element.
+                            newOpcode = InstructionConstants.OP_SWAP;
+                        }
+                        else if (skipCount == 2)
+                        {
+                            // We can't easily move the original element.
+                            throw new UnsupportedOperationException("Can't handle dup_x2 instruction moving original element across two elements at ["+dupOffset +"]");
+                        }
+                    }
+                }
+                break;
+            }
+            case InstructionConstants.OP_DUP2:
+            {
+                boolean stackEntriesPresent01 = isStackEntriesNecessaryAfter(dupOffset, top - 0, top - 1);
+                boolean stackEntriesPresent23 = isStackEntriesNecessaryAfter(dupOffset, top - 2, top - 3);
+
+                // Should either the original element or the copy be present?
+                if (stackEntriesPresent01 ||
+                    stackEntriesPresent23)
+                {
+                    // Should both the original element and the copy be present?
+                    if (stackEntriesPresent01 &&
+                        stackEntriesPresent23)
+                    {
+                        newOpcode = InstructionConstants.OP_DUP2;
+                    }
+                }
+                break;
+            }
+            case InstructionConstants.OP_DUP2_X1:
+            {
+                boolean stackEntriesPresent01 = isStackEntriesNecessaryAfter(dupOffset, top - 0, top - 1);
+                boolean stackEntryPresent2    = isStackEntryNecessaryAfter(dupOffset, top - 2);
+                boolean stackEntriesPresent34 = isStackEntriesNecessaryAfter(dupOffset, top - 3, top - 4);
+
+                // Should either the original element or the copy be present?
+                if (stackEntriesPresent01 ||
+                    stackEntriesPresent34)
+                {
+                    // Should the copy be present?
+                    if (stackEntriesPresent34)
+                    {
+                        int skipCount = stackEntryPresent2 ? 1 : 0;
+
+                        // Should the original element be present?
+                        if (stackEntriesPresent01)
+                        {
+                            // Copy the original element.
+                            newOpcode = (byte)(InstructionConstants.OP_DUP2 + skipCount);
+                        }
+                        else if (skipCount > 0)
+                        {
+                            // We can't easily move the original element.
+                            throw new UnsupportedOperationException("Can't handle dup2_x1 instruction moving original element across "+skipCount+" elements at ["+dupOffset +"]");
+                        }
+                    }
+                }
+                break;
+            }
+            case InstructionConstants.OP_DUP2_X2:
+            {
+                boolean stackEntriesPresent01 = isStackEntriesNecessaryAfter(dupOffset, top - 0, top - 1);
+                boolean stackEntryPresent2    = isStackEntryNecessaryAfter(dupOffset, top - 2);
+                boolean stackEntryPresent3    = isStackEntryNecessaryAfter(dupOffset, top - 3);
+                boolean stackEntriesPresent45 = isStackEntriesNecessaryAfter(dupOffset, top - 4, top - 5);
+
+                // Should either the original element or the copy be present?
+                if (stackEntriesPresent01 ||
+                    stackEntriesPresent45)
+                {
+                    // Should the copy be present?
+                    if (stackEntriesPresent45)
+                    {
+                        int skipCount = (stackEntryPresent2 ? 1 : 0) +
+                                        (stackEntryPresent3 ? 1 : 0);
+
+                        // Should the original element be present?
+                        if (stackEntriesPresent01)
+                        {
+                            // Copy the original element.
+                            newOpcode = (byte)(InstructionConstants.OP_DUP2 + skipCount);
+                        }
+                        else if (skipCount > 0)
+                        {
+                            // We can't easily move the original element.
+                            throw new UnsupportedOperationException("Can't handle dup2_x2 instruction moving original element across "+skipCount+" elements at ["+dupOffset +"]");
+                        }
+                    }
+                }
+                break;
+            }
+            case InstructionConstants.OP_SWAP:
+            {
+                boolean stackEntryPresent0 = isStackEntryNecessaryAfter(dupOffset, top - 0);
+                boolean stackEntryPresent1 = isStackEntryNecessaryAfter(dupOffset, top - 1);
+
+                // Will either element be present?
+                if (stackEntryPresent0 ||
+                    stackEntryPresent1)
+                {
+                    // Will both elements be present?
+                    if (stackEntryPresent0 &&
+                        stackEntryPresent1)
+                    {
+                        newOpcode = InstructionConstants.OP_SWAP;
+                    }
+                }
+                break;
+            }
+        }
+
+        if      (newOpcode == 0)
+        {
+            // Delete the instruction.
+            codeAttributeEditor.deleteInstruction(dupOffset);
+
+            if (extraDeletedInstructionVisitor != null)
+            {
+                extraDeletedInstructionVisitor.visitSimpleInstruction(null, null, null, dupOffset, null);
+            }
+
+            if (DEBUG) System.out.println("  Marking but deleting instruction "+instruction.toString(dupOffset));
+        }
+        else if (newOpcode == oldOpcode)
+        {
+            // Leave the instruction unchanged.
+            codeAttributeEditor.undeleteInstruction(dupOffset);
+
+            if (DEBUG) System.out.println("  Marking unchanged instruction "+instruction.toString(dupOffset));
+        }
+        else
+        {
+            // Replace the instruction.
+            Instruction replacementInstruction = new SimpleInstruction(newOpcode);
+            codeAttributeEditor.replaceInstruction(dupOffset,
+                                                   replacementInstruction);
+
+            if (DEBUG) System.out.println("  Replacing instruction "+instruction.toString(dupOffset)+" by "+replacementInstruction.toString());
+        }
+    }
+
+
+    /**
+     * Pushes a specified type of stack entry before or at the given offset.
+     * The instruction is marked as necessary.
+     */
+    private void insertPushInstructions(int     offset,
+                                        boolean replace,
+                                        int     computationalType)
+    {
+        // Mark this instruction.
+        markInstruction(offset);
+
+        // Create a simple push instrucion.
+        Instruction replacementInstruction =
+            new SimpleInstruction(pushOpcode(computationalType));
+
+        if (DEBUG) System.out.println(": "+replacementInstruction.toString(offset));
+
+        // Replace or insert the push instruction.
+        if (replace)
+        {
+            // Replace the push instruction.
+            codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+        }
+        else
+        {
+            // Insert the push instruction.
+            codeAttributeEditor.insertBeforeInstruction(offset, replacementInstruction);
+
+            if (extraAddedInstructionVisitor != null)
+            {
+                replacementInstruction.accept(null, null, null, offset, extraAddedInstructionVisitor);
+            }
+        }
+    }
+
+
+    /**
+     * Returns the opcode of a push instruction corresponding to the given
+     * computational type.
+     * @param computationalType the computational type to be pushed on the stack.
+     */
+    private byte pushOpcode(int computationalType)
+    {
+        switch (computationalType)
+        {
+            case Value.TYPE_INTEGER:            return InstructionConstants.OP_ICONST_0;
+            case Value.TYPE_LONG:               return InstructionConstants.OP_LCONST_0;
+            case Value.TYPE_FLOAT:              return InstructionConstants.OP_FCONST_0;
+            case Value.TYPE_DOUBLE:             return InstructionConstants.OP_DCONST_0;
+            case Value.TYPE_REFERENCE:
+            case Value.TYPE_INSTRUCTION_OFFSET: return InstructionConstants.OP_ACONST_NULL;
+        }
+
+        throw new IllegalArgumentException("No push opcode for computational type ["+computationalType+"]");
+    }
+
+
+    /**
+     * Pops the given number of stack entries at or after the given offset.
+     * The instructions are marked as necessary.
+     */
+    private void insertPopInstructions(int offset, boolean replace, int popCount)
+    {
+        // Mark this instruction.
+        markInstruction(offset);
+
+        switch (popCount)
+        {
+            case 1:
+            {
+                // Replace or insert a single pop instruction.
+                Instruction popInstruction =
+                    new SimpleInstruction(InstructionConstants.OP_POP);
+
+                if (replace)
+                {
+                    codeAttributeEditor.replaceInstruction(offset, popInstruction);
+                }
+                else
+                {
+                    codeAttributeEditor.insertAfterInstruction(offset, popInstruction);
+
+                    if (extraAddedInstructionVisitor != null)
+                    {
+                        popInstruction.accept(null, null, null, offset, extraAddedInstructionVisitor);
+                    }
+                }
+                break;
+            }
+            case 2:
+            {
+                // Replace or insert a single pop2 instruction.
+                Instruction popInstruction =
+                    new SimpleInstruction(InstructionConstants.OP_POP2);
+
+                if (replace)
+                {
+                    codeAttributeEditor.replaceInstruction(offset, popInstruction);
+                }
+                else
+                {
+                    codeAttributeEditor.insertAfterInstruction(offset, popInstruction);
+
+                    if (extraAddedInstructionVisitor != null)
+                    {
+                        popInstruction.accept(null, null, null, offset, extraAddedInstructionVisitor);
+                    }
+                }
+                break;
+            }
+            default:
+            {
+                // Replace or insert the specified number of pop instructions.
+                Instruction[] popInstructions =
+                    new Instruction[popCount / 2 + popCount % 2];
+
+                Instruction popInstruction =
+                    new SimpleInstruction(InstructionConstants.OP_POP2);
+
+                for (int index = 0; index < popCount / 2; index++)
+                {
+                      popInstructions[index] = popInstruction;
+                }
+
+                if (popCount % 2 == 1)
+                {
+                    popInstruction =
+                        new SimpleInstruction(InstructionConstants.OP_POP);
+
+                    popInstructions[popCount / 2] = popInstruction;
+                }
+
+                if (replace)
+                {
+                    codeAttributeEditor.replaceInstruction(offset, popInstructions);
+
+                    for (int index = 1; index < popInstructions.length; index++)
+                    {
+                        if (extraAddedInstructionVisitor != null)
+                        {
+                            popInstructions[index].accept(null, null, null, offset, extraAddedInstructionVisitor);
+                        }
+                    }
+                }
+                else
+                {
+                    codeAttributeEditor.insertAfterInstruction(offset, popInstructions);
+
+                    for (int index = 0; index < popInstructions.length; index++)
+                    {
+                        if (extraAddedInstructionVisitor != null)
+                        {
+                            popInstructions[index].accept(null, null, null, offset, extraAddedInstructionVisitor);
+                        }
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+
+    /**
+     * Replaces the instruction at a given offset by a static invocation.
+     */
+    private void replaceByStaticInvocation(Clazz               clazz,
+                                           int                 offset,
+                                           ConstantInstruction constantInstruction)
+    {
+        // Remember the replacement instruction.
+        Instruction replacementInstruction =
+             new ConstantInstruction(InstructionConstants.OP_INVOKESTATIC,
+                                     constantInstruction.constantIndex).shrink();
+
+        if (DEBUG) System.out.println("  Replacing by static invocation "+constantInstruction.toString(offset)+" -> "+replacementInstruction.toString());
+
+        codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+    }
+
+
+    /**
+     * Replaces the given instruction by an infinite loop.
+     */
+    private void replaceByInfiniteLoop(Clazz clazz,
+                                       int   offset)
+    {
+        if (DEBUG) System.out.println("  Inserting infinite loop at ["+offset+"]");
+
+        // Mark the instruction.
+        markInstruction(offset);
+
+        // Replace the instruction by an infinite loop.
+        Instruction replacementInstruction =
+            new BranchInstruction(InstructionConstants.OP_GOTO, 0);
+
+        codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the given instruction is a dup or swap instruction
+     * (dup, dup_x1, dup_x2, dup2, dup2_x1, dup2_x2, swap).
+     */
+    private boolean isDupOrSwap(Instruction instruction)
+    {
+        return instruction.opcode >= InstructionConstants.OP_DUP &&
+               instruction.opcode <= InstructionConstants.OP_SWAP;
+    }
+
+
+    /**
+     * Returns whether the given instruction is a pop instruction
+     * (pop, pop2).
+     */
+    private boolean isPop(Instruction instruction)
+    {
+        return instruction.opcode == InstructionConstants.OP_POP ||
+               instruction.opcode == InstructionConstants.OP_POP2;
+    }
+
+
+    /**
+     * Returns whether any traced but unnecessary instruction between the two
+     * given offsets is branching over the second given offset.
+     */
+    private boolean isAnyUnnecessaryInstructionBranchingOver(int instructionOffset1,
+                                                             int instructionOffset2)
+    {
+        for (int offset = instructionOffset1; offset < instructionOffset2; offset++)
+        {
+            // Is it a traced but unmarked straddling branch?
+            if (partialEvaluator.isTraced(offset) &&
+                !isInstructionNecessary(offset)   &&
+                isAnyLargerThan(partialEvaluator.branchTargets(offset),
+                                instructionOffset2))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns whether all of the given instruction offsets (at least one)
+     * are smaller than or equal to the given offset.
+     */
+    private boolean isAllSmallerThanOrEqual(InstructionOffsetValue instructionOffsets,
+                                            int                    instructionOffset)
+    {
+        if (instructionOffsets != null)
+        {
+            // Loop over all instruction offsets.
+            int branchCount = instructionOffsets.instructionOffsetCount();
+            if (branchCount > 0)
+            {
+                for (int branchIndex = 0; branchIndex < branchCount; branchIndex++)
+                {
+                    // Is the offset larger than the reference offset?
+                    if (instructionOffsets.instructionOffset(branchIndex) > instructionOffset)
+                    {
+                        return false;
+                    }
+                }
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns whether any of the given instruction offsets (at least one)
+     * is larger than the given offset.
+     */
+    private boolean isAnyLargerThan(InstructionOffsetValue instructionOffsets,
+                                    int                    instructionOffset)
+    {
+        if (instructionOffsets != null)
+        {
+            // Loop over all instruction offsets.
+            int branchCount = instructionOffsets.instructionOffsetCount();
+            if (branchCount > 0)
+            {
+                for (int branchIndex = 0; branchIndex < branchCount; branchIndex++)
+                {
+                    // Is the offset larger than the reference offset?
+                    if (instructionOffsets.instructionOffset(branchIndex) > instructionOffset)
+                    {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Initializes the necessary data structure.
+     */
+    private void initializeNecessary(CodeAttribute codeAttribute)
+    {
+        int codeLength = codeAttribute.u4codeLength;
+        int maxLocals  = codeAttribute.u2maxLocals;
+        int maxStack   = codeAttribute.u2maxStack;
+
+        // Create new arrays for storing information at each instruction offset.
+        if (variablesNecessaryAfter.length    < codeLength ||
+            variablesNecessaryAfter[0].length < maxLocals)
+        {
+            variablesNecessaryAfter = new boolean[codeLength][maxLocals];
+        }
+        else
+        {
+            for (int offset = 0; offset < codeLength; offset++)
+            {
+                for (int index = 0; index < maxLocals; index++)
+                {
+                    variablesNecessaryAfter[offset][index] = false;
+                }
+            }
+        }
+
+        if (stacksNecessaryAfter.length    < codeLength ||
+            stacksNecessaryAfter[0].length < maxStack)
+        {
+            stacksNecessaryAfter = new boolean[codeLength][maxStack];
+        }
+        else
+        {
+            for (int offset = 0; offset < codeLength; offset++)
+            {
+                for (int index = 0; index < maxStack; index++)
+                {
+                    stacksNecessaryAfter[offset][index] = false;
+                }
+            }
+        }
+
+        if (stacksSimplifiedBefore.length    < codeLength ||
+            stacksSimplifiedBefore[0].length < maxStack)
+        {
+            stacksSimplifiedBefore = new boolean[codeLength][maxStack];
+        }
+        else
+        {
+            for (int offset = 0; offset < codeLength; offset++)
+            {
+                for (int index = 0; index < maxStack; index++)
+                {
+                    stacksSimplifiedBefore[offset][index] = false;
+                }
+            }
+        }
+
+        if (instructionsNecessary.length < codeLength)
+        {
+            instructionsNecessary = new boolean[codeLength];
+        }
+        else
+        {
+            for (int index = 0; index < codeLength; index++)
+            {
+                instructionsNecessary[index] = false;
+            }
+        }
+    }
+
+
+    /**
+     * Returns whether the given stack entry is present after execution of the
+     * instruction at the given offset.
+     */
+    private boolean isStackEntriesNecessaryAfter(int instructionOffset,
+                                                 int stackIndex1,
+                                                 int stackIndex2)
+    {
+        boolean present1 = isStackEntryNecessaryAfter(instructionOffset, stackIndex1);
+        boolean present2 = isStackEntryNecessaryAfter(instructionOffset, stackIndex2);
+
+//        if (present1 ^ present2)
+//        {
+//            throw new UnsupportedOperationException("Can't handle partial use of dup2 instructions");
+//        }
+
+        return present1 || present2;
+    }
+
+
+    /**
+     * Returns whether the specified variable must be initialized at the
+     * specified offset, according to the verifier of the JVM.
+     */
+    private boolean isVariableInitializationNecessary(Clazz         clazz,
+                                                      Method        method,
+                                                      CodeAttribute codeAttribute,
+                                                      int           initializationOffset,
+                                                      int           variableIndex)
+    {
+        int codeLength = codeAttribute.u4codeLength;
+
+        // Is the variable necessary anywhere at all?
+        if (isVariableNecessaryAfterAny(0, codeLength, variableIndex))
+        {
+            if (DEBUG) System.out.println("Simple partial evaluation for initialization of variable v"+variableIndex+" at ["+initializationOffset+"]");
+
+            // Lazily compute perform simple partial evaluation, the way the
+            // JVM preverifier would do it.
+            simplePartialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
+
+            if (DEBUG) System.out.println("End of simple partial evaluation for initialization of variable v"+variableIndex+" at ["+initializationOffset+"]");
+
+            // Check if the variable is necessary elsewhere.
+            for (int offset = 0; offset < codeLength; offset++)
+            {
+                if (isInstructionNecessary(offset))
+                {
+                    Value producer = partialEvaluator.getVariablesBefore(offset).getProducerValue(variableIndex);
+                    if (producer != null)
+                    {
+                        Value simpleProducer = simplePartialEvaluator.getVariablesBefore(offset).getProducerValue(variableIndex);
+                        if (simpleProducer != null)
+                        {
+                            InstructionOffsetValue producerOffsets =
+                                producer.instructionOffsetValue();
+                            InstructionOffsetValue simpleProducerOffsets =
+                                simpleProducer.instructionOffsetValue();
+
+                            // Does the sophisticated partial evaluation have fewer
+                            // producers than the simple one?
+                            // And does the simple partial evaluation point to an
+                            // initialization of the variable?
+                            if (producerOffsets.instructionOffsetCount() <
+                                simpleProducerOffsets.instructionOffsetCount() &&
+                                isVariableNecessaryAfterAny(producerOffsets, variableIndex) &&
+                                simpleProducerOffsets.contains(initializationOffset))
+                            {
+                                // Then the initialization is necessary.
+                                return true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+    private void markVariableAfter(int instructionOffset,
+                                   int variableIndex)
+    {
+        if (!isVariableNecessaryAfter(instructionOffset, variableIndex))
+        {
+            if (DEBUG) System.out.print("["+instructionOffset+".v"+variableIndex+"],");
+
+            variablesNecessaryAfter[instructionOffset][variableIndex] = true;
+
+            if (maxMarkedOffset < instructionOffset)
+            {
+                maxMarkedOffset = instructionOffset;
+            }
+        }
+    }
+
+
+    /**
+     * Returns whether the specified variable is ever necessary after any
+     * instruction in the specified block.
+     */
+    private boolean isVariableNecessaryAfterAny(int startOffset,
+                                                int endOffset,
+                                                int variableIndex)
+    {
+        for (int offset = startOffset; offset < endOffset; offset++)
+        {
+            if (isVariableNecessaryAfter(offset, variableIndex))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns whether the specified variable is ever necessary after any
+     * instruction in the specified set of instructions offsets.
+     */
+    private boolean isVariableNecessaryAfterAny(InstructionOffsetValue instructionOffsetValue,
+                                                int                    variableIndex)
+    {
+        int count = instructionOffsetValue.instructionOffsetCount();
+
+        for (int index = 0; index < count; index++)
+        {
+            if (isVariableNecessaryAfter(instructionOffsetValue.instructionOffset(index),
+                                         variableIndex))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    private boolean isVariableNecessaryAfter(int instructionOffset,
+                                             int variableIndex)
+    {
+        return instructionOffset == PartialEvaluator.AT_METHOD_ENTRY ||
+               variablesNecessaryAfter[instructionOffset][variableIndex];
+    }
+
+
+    private void markStackEntryAfter(int instructionOffset,
+                                     int stackIndex)
+    {
+        if (!isStackEntryNecessaryAfter(instructionOffset, stackIndex))
+        {
+            if (DEBUG) System.out.print("["+instructionOffset+".s"+stackIndex+"],");
+
+            stacksNecessaryAfter[instructionOffset][stackIndex] = true;
+
+            if (maxMarkedOffset < instructionOffset)
+            {
+                maxMarkedOffset = instructionOffset;
+            }
+        }
+    }
+
+
+    private boolean isAnyStackEntryNecessaryAfter(InstructionOffsetValue instructionOffsets,
+                                                  int                    stackIndex)
+    {
+        int offsetCount = instructionOffsets.instructionOffsetCount();
+
+        for (int offsetIndex = 0; offsetIndex < offsetCount; offsetIndex++)
+        {
+            if (isStackEntryNecessaryAfter(instructionOffsets.instructionOffset(offsetIndex), stackIndex))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    private boolean isStackEntryNecessaryAfter(int instructionOffset,
+                                               int stackIndex)
+    {
+        return instructionOffset == PartialEvaluator.AT_CATCH_ENTRY ||
+               stacksNecessaryAfter[instructionOffset][stackIndex];
+    }
+
+
+    private void markStackSimplificationBefore(int instructionOffset,
+                                               int stackIndex)
+    {
+        stacksSimplifiedBefore[instructionOffset][stackIndex] = true;
+    }
+
+
+    private boolean isStackSimplifiedBefore(int instructionOffset,
+                                            int stackIndex)
+    {
+        return stacksSimplifiedBefore[instructionOffset][stackIndex];
+    }
+
+
+    private void markInstruction(int instructionOffset)
+    {
+        if (!isInstructionNecessary(instructionOffset))
+        {
+            if (DEBUG) System.out.print(instructionOffset+",");
+
+            instructionsNecessary[instructionOffset] = true;
+
+            if (maxMarkedOffset < instructionOffset)
+            {
+                maxMarkedOffset = instructionOffset;
+            }
+        }
+    }
+
+
+    private boolean isAnyInstructionNecessary(int instructionOffset1,
+                                              int instructionOffset2)
+    {
+        for (int instructionOffset = instructionOffset1;
+             instructionOffset < instructionOffset2;
+             instructionOffset++)
+        {
+            if (isInstructionNecessary(instructionOffset))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns the highest offset of an instruction that has been marked as
+     * necessary, before the given offset.
+     */
+    private int lastNecessaryInstructionOffset(int instructionOffset)
+    {
+        for (int offset = instructionOffset-1; offset >= 0; offset--)
+        {
+            if (isInstructionNecessary(instructionOffset))
+            {
+                return offset;
+            }
+        }
+
+        return 0;
+    }
+
+
+    private boolean isInstructionNecessary(int instructionOffset)
+    {
+        return instructionOffset == PartialEvaluator.AT_METHOD_ENTRY ||
+               instructionsNecessary[instructionOffset];
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/evaluation/EvaluationSimplifier.java b/src/proguard/optimize/evaluation/EvaluationSimplifier.java
new file mode 100644
index 0000000..0c3a9c7
--- /dev/null
+++ b/src/proguard/optimize/evaluation/EvaluationSimplifier.java
@@ -0,0 +1,968 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.evaluation.*;
+import proguard.evaluation.value.*;
+import proguard.optimize.info.*;
+
+/**
+ * This AttributeVisitor simplifies the code attributes that it visits, based
+ * on partial evaluation.
+ *
+ * @author Eric Lafortune
+ */
+public class EvaluationSimplifier
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static boolean DEBUG       = true;
+    //*/
+
+    private final InstructionVisitor extraInstructionVisitor;
+
+    private final PartialEvaluator             partialEvaluator;
+    private final SideEffectInstructionChecker sideEffectInstructionChecker = new SideEffectInstructionChecker(true);
+    private final CodeAttributeEditor          codeAttributeEditor          = new CodeAttributeEditor(false);
+
+
+    /**
+     * Creates a new EvaluationSimplifier.
+     */
+    public EvaluationSimplifier()
+    {
+        this(new PartialEvaluator(), null);
+    }
+
+
+    /**
+     * Creates a new EvaluationSimplifier.
+     * @param partialEvaluator        the partial evaluator that will
+     *                                execute the code and provide
+     *                                information about the results.
+     * @param extraInstructionVisitor an optional extra visitor for all
+     *                                simplified instructions.
+     */
+    public EvaluationSimplifier(PartialEvaluator   partialEvaluator,
+                                InstructionVisitor extraInstructionVisitor)
+    {
+        this.partialEvaluator        = partialEvaluator;
+        this.extraInstructionVisitor = extraInstructionVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // TODO: Remove this when the evaluation simplifier has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            // Process the code.
+            visitCodeAttribute0(clazz, method, codeAttribute);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while simplifying instructions after partial evaluation:");
+            System.err.println("  Class       = ["+clazz.getName()+"]");
+            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+            System.err.println("Not optimizing this method");
+
+            if (DEBUG)
+            {
+                method.accept(clazz, new ClassPrinter());
+
+                throw ex;
+            }
+        }
+    }
+
+
+    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (DEBUG)
+        {
+            System.out.println();
+            System.out.println("Class "+ClassUtil.externalClassName(clazz.getName()));
+            System.out.println("Method "+ClassUtil.externalFullMethodDescription(clazz.getName(),
+                                                                                 0,
+                                                                                 method.getName(clazz),
+                                                                                 method.getDescriptor(clazz)));
+        }
+
+        // Evaluate the method.
+        partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
+
+        int codeLength = codeAttribute.u4codeLength;
+
+        // Reset the code changes.
+        codeAttributeEditor.reset(codeLength);
+
+        // Replace any instructions that can be simplified.
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            if (partialEvaluator.isTraced(offset))
+            {
+                Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                    offset);
+
+                instruction.accept(clazz, method, codeAttribute, offset, this);
+            }
+        }
+
+        // Apply all accumulated changes to the code.
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        switch (simpleInstruction.opcode)
+        {
+            case InstructionConstants.OP_IALOAD:
+            case InstructionConstants.OP_BALOAD:
+            case InstructionConstants.OP_CALOAD:
+            case InstructionConstants.OP_SALOAD:
+            case InstructionConstants.OP_IADD:
+            case InstructionConstants.OP_ISUB:
+            case InstructionConstants.OP_IMUL:
+            case InstructionConstants.OP_IDIV:
+            case InstructionConstants.OP_IREM:
+            case InstructionConstants.OP_INEG:
+            case InstructionConstants.OP_ISHL:
+            case InstructionConstants.OP_ISHR:
+            case InstructionConstants.OP_IUSHR:
+            case InstructionConstants.OP_IAND:
+            case InstructionConstants.OP_IOR:
+            case InstructionConstants.OP_IXOR:
+            case InstructionConstants.OP_L2I:
+            case InstructionConstants.OP_F2I:
+            case InstructionConstants.OP_D2I:
+            case InstructionConstants.OP_I2B:
+            case InstructionConstants.OP_I2C:
+            case InstructionConstants.OP_I2S:
+                replaceIntegerPushInstruction(clazz, offset, simpleInstruction);
+                break;
+
+            case InstructionConstants.OP_LALOAD:
+            case InstructionConstants.OP_LADD:
+            case InstructionConstants.OP_LSUB:
+            case InstructionConstants.OP_LMUL:
+            case InstructionConstants.OP_LDIV:
+            case InstructionConstants.OP_LREM:
+            case InstructionConstants.OP_LNEG:
+            case InstructionConstants.OP_LSHL:
+            case InstructionConstants.OP_LSHR:
+            case InstructionConstants.OP_LUSHR:
+            case InstructionConstants.OP_LAND:
+            case InstructionConstants.OP_LOR:
+            case InstructionConstants.OP_LXOR:
+            case InstructionConstants.OP_I2L:
+            case InstructionConstants.OP_F2L:
+            case InstructionConstants.OP_D2L:
+                replaceLongPushInstruction(clazz, offset, simpleInstruction);
+                break;
+
+            case InstructionConstants.OP_FALOAD:
+            case InstructionConstants.OP_FADD:
+            case InstructionConstants.OP_FSUB:
+            case InstructionConstants.OP_FMUL:
+            case InstructionConstants.OP_FDIV:
+            case InstructionConstants.OP_FREM:
+            case InstructionConstants.OP_FNEG:
+            case InstructionConstants.OP_I2F:
+            case InstructionConstants.OP_L2F:
+            case InstructionConstants.OP_D2F:
+                replaceFloatPushInstruction(clazz, offset, simpleInstruction);
+                break;
+
+            case InstructionConstants.OP_DALOAD:
+            case InstructionConstants.OP_DADD:
+            case InstructionConstants.OP_DSUB:
+            case InstructionConstants.OP_DMUL:
+            case InstructionConstants.OP_DDIV:
+            case InstructionConstants.OP_DREM:
+            case InstructionConstants.OP_DNEG:
+            case InstructionConstants.OP_I2D:
+            case InstructionConstants.OP_L2D:
+            case InstructionConstants.OP_F2D:
+                replaceDoublePushInstruction(clazz, offset, simpleInstruction);
+                break;
+
+            case InstructionConstants.OP_AALOAD:
+                replaceReferencePushInstruction(clazz, offset, simpleInstruction);
+                break;
+        }
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        int variableIndex = variableInstruction.variableIndex;
+
+        switch (variableInstruction.opcode)
+        {
+            case InstructionConstants.OP_ILOAD:
+            case InstructionConstants.OP_ILOAD_0:
+            case InstructionConstants.OP_ILOAD_1:
+            case InstructionConstants.OP_ILOAD_2:
+            case InstructionConstants.OP_ILOAD_3:
+                replaceIntegerPushInstruction(clazz, offset, variableInstruction, variableIndex);
+                break;
+
+            case InstructionConstants.OP_LLOAD:
+            case InstructionConstants.OP_LLOAD_0:
+            case InstructionConstants.OP_LLOAD_1:
+            case InstructionConstants.OP_LLOAD_2:
+            case InstructionConstants.OP_LLOAD_3:
+                replaceLongPushInstruction(clazz, offset, variableInstruction, variableIndex);
+                break;
+
+            case InstructionConstants.OP_FLOAD:
+            case InstructionConstants.OP_FLOAD_0:
+            case InstructionConstants.OP_FLOAD_1:
+            case InstructionConstants.OP_FLOAD_2:
+            case InstructionConstants.OP_FLOAD_3:
+                replaceFloatPushInstruction(clazz, offset, variableInstruction, variableIndex);
+                break;
+
+            case InstructionConstants.OP_DLOAD:
+            case InstructionConstants.OP_DLOAD_0:
+            case InstructionConstants.OP_DLOAD_1:
+            case InstructionConstants.OP_DLOAD_2:
+            case InstructionConstants.OP_DLOAD_3:
+                replaceDoublePushInstruction(clazz, offset, variableInstruction, variableIndex);
+                break;
+
+            case InstructionConstants.OP_ALOAD:
+            case InstructionConstants.OP_ALOAD_0:
+            case InstructionConstants.OP_ALOAD_1:
+            case InstructionConstants.OP_ALOAD_2:
+            case InstructionConstants.OP_ALOAD_3:
+                replaceReferencePushInstruction(clazz, offset, variableInstruction);
+                break;
+
+            case InstructionConstants.OP_ASTORE:
+            case InstructionConstants.OP_ASTORE_0:
+            case InstructionConstants.OP_ASTORE_1:
+            case InstructionConstants.OP_ASTORE_2:
+            case InstructionConstants.OP_ASTORE_3:
+                deleteReferencePopInstruction(clazz, offset, variableInstruction);
+                break;
+
+            case InstructionConstants.OP_RET:
+                replaceBranchInstruction(clazz, offset, variableInstruction);
+                break;
+        }
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        switch (constantInstruction.opcode)
+        {
+            case InstructionConstants.OP_GETSTATIC:
+            case InstructionConstants.OP_GETFIELD:
+                replaceAnyPushInstruction(clazz, offset, constantInstruction);
+                break;
+
+            case InstructionConstants.OP_INVOKEVIRTUAL:
+            case InstructionConstants.OP_INVOKESPECIAL:
+            case InstructionConstants.OP_INVOKESTATIC:
+            case InstructionConstants.OP_INVOKEINTERFACE:
+                if (constantInstruction.stackPushCount(clazz) > 0 &&
+                    !sideEffectInstructionChecker.hasSideEffects(clazz,
+                                                                 method,
+                                                                 codeAttribute,
+                                                                 offset,
+                                                                 constantInstruction))
+                {
+                    replaceAnyPushInstruction(clazz, offset, constantInstruction);
+                }
+
+                break;
+
+            case InstructionConstants.OP_CHECKCAST:
+                replaceReferencePushInstruction(clazz, offset, constantInstruction);
+                break;
+        }
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        switch (branchInstruction.opcode)
+        {
+            case InstructionConstants.OP_GOTO:
+            case InstructionConstants.OP_GOTO_W:
+                // Don't replace unconditional branches.
+                break;
+
+            case InstructionConstants.OP_JSR:
+            case InstructionConstants.OP_JSR_W:
+                replaceJsrInstruction(clazz, offset, branchInstruction);
+                break;
+
+            default:
+                replaceBranchInstruction(clazz, offset, branchInstruction);
+                break;
+        }
+    }
+
+
+    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
+    {
+        // First try to simplify it to a simple branch.
+        replaceBranchInstruction(clazz, offset, switchInstruction);
+
+        // Otherwise make sure all branch targets are valid.
+        if (!codeAttributeEditor.isModified(offset))
+        {
+            replaceSwitchInstruction(clazz, offset, switchInstruction);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Replaces the push instruction at the given offset by a simpler push
+     * instruction, if possible.
+     */
+    private void replaceAnyPushInstruction(Clazz       clazz,
+                                           int         offset,
+                                           Instruction instruction)
+    {
+        Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0);
+        if (pushedValue.isParticular())
+        {
+            switch (pushedValue.computationalType())
+            {
+                case Value.TYPE_INTEGER:
+                    replaceIntegerPushInstruction(clazz, offset, instruction);
+                    break;
+                case Value.TYPE_LONG:
+                    replaceLongPushInstruction(clazz, offset, instruction);
+                    break;
+                case Value.TYPE_FLOAT:
+                    replaceFloatPushInstruction(clazz, offset, instruction);
+                    break;
+                case Value.TYPE_DOUBLE:
+                    replaceDoublePushInstruction(clazz, offset, instruction);
+                    break;
+                case Value.TYPE_REFERENCE:
+                    replaceReferencePushInstruction(clazz, offset, instruction);
+                    break;
+            }
+        }
+    }
+
+
+    /**
+     * Replaces the integer pushing instruction at the given offset by a simpler
+     * push instruction, if possible.
+     */
+    private void replaceIntegerPushInstruction(Clazz       clazz,
+                                               int         offset,
+                                               Instruction instruction)
+    {
+        replaceIntegerPushInstruction(clazz,
+                                      offset,
+                                      instruction,
+                                      partialEvaluator.getVariablesBefore(offset).size());
+    }
+
+
+    /**
+     * Replaces the integer pushing instruction at the given offset by a simpler
+     * push instruction, if possible.
+     */
+    private void replaceIntegerPushInstruction(Clazz       clazz,
+                                               int         offset,
+                                               Instruction instruction,
+                                               int         maxVariableIndex)
+    {
+        Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0);
+        if (pushedValue.isParticular())
+        {
+            int value = pushedValue.integerValue().value();
+            if (value << 16 >> 16 == value)
+            {
+                replaceConstantPushInstruction(clazz,
+                                               offset,
+                                               instruction,
+                                               InstructionConstants.OP_SIPUSH,
+                                               value);
+            }
+            else
+            {
+                ConstantPoolEditor constantPoolEditor =
+                    new ConstantPoolEditor((ProgramClass)clazz);
+
+                Instruction replacementInstruction =
+                    new ConstantInstruction(InstructionConstants.OP_LDC,
+                                            constantPoolEditor.addIntegerConstant(value)).shrink();
+
+                replaceInstruction(clazz, offset, instruction, replacementInstruction);
+            }
+        }
+        else if (pushedValue.isSpecific())
+        {
+            TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
+            for (int variableIndex = 0; variableIndex < maxVariableIndex; variableIndex++)
+            {
+                if (pushedValue.equals(variables.load(variableIndex)))
+                {
+                    replaceVariablePushInstruction(clazz,
+                                                   offset,
+                                                   instruction,
+                                                   InstructionConstants.OP_ILOAD,
+                                                   variableIndex);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Replaces the long pushing instruction at the given offset by a simpler
+     * push instruction, if possible.
+     */
+    private void replaceLongPushInstruction(Clazz       clazz,
+                                            int         offset,
+                                            Instruction instruction)
+    {
+        replaceLongPushInstruction(clazz,
+                                   offset,
+                                   instruction,
+                                   partialEvaluator.getVariablesBefore(offset).size());
+    }
+
+
+    /**
+     * Replaces the long pushing instruction at the given offset by a simpler
+     * push instruction, if possible.
+     */
+    private void replaceLongPushInstruction(Clazz       clazz,
+                                            int         offset,
+                                            Instruction instruction,
+                                            int         maxVariableIndex)
+    {
+        Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0);
+        if (pushedValue.isParticular())
+        {
+            long value = pushedValue.longValue().value();
+            if (value == 0L ||
+                value == 1L)
+            {
+                replaceConstantPushInstruction(clazz,
+                                       offset,
+                                       instruction,
+                                       InstructionConstants.OP_LCONST_0,
+                                       (int)value);
+            }
+            else
+            {
+                ConstantPoolEditor constantPoolEditor =
+                    new ConstantPoolEditor((ProgramClass)clazz);
+
+                Instruction replacementInstruction =
+                    new ConstantInstruction(InstructionConstants.OP_LDC2_W,
+                                            constantPoolEditor.addLongConstant(value)).shrink();
+
+                replaceInstruction(clazz, offset, instruction, replacementInstruction);
+            }
+        }
+        else if (pushedValue.isSpecific())
+        {
+            TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
+            for (int variableIndex = 0; variableIndex < maxVariableIndex; variableIndex++)
+            {
+                if (pushedValue.equals(variables.load(variableIndex)))
+                {
+                    replaceVariablePushInstruction(clazz,
+                                                   offset,
+                                                   instruction,
+                                                   InstructionConstants.OP_LLOAD,
+                                                   variableIndex);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Replaces the float pushing instruction at the given offset by a simpler
+     * push instruction, if possible.
+     */
+    private void replaceFloatPushInstruction(Clazz       clazz,
+                                             int         offset,
+                                             Instruction instruction)
+    {
+        replaceFloatPushInstruction(clazz,
+                                    offset,
+                                    instruction,
+                                    partialEvaluator.getVariablesBefore(offset).size());
+    }
+
+
+    /**
+     * Replaces the float pushing instruction at the given offset by a simpler
+     * push instruction, if possible.
+     */
+    private void replaceFloatPushInstruction(Clazz       clazz,
+                                             int         offset,
+                                             Instruction instruction,
+                                             int         maxVariableIndex)
+    {
+        Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0);
+        if (pushedValue.isParticular())
+        {
+            float value = pushedValue.floatValue().value();
+            if (value == 0f ||
+                value == 1f ||
+                value == 2f)
+            {
+                replaceConstantPushInstruction(clazz,
+                                               offset,
+                                               instruction,
+                                               InstructionConstants.OP_FCONST_0,
+                                               (int)value);
+            }
+            else
+            {
+                ConstantPoolEditor constantPoolEditor =
+                    new ConstantPoolEditor((ProgramClass)clazz);
+
+                Instruction replacementInstruction =
+                    new ConstantInstruction(InstructionConstants.OP_LDC,
+                                            constantPoolEditor.addFloatConstant(value)).shrink();
+
+                replaceInstruction(clazz, offset, instruction, replacementInstruction);
+            }
+        }
+        else if (pushedValue.isSpecific())
+        {
+            TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
+            for (int variableIndex = 0; variableIndex < maxVariableIndex; variableIndex++)
+            {
+                if (pushedValue.equals(variables.load(variableIndex)))
+                {
+                    replaceVariablePushInstruction(clazz,
+                                                   offset,
+                                                   instruction,
+                                                   InstructionConstants.OP_FLOAD,
+                                                   variableIndex);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Replaces the double pushing instruction at the given offset by a simpler
+     * push instruction, if possible.
+     */
+    private void replaceDoublePushInstruction(Clazz       clazz,
+                                              int         offset,
+                                              Instruction instruction)
+    {
+        replaceDoublePushInstruction(clazz,
+                                     offset,
+                                     instruction,
+                                     partialEvaluator.getVariablesBefore(offset).size());
+    }
+
+
+    /**
+     * Replaces the double pushing instruction at the given offset by a simpler
+     * push instruction, if possible.
+     */
+    private void replaceDoublePushInstruction(Clazz       clazz,
+                                              int         offset,
+                                              Instruction instruction,
+                                              int         maxVariableIndex)
+    {
+        Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0);
+        if (pushedValue.isParticular())
+        {
+            double value = pushedValue.doubleValue().value();
+            if (value == 0.0 ||
+                value == 1.0)
+            {
+                replaceConstantPushInstruction(clazz,
+                                               offset,
+                                               instruction,
+                                               InstructionConstants.OP_DCONST_0,
+                                               (int)value);
+            }
+            else
+            {
+                ConstantPoolEditor constantPoolEditor =
+                    new ConstantPoolEditor((ProgramClass)clazz);
+
+                Instruction replacementInstruction =
+                    new ConstantInstruction(InstructionConstants.OP_LDC2_W,
+                                            constantPoolEditor.addDoubleConstant(value)).shrink();
+
+                replaceInstruction(clazz, offset, instruction, replacementInstruction);
+            }
+        }
+        else if (pushedValue.isSpecific())
+        {
+            TracedVariables variables = partialEvaluator.getVariablesBefore(offset);
+            for (int variableIndex = 0; variableIndex < maxVariableIndex; variableIndex++)
+            {
+                if (pushedValue.equals(variables.load(variableIndex)))
+                {
+                    replaceVariablePushInstruction(clazz,
+                                                   offset,
+                                                   instruction,
+                                                   InstructionConstants.OP_DLOAD,
+                                                   variableIndex);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Replaces the reference pushing instruction at the given offset by a
+     * simpler push instruction, if possible.
+     */
+    private void replaceReferencePushInstruction(Clazz       clazz,
+                                                 int         offset,
+                                                 Instruction instruction)
+    {
+        Value pushedValue = partialEvaluator.getStackAfter(offset).getTop(0);
+        if (pushedValue.isParticular())
+        {
+            // A reference value can only be specific if it is null.
+            replaceConstantPushInstruction(clazz,
+                                           offset,
+                                           instruction,
+                                           InstructionConstants.OP_ACONST_NULL,
+                                           0);
+        }
+    }
+
+
+    /**
+     * Replaces the instruction at a given offset by a given push instruction
+     * of a constant.
+     */
+    private void replaceConstantPushInstruction(Clazz       clazz,
+                                                int         offset,
+                                                Instruction instruction,
+                                                byte        replacementOpcode,
+                                                int         value)
+    {
+        Instruction replacementInstruction =
+            new SimpleInstruction(replacementOpcode, value).shrink();
+
+        replaceInstruction(clazz, offset, instruction, replacementInstruction);
+    }
+
+
+    /**
+     * Replaces the instruction at a given offset by a given push instruction
+     * of a variable.
+     */
+    private void replaceVariablePushInstruction(Clazz       clazz,
+                                                int         offset,
+                                                Instruction instruction,
+                                                byte        replacementOpcode,
+                                                int         variableIndex)
+    {
+        Instruction replacementInstruction =
+            new VariableInstruction(replacementOpcode, variableIndex).shrink();
+
+        replaceInstruction(clazz, offset, instruction, replacementInstruction);
+    }
+
+
+    /**
+     * Replaces the given 'jsr' instruction by a simpler branch instruction,
+     * if it jumps to a subroutine that doesn't return or a subroutine that
+     * is only called from one place.
+     */
+    private void replaceJsrInstruction(Clazz             clazz,
+                                       int               offset,
+                                       BranchInstruction branchInstruction)
+    {
+        // Is the subroutine ever returning?
+        int subroutineStart = offset + branchInstruction.branchOffset;
+        if (!partialEvaluator.isSubroutineReturning(subroutineStart) ||
+            partialEvaluator.branchOrigins(subroutineStart).instructionOffsetCount() == 1)
+        {
+            // All 'jsr' instructions to this subroutine can be replaced
+            // by unconditional branch instructions.
+            replaceBranchInstruction(clazz, offset, branchInstruction);
+        }
+        else if (!partialEvaluator.isTraced(offset + branchInstruction.length(offset)))
+        {
+            // We have to make sure the instruction after this 'jsr'
+            // instruction is valid, even if it is never reached.
+            replaceByInfiniteLoop(clazz, offset + branchInstruction.length(offset), branchInstruction);
+        }
+    }
+
+
+    /**
+     * Deletes the reference popping instruction at the given offset, if
+     * it is at the start of a subroutine that doesn't return or a subroutine
+     * that is only called from one place.
+     */
+    private void deleteReferencePopInstruction(Clazz       clazz,
+                                               int         offset,
+                                               Instruction instruction)
+    {
+        if (partialEvaluator.isSubroutineStart(offset) &&
+            (!partialEvaluator.isSubroutineReturning(offset) ||
+             partialEvaluator.branchOrigins(offset).instructionOffsetCount() == 1))
+        {
+            if (DEBUG) System.out.println("  Deleting store of subroutine return address "+instruction.toString(offset));
+
+            // A reference value can only be specific if it is null.
+            codeAttributeEditor.deleteInstruction(offset);
+        }
+    }
+
+
+    /**
+     * Deletes the given branch instruction, or replaces it by a simpler branch
+     * instruction, if possible.
+     */
+    private void replaceBranchInstruction(Clazz       clazz,
+                                          int         offset,
+                                          Instruction instruction)
+    {
+        InstructionOffsetValue branchTargets = partialEvaluator.branchTargets(offset);
+
+        // Is there exactly one branch target (not from a goto or jsr)?
+        if (branchTargets != null &&
+            branchTargets.instructionOffsetCount() == 1)
+        {
+            // Is it branching to the next instruction?
+            int branchOffset = branchTargets.instructionOffset(0) - offset;
+            if (branchOffset == instruction.length(offset))
+            {
+                if (DEBUG) System.out.println("  Ignoring zero branch instruction at ["+offset+"]");
+            }
+            else
+            {
+                // Replace the branch instruction by a simple branch instruction.
+                Instruction replacementInstruction =
+                    new BranchInstruction(InstructionConstants.OP_GOTO_W,
+                                          branchOffset).shrink();
+
+                replaceInstruction(clazz, offset, instruction, replacementInstruction);
+            }
+        }
+    }
+
+
+    /**
+     * Makes sure all branch targets of the given switch instruction are valid.
+     */
+    private void replaceSwitchInstruction(Clazz             clazz,
+                                          int               offset,
+                                          SwitchInstruction switchInstruction)
+    {
+        // Get the actual branch targets.
+        InstructionOffsetValue branchTargets = partialEvaluator.branchTargets(offset);
+
+        // Get an offset that can serve as a valid default offset.
+        int defaultOffset =
+            branchTargets.instructionOffset(branchTargets.instructionOffsetCount()-1) -
+            offset;
+
+        Instruction replacementInstruction = null;
+
+        // Check the jump offsets.
+        int[] jumpOffsets = switchInstruction.jumpOffsets;
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            if (!branchTargets.contains(offset + jumpOffsets[index]))
+            {
+                // Replace the unused offset.
+                jumpOffsets[index] = defaultOffset;
+
+                // Remember to replace the instruction.
+                replacementInstruction = switchInstruction;
+            }
+        }
+
+        // Check the default offset.
+        if (!branchTargets.contains(offset + switchInstruction.defaultOffset))
+        {
+            // Replace the unused offset.
+            switchInstruction.defaultOffset = defaultOffset;
+
+            // Remember to replace the instruction.
+            replacementInstruction = switchInstruction;
+        }
+
+        if (replacementInstruction != null)
+        {
+            replaceInstruction(clazz, offset, switchInstruction, replacementInstruction);
+        }
+    }
+
+
+    /**
+     * Replaces the given instruction by an infinite loop.
+     */
+    private void replaceByInfiniteLoop(Clazz       clazz,
+                                       int         offset,
+                                       Instruction instruction)
+    {
+        // Replace the instruction by an infinite loop.
+        Instruction replacementInstruction =
+            new BranchInstruction(InstructionConstants.OP_GOTO, 0);
+
+        if (DEBUG) System.out.println("  Replacing unreachable instruction by infinite loop "+replacementInstruction.toString(offset));
+
+        codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+
+        // Visit the instruction, if required.
+        if (extraInstructionVisitor != null)
+        {
+            // Note: we're not passing the right arguments for now, knowing that
+            // they aren't used anyway.
+            instruction.accept(clazz, null, null, offset, extraInstructionVisitor);
+        }
+    }
+
+
+    /**
+     * Replaces the instruction at a given offset by a given push instruction.
+     */
+    private void replaceInstruction(Clazz       clazz,
+                                    int         offset,
+                                    Instruction instruction,
+                                    Instruction replacementInstruction)
+    {
+        // Pop unneeded stack entries if necessary.
+        int popCount =
+            instruction.stackPopCount(clazz) -
+            replacementInstruction.stackPopCount(clazz);
+
+        insertPopInstructions(offset, popCount);
+
+        if (DEBUG) System.out.println("  Replacing instruction "+instruction.toString(offset)+" -> "+replacementInstruction.toString()+(popCount == 0 ? "" : " ("+popCount+" pops)"));
+
+        codeAttributeEditor.replaceInstruction(offset, replacementInstruction);
+
+        // Visit the instruction, if required.
+        if (extraInstructionVisitor != null)
+        {
+            // Note: we're not passing the right arguments for now, knowing that
+            // they aren't used anyway.
+            instruction.accept(clazz, null, null, offset, extraInstructionVisitor);
+        }
+    }
+
+
+    /**
+     * Pops the given number of stack entries before the instruction at the
+     * given offset.
+     */
+    private void insertPopInstructions(int offset, int popCount)
+    {
+        switch (popCount)
+        {
+            case 0:
+            {
+                break;
+            }
+            case 1:
+            {
+                // Insert a single pop instruction.
+                Instruction popInstruction =
+                    new SimpleInstruction(InstructionConstants.OP_POP);
+
+                codeAttributeEditor.insertBeforeInstruction(offset,
+                                                            popInstruction);
+                break;
+            }
+            case 2:
+            {
+                // Insert a single pop2 instruction.
+                Instruction popInstruction =
+                    new SimpleInstruction(InstructionConstants.OP_POP2);
+
+                codeAttributeEditor.insertBeforeInstruction(offset,
+                                                            popInstruction);
+                break;
+            }
+            default:
+            {
+                // Insert the specified number of pop instructions.
+                Instruction[] popInstructions =
+                    new Instruction[popCount / 2 + popCount % 2];
+
+                Instruction popInstruction =
+                    new SimpleInstruction(InstructionConstants.OP_POP2);
+
+                for (int index = 0; index < popCount / 2; index++)
+                {
+                      popInstructions[index] = popInstruction;
+                }
+
+                if (popCount % 2 == 1)
+                {
+                    popInstruction =
+                        new SimpleInstruction(InstructionConstants.OP_POP);
+
+                    popInstructions[popCount / 2] = popInstruction;
+                }
+
+                codeAttributeEditor.insertBeforeInstruction(offset,
+                                                            popInstructions);
+                break;
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/evaluation/LivenessAnalyzer.java b/src/proguard/optimize/evaluation/LivenessAnalyzer.java
new file mode 100644
index 0000000..9915027
--- /dev/null
+++ b/src/proguard/optimize/evaluation/LivenessAnalyzer.java
@@ -0,0 +1,516 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.evaluation.value.*;
+
+/**
+ * This AttributeVisitor analyzes the liveness of the variables in the code
+ * attributes that it visits, based on partial evaluation.
+ *
+ * @author Eric Lafortune
+ */
+public class LivenessAnalyzer
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ExceptionInfoVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+    private static final int MAX_VARIABLES_SIZE = 64;
+
+    private final PartialEvaluator partialEvaluator;
+
+    private long[] isAliveBefore = new long[ClassConstants.TYPICAL_CODE_LENGTH];
+    private long[] isAliveAfter  = new long[ClassConstants.TYPICAL_CODE_LENGTH];
+    private long[] isCategory2   = new long[ClassConstants.TYPICAL_CODE_LENGTH];
+
+    // Fields acting as global temporary variables.
+    private boolean checkAgain;
+    private long    alive;
+
+
+    /**
+     * Creates a new LivenessAnalyzer.
+     */
+    public LivenessAnalyzer()
+    {
+        this(new PartialEvaluator());
+    }
+
+
+    /**
+     * Creates a new LivenessAnalyzer that will use the given partial evaluator.
+     * It will run this evaluator on every code attribute that it visits.
+     */
+    public LivenessAnalyzer(PartialEvaluator partialEvaluator)
+    {
+        this.partialEvaluator = partialEvaluator;
+    }
+
+
+    /**
+     * Returns whether the specified variable is alive before the instruction
+     * at the given offset.
+     */
+    public boolean isAliveBefore(int instructionOffset, int variableIndex)
+    {
+        return variableIndex >= MAX_VARIABLES_SIZE ||
+               (isAliveBefore[instructionOffset] & (1L << variableIndex)) != 0;
+    }
+
+
+    /**
+     * Sets whether the specified variable is alive before the instruction
+     * at the given offset.
+     */
+    public void setAliveBefore(int instructionOffset, int variableIndex, boolean alive)
+    {
+        if (variableIndex < MAX_VARIABLES_SIZE)
+        {
+            if (alive)
+            {
+                isAliveBefore[instructionOffset] |= 1L << variableIndex;
+            }
+            else
+            {
+                isAliveBefore[instructionOffset] &= ~(1L << variableIndex);
+            }
+        }
+    }
+
+
+    /**
+     * Returns whether the specified variable is alive after the instruction
+     * at the given offset.
+     */
+    public boolean isAliveAfter(int instructionOffset, int variableIndex)
+    {
+        return variableIndex >= MAX_VARIABLES_SIZE ||
+               (isAliveAfter[instructionOffset] & (1L << variableIndex)) != 0;
+    }
+
+
+    /**
+     * Sets whether the specified variable is alive after the instruction
+     * at the given offset.
+     */
+    public void setAliveAfter(int instructionOffset, int variableIndex, boolean alive)
+    {
+        if (variableIndex < MAX_VARIABLES_SIZE)
+        {
+            if (alive)
+            {
+                isAliveAfter[instructionOffset] |= 1L << variableIndex;
+            }
+            else
+            {
+                isAliveAfter[instructionOffset] &= ~(1L << variableIndex);
+            }
+        }
+    }
+
+
+    /**
+     * Returns whether the specified variable takes up two entries after the
+     * instruction at the given offset.
+     */
+    public boolean isCategory2(int instructionOffset, int variableIndex)
+    {
+        return variableIndex < MAX_VARIABLES_SIZE &&
+               (isCategory2[instructionOffset] & (1L << variableIndex)) != 0;
+    }
+
+
+    /**
+     * Sets whether the specified variable takes up two entries after the
+     * instruction at the given offset.
+     */
+    public void setCategory2(int instructionOffset, int variableIndex, boolean category2)
+    {
+        if (variableIndex < MAX_VARIABLES_SIZE)
+        {
+            if (category2)
+            {
+                isCategory2[instructionOffset] |= 1L << variableIndex;
+            }
+            else
+            {
+                isCategory2[instructionOffset] &= ~(1L << variableIndex);
+            }
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        if (DEBUG)
+        {
+            System.out.println();
+            System.out.println("Liveness analysis: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
+        }
+
+        // Initialize the global arrays.
+        initializeArrays(codeAttribute);
+
+        // Evaluate the method.
+        partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
+
+        int codeLength    = codeAttribute.u4codeLength;
+        int variablesSize = codeAttribute.u2maxLocals;
+
+        // We'll only really analyze the first 64 variables.
+        if (variablesSize > MAX_VARIABLES_SIZE)
+        {
+            variablesSize = MAX_VARIABLES_SIZE;
+        }
+
+        // Mark liveness blocks, as many times as necessary.
+        do
+        {
+            checkAgain = false;
+            alive      = 0L;
+
+            // Loop over all traced instructions, backward.
+            for (int offset = codeLength - 1; offset >= 0; offset--)
+            {
+                if (partialEvaluator.isTraced(offset))
+                {
+                    // Update the liveness based on the branch targets.
+                    InstructionOffsetValue branchTargets = partialEvaluator.branchTargets(offset);
+                    if (branchTargets != null)
+                    {
+                        // Update the liveness right after the branch instruction.
+                        alive = combinedLiveness(branchTargets);
+                    }
+
+                    // Merge the current liveness.
+                    alive |= isAliveAfter[offset];
+
+                    // Update the liveness after the instruction.
+                    isAliveAfter[offset] = alive;
+
+                    // Update the current liveness based on the instruction.
+                    codeAttribute.instructionAccept(clazz, method, offset, this);
+
+                    // Merge the current liveness.
+                    alive |= isAliveBefore[offset];
+
+                    // Update the liveness before the instruction.
+                    if ((~isAliveBefore[offset] & alive) != 0L)
+                    {
+                        isAliveBefore[offset] = alive;
+
+                        // Do we have to check again after this loop?
+                        checkAgain |= offset < maxOffset(partialEvaluator.branchOrigins(offset));
+                    }
+                }
+            }
+
+            // Account for the liveness at the start of the exception handlers.
+            codeAttribute.exceptionsAccept(clazz, method, this);
+        }
+        while (checkAgain);
+
+        // Loop over all instructions, to mark variables that take up two entries.
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            if (partialEvaluator.isTraced(offset))
+            {
+                // Loop over all variables.
+                for (int variableIndex = 0; variableIndex < variablesSize; variableIndex++)
+                {
+                    // Is the variable alive and a category 2 type?
+                    if (isAliveBefore(offset, variableIndex))
+                    {
+                        Value value = partialEvaluator.getVariablesBefore(offset).getValue(variableIndex);
+                        if (value != null && value.isCategory2())
+                        {
+                            // Mark it as such.
+                            setCategory2(offset, variableIndex, true);
+
+                            // Mark the next variable as well.
+                            setAliveBefore(offset, variableIndex + 1, true);
+                            setCategory2(  offset, variableIndex + 1, true);
+                        }
+                    }
+
+                    // Is the variable alive and a category 2 type?
+                    if (isAliveAfter(offset, variableIndex))
+                    {
+                        Value value = partialEvaluator.getVariablesAfter(offset).getValue(variableIndex);
+                        if (value != null && value.isCategory2())
+                        {
+                            // Mark it as such.
+                            setCategory2(offset, variableIndex, true);
+
+                            // Mark the next variable as well.
+                            setAliveAfter(offset, variableIndex + 1, true);
+                            setCategory2( offset, variableIndex + 1, true);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (DEBUG)
+        {
+            // Loop over all instructions.
+            for (int offset = 0; offset < codeLength; offset++)
+            {
+                if (partialEvaluator.isTraced(offset))
+                {
+                    long aliveBefore = isAliveBefore[offset];
+                    long aliveAfter  = isAliveAfter[offset];
+                    long category2   = isCategory2[offset];
+
+                    // Print out the liveness of all variables before the instruction.
+                    for (int variableIndex = 0; variableIndex < variablesSize; variableIndex++)
+                    {
+                        long variableMask = (1L << variableIndex);
+                        System.out.print((aliveBefore & variableMask) == 0L ? '.' :
+                                         (category2   & variableMask) == 0L ? 'x' :
+                                                                              '*');
+                    }
+
+                    // Print out the instruction itself.
+                    System.out.println(" "+ InstructionFactory.create(codeAttribute.code, offset).toString(offset));
+
+                    // Print out the liveness of all variables after the instruction.
+                    for (int variableIndex = 0; variableIndex < variablesSize; variableIndex++)
+                    {
+                        long variableMask = (1L << variableIndex);
+                        System.out.print((aliveAfter & variableMask) == 0L ? '.' :
+                                         (category2  & variableMask) == 0L ? 'x' :
+                                                                             '=');
+                    }
+
+                    System.out.println();
+                }
+            }
+        }
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        int variableIndex = variableInstruction.variableIndex;
+        if (variableIndex < MAX_VARIABLES_SIZE)
+        {
+            long livenessMask = 1L << variableIndex;
+
+            // Is it a load instruction or a store instruction?
+            if (variableInstruction.isLoad())
+            {
+                // Start marking the variable before the load instruction.
+                alive |= livenessMask;
+            }
+            else
+            {
+                // Stop marking the variable before the store instruction.
+                alive &= ~livenessMask;
+
+                // But do mark the variable right after the store instruction.
+                isAliveAfter[offset] |= livenessMask;
+            }
+        }
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        // Special case: variable 0 ('this') in an initializer has to be alive
+        // as long as it hasn't been initialized.
+         if (offset == partialEvaluator.superInitializationOffset())
+        {
+            alive |= 1L;
+        }
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        // Are any variables alive at the start of the handler?
+        long alive = isAliveBefore[exceptionInfo.u2handlerPC];
+        if (alive != 0L)
+        {
+            // Set the same liveness flags for the entire try block.
+            int startOffset = exceptionInfo.u2startPC;
+            int endOffset   = exceptionInfo.u2endPC;
+
+            for (int offset = startOffset; offset < endOffset; offset++)
+            {
+                if (partialEvaluator.isTraced(offset))
+                {
+                    if ((~(isAliveBefore[offset] & isAliveAfter[offset]) & alive) != 0L)
+                    {
+                        isAliveBefore[offset] |= alive;
+                        isAliveAfter[offset]  |= alive;
+
+                        // Check again after having marked this try block.
+                        checkAgain = true;
+                    }
+                }
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Initializes the global arrays.
+     */
+    private void initializeArrays(CodeAttribute codeAttribute)
+    {
+        int codeLength = codeAttribute.u4codeLength;
+
+        // Create new arrays for storing information at each instruction offset.
+        if (isAliveBefore.length < codeLength)
+        {
+            isAliveBefore = new long[codeLength];
+            isAliveAfter  = new long[codeLength];
+            isCategory2   = new long[codeLength];
+        }
+        else
+        {
+            for (int index = 0; index < codeLength; index++)
+            {
+                isAliveBefore[index] = 0L;
+                isAliveAfter[index]  = 0L;
+                isCategory2[index]   = 0L;
+            }
+        }
+    }
+
+
+    /**
+     * Returns the combined liveness mask of the variables right before the
+     * specified instruction offsets.
+     */
+    private long combinedLiveness(InstructionOffsetValue instructionOffsetValue)
+    {
+        long alive = 0L;
+
+        int count = instructionOffsetValue.instructionOffsetCount();
+        for (int index = 0; index < count; index++)
+        {
+            alive |= isAliveBefore[instructionOffsetValue.instructionOffset(index)];
+        }
+
+        return alive;
+    }
+
+
+    /**
+     * Returns the minimum offset from the given instruction offsets.
+     */
+    private int minOffset(Value instructionOffsets)
+    {
+        return minOffset(instructionOffsets, Integer.MAX_VALUE);
+    }
+
+
+    /**
+     * Returns the minimum offset from the given instruction offsets.
+     */
+    private int minOffset(Value instructionOffsets, int minOffset)
+    {
+        if (instructionOffsets != null)
+        {
+            InstructionOffsetValue instructionOffsetValue =
+                instructionOffsets.instructionOffsetValue();
+
+            int count = instructionOffsetValue.instructionOffsetCount();
+            for (int index = 0; index < count; index++)
+            {
+                int offset = instructionOffsetValue.instructionOffset(index);
+                if (minOffset > offset)
+                {
+                    minOffset = offset;
+                }
+            }
+        }
+
+        return minOffset;
+    }
+
+
+    /**
+     * Returns the maximum offset from the given instruction offsets.
+     */
+    private int maxOffset(Value instructionOffsets)
+    {
+        return maxOffset(instructionOffsets, Integer.MIN_VALUE);
+    }
+
+
+    /**
+     * Returns the maximum offset from the given instruction offsets.
+     */
+    private int maxOffset(Value instructionOffsets, int maxOffset)
+    {
+        if (instructionOffsets != null)
+        {
+            InstructionOffsetValue instructionOffsetValue =
+                instructionOffsets.instructionOffsetValue();
+
+            int count = instructionOffsetValue.instructionOffsetCount();
+            for (int index = 0; index < count; index++)
+            {
+                int offset = instructionOffsetValue.instructionOffset(index);
+                if (maxOffset < offset)
+                {
+                    maxOffset = offset;
+                }
+            }
+        }
+
+        return maxOffset;
+    }
+}
diff --git a/src/proguard/optimize/evaluation/LoadingInvocationUnit.java b/src/proguard/optimize/evaluation/LoadingInvocationUnit.java
new file mode 100644
index 0000000..8379c57
--- /dev/null
+++ b/src/proguard/optimize/evaluation/LoadingInvocationUnit.java
@@ -0,0 +1,203 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.RefConstant;
+import proguard.evaluation.BasicInvocationUnit;
+import proguard.evaluation.value.*;
+
+/**
+ * This InvocationUbit loads parameter values and return values that were
+ * previously stored with the methods that are invoked.
+ *
+ * @see StoringInvocationUnit
+ * @author Eric Lafortune
+ */
+public class LoadingInvocationUnit
+extends      BasicInvocationUnit
+{
+    private boolean loadFieldValues;
+    private boolean loadMethodParameterValues;
+    private boolean loadMethodReturnValues;
+
+
+    /**
+     * Creates a new LoadingInvocationUnit with the given value factory.
+     */
+    public LoadingInvocationUnit(ValueFactory valueFactory)
+    {
+        this(valueFactory, false, false, false);
+    }
+
+
+    /**
+     * Creates a new LoadingInvocationUnit with the given value factory, for
+     * loading the specified values.
+     */
+    public LoadingInvocationUnit(ValueFactory valueFactory,
+                                 boolean      loadFieldValues,
+                                 boolean      loadMethodParameterValues,
+                                 boolean      loadMethodReturnValues)
+    {
+        super(valueFactory);
+
+        this.loadFieldValues           = loadFieldValues;
+        this.loadMethodParameterValues = loadMethodParameterValues;
+        this.loadMethodReturnValues    = loadMethodReturnValues;
+    }
+
+
+    // Implementations for BasicInvocationUnit.
+
+    protected Value getFieldClassValue(Clazz       clazz,
+                                       RefConstant refConstant,
+                                       String      type)
+    {
+        if (loadFieldValues)
+        {
+            // Do we know this field?
+            Member referencedMember = refConstant.referencedMember;
+            if (referencedMember != null)
+            {
+                // Retrieve the stored field class value.
+                ReferenceValue value = StoringInvocationUnit.getFieldClassValue((Field)referencedMember);
+                if (value != null &&
+                    value.isParticular())
+                {
+                    return value;
+//                // Make sure the value is refreshed.
+//                return refresh(value);
+                }
+            }
+        }
+
+        return super.getFieldClassValue(clazz, refConstant, type);
+    }
+
+
+    protected Value getFieldValue(Clazz       clazz,
+                                  RefConstant refConstant,
+                                  String      type)
+    {
+        if (loadFieldValues)
+        {
+            // Do we know this field?
+            Member referencedMember = refConstant.referencedMember;
+            if (referencedMember != null)
+            {
+                // Retrieve the stored field value.
+                Value value = StoringInvocationUnit.getFieldValue((Field)referencedMember);
+                if (value != null &&
+                    value.isParticular())
+                {
+                    return value;
+//                // Make sure the value is refreshed.
+//                return refresh(value);
+                }
+            }
+        }
+
+        return super.getFieldValue(clazz, refConstant, type);
+    }
+
+
+    protected Value getMethodParameterValue(Clazz  clazz,
+                                            Method method,
+                                            int    parameterIndex,
+                                            String type,
+                                            Clazz  referencedClass)
+    {
+        if (loadMethodParameterValues)
+        {
+            // Retrieve the stored method parameter value.
+            Value value = StoringInvocationUnit.getMethodParameterValue(method, parameterIndex);
+            if (value != null &&
+                value.isParticular())
+            {
+                return value;
+//            // Make sure the value is refreshed.
+//            return refresh(value);
+            }
+        }
+
+        return super.getMethodParameterValue(clazz,
+                                             method,
+                                             parameterIndex,
+                                             type,
+                                             referencedClass);
+    }
+
+
+    protected Value getMethodReturnValue(Clazz       clazz,
+                                         RefConstant refConstant,
+                                         String      type)
+    {
+        if (loadMethodReturnValues)
+        {
+            // Do we know this method?
+            Member referencedMember = refConstant.referencedMember;
+            if (referencedMember != null)
+            {
+                // Retrieve the stored method return value.
+                Value value = StoringInvocationUnit.getMethodReturnValue((Method)referencedMember);
+                if (value != null &&
+                    value.isParticular())
+                {
+                    return value;
+//                // Make sure the value is refreshed.
+//                return refresh(value);
+                }
+            }
+        }
+
+        return super.getMethodReturnValue(clazz,
+                                          refConstant,
+                                          type);
+    }
+//
+//
+//    // Small utility methods.
+//
+//    private Value refresh(Value value)
+//    {
+//        if (value.isParticular())
+//        {
+//            return value;
+//        }
+//
+//        switch (value.computationalType())
+//        {
+//            case Value.TYPE_INTEGER: return valueFactory.createIntegerValue();
+//            case Value.TYPE_LONG:    return valueFactory.createLongValue();
+//            case Value.TYPE_FLOAT:   return valueFactory.createFloatValue();
+//            case Value.TYPE_DOUBLE:  return valueFactory.createDoubleValue();
+//            default:
+//            {
+//                ReferenceValue referenceValue = value.referenceValue();
+//
+//                return valueFactory.createReferenceValue(referenceValue.getType(),
+//                                                         referenceValue.getReferencedClass(),
+//                                                         referenceValue.isNull() != Value.NEVER);
+//            }
+//        }
+//    }
+}
diff --git a/src/proguard/optimize/evaluation/PartialEvaluator.java b/src/proguard/optimize/evaluation/PartialEvaluator.java
new file mode 100644
index 0000000..5790a36
--- /dev/null
+++ b/src/proguard/optimize/evaluation/PartialEvaluator.java
@@ -0,0 +1,1269 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.instruction.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.evaluation.*;
+import proguard.evaluation.value.*;
+import proguard.optimize.peephole.BranchTargetFinder;
+
+/**
+ * This AttributeVisitor performs partial evaluation on the code attributes
+ * that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class PartialEvaluator
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             ExceptionInfoVisitor
+{
+    //*
+    private static final boolean DEBUG         = false;
+    private static final boolean DEBUG_RESULTS = false;
+    /*/
+    private static boolean DEBUG         = true;
+    private static boolean DEBUG_RESULTS = true;
+    //*/
+
+    private static final int MAXIMUM_EVALUATION_COUNT = 5;
+
+    public static final int NONE            = -2;
+    public static final int AT_METHOD_ENTRY = -1;
+    public static final int AT_CATCH_ENTRY  = -1;
+
+    private final ValueFactory   valueFactory;
+    private final InvocationUnit invocationUnit;
+    private final boolean        evaluateAllCode;
+
+    private InstructionOffsetValue[] branchOriginValues   = new InstructionOffsetValue[ClassConstants.TYPICAL_CODE_LENGTH];
+    private InstructionOffsetValue[] branchTargetValues   = new InstructionOffsetValue[ClassConstants.TYPICAL_CODE_LENGTH];
+    private TracedVariables[]        variablesBefore      = new TracedVariables[ClassConstants.TYPICAL_CODE_LENGTH];
+    private TracedStack[]            stacksBefore         = new TracedStack[ClassConstants.TYPICAL_CODE_LENGTH];
+    private TracedVariables[]        variablesAfter       = new TracedVariables[ClassConstants.TYPICAL_CODE_LENGTH];
+    private TracedStack[]            stacksAfter          = new TracedStack[ClassConstants.TYPICAL_CODE_LENGTH];
+    private boolean[]                generalizedContexts  = new boolean[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int[]                    evaluationCounts     = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int[]                    initializedVariables = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+    private boolean                  evaluateExceptions;
+
+    private final BasicBranchUnit    branchUnit;
+    private final BranchTargetFinder branchTargetFinder;
+
+    private final java.util.Stack callingInstructionBlockStack;
+    private final java.util.Stack instructionBlockStack = new java.util.Stack();
+
+
+    /**
+     * Creates a simple PartialEvaluator.
+     */
+    public PartialEvaluator()
+    {
+        this(new ValueFactory(),
+             new BasicInvocationUnit(new ValueFactory()),
+             true);
+    }
+
+
+    /**
+     * Creates a new PartialEvaluator.
+     * @param valueFactory    the value factory that will create all values
+     *                        during evaluation.
+     * @param invocationUnit  the invocation unit that will handle all
+     *                        communication with other fields and methods.
+     * @param evaluateAllCode a flag that specifies whether all branch targets
+     *                        and exception handlers should be evaluated,
+     *                        even if they are unreachable.
+     */
+    public PartialEvaluator(ValueFactory   valueFactory,
+                            InvocationUnit invocationUnit,
+                            boolean        evaluateAllCode)
+    {
+        this(valueFactory,
+             invocationUnit,
+             evaluateAllCode,
+             evaluateAllCode ?
+                 new BasicBranchUnit() :
+                 new TracedBranchUnit(),
+             new BranchTargetFinder(),
+             null);
+    }
+
+
+    /**
+     * Creates a new PartialEvaluator, based on an existing one.
+     * @param partialEvaluator the subroutine calling partial evaluator.
+     */
+    private PartialEvaluator(PartialEvaluator partialEvaluator)
+    {
+        this(partialEvaluator.valueFactory,
+             partialEvaluator.invocationUnit,
+             partialEvaluator.evaluateAllCode,
+             partialEvaluator.branchUnit,
+             partialEvaluator.branchTargetFinder,
+             partialEvaluator.instructionBlockStack);
+    }
+
+
+    /**
+     * Creates a new PartialEvaluator.
+     * @param valueFactory            the value factory that will create all
+     *                                values during evaluation.
+     * @param invocationUnit          the invocation unit that will handle all
+     *                                communication with other fields and methods.
+     * @param evaluateAllCode         a flag that specifies whether all branch
+     *                                targets and exception handlers should be
+     *                                evaluated, even if they are unreachable.
+     * @param branchUnit              the branch unit that will handle all
+     *                                branches.
+     * @param branchTargetFinder      the utility class that will find all
+     *                                branches.
+     */
+    private PartialEvaluator(ValueFactory       valueFactory,
+                             InvocationUnit     invocationUnit,
+                             boolean            evaluateAllCode,
+                             BasicBranchUnit    branchUnit,
+                             BranchTargetFinder branchTargetFinder,
+                             java.util.Stack    callingInstructionBlockStack)
+    {
+        this.valueFactory       = valueFactory;
+        this.invocationUnit     = invocationUnit;
+        this.evaluateAllCode    = evaluateAllCode;
+        this.branchUnit         = branchUnit;
+        this.branchTargetFinder = branchTargetFinder;
+        this.callingInstructionBlockStack = callingInstructionBlockStack == null ?
+            this.instructionBlockStack :
+            callingInstructionBlockStack;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG = DEBUG_RESULTS =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // TODO: Remove this when the partial evaluator has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            // Process the code.
+            visitCodeAttribute0(clazz, method, codeAttribute);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while performing partial evaluation:");
+            System.err.println("  Class       = ["+clazz.getName()+"]");
+            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+
+            if (DEBUG)
+            {
+                method.accept(clazz, new ClassPrinter());
+            }
+
+            throw ex;
+        }
+    }
+
+
+    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Evaluate the instructions, starting at the entry point.
+        if (DEBUG)
+        {
+            System.out.println();
+            System.out.println("Partial evaluation: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
+            System.out.println("  Max locals = "+codeAttribute.u2maxLocals);
+            System.out.println("  Max stack  = "+codeAttribute.u2maxStack);
+        }
+
+        // Reuse the existing variables and stack objects, ensuring the right size.
+        TracedVariables variables = new TracedVariables(codeAttribute.u2maxLocals);
+        TracedStack     stack     = new TracedStack(codeAttribute.u2maxStack);
+
+        // Initialize the reusable arrays and variables.
+        initializeVariables(clazz, method, codeAttribute, variables, stack);
+
+        // Find all instruction offsets,...
+        codeAttribute.accept(clazz, method, branchTargetFinder);
+
+        // Start executing the first instruction block.
+        evaluateInstructionBlockAndExceptionHandlers(clazz,
+                                                     method,
+                                                     codeAttribute,
+                                                     variables,
+                                                     stack,
+                                                     0,
+                                                     codeAttribute.u4codeLength);
+
+        if (DEBUG_RESULTS)
+        {
+            System.out.println("Evaluation results:");
+
+            int offset = 0;
+            do
+            {
+                if (isBranchOrExceptionTarget(offset))
+                {
+                    System.out.println("Branch target from ["+branchOriginValues[offset]+"]:");
+                    if (isTraced(offset))
+                    {
+                        System.out.println("  Vars:  "+variablesBefore[offset]);
+                        System.out.println("  Stack: "+stacksBefore[offset]);
+                    }
+                }
+
+                Instruction instruction = InstructionFactory.create(codeAttribute.code,
+                                                                    offset);
+                System.out.println(instruction.toString(offset));
+
+                if (isTraced(offset))
+                {
+                    int variableIndex = initializedVariable(offset);
+                    if (variableIndex >= 0)
+                    {
+                        System.out.println("     is initializing variable v"+variableIndex);
+                    }
+
+                    int initializationOffset = branchTargetFinder.initializationOffset(offset);
+                    if (initializationOffset != NONE)
+                    {
+                        System.out.println("     is to be initialized at ["+initializationOffset+"]");
+                    }
+
+                    InstructionOffsetValue branchTargets = branchTargets(offset);
+                    if (branchTargets != null)
+                    {
+                        System.out.println("     has overall been branching to "+branchTargets);
+                    }
+
+                    System.out.println("  Vars:  "+variablesAfter[offset]);
+                    System.out.println("  Stack: "+stacksAfter[offset]);
+                }
+
+                offset += instruction.length(offset);
+            }
+            while (offset < codeAttribute.u4codeLength);
+        }
+    }
+
+
+    /**
+     * Returns whether a block of instructions is ever used.
+     */
+    public boolean isTraced(int startOffset, int endOffset)
+    {
+        for (int index = startOffset; index < endOffset; index++)
+        {
+            if (isTraced(index))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset has ever been
+     * executed during the partial evaluation.
+     */
+    public boolean isTraced(int instructionOffset)
+    {
+        return evaluationCounts[instructionOffset] > 0;
+    }
+
+
+    /**
+     * Returns whether there is an instruction at the given offset.
+     */
+    public boolean isInstruction(int instructionOffset)
+    {
+        return branchTargetFinder.isInstruction(instructionOffset);
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the target of a
+     * branch instruction or an exception.
+     */
+    public boolean isBranchOrExceptionTarget(int instructionOffset)
+    {
+        return branchTargetFinder.isBranchTarget(instructionOffset) ||
+               branchTargetFinder.isExceptionHandler(instructionOffset);
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the start of a
+     * subroutine.
+     */
+    public boolean isSubroutineStart(int instructionOffset)
+    {
+        return branchTargetFinder.isSubroutineStart(instructionOffset);
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is a subroutine
+     * invocation.
+     */
+    public boolean isSubroutineInvocation(int instructionOffset)
+    {
+        return branchTargetFinder.isSubroutineInvocation(instructionOffset);
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is part of a
+     * subroutine.
+     */
+    public boolean isSubroutine(int instructionOffset)
+    {
+        return branchTargetFinder.isSubroutine(instructionOffset);
+    }
+
+
+    /**
+     * Returns whether the subroutine at the given offset is ever returning
+     * by means of a regular 'ret' instruction.
+     */
+    public boolean isSubroutineReturning(int instructionOffset)
+    {
+        return branchTargetFinder.isSubroutineReturning(instructionOffset);
+    }
+
+
+    /**
+     * Returns the offset after the subroutine that starts at the given
+     * offset.
+     */
+    public int subroutineEnd(int instructionOffset)
+    {
+        return branchTargetFinder.subroutineEnd(instructionOffset);
+    }
+
+
+    /**
+     * Returns the instruction offset at which the object instance that is
+     * created at the given 'new' instruction offset is initialized, or
+     * <code>NONE</code> if it is not being created.
+     */
+    public int initializationOffset(int instructionOffset)
+    {
+        return branchTargetFinder.initializationOffset(instructionOffset);
+    }
+
+
+    /**
+     * Returns whether the method is an instance initializer.
+     */
+    public boolean isInitializer()
+    {
+        return branchTargetFinder.isInitializer();
+    }
+
+
+    /**
+     * Returns the instruction offset at which this initializer is calling
+     * the "super" or "this" initializer method, or <code>NONE</code> if it is
+     * not an initializer.
+     */
+    public int superInitializationOffset()
+    {
+        return branchTargetFinder.superInitializationOffset();
+    }
+
+
+    /**
+     * Returns the offset of the 'new' instruction that corresponds to the
+     * invocation of the instance initializer at the given offset, or
+     * <code>AT_METHOD_ENTRY</code> if the invocation is calling the "super" or
+     * "this" initializer method, , or <code>NONE</code> if it is not a 'new'
+     * instruction.
+     */
+    public int creationOffset(int offset)
+    {
+        return branchTargetFinder.creationOffset(offset);
+    }
+
+
+    /**
+     * Returns the variables before execution of the instruction at the given
+     * offset.
+     */
+    public TracedVariables getVariablesBefore(int instructionOffset)
+    {
+        return variablesBefore[instructionOffset];
+    }
+
+
+    /**
+     * Returns the variables after execution of the instruction at the given
+     * offset.
+     */
+    public TracedVariables getVariablesAfter(int instructionOffset)
+    {
+        return variablesAfter[instructionOffset];
+    }
+
+
+    /**
+     * Returns the stack before execution of the instruction at the given
+     * offset.
+     */
+    public TracedStack getStackBefore(int instructionOffset)
+    {
+        return stacksBefore[instructionOffset];
+    }
+
+
+    /**
+     * Returns the stack after execution of the instruction at the given
+     * offset.
+     */
+    public TracedStack getStackAfter(int instructionOffset)
+    {
+        return stacksAfter[instructionOffset];
+    }
+
+
+    /**
+     * Returns the instruction offsets that branch to the given instruction
+     * offset.
+     */
+    public InstructionOffsetValue branchOrigins(int instructionOffset)
+    {
+        return branchOriginValues[instructionOffset];
+    }
+
+
+    /**
+     * Returns the instruction offsets to which the given instruction offset
+     * branches.
+     */
+    public InstructionOffsetValue branchTargets(int instructionOffset)
+    {
+        return branchTargetValues[instructionOffset];
+    }
+
+
+    /**
+     * Returns the variable that is initialized at the given instruction offset,
+     * or <code>NONE</code> if no variable was initialized.
+     */
+    public int initializedVariable(int instructionOffset)
+    {
+        return initializedVariables[instructionOffset];
+    }
+
+
+    // Utility methods to evaluate instruction blocks.
+
+    /**
+     * Pushes block of instructions to be executed in the calling partial
+     * evaluator.
+     */
+    private void pushCallingInstructionBlock(TracedVariables variables,
+                                             TracedStack     stack,
+                                             int             startOffset)
+    {
+        callingInstructionBlockStack.push(new MyInstructionBlock(variables,
+                                                                 stack,
+                                                                 startOffset));
+    }
+
+
+    /**
+     * Pushes block of instructions to be executed in this partial evaluator.
+     */
+    private void pushInstructionBlock(TracedVariables variables,
+                                      TracedStack     stack,
+                                      int             startOffset)
+    {
+        instructionBlockStack.push(new MyInstructionBlock(variables,
+                                                          stack,
+                                                          startOffset));
+    }
+
+
+    /**
+     * Evaluates the instruction block and the exception handlers covering the
+     * given instruction range in the given code.
+     */
+    private void evaluateInstructionBlockAndExceptionHandlers(Clazz           clazz,
+                                                              Method          method,
+                                                              CodeAttribute   codeAttribute,
+                                                              TracedVariables variables,
+                                                              TracedStack     stack,
+                                                              int             startOffset,
+                                                              int             endOffset)
+    {
+        evaluateInstructionBlock(clazz,
+                                 method,
+                                 codeAttribute,
+                                 variables,
+                                 stack,
+                                 startOffset);
+
+        evaluateExceptionHandlers(clazz,
+                                  method,
+                                  codeAttribute,
+                                  startOffset,
+                                  endOffset);
+    }
+
+
+    /**
+     * Evaluates a block of instructions, starting at the given offset and ending
+     * at a branch instruction, a return instruction, or a throw instruction.
+     */
+    private void evaluateInstructionBlock(Clazz           clazz,
+                                          Method          method,
+                                          CodeAttribute   codeAttribute,
+                                          TracedVariables variables,
+                                          TracedStack     stack,
+                                          int             startOffset)
+    {
+        // Execute the initial instruction block.
+        evaluateSingleInstructionBlock(clazz,
+                                       method,
+                                       codeAttribute,
+                                       variables,
+                                       stack,
+                                       startOffset);
+
+        // Execute all resulting instruction blocks on the execution stack.
+        while (!instructionBlockStack.empty())
+        {
+            if (DEBUG) System.out.println("Popping alternative branch out of "+instructionBlockStack.size()+" blocks");
+
+            MyInstructionBlock instructionBlock =
+                (MyInstructionBlock)instructionBlockStack.pop();
+
+            evaluateSingleInstructionBlock(clazz,
+                                           method,
+                                           codeAttribute,
+                                           instructionBlock.variables,
+                                           instructionBlock.stack,
+                                           instructionBlock.startOffset);
+        }
+    }
+
+
+    /**
+     * Evaluates a block of instructions, starting at the given offset and ending
+     * at a branch instruction, a return instruction, or a throw instruction.
+     * Instruction blocks that are to be evaluated as a result are pushed on
+     * the given stack.
+     */
+    private void evaluateSingleInstructionBlock(Clazz            clazz,
+                                                Method           method,
+                                                CodeAttribute    codeAttribute,
+                                                TracedVariables  variables,
+                                                TracedStack      stack,
+                                                int              startOffset)
+    {
+        byte[] code = codeAttribute.code;
+
+        if (DEBUG)
+        {
+             System.out.println("Instruction block starting at ["+startOffset+"] in "+
+                                ClassUtil.externalFullMethodDescription(clazz.getName(),
+                                                                        0,
+                                                                        method.getName(clazz),
+                                                                        method.getDescriptor(clazz)));
+             System.out.println("Init vars:  "+variables);
+             System.out.println("Init stack: "+stack);
+        }
+
+        Processor processor = new Processor(variables,
+                                            stack,
+                                            valueFactory,
+                                            branchUnit,
+                                            invocationUnit);
+
+        int instructionOffset = startOffset;
+
+        int maxOffset = startOffset;
+
+        // Evaluate the subsequent instructions.
+        while (true)
+        {
+            if (maxOffset < instructionOffset)
+            {
+                maxOffset = instructionOffset;
+            }
+
+            // Maintain a generalized local variable frame and stack at this
+            // instruction offset, before execution.
+            int evaluationCount = evaluationCounts[instructionOffset];
+            if (evaluationCount == 0)
+            {
+                // First time we're passing by this instruction.
+                if (variablesBefore[instructionOffset] == null)
+                {
+                    // There's not even a context at this index yet.
+                    variablesBefore[instructionOffset] = new TracedVariables(variables);
+                    stacksBefore[instructionOffset]    = new TracedStack(stack);
+                }
+                else
+                {
+                    // Reuse the context objects at this index.
+                    variablesBefore[instructionOffset].initialize(variables);
+                    stacksBefore[instructionOffset].copy(stack);
+                }
+
+                // We'll execute in the generalized context, because it is
+                // the same as the current context.
+                generalizedContexts[instructionOffset] = true;
+            }
+            else
+            {
+                // Merge in the current context.
+                boolean variablesChanged = variablesBefore[instructionOffset].generalize(variables, true);
+                boolean stackChanged     = stacksBefore[instructionOffset].generalize(stack);
+
+                //System.out.println("GVars:  "+variablesBefore[instructionOffset]);
+                //System.out.println("GStack: "+stacksBefore[instructionOffset]);
+
+                // Bail out if the current context is the same as last time.
+                if (!variablesChanged &&
+                    !stackChanged     &&
+                    generalizedContexts[instructionOffset])
+                {
+                    if (DEBUG) System.out.println("Repeated variables, stack, and branch targets");
+
+                    break;
+                }
+
+                // See if this instruction has been evaluated an excessive number
+                // of times.
+                if (evaluationCount >= MAXIMUM_EVALUATION_COUNT)
+                {
+                    if (DEBUG) System.out.println("Generalizing current context after "+evaluationCount+" evaluations");
+
+                    // Continue, but generalize the current context.
+                    // Note that the most recent variable values have to remain
+                    // last in the generalizations, for the sake of the ret
+                    // instruction.
+                    variables.generalize(variablesBefore[instructionOffset], false);
+                    stack.generalize(stacksBefore[instructionOffset]);
+
+                    // We'll execute in the generalized context.
+                    generalizedContexts[instructionOffset] = true;
+                }
+                else
+                {
+                    // We'll execute in the current context.
+                    generalizedContexts[instructionOffset] = false;
+                }
+            }
+
+            // We'll evaluate this instruction.
+            evaluationCounts[instructionOffset]++;
+
+            // Remember this instruction's offset with any stored value.
+            Value storeValue = new InstructionOffsetValue(instructionOffset);
+            variables.setProducerValue(storeValue);
+            stack.setProducerValue(storeValue);
+
+            // Reset the trace value.
+            InstructionOffsetValue traceValue = InstructionOffsetValue.EMPTY_VALUE;
+
+            // Reset the initialization flag.
+            variables.resetInitialization();
+
+            // Note that the instruction is only volatile.
+            Instruction instruction = InstructionFactory.create(code, instructionOffset);
+
+            // By default, the next instruction will be the one after this
+            // instruction.
+            int nextInstructionOffset = instructionOffset +
+                                        instruction.length(instructionOffset);
+            InstructionOffsetValue nextInstructionOffsetValue = new InstructionOffsetValue(nextInstructionOffset);
+            branchUnit.resetCalled();
+            branchUnit.setTraceBranchTargets(nextInstructionOffsetValue);
+
+            if (DEBUG)
+            {
+                System.out.println(instruction.toString(instructionOffset));
+            }
+
+            try
+            {
+                // Process the instruction. The processor may modify the
+                // variables and the stack, and it may call the branch unit
+                // and the invocation unit.
+                instruction.accept(clazz,
+                                   method,
+                                   codeAttribute,
+                                   instructionOffset,
+                                   processor);
+            }
+            catch (RuntimeException ex)
+            {
+                System.err.println("Unexpected error while evaluating instruction:");
+                System.err.println("  Class       = ["+clazz.getName()+"]");
+                System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+                System.err.println("  Instruction = "+instruction.toString(instructionOffset));
+                System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+
+                throw ex;
+            }
+
+            // Collect the offsets of the instructions whose results were used.
+            initializedVariables[instructionOffset] = variables.getInitializationIndex();
+
+            // Collect the branch targets from the branch unit.
+            InstructionOffsetValue branchTargets = branchUnit.getTraceBranchTargets();
+            int branchTargetCount = branchTargets.instructionOffsetCount();
+
+            // Stop tracing.
+            branchUnit.setTraceBranchTargets(traceValue);
+
+            if (DEBUG)
+            {
+                if (branchUnit.wasCalled())
+                {
+                    System.out.println("     is branching to "+branchTargets);
+                }
+                if (branchTargetValues[instructionOffset] != null)
+                {
+                    System.out.println("     has up till now been branching to "+branchTargetValues[instructionOffset]);
+                }
+
+                System.out.println(" Vars:  "+variables);
+                System.out.println(" Stack: "+stack);
+            }
+
+            // Maintain a generalized local variable frame and stack at this
+            // instruction offset, after execution.
+            if (evaluationCount == 0)
+            {
+                // First time we're passing by this instruction.
+                if (variablesAfter[instructionOffset] == null)
+                {
+                    // There's not even a context at this index yet.
+                    variablesAfter[instructionOffset] = new TracedVariables(variables);
+                    stacksAfter[instructionOffset]    = new TracedStack(stack);
+                }
+                else
+                {
+                    // Reuse the context objects at this index.
+                    variablesAfter[instructionOffset].initialize(variables);
+                    stacksAfter[instructionOffset].copy(stack);
+                }
+            }
+            else
+            {
+                // Merge in the current context.
+                variablesAfter[instructionOffset].generalize(variables, true);
+                stacksAfter[instructionOffset].generalize(stack);
+            }
+
+            // Did the branch unit get called?
+            if (branchUnit.wasCalled())
+            {
+                // Accumulate the branch targets at this offset.
+                branchTargetValues[instructionOffset] = branchTargetValues[instructionOffset] == null ?
+                    branchTargets :
+                    branchTargetValues[instructionOffset].generalize(branchTargets).instructionOffsetValue();
+
+                // Are there no branch targets at all?
+                if (branchTargetCount == 0)
+                {
+                    // Exit from this code block.
+                    break;
+                }
+
+                // Accumulate the branch origins at the branch target offsets.
+                InstructionOffsetValue instructionOffsetValue = new InstructionOffsetValue(instructionOffset);
+                for (int index = 0; index < branchTargetCount; index++)
+                {
+                    int branchTarget = branchTargets.instructionOffset(index);
+                    branchOriginValues[branchTarget] = branchOriginValues[branchTarget] == null ?
+                        instructionOffsetValue:
+                        branchOriginValues[branchTarget].generalize(instructionOffsetValue).instructionOffsetValue();
+                }
+
+                // Are there multiple branch targets?
+                if (branchTargetCount > 1)
+                {
+                    // Push them on the execution stack and exit from this block.
+                    for (int index = 0; index < branchTargetCount; index++)
+                    {
+                        if (DEBUG) System.out.println("Pushing alternative branch #"+index+" out of "+branchTargetCount+", from ["+instructionOffset+"] to ["+branchTargets.instructionOffset(index)+"]");
+
+                        pushInstructionBlock(new TracedVariables(variables),
+                                             new TracedStack(stack),
+                                             branchTargets.instructionOffset(index));
+                    }
+
+                    break;
+                }
+
+                if (DEBUG) System.out.println("Definite branch from ["+instructionOffset+"] to ["+branchTargets.instructionOffset(0)+"]");
+            }
+
+            // Just continue with the next instruction.
+            instructionOffset = branchTargets.instructionOffset(0);
+
+            // Is this a subroutine invocation?
+            if (instruction.opcode == InstructionConstants.OP_JSR ||
+                instruction.opcode == InstructionConstants.OP_JSR_W)
+            {
+                // Evaluate the subroutine, possibly in another partial
+                // evaluator.
+                evaluateSubroutine(clazz,
+                                   method,
+                                   codeAttribute,
+                                   variables,
+                                   stack,
+                                   instructionOffset,
+                                   instructionBlockStack);
+
+                break;
+            }
+            else if (instruction.opcode == InstructionConstants.OP_RET)
+            {
+                // Let the partial evaluator that has called the subroutine
+                // handle the evaluation after the return.
+                pushCallingInstructionBlock(new TracedVariables(variables),
+                                            new TracedStack(stack),
+                                            instructionOffset);
+                break;
+            }
+        }
+
+        if (DEBUG) System.out.println("Ending processing of instruction block starting at ["+startOffset+"]");
+    }
+
+
+    /**
+     * Evaluates a subroutine and its exception handlers, starting at the given
+     * offset and ending at a subroutine return instruction.
+     */
+    private void evaluateSubroutine(Clazz           clazz,
+                                    Method          method,
+                                    CodeAttribute   codeAttribute,
+                                    TracedVariables variables,
+                                    TracedStack     stack,
+                                    int             subroutineStart,
+                                    java.util.Stack instructionBlockStack)
+    {
+        int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart);
+
+        if (DEBUG) System.out.println("Evaluating subroutine from "+subroutineStart+" to "+subroutineEnd);
+
+        PartialEvaluator subroutinePartialEvaluator = this;
+
+        // Create a temporary partial evaluator if necessary.
+        if (evaluationCounts[subroutineStart] > 0)
+        {
+            if (DEBUG) System.out.println("Creating new partial evaluator for subroutine");
+
+            subroutinePartialEvaluator = new PartialEvaluator(this);
+
+            subroutinePartialEvaluator.initializeVariables(clazz,
+                                                           method,
+                                                           codeAttribute,
+                                                           variables,
+                                                           stack);
+        }
+
+        // Evaluate the subroutine.
+        subroutinePartialEvaluator.evaluateInstructionBlockAndExceptionHandlers(clazz,
+                                                                                method,
+                                                                                codeAttribute,
+                                                                                variables,
+                                                                                stack,
+                                                                                subroutineStart,
+                                                                                subroutineEnd);
+
+        // Merge back the temporary partial evaluator if necessary.
+        if (subroutinePartialEvaluator != this)
+        {
+            generalize(subroutinePartialEvaluator, 0, codeAttribute.u4codeLength);
+        }
+
+        if (DEBUG) System.out.println("Ending subroutine from "+subroutineStart+" to "+subroutineEnd);
+    }
+
+
+    /**
+     * Generalizes the results of this partial evaluator with those of another
+     * given partial evaluator, over a given range of instructions.
+     */
+    private void generalize(PartialEvaluator other,
+                            int              codeStart,
+                            int              codeEnd)
+    {
+        if (DEBUG) System.out.println("Generalizing with temporary partial evaluation");
+
+        for (int offset = codeStart; offset < codeEnd; offset++)
+        {
+            if (other.branchOriginValues[offset] != null)
+            {
+                branchOriginValues[offset] = branchOriginValues[offset] == null ?
+                    other.branchOriginValues[offset] :
+                    branchOriginValues[offset].generalize(other.branchOriginValues[offset]).instructionOffsetValue();
+            }
+
+            if (other.isTraced(offset))
+            {
+                if (other.branchTargetValues[offset] != null)
+                {
+                    branchTargetValues[offset] = branchTargetValues[offset] == null ?
+                        other.branchTargetValues[offset] :
+                        branchTargetValues[offset].generalize(other.branchTargetValues[offset]).instructionOffsetValue();
+                }
+
+                if (evaluationCounts[offset] == 0)
+                {
+                    variablesBefore[offset]      = other.variablesBefore[offset];
+                    stacksBefore[offset]         = other.stacksBefore[offset];
+                    variablesAfter[offset]       = other.variablesAfter[offset];
+                    stacksAfter[offset]          = other.stacksAfter[offset];
+                    generalizedContexts[offset]  = other.generalizedContexts[offset];
+                    evaluationCounts[offset]     = other.evaluationCounts[offset];
+                    initializedVariables[offset] = other.initializedVariables[offset];
+                }
+                else
+                {
+                    variablesBefore[offset].generalize(other.variablesBefore[offset], false);
+                    stacksBefore[offset]   .generalize(other.stacksBefore[offset]);
+                    variablesAfter[offset] .generalize(other.variablesAfter[offset], false);
+                    stacksAfter[offset]    .generalize(other.stacksAfter[offset]);
+                    //generalizedContexts[offset]
+                    evaluationCounts[offset] += other.evaluationCounts[offset];
+                    //initializedVariables[offset]
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Evaluates the exception handlers covering and targeting the given
+     * instruction range in the given code.
+     */
+    private void evaluateExceptionHandlers(Clazz         clazz,
+                                           Method        method,
+                                           CodeAttribute codeAttribute,
+                                           int           startOffset,
+                                           int           endOffset)
+    {
+        if (DEBUG) System.out.println("Evaluating exceptions covering ["+startOffset+" -> "+endOffset+"]:");
+
+        ExceptionHandlerFilter exceptionEvaluator =
+            new ExceptionHandlerFilter(startOffset,
+                                       endOffset,
+                                       this);
+
+        // Evaluate the exception catch blocks, until their entry variables
+        // have stabilized.
+        do
+        {
+            // Reset the flag to stop evaluating.
+            evaluateExceptions = false;
+
+            // Evaluate all relevant exception catch blocks once.
+            codeAttribute.exceptionsAccept(clazz,
+                                           method,
+                                           startOffset,
+                                           endOffset,
+                                           exceptionEvaluator);
+        }
+        while (evaluateExceptions);
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        int startPC = exceptionInfo.u2startPC;
+        int endPC   = exceptionInfo.u2endPC;
+
+        // Do we have to evaluate this exception catch block?
+        if (isTraced(startPC, endPC))
+        {
+            int handlerPC = exceptionInfo.u2handlerPC;
+            int catchType = exceptionInfo.u2catchType;
+
+            if (DEBUG) System.out.println("Evaluating exception ["+startPC +" -> "+endPC +": "+handlerPC+"]:");
+
+            // Reuse the existing variables and stack objects, ensuring the
+            // right size.
+            TracedVariables variables = new TracedVariables(codeAttribute.u2maxLocals);
+            TracedStack     stack     = new TracedStack(codeAttribute.u2maxStack);
+
+            // Initialize the trace values.
+            Value storeValue = new InstructionOffsetValue(AT_CATCH_ENTRY);
+            variables.setProducerValue(storeValue);
+            stack.setProducerValue(storeValue);
+
+            // Initialize the variables by generalizing the variables of the
+            // try block. Make sure to include the results of the last
+            // instruction for preverification.
+            generalizeVariables(startPC,
+                                endPC,
+                                evaluateAllCode,
+                                variables);
+
+            // Initialize the the stack.
+            //stack.push(valueFactory.createReference((ClassConstant)((ProgramClass)clazz).getConstant(exceptionInfo.u2catchType), false));
+            String catchClassName = catchType != 0 ?
+                 clazz.getClassName(catchType) :
+                 ClassConstants.INTERNAL_NAME_JAVA_LANG_THROWABLE;
+
+            Clazz catchClass = catchType != 0 ?
+                ((ClassConstant)((ProgramClass)clazz).getConstant(catchType)).referencedClass :
+                null;
+
+            stack.push(valueFactory.createReferenceValue(catchClassName,
+                                                         catchClass,
+                                                         false));
+
+            int evaluationCount = evaluationCounts[handlerPC];
+
+            // Evaluate the instructions, starting at the entry point.
+            evaluateInstructionBlock(clazz,
+                                     method,
+                                     codeAttribute,
+                                     variables,
+                                     stack,
+                                     handlerPC);
+
+            // Remember to evaluate all exception handlers once more.
+            if (!evaluateExceptions)
+            {
+                evaluateExceptions = evaluationCount < evaluationCounts[handlerPC];
+            }
+        }
+//        else if (evaluateAllCode)
+//        {
+//            if (DEBUG) System.out.println("No information for partial evaluation of exception ["+startPC +" -> "+endPC +": "+exceptionInfo.u2handlerPC+"] yet");
+//
+//            // We don't have any information on the try block yet, but we do
+//            // have to evaluate the exception handler.
+//            // Remember to evaluate all exception handlers once more.
+//            evaluateExceptions = true;
+//        }
+        else
+        {
+            if (DEBUG) System.out.println("No information for partial evaluation of exception ["+startPC +" -> "+endPC +": "+exceptionInfo.u2handlerPC+"]");
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Initializes the data structures for the variables, stack, etc.
+     */
+    private void initializeVariables(Clazz           clazz,
+                                     Method          method,
+                                     CodeAttribute   codeAttribute,
+                                     TracedVariables variables,
+                                     TracedStack     stack)
+    {
+        int codeLength = codeAttribute.u4codeLength;
+
+        // Create new arrays for storing information at each instruction offset.
+        if (variablesAfter.length < codeLength)
+        {
+            // Create new arrays.
+            branchOriginValues   = new InstructionOffsetValue[codeLength];
+            branchTargetValues   = new InstructionOffsetValue[codeLength];
+            variablesBefore      = new TracedVariables[codeLength];
+            stacksBefore         = new TracedStack[codeLength];
+            variablesAfter       = new TracedVariables[codeLength];
+            stacksAfter          = new TracedStack[codeLength];
+            generalizedContexts  = new boolean[codeLength];
+            evaluationCounts     = new int[codeLength];
+            initializedVariables = new int[codeLength];
+
+            // Reset the arrays.
+            for (int index = 0; index < codeLength; index++)
+            {
+                initializedVariables[index] = NONE;
+            }
+        }
+        else
+        {
+            // Reset the arrays.
+            for (int index = 0; index < codeLength; index++)
+            {
+                branchOriginValues[index]   = null;
+                branchTargetValues[index]   = null;
+                generalizedContexts[index]  = false;
+                evaluationCounts[index]     = 0;
+                initializedVariables[index] = NONE;
+
+                if (variablesBefore[index] != null)
+                {
+                    variablesBefore[index].reset(codeAttribute.u2maxLocals);
+                }
+
+                if (stacksBefore[index] != null)
+                {
+                    stacksBefore[index].reset(codeAttribute.u2maxStack);
+                }
+
+                if (variablesAfter[index] != null)
+                {
+                    variablesAfter[index].reset(codeAttribute.u2maxLocals);
+                }
+
+                if (stacksAfter[index] != null)
+                {
+                    stacksAfter[index].reset(codeAttribute.u2maxStack);
+                }
+            }
+        }
+
+        // Create the method parameters.
+        TracedVariables parameters = new TracedVariables(codeAttribute.u2maxLocals);
+
+        // Remember this instruction's offset with any stored value.
+        Value storeValue = new InstructionOffsetValue(AT_METHOD_ENTRY);
+        parameters.setProducerValue(storeValue);
+
+        // Initialize the method parameters.
+        invocationUnit.enterMethod(clazz, method, parameters);
+
+        if (DEBUG)
+        {
+            System.out.println("  Params: "+parameters);
+        }
+
+        // Initialize the variables with the parameters.
+        variables.initialize(parameters);
+
+        // Set the store value of each parameter variable.
+        InstructionOffsetValue atMethodEntry = new InstructionOffsetValue(AT_METHOD_ENTRY);
+
+        for (int index = 0; index < parameters.size(); index++)
+        {
+            variables.setProducerValue(index, atMethodEntry);
+        }
+    }
+
+
+    /**
+     * Generalize the local variable frames of a block of instructions.
+     */
+    private void generalizeVariables(int             startOffset,
+                                     int             endOffset,
+                                     boolean         includeAfterLastInstruction,
+                                     TracedVariables generalizedVariables)
+    {
+        boolean first     = true;
+        int     lastIndex = -1;
+
+        // Generalize the variables before each of the instructions in the block.
+        for (int index = startOffset; index < endOffset; index++)
+        {
+            if (isTraced(index))
+            {
+                TracedVariables tracedVariables = variablesBefore[index];
+
+                if (first)
+                {
+                    // Initialize the variables with the first traced local
+                    // variable frame.
+                    generalizedVariables.initialize(tracedVariables);
+
+                    first = false;
+                }
+                else
+                {
+                    // Generalize the variables with the traced local variable
+                    // frame. We can't use the return value, because local
+                    // generalization can be different a couple of times,
+                    // with the global generalization being the same.
+                    generalizedVariables.generalize(tracedVariables, false);
+                }
+
+                lastIndex = index;
+            }
+        }
+
+        // Generalize the variables after the last instruction in the block,
+        // if required.
+        if (includeAfterLastInstruction &&
+            lastIndex >= 0)
+        {
+            TracedVariables tracedVariables = variablesAfter[lastIndex];
+
+            if (first)
+            {
+                // Initialize the variables with the local variable frame.
+                generalizedVariables.initialize(tracedVariables);
+            }
+            else
+            {
+                // Generalize the variables with the local variable frame.
+                generalizedVariables.generalize(tracedVariables, false);
+            }
+        }
+
+        // Just clear the variables if there aren't any traced instructions
+        // in the block.
+        if (first)
+        {
+            generalizedVariables.reset(generalizedVariables.size());
+        }
+    }
+
+
+    private static class MyInstructionBlock
+    {
+        private TracedVariables variables;
+        private TracedStack     stack;
+        private int             startOffset;
+
+
+        private MyInstructionBlock(TracedVariables variables,
+                                   TracedStack     stack,
+                                   int             startOffset)
+        {
+            this.variables   = variables;
+            this.stack       = stack;
+            this.startOffset = startOffset;
+        }
+    }
+}
diff --git a/src/proguard/optimize/evaluation/StoringInvocationUnit.java b/src/proguard/optimize/evaluation/StoringInvocationUnit.java
new file mode 100644
index 0000000..bcbb69f
--- /dev/null
+++ b/src/proguard/optimize/evaluation/StoringInvocationUnit.java
@@ -0,0 +1,207 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.RefConstant;
+import proguard.evaluation.BasicInvocationUnit;
+import proguard.evaluation.value.*;
+import proguard.optimize.info.*;
+
+/**
+ * This InvocationUbit stores parameter values and return values with the
+ * methods that are invoked.
+ *
+ * @see LoadingInvocationUnit
+ * @author Eric Lafortune
+ */
+public class StoringInvocationUnit
+extends      BasicInvocationUnit
+{
+    private boolean storeFieldValues;
+    private boolean storeMethodParameterValues;
+    private boolean storeMethodReturnValues;
+
+
+    /**
+     * Creates a new StoringInvocationUnit with the given value factory.
+     */
+    public StoringInvocationUnit(ValueFactory valueFactory)
+    {
+        this(valueFactory, true, true, true);
+    }
+
+
+    /**
+     * Creates a new StoringInvocationUnit with the given value factory, for
+     * storing the specified values.
+     */
+    public StoringInvocationUnit(ValueFactory valueFactory,
+                                 boolean      storeFieldValues,
+                                 boolean      storeMethodParameterValues,
+                                 boolean      storeMethodReturnValues)
+    {
+        super(valueFactory);
+
+        this.storeFieldValues           = storeFieldValues;
+        this.storeMethodParameterValues = storeMethodParameterValues;
+        this.storeMethodReturnValues    = storeMethodReturnValues;
+    }
+
+
+    // Implementations for BasicInvocationUnit.
+
+    protected void setFieldClassValue(Clazz          clazz,
+                                      RefConstant    refConstant,
+                                      ReferenceValue value)
+    {
+        if (storeFieldValues)
+        {
+            Member referencedMember = refConstant.referencedMember;
+            if (referencedMember != null)
+            {
+                generalizeFieldClassValue((Field)referencedMember, value);
+            }
+        }
+    }
+
+
+    protected void setFieldValue(Clazz       clazz,
+                                 RefConstant refConstant,
+                                 Value       value)
+    {
+        if (storeFieldValues)
+        {
+            Member referencedMember = refConstant.referencedMember;
+            if (referencedMember != null)
+            {
+                generalizeFieldValue((Field)referencedMember, value);
+            }
+        }
+    }
+
+
+    protected void setMethodParameterValue(Clazz       clazz,
+                                           RefConstant refConstant,
+                                           int         parameterIndex,
+                                           Value       value)
+    {
+        if (storeMethodParameterValues)
+        {
+            Member referencedMember = refConstant.referencedMember;
+            if (referencedMember != null)
+            {
+                generalizeMethodParameterValue((Method)referencedMember,
+                                               parameterIndex,
+                                               value);
+            }
+        }
+    }
+
+
+    protected void setMethodReturnValue(Clazz  clazz,
+                                        Method method,
+                                        Value  value)
+    {
+        if (storeMethodReturnValues)
+        {
+            generalizeMethodReturnValue(method, value);
+        }
+    }
+    
+
+    // Small utility methods.
+
+    private static void generalizeFieldClassValue(Field field, ReferenceValue value)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        if (info != null)
+        {
+            info.generalizeReferencedClass(value);
+        }
+    }
+
+
+    public static ReferenceValue getFieldClassValue(Field field)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        return info != null ?
+            info.getReferencedClass() :
+            null;
+    }
+
+
+    private static void generalizeFieldValue(Field field, Value value)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        if (info != null)
+        {
+            info.generalizeValue(value);
+        }
+    }
+
+
+    public static Value getFieldValue(Field field)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        return info != null ?
+            info.getValue() :
+            null;
+    }
+
+
+    private static void generalizeMethodParameterValue(Method method, int parameterIndex, Value value)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.generalizeParameter(parameterIndex, value);
+        }
+    }
+
+
+    public static Value getMethodParameterValue(Method method, int parameterIndex)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info != null ?
+            info.getParameter(parameterIndex) :
+            null;
+    }
+
+
+    private static void generalizeMethodReturnValue(Method method, Value value)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.generalizeReturnValue(value);
+        }
+    }
+
+
+    public static Value getMethodReturnValue(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info != null ?
+            info.getReturnValue() :
+            null;
+    }
+}
diff --git a/src/proguard/optimize/evaluation/TracedBranchUnit.java b/src/proguard/optimize/evaluation/TracedBranchUnit.java
new file mode 100644
index 0000000..fa5bb79
--- /dev/null
+++ b/src/proguard/optimize/evaluation/TracedBranchUnit.java
@@ -0,0 +1,59 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.evaluation;
+
+import proguard.classfile.Clazz;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.evaluation.BasicBranchUnit;
+import proguard.evaluation.value.Value;
+
+/**
+ * This BranchUnit remembers the branch unit commands that are invoked on it.
+ *
+ * @author Eric Lafortune
+ */
+class   TracedBranchUnit
+extends BasicBranchUnit
+{
+    // Implementations for BranchUnit.
+
+    public void branchConditionally(Clazz         clazz,
+                                    CodeAttribute codeAttribute,
+                                    int           offset,
+                                    int           branchTarget,
+                                    int           conditional)
+    {
+        if      (conditional == Value.ALWAYS)
+        {
+            // Always branch.
+            super.branch(clazz, codeAttribute, offset, branchTarget);
+        }
+        else if (conditional != Value.NEVER)
+        {
+            // Maybe branch.
+            super.branchConditionally(clazz, codeAttribute, offset, branchTarget, conditional);
+        }
+        else
+        {
+            super.setCalled();
+        }
+    }
+}
diff --git a/src/proguard/optimize/evaluation/VariableOptimizer.java b/src/proguard/optimize/evaluation/VariableOptimizer.java
new file mode 100644
index 0000000..b3ae81c
--- /dev/null
+++ b/src/proguard/optimize/evaluation/VariableOptimizer.java
@@ -0,0 +1,244 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.evaluation;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.VariableRemapper;
+import proguard.classfile.util.*;
+
+/**
+ * This AttributeVisitor optimizes variable allocation based on their the liveness,
+ * in the code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableOptimizer
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+    private static final int MAX_VARIABLES_SIZE = 64;
+
+
+    private final boolean       reuseThis;
+    private final MemberVisitor extraVariableMemberVisitor;
+
+    private final LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer();
+    private final VariableRemapper variableRemapper = new VariableRemapper();
+
+    private int[] variableMap = new int[ClassConstants.TYPICAL_VARIABLES_SIZE];
+
+
+    /**
+     * Creates a new VariableOptimizer.
+     * @param reuseThis specifies whether the 'this' variable can be reused.
+     *                  Many JVMs for JME and IBM's JVMs for JSE can't handle
+     *                  such reuse.
+     */
+    public VariableOptimizer(boolean reuseThis)
+    {
+        this(reuseThis, null);
+    }
+
+
+    /**
+     * Creates a new VariableOptimizer with an extra visitor.
+     * @param reuseThis                  specifies whether the 'this' variable
+     *                                   can be reused. Many JVMs for JME and
+     *                                   IBM's JVMs for JSE can't handle such
+     *                                   reuse.
+     * @param extraVariableMemberVisitor an optional extra visitor for all
+     *                                   removed variables.
+     */
+    public VariableOptimizer(boolean       reuseThis,
+                             MemberVisitor extraVariableMemberVisitor)
+    {
+        this.reuseThis                  = reuseThis;
+        this.extraVariableMemberVisitor = extraVariableMemberVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // Initialize the global arrays.
+        initializeArrays(codeAttribute);
+
+        // Analyze the liveness of the variables in the code.
+        livenessAnalyzer.visitCodeAttribute(clazz, method, codeAttribute);
+
+        int startIndex =
+            (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0 ||
+            reuseThis ? 0 : 1;
+
+        int parameterSize =
+            ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz),
+                                                  method.getAccessFlags());
+
+        int variableSize = codeAttribute.u2maxLocals;
+        int codeLength   = codeAttribute.u4codeLength;
+
+        boolean remapping = false;
+
+        // Loop over all variables.
+        for (int oldIndex = 0; oldIndex < variableSize; oldIndex++)
+        {
+            // By default, the variable will be mapped onto itself.
+            variableMap[oldIndex] = oldIndex;
+
+            // Only try remapping the variable if it's not a parameter.
+            if (oldIndex >= parameterSize &&
+                oldIndex < MAX_VARIABLES_SIZE)
+            {
+                // Try to remap the variable to a variable with a smaller index.
+                for (int newIndex = startIndex; newIndex < oldIndex; newIndex++)
+                {
+                    if (areNonOverlapping(oldIndex, newIndex, codeLength))
+                    {
+                        variableMap[oldIndex] = newIndex;
+
+                        updateLiveness(oldIndex, newIndex, codeLength);
+
+                        remapping = true;
+
+                        // This variable has been remapped. Go to the next one.
+                        break;
+                    }
+                }
+            }
+        }
+
+        // Remap the variables.
+        if (remapping)
+        {
+            if (DEBUG)
+            {
+                System.out.println("Remapping variables:");
+                System.out.println("  Class "+ ClassUtil.externalClassName(clazz.getName()));
+                System.out.println("  Method "+ClassUtil.externalFullMethodDescription(clazz.getName(),
+                                                                                       0,
+                                                                                       method.getName(clazz),
+                                                                                       method.getDescriptor(clazz)));
+                for (int index = 0; index < variableSize; index++)
+                {
+                    System.out.println("  ["+index+"] -> ["+variableMap[index]+"]");
+                }
+            }
+
+            variableRemapper.setVariableMap(variableMap);
+            variableRemapper.visitCodeAttribute(clazz, method, codeAttribute);
+
+            // Visit the method, if required.
+            if (extraVariableMemberVisitor != null)
+            {
+                method.accept(clazz, extraVariableMemberVisitor);
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Initializes the global arrays.
+     */
+    private void initializeArrays(CodeAttribute codeAttribute)
+    {
+        int codeLength = codeAttribute.u4codeLength;
+
+        // Create new arrays for storing information at each instruction offset.
+        if (variableMap.length < codeLength)
+        {
+            variableMap = new int[codeLength];
+        }
+    }
+
+
+    /**
+     * Returns whether the given variables are never alive at the same time.
+     */
+    private boolean areNonOverlapping(int variableIndex1,
+                                      int variableIndex2,
+                                      int codeLength)
+    {
+        // Loop over all instructions.
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            if ((livenessAnalyzer.isAliveBefore(offset, variableIndex1) &&
+                 livenessAnalyzer.isAliveBefore(offset, variableIndex2)) ||
+
+                (livenessAnalyzer.isAliveAfter(offset, variableIndex1) &&
+                 livenessAnalyzer.isAliveAfter(offset, variableIndex2)) ||
+
+                // For now, exclude Category 2 variables.
+                livenessAnalyzer.isCategory2(offset, variableIndex1))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Updates the liveness resulting from mapping the given old variable on
+     * the given new variable.
+     */
+    private void updateLiveness(int oldVariableIndex,
+                                int newVariableIndex,
+                                int codeLength)
+    {
+        // Loop over all instructions.
+        for (int offset = 0; offset < codeLength; offset++)
+        {
+            // Update the liveness before the instruction.
+            if (livenessAnalyzer.isAliveBefore(offset, oldVariableIndex))
+            {
+                livenessAnalyzer.setAliveBefore(offset, oldVariableIndex, false);
+                livenessAnalyzer.setAliveBefore(offset, newVariableIndex, true);
+            }
+
+            // Update the liveness after the instruction.
+            if (livenessAnalyzer.isAliveAfter(offset, oldVariableIndex))
+            {
+                livenessAnalyzer.setAliveAfter(offset, oldVariableIndex, false);
+                livenessAnalyzer.setAliveAfter(offset, newVariableIndex, true);
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/evaluation/package.html b/src/proguard/optimize/evaluation/package.html
new file mode 100644
index 0000000..5341f9f
--- /dev/null
+++ b/src/proguard/optimize/evaluation/package.html
@@ -0,0 +1,4 @@
+<body>
+This package contains visitors that perform partial evaluation and subsequent
+optimizations on byte code.
+</body>
diff --git a/src/proguard/optimize/info/AccessMethodMarker.java b/src/proguard/optimize/info/AccessMethodMarker.java
new file mode 100644
index 0000000..6965cec
--- /dev/null
+++ b/src/proguard/optimize/info/AccessMethodMarker.java
@@ -0,0 +1,187 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This InstructionVisitor marks the types of class accesses and class member
+ * accesses of the methods whose instructions it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class AccessMethodMarker
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             ClassVisitor,
+             MemberVisitor
+{
+    private Method invokingMethod;
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        invokingMethod = method;
+
+        clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Check the referenced class or class member, if any.
+       stringConstant.referencedClassAccept(this);
+       stringConstant.referencedMemberAccept(this);
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        // Check the referenced class.
+        clazz.constantPoolEntryAccept(refConstant.u2classIndex, this);
+
+        // Check the referenced class member itself.
+        refConstant.referencedClassAccept(this);
+        refConstant.referencedMemberAccept(this);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Check the referenced class.
+       classConstant.referencedClassAccept(this);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitAnyClass(Clazz clazz)
+    {
+        int accessFlags = clazz.getAccessFlags();
+
+        if ((accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC) == 0)
+        {
+            setAccessesPackageCode(invokingMethod);
+        }
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        int accessFlags = member.getAccessFlags();
+
+        if      ((accessFlags & ClassConstants.INTERNAL_ACC_PRIVATE)   != 0)
+        {
+            setAccessesPrivateCode(invokingMethod);
+        }
+        else if ((accessFlags & ClassConstants.INTERNAL_ACC_PROTECTED) != 0)
+        {
+            setAccessesProtectedCode(invokingMethod);
+        }
+        else if ((accessFlags & ClassConstants.INTERNAL_ACC_PUBLIC)    == 0)
+        {
+            setAccessesPackageCode(invokingMethod);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private static void setAccessesPrivateCode(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setAccessesPrivateCode();
+        }
+    }
+
+
+    /**
+     * Returns whether the given method accesses private class members.
+     */
+    public static boolean accessesPrivateCode(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info == null || info.accessesPrivateCode();
+    }
+
+
+    private static void setAccessesPackageCode(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setAccessesPackageCode();
+        }
+    }
+
+
+    /**
+     * Returns whether the given method accesses package visible classes or class
+     * members.
+     */
+    public static boolean accessesPackageCode(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info == null || info.accessesPackageCode();
+    }
+
+
+    private static void setAccessesProtectedCode(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setAccessesProtectedCode();
+        }
+    }
+
+
+    /**
+     * Returns whether the given method accesses protected class members.
+     */
+    public static boolean accessesProtectedCode(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info == null || info.accessesProtectedCode();
+    }
+}
diff --git a/src/proguard/optimize/info/BackwardBranchMarker.java b/src/proguard/optimize/info/BackwardBranchMarker.java
new file mode 100644
index 0000000..9e09b0f
--- /dev/null
+++ b/src/proguard/optimize/info/BackwardBranchMarker.java
@@ -0,0 +1,90 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor marks all methods that branch backward in any of the
+ * instructions that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class BackwardBranchMarker
+extends      SimplifiedVisitor
+implements   InstructionVisitor
+{
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        markBackwardBranch(method, branchInstruction.branchOffset);
+    }
+
+
+    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
+    {
+        markBackwardBranch(method, switchInstruction.defaultOffset);
+
+        for (int index = 0; index < switchInstruction.jumpOffsets.length; index++)
+        {
+            markBackwardBranch(method, switchInstruction.jumpOffsets[index]);
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the given method if the given branch offset is negative.
+     */
+    private void markBackwardBranch(Method method, int branchOffset)
+    {
+        if (branchOffset < 0)
+        {
+            setBranchesBackward(method);
+        }
+    }
+
+
+    private static void setBranchesBackward(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setBranchesBackward();
+        }
+    }
+
+
+    public static boolean branchesBackward(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info == null || info.branchesBackward();
+    }
+}
diff --git a/src/proguard/optimize/info/CatchExceptionMarker.java b/src/proguard/optimize/info/CatchExceptionMarker.java
new file mode 100644
index 0000000..3f2a06f
--- /dev/null
+++ b/src/proguard/optimize/info/CatchExceptionMarker.java
@@ -0,0 +1,69 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor marks all methods that catch exceptions.
+ *
+ * @author Eric Lafortune
+ */
+public class CatchExceptionMarker
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (codeAttribute.u2exceptionTableLength > 0)
+        {
+            markCatchException(method);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private static void markCatchException(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setCatchesExceptions();
+        }
+    }
+
+
+    public static boolean catchesExceptions(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info == null ||
+               info.catchesExceptions();
+    }
+}
diff --git a/src/proguard/optimize/info/CaughtClassFilter.java b/src/proguard/optimize/info/CaughtClassFilter.java
new file mode 100644
index 0000000..5e17763
--- /dev/null
+++ b/src/proguard/optimize/info/CaughtClassFilter.java
@@ -0,0 +1,63 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor delegates all its method calls to another ClassVisitor,
+ * but only for Clazz objects that are caught as exceptions.
+ *
+ * @see CaughtClassMarker
+ * @author Eric Lafortune
+ */
+public class CaughtClassFilter
+implements   ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    public CaughtClassFilter(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (CaughtClassMarker.isCaught(programClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (CaughtClassMarker.isCaught(libraryClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/CaughtClassMarker.java b/src/proguard/optimize/info/CaughtClassMarker.java
new file mode 100644
index 0000000..0cc350e
--- /dev/null
+++ b/src/proguard/optimize/info/CaughtClassMarker.java
@@ -0,0 +1,63 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This InstructionVisitor marks all classes that are used in an 'instanceof'
+ * test by any of the instructions that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class CaughtClassMarker
+implements   ClassVisitor
+{
+    // Implementations for ClassVisitor.
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        setCaught(programClass);
+    }
+
+
+    // Small utility methods.
+
+    private static void setCaught(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        if (info != null)
+        {
+            info.setCaught();
+        }
+    }
+
+
+    public static boolean isCaught(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        return info == null || info.isCaught();
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/ClassOptimizationInfo.java b/src/proguard/optimize/info/ClassOptimizationInfo.java
new file mode 100644
index 0000000..99b6e7b
--- /dev/null
+++ b/src/proguard/optimize/info/ClassOptimizationInfo.java
@@ -0,0 +1,151 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.Clazz;
+
+/**
+ * This class stores some optimization information that can be attached to
+ * a class.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassOptimizationInfo
+{
+    private boolean isInstantiated                = false;
+    private boolean isInstanceofed                = false;
+    private boolean isDotClassed                  = false;
+    private boolean isCaught                      = false;
+    private boolean containsPackageVisibleMembers = false;
+    private boolean invokesPackageVisibleMembers  = false;
+    private Clazz   targetClass;
+
+
+    public void setInstantiated()
+    {
+        isInstantiated = true;
+    }
+
+
+    public boolean isInstantiated()
+    {
+        return isInstantiated;
+    }
+
+
+    public void setInstanceofed()
+    {
+        isInstanceofed = true;
+    }
+
+
+    public boolean isInstanceofed()
+    {
+        return isInstanceofed;
+    }
+
+
+    public void setDotClassed()
+    {
+        isDotClassed = true;
+    }
+
+
+    public boolean isDotClassed()
+    {
+        return isDotClassed;
+    }
+
+
+    public void setCaught()
+    {
+        isCaught = true;
+    }
+
+
+    public boolean isCaught()
+    {
+        return isCaught;
+    }
+
+
+    public void setContainsPackageVisibleMembers()
+    {
+        containsPackageVisibleMembers = true;
+    }
+
+
+    public boolean containsPackageVisibleMembers()
+    {
+        return containsPackageVisibleMembers;
+    }
+
+
+    public void setInvokesPackageVisibleMembers()
+    {
+        invokesPackageVisibleMembers = true;
+    }
+
+
+    public boolean invokesPackageVisibleMembers()
+    {
+        return invokesPackageVisibleMembers;
+    }
+
+
+    public void setTargetClass(Clazz targetClass)
+    {
+        this.targetClass = targetClass;
+    }
+
+
+    public Clazz getTargetClass()
+    {
+        return targetClass;
+    }
+
+
+    public void merge(ClassOptimizationInfo other)
+    {
+        this.isInstantiated                |= other.isInstantiated;
+        this.isInstanceofed                |= other.isInstanceofed;
+        this.isDotClassed                  |= other.isDotClassed;
+        this.isCaught                      |= other.isCaught;
+        this.containsPackageVisibleMembers |= other.containsPackageVisibleMembers;
+        this.invokesPackageVisibleMembers  |= other.invokesPackageVisibleMembers;
+    }
+
+
+    public static void setClassOptimizationInfo(Clazz clazz)
+    {
+        clazz.setVisitorInfo(new ClassOptimizationInfo());
+    }
+
+
+    public static ClassOptimizationInfo getClassOptimizationInfo(Clazz clazz)
+    {
+        Object visitorInfo = clazz.getVisitorInfo();
+
+        return visitorInfo instanceof ClassOptimizationInfo ?
+            (ClassOptimizationInfo)visitorInfo :
+            null;
+    }
+}
diff --git a/src/proguard/optimize/info/ClassOptimizationInfoSetter.java b/src/proguard/optimize/info/ClassOptimizationInfoSetter.java
new file mode 100644
index 0000000..9cb167c
--- /dev/null
+++ b/src/proguard/optimize/info/ClassOptimizationInfoSetter.java
@@ -0,0 +1,47 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+import proguard.optimize.KeepMarker;
+
+/**
+ * This ClassVisitor attaches a ClassOptimizationInfo instance to every class
+ * that is not being kept that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassOptimizationInfoSetter
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    // Implementations for MemberVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (!KeepMarker.isKept(programClass))
+        {
+            ClassOptimizationInfo.setClassOptimizationInfo(programClass);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/DotClassFilter.java b/src/proguard/optimize/info/DotClassFilter.java
new file mode 100644
index 0000000..8cbe7f0
--- /dev/null
+++ b/src/proguard/optimize/info/DotClassFilter.java
@@ -0,0 +1,63 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor delegates all its method calls to another ClassVisitor,
+ * but only for Clazz objects that are used in a .class construct.
+ *
+ * @see DotClassMarker
+ * @author Eric Lafortune
+ */
+public class DotClassFilter
+implements   ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    public DotClassFilter(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (DotClassMarker.isDotClassed(programClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (DotClassMarker.isDotClassed(libraryClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/DotClassMarker.java b/src/proguard/optimize/info/DotClassMarker.java
new file mode 100644
index 0000000..b5f12a7
--- /dev/null
+++ b/src/proguard/optimize/info/DotClassMarker.java
@@ -0,0 +1,96 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This InstructionVisitor marks all classes that are used in a .class
+ * construct by any of the instructions that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class DotClassMarker
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             ClassVisitor
+{
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        if (constantInstruction.opcode == InstructionConstants.OP_LDC ||
+            constantInstruction.opcode == InstructionConstants.OP_LDC_W)
+        {
+            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classConstant.referencedClassAccept(this);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        setDotClassed(programClass);
+    }
+
+
+    // Small utility methods.
+
+    private static void setDotClassed(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        if (info != null)
+        {
+            info.setDotClassed();
+        }
+    }
+
+
+    public static boolean isDotClassed(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        return info == null || info.isDotClassed();
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/ExceptionInstructionChecker.java b/src/proguard/optimize/info/ExceptionInstructionChecker.java
new file mode 100644
index 0000000..2792d90
--- /dev/null
+++ b/src/proguard/optimize/info/ExceptionInstructionChecker.java
@@ -0,0 +1,187 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.RefConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This class can tell whether an instruction might throw exceptions.
+ *
+ * @author Eric Lafortune
+ */
+public class ExceptionInstructionChecker
+extends      SimplifiedVisitor
+implements   InstructionVisitor
+//             ConstantVisitor,
+//             MemberVisitor
+{
+    // A return value for the visitor methods.
+    private boolean mayThrowExceptions;
+
+
+    /**
+     * Returns whether the given instruction may throw exceptions.
+     */
+    public boolean mayThrowExceptions(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        mayThrowExceptions = false;
+
+        instruction.accept(clazz, method,  codeAttribute, offset, this);
+
+        return mayThrowExceptions;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        byte opcode = simpleInstruction.opcode;
+
+        // Check for instructions that may throw exceptions.
+        if (opcode == InstructionConstants.OP_IDIV         ||
+            opcode == InstructionConstants.OP_LDIV         ||
+            opcode == InstructionConstants.OP_IREM         ||
+            opcode == InstructionConstants.OP_LREM         ||
+            opcode == InstructionConstants.OP_IALOAD       ||
+            opcode == InstructionConstants.OP_LALOAD       ||
+            opcode == InstructionConstants.OP_FALOAD       ||
+            opcode == InstructionConstants.OP_DALOAD       ||
+            opcode == InstructionConstants.OP_AALOAD       ||
+            opcode == InstructionConstants.OP_BALOAD       ||
+            opcode == InstructionConstants.OP_CALOAD       ||
+            opcode == InstructionConstants.OP_SALOAD       ||
+            opcode == InstructionConstants.OP_IASTORE      ||
+            opcode == InstructionConstants.OP_LASTORE      ||
+            opcode == InstructionConstants.OP_FASTORE      ||
+            opcode == InstructionConstants.OP_DASTORE      ||
+            opcode == InstructionConstants.OP_AASTORE      ||
+            opcode == InstructionConstants.OP_BASTORE      ||
+            opcode == InstructionConstants.OP_CASTORE      ||
+            opcode == InstructionConstants.OP_SASTORE      ||
+            opcode == InstructionConstants.OP_NEWARRAY     ||
+            opcode == InstructionConstants.OP_ARRAYLENGTH  ||
+            opcode == InstructionConstants.OP_ATHROW       ||
+            opcode == InstructionConstants.OP_MONITORENTER ||
+            opcode == InstructionConstants.OP_MONITOREXIT)
+        {
+            // These instructions may throw exceptions.
+            mayThrowExceptions = true;
+        }
+
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        byte opcode = constantInstruction.opcode;
+
+        // Check for instructions that may throw exceptions.
+        if (opcode == InstructionConstants.OP_GETSTATIC       ||
+            opcode == InstructionConstants.OP_PUTSTATIC       ||
+            opcode == InstructionConstants.OP_GETFIELD        ||
+            opcode == InstructionConstants.OP_PUTFIELD        ||
+            opcode == InstructionConstants.OP_INVOKEVIRTUAL   ||
+            opcode == InstructionConstants.OP_INVOKESPECIAL   ||
+            opcode == InstructionConstants.OP_INVOKESTATIC    ||
+            opcode == InstructionConstants.OP_INVOKEINTERFACE ||
+            opcode == InstructionConstants.OP_NEW             ||
+            opcode == InstructionConstants.OP_ANEWARRAY       ||
+            opcode == InstructionConstants.OP_CHECKCAST       ||
+            opcode == InstructionConstants.OP_MULTIANEWARRAY)
+        {
+            // These instructions may throw exceptions.
+            mayThrowExceptions = true;
+        }
+//        else
+//        if (opcode == InstructionConstants.OP_INVOKEVIRTUAL   ||
+//            opcode == InstructionConstants.OP_INVOKESPECIAL   ||
+//            opcode == InstructionConstants.OP_INVOKESTATIC    ||
+//            opcode == InstructionConstants.OP_INVOKEINTERFACE)
+//        {
+//            // Check if the invoking the method may throw an exception.
+//            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+//        }
+    }
+
+
+//    // Implementations for ConstantVisitor.
+//
+//    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant)
+//    {
+//        Member referencedMember = refConstant.referencedMember;
+//
+//        // Do we have a reference to the method?
+//        if (referencedMember == null)
+//        {
+//            // We'll have to assume invoking the unknown method may throw an
+//            // an exception.
+//            mayThrowExceptions = true;
+//        }
+//        else
+//        {
+//            // First check the referenced method itself.
+//            refConstant.referencedMemberAccept(this);
+//
+//            // If the result isn't conclusive, check down the hierarchy.
+//            if (!mayThrowExceptions)
+//            {
+//                Clazz  referencedClass  = refConstant.referencedClass;
+//                Method referencedMethod = (Method)referencedMember;
+//
+//                // Check all other implementations of the method in the class
+//                // hierarchy.
+//                referencedClass.methodImplementationsAccept(referencedMethod,
+//                                                            false,
+//                                                            false,
+//                                                            true,
+//                                                            true,
+//                                                            this);
+//            }
+//        }
+//    }
+//
+//
+//    // Implementations for MemberVisitor.
+//
+//    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+//    {
+//        mayThrowExceptions = mayThrowExceptions ||
+//                             ExceptionMethodMarker.mayThrowExceptions(programMethod);
+//    }
+//
+//
+//    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+//    {
+//        mayThrowExceptions = mayThrowExceptions ||
+//                             !NoExceptionMethodMarker.doesntThrowExceptions(libraryMethod);
+//    }
+}
diff --git a/src/proguard/optimize/info/FieldOptimizationInfo.java b/src/proguard/optimize/info/FieldOptimizationInfo.java
new file mode 100644
index 0000000..7a2d068
--- /dev/null
+++ b/src/proguard/optimize/info/FieldOptimizationInfo.java
@@ -0,0 +1,162 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.util.MethodLinker;
+import proguard.evaluation.value.*;
+
+/**
+ * This class stores some optimization information that can be attached to
+ * a field.
+ *
+ * @author Eric Lafortune
+ */
+public class FieldOptimizationInfo
+{
+    private static final SpecificValueFactory VALUE_FACTORY = new SpecificValueFactory();
+
+    private boolean        isWritten;
+    private boolean        isRead;
+    private boolean        canBeMadePrivate = true;
+    private ReferenceValue referencedClass;
+    private Value          value;
+
+
+    public FieldOptimizationInfo(Clazz clazz, Field field)
+    {
+        isWritten =
+        isRead    = (field.getAccessFlags() & ClassConstants.INTERNAL_ACC_VOLATILE) != 0;
+        value     = initialValue(field.getDescriptor(clazz));
+    }
+
+
+    public void setWritten()
+    {
+        isWritten = true;
+    }
+
+
+    public boolean isWritten()
+    {
+        return isWritten;
+    }
+
+
+    public void setRead()
+    {
+        isRead = true;
+    }
+
+
+    public boolean isRead()
+    {
+        return isRead;
+    }
+
+
+    public void setCanNotBeMadePrivate()
+    {
+        canBeMadePrivate = false;
+    }
+
+
+    public boolean canBeMadePrivate()
+    {
+        return canBeMadePrivate;
+    }
+
+
+    public void generalizeReferencedClass(ReferenceValue referencedClass)
+    {
+        this.referencedClass = this.referencedClass != null ?
+            this.referencedClass.generalize(referencedClass) :
+            referencedClass;
+    }
+
+
+    public ReferenceValue getReferencedClass()
+    {
+        return referencedClass;
+    }
+
+
+    public void generalizeValue(Value value)
+    {
+        this.value = this.value != null ?
+            this.value.generalize(value) :
+            value;
+    }
+
+
+    public Value getValue()
+    {
+        return value;
+    }
+
+
+    // Small utility methods.
+
+    private Value initialValue(String type)
+    {
+        switch (type.charAt(0))
+        {
+            case ClassConstants.INTERNAL_TYPE_BOOLEAN:
+            case ClassConstants.INTERNAL_TYPE_BYTE:
+            case ClassConstants.INTERNAL_TYPE_CHAR:
+            case ClassConstants.INTERNAL_TYPE_SHORT:
+            case ClassConstants.INTERNAL_TYPE_INT:
+                return VALUE_FACTORY.createIntegerValue(0);
+
+            case ClassConstants.INTERNAL_TYPE_LONG:
+                return VALUE_FACTORY.createLongValue(0L);
+
+            case ClassConstants.INTERNAL_TYPE_FLOAT:
+                return VALUE_FACTORY.createFloatValue(0.0f);
+
+            case ClassConstants.INTERNAL_TYPE_DOUBLE:
+                return VALUE_FACTORY.createDoubleValue(0.0);
+
+            case ClassConstants.INTERNAL_TYPE_CLASS_START:
+            case ClassConstants.INTERNAL_TYPE_ARRAY:
+                return VALUE_FACTORY.createReferenceValueNull();
+
+            default:
+                throw new IllegalArgumentException("Invalid type ["+type+"]");
+        }
+    }
+
+
+    public static void setFieldOptimizationInfo(Clazz clazz, Field field)
+    {
+        MethodLinker.lastMember(field).setVisitorInfo(new FieldOptimizationInfo(clazz, field));
+    }
+
+
+    public static FieldOptimizationInfo getFieldOptimizationInfo(Field field)
+    {
+        Object visitorInfo = MethodLinker.lastMember(field).getVisitorInfo();
+
+        return visitorInfo instanceof FieldOptimizationInfo ?
+            (FieldOptimizationInfo)visitorInfo :
+            null;
+    }
+}
diff --git a/src/proguard/optimize/info/InstanceofClassFilter.java b/src/proguard/optimize/info/InstanceofClassFilter.java
new file mode 100644
index 0000000..35e1d77
--- /dev/null
+++ b/src/proguard/optimize/info/InstanceofClassFilter.java
@@ -0,0 +1,63 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor delegates all its method calls to another ClassVisitor,
+ * but only for Clazz objects that are used in an 'instanceof' test.
+ *
+ * @see InstanceofClassMarker
+ * @author Eric Lafortune
+ */
+public class InstanceofClassFilter
+implements   ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    public InstanceofClassFilter(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (InstanceofClassMarker.isInstanceofed(programClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (InstanceofClassMarker.isInstanceofed(libraryClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/InstanceofClassMarker.java b/src/proguard/optimize/info/InstanceofClassMarker.java
new file mode 100644
index 0000000..c60e1f8
--- /dev/null
+++ b/src/proguard/optimize/info/InstanceofClassMarker.java
@@ -0,0 +1,93 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This InstructionVisitor marks all classes that are used in an 'instanceof'
+ * test by any of the instructions that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class InstanceofClassMarker
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             ClassVisitor
+{
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        if (constantInstruction.opcode == InstructionConstants.OP_INSTANCEOF)
+        {
+            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classConstant.referencedClassAccept(this);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        setInstanceofed(programClass);
+    }
+
+
+    // Small utility methods.
+
+    private static void setInstanceofed(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        if (info != null)
+        {
+            info.setInstanceofed();
+        }
+    }
+
+
+    public static boolean isInstanceofed(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        return info == null || info.isInstanceofed();
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/InstantiationClassFilter.java b/src/proguard/optimize/info/InstantiationClassFilter.java
new file mode 100644
index 0000000..a24e617
--- /dev/null
+++ b/src/proguard/optimize/info/InstantiationClassFilter.java
@@ -0,0 +1,62 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor delegates all its method calls to another ClassVisitor,
+ * but only for Clazz objects that are instantiated.
+ *
+ * @author Eric Lafortune
+ */
+public class InstantiationClassFilter
+implements   ClassVisitor
+{
+    private final ClassVisitor classVisitor;
+
+
+    public InstantiationClassFilter(ClassVisitor classVisitor)
+    {
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (InstantiationClassMarker.isInstantiated(programClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (InstantiationClassMarker.isInstantiated(libraryClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/InstantiationClassMarker.java b/src/proguard/optimize/info/InstantiationClassMarker.java
new file mode 100644
index 0000000..124c23b
--- /dev/null
+++ b/src/proguard/optimize/info/InstantiationClassMarker.java
@@ -0,0 +1,93 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This InstructionVisitor marks all classes that are instantiated by any of
+ * the instructions that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class InstantiationClassMarker
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             ClassVisitor
+{
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        if (constantInstruction.opcode == InstructionConstants.OP_NEW)
+        {
+            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classConstant.referencedClassAccept(this);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitLibraryClass(LibraryClass libraryClass) {}
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        setInstantiated(programClass);
+    }
+
+
+    // Small utility methods.
+
+    private static void setInstantiated(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        if (info != null)
+        {
+            info.setInstantiated();
+        }
+    }
+
+
+    public static boolean isInstantiated(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        return info == null || info.isInstantiated();
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/MemberOptimizationInfoSetter.java b/src/proguard/optimize/info/MemberOptimizationInfoSetter.java
new file mode 100644
index 0000000..a170a8e
--- /dev/null
+++ b/src/proguard/optimize/info/MemberOptimizationInfoSetter.java
@@ -0,0 +1,59 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.KeepMarker;
+
+/**
+ * This MemberVisitor attaches a FieldOptimizationInfo instance to every field
+ * and a MethodOptimizationInfo instance to every method that is not being kept
+ * that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class MemberOptimizationInfoSetter
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (!KeepMarker.isKept(programMethod))
+        {
+            MethodOptimizationInfo.setMethodOptimizationInfo(programClass,
+                                                             programMethod);
+        }
+    }
+
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (!KeepMarker.isKept(programField))
+        {
+            FieldOptimizationInfo.setFieldOptimizationInfo(programClass,
+                                                           programField);
+        }
+    }
+}
diff --git a/src/proguard/optimize/info/MethodInvocationMarker.java b/src/proguard/optimize/info/MethodInvocationMarker.java
new file mode 100644
index 0000000..2528c94
--- /dev/null
+++ b/src/proguard/optimize/info/MethodInvocationMarker.java
@@ -0,0 +1,107 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This InstructionVisitor counts the number of times methods are invoked from
+ * the instructions that are visited.
+ *
+ * @author Eric Lafortune
+ */
+public class MethodInvocationMarker
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             MemberVisitor
+{
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Mark the referenced method, if any.
+        stringConstant.referencedMemberAccept(this);
+    }
+
+
+    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        // Mark the referenced method.
+        refConstant.referencedMemberAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz Clazz, Member member) {}
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        incrementInvocationCount(programMethod);
+    }
+
+
+    // Small utility methods.
+
+    private static void incrementInvocationCount(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.incrementInvocationCount();
+        }
+    }
+
+
+    /**
+     * Returns the number of times the given method was invoked by the
+     * instructions that were visited.
+     */
+    public static int getInvocationCount(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info != null ? info.getInvocationCount() :
+                              Integer.MAX_VALUE;
+    }
+}
diff --git a/src/proguard/optimize/info/MethodOptimizationInfo.java b/src/proguard/optimize/info/MethodOptimizationInfo.java
new file mode 100644
index 0000000..d3b1bde
--- /dev/null
+++ b/src/proguard/optimize/info/MethodOptimizationInfo.java
@@ -0,0 +1,302 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.evaluation.value.Value;
+
+/**
+ * This class stores some optimization information that can be attached to
+ * a method.
+ *
+ * @author Eric Lafortune
+ */
+public class MethodOptimizationInfo
+{
+    private boolean hasNoSideEffects      = false;
+    private boolean hasSideEffects        = false;
+    private boolean canBeMadePrivate      = true;
+    private boolean catchesExceptions     = false;
+    private boolean branchesBackward      = false;
+    private boolean invokesSuperMethods   = false;
+    private boolean accessesPrivateCode   = false;
+    private boolean accessesPackageCode   = false;
+    private boolean accessesProtectedCode = false;
+    private int     invocationCount       = 0;
+    private int     parameterSize         = 0;
+    private long    usedParameters        = 0L;
+    private Value[] parameters;
+    private Value   returnValue;
+
+
+    /**
+     * Creates a new MethodOptimizationInfo for the given method.
+     */
+    public MethodOptimizationInfo(Clazz clazz, Method method)
+    {
+        // Set up an array of the right size for storing information about the
+        // passed parameters.
+        int parameterCount =
+            ClassUtil.internalMethodParameterCount(method.getDescriptor(clazz));
+
+        if ((method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) == 0)
+        {
+            parameterCount++;
+        }
+
+        if (parameterCount > 0)
+        {
+            parameters = new Value[parameterCount];
+        }
+    }
+
+
+    public void setNoSideEffects()
+    {
+        hasNoSideEffects = true;
+    }
+
+
+    public boolean hasNoSideEffects()
+    {
+        return hasNoSideEffects;
+    }
+
+
+    public void setSideEffects()
+    {
+        hasSideEffects = true;
+    }
+
+
+    public boolean hasSideEffects()
+    {
+        return hasSideEffects;
+    }
+
+
+    public void setCanNotBeMadePrivate()
+    {
+        canBeMadePrivate = false;
+    }
+
+
+    public boolean canBeMadePrivate()
+    {
+        return canBeMadePrivate;
+    }
+
+
+    public void setCatchesExceptions()
+    {
+        catchesExceptions = true;
+    }
+
+
+    public boolean catchesExceptions()
+    {
+        return catchesExceptions;
+    }
+
+
+    public void setBranchesBackward()
+    {
+        branchesBackward = true;
+    }
+
+
+    public boolean branchesBackward()
+    {
+        return branchesBackward;
+    }
+
+
+    public void setInvokesSuperMethods()
+    {
+        invokesSuperMethods = true;
+    }
+
+
+    public boolean invokesSuperMethods()
+    {
+        return invokesSuperMethods;
+    }
+
+
+    public void setAccessesPrivateCode()
+    {
+        accessesPrivateCode = true;
+    }
+
+
+    public boolean accessesPrivateCode()
+    {
+        return accessesPrivateCode;
+    }
+
+
+    public void setAccessesPackageCode()
+    {
+        accessesPackageCode = true;
+    }
+
+
+    public boolean accessesPackageCode()
+    {
+        return accessesPackageCode;
+    }
+
+
+    public void setAccessesProtectedCode()
+    {
+        accessesProtectedCode = true;
+    }
+
+
+    public boolean accessesProtectedCode()
+    {
+        return accessesProtectedCode;
+    }
+
+
+    public void incrementInvocationCount()
+    {
+        invocationCount++;
+    }
+
+
+    public int getInvocationCount()
+    {
+        return invocationCount;
+    }
+
+
+    public void setParameterSize(int parameterSize)
+    {
+        this.parameterSize = parameterSize;
+    }
+
+
+    public int getParameterSize()
+    {
+        return parameterSize;
+    }
+
+
+    public void setParameterUsed(int parameterIndex)
+    {
+        usedParameters |= 1 << parameterIndex;
+    }
+
+
+    public void setUsedParameters(long usedParameters)
+    {
+        this.usedParameters = usedParameters;
+    }
+
+
+    public boolean isParameterUsed(int parameterIndex)
+    {
+        return parameterIndex >= 64 || (usedParameters & (1 << parameterIndex)) != 0;
+    }
+
+
+    public long getUsedParameters()
+    {
+        return usedParameters;
+    }
+
+
+    public void generalizeParameter(int parameterIndex, Value parameter)
+    {
+        parameters[parameterIndex] = parameters[parameterIndex] != null ?
+            parameters[parameterIndex].generalize(parameter) :
+            parameter;
+    }
+
+
+    public Value getParameter(int parameterIndex)
+    {
+        return parameters != null ?
+            parameters[parameterIndex] :
+            null;
+    }
+
+
+    public void generalizeReturnValue(Value returnValue)
+    {
+        this.returnValue = this.returnValue != null ?
+            this.returnValue.generalize(returnValue) :
+            returnValue;
+    }
+
+
+    public Value getReturnValue()
+    {
+        return returnValue;
+    }
+
+
+    public void merge(MethodOptimizationInfo other)
+    {
+        if (other != null)
+        {
+            this.hasNoSideEffects      &= other.hasNoSideEffects;
+            this.hasSideEffects        |= other.hasSideEffects;
+            //this.canBeMadePrivate    &= other.canBeMadePrivate;
+            this.catchesExceptions     |= other.catchesExceptions;
+            this.branchesBackward      |= other.branchesBackward;
+            this.invokesSuperMethods   |= other.invokesSuperMethods;
+            this.accessesPrivateCode   |= other.accessesPrivateCode;
+            this.accessesPackageCode   |= other.accessesPackageCode;
+            this.accessesProtectedCode |= other.accessesProtectedCode;
+        }
+        else
+        {
+            this.hasNoSideEffects      = false;
+            this.hasSideEffects        = true;
+            //this.canBeMadePrivate    = false;
+            this.catchesExceptions     = true;
+            this.branchesBackward      = true;
+            this.invokesSuperMethods   = true;
+            this.accessesPrivateCode   = true;
+            this.accessesPackageCode   = true;
+            this.accessesProtectedCode = true;
+        }
+    }
+
+
+    public static void setMethodOptimizationInfo(Clazz clazz, Method method)
+    {
+        MethodLinker.lastMember(method).setVisitorInfo(new MethodOptimizationInfo(clazz, method));
+    }
+
+
+    public static MethodOptimizationInfo getMethodOptimizationInfo(Method method)
+    {
+        Object visitorInfo = MethodLinker.lastMember(method).getVisitorInfo();
+
+        return visitorInfo instanceof MethodOptimizationInfo ?
+            (MethodOptimizationInfo)visitorInfo :
+            null;
+    }
+}
diff --git a/src/proguard/optimize/info/NoSideEffectMethodMarker.java b/src/proguard/optimize/info/NoSideEffectMethodMarker.java
new file mode 100644
index 0000000..5c78408
--- /dev/null
+++ b/src/proguard/optimize/info/NoSideEffectMethodMarker.java
@@ -0,0 +1,91 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This MemberVisitor marks all methods that it visits as not having any side
+ * effects. It will make the SideEffectMethodMarker consider them as such
+ * without further analysis.
+ *
+ * @see SideEffectMethodMarker
+ * @author Eric Lafortune
+ */
+public class NoSideEffectMethodMarker
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    // A visitor info flag to indicate the visitor accepter is being kept,
+    // but that it doesn't have any side effects.
+    private static final Object KEPT_BUT_NO_SIDE_EFFECTS = new Object();
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz Clazz, Member member)
+    {
+        // Ignore any attempts to mark fields.
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        markNoSideEffects(programMethod);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        markNoSideEffects(libraryMethod);
+    }
+
+
+    // Small utility methods.
+
+    private static void markNoSideEffects(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setNoSideEffects();
+        }
+        else
+        {
+            MethodLinker.lastMember(method).setVisitorInfo(KEPT_BUT_NO_SIDE_EFFECTS);
+        }
+    }
+
+
+    public static boolean hasNoSideEffects(Method method)
+    {
+        if (MethodLinker.lastVisitorAccepter(method).getVisitorInfo() == KEPT_BUT_NO_SIDE_EFFECTS)
+        {
+            return true;
+        }
+
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info != null &&
+               info.hasNoSideEffects();
+    }
+}
diff --git a/src/proguard/optimize/info/NonPrivateMemberMarker.java b/src/proguard/optimize/info/NonPrivateMemberMarker.java
new file mode 100644
index 0000000..d451643
--- /dev/null
+++ b/src/proguard/optimize/info/NonPrivateMemberMarker.java
@@ -0,0 +1,177 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor marks all class members that can not be made private in the
+ * classes that it visits, and in the classes to which they refer.
+ *
+ * @author Eric Lafortune
+ */
+public class NonPrivateMemberMarker
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             MemberVisitor
+{
+    private final MethodImplementationFilter filteredMethodMarker = new MethodImplementationFilter(this);
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Mark all referenced class members in different classes.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Explicitly mark the <clinit> method.
+        programClass.methodAccept(ClassConstants.INTERNAL_METHOD_NAME_CLINIT,
+                                  ClassConstants.INTERNAL_METHOD_TYPE_CLINIT,
+                                  this);
+
+        // Explicitly mark the parameterless <init> method.
+        programClass.methodAccept(ClassConstants.INTERNAL_METHOD_NAME_INIT,
+                                  ClassConstants.INTERNAL_METHOD_TYPE_INIT,
+                                  this);
+
+        // Mark all methods that may have implementations.
+        programClass.methodsAccept(filteredMethodMarker);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Go over all methods.
+        libraryClass.methodsAccept(this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        Clazz referencedClass = stringConstant.referencedClass;
+
+        // Is it refering to another class or class member?
+        if (referencedClass != null &&
+            !referencedClass.equals(clazz))
+        {
+            // The referenced class member, if any, can never be made private.
+            stringConstant.referencedMemberAccept(this);
+        }
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        Clazz referencedClass = refConstant.referencedClass;
+
+        // Is it refering to a class member in another class?
+        // The class member might be in another class, or
+        // it may be referenced through another class.
+        if (referencedClass != null &&
+            !referencedClass.equals(clazz) ||
+            !refConstant.getClassName(clazz).equals(clazz.getName()))
+        {
+            // The referenced class member can never be made private.
+            refConstant.referencedMemberAccept(this);
+        }
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        markCanNotBeMadePrivate(programField);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        markCanNotBeMadePrivate(libraryField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        markCanNotBeMadePrivate(programMethod);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        markCanNotBeMadePrivate(libraryMethod);
+    }
+
+
+    // Small utility methods.
+
+    private static void markCanNotBeMadePrivate(Field field)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        if (info != null)
+        {
+            info.setCanNotBeMadePrivate();
+        }
+    }
+
+
+    /**
+     * Returns whether the given field can be made private.
+     */
+    public static boolean canBeMadePrivate(Field field)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        return info != null &&
+               info.canBeMadePrivate();
+    }
+
+
+    private static void markCanNotBeMadePrivate(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setCanNotBeMadePrivate();
+        }
+    }
+
+
+    /**
+     * Returns whether the given method can be made private.
+     */
+    public static boolean canBeMadePrivate(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info != null &&
+               info.canBeMadePrivate();
+    }
+}
diff --git a/src/proguard/optimize/info/PackageVisibleMemberContainingClassMarker.java b/src/proguard/optimize/info/PackageVisibleMemberContainingClassMarker.java
new file mode 100644
index 0000000..d40bc6b
--- /dev/null
+++ b/src/proguard/optimize/info/PackageVisibleMemberContainingClassMarker.java
@@ -0,0 +1,67 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This MemberVisitor marks all classes that contain visited package visible
+ * members.
+ *
+ * @author Eric Lafortune
+ */
+public class PackageVisibleMemberContainingClassMarker
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz clazz, Member member)
+    {
+        if ((member.getAccessFlags() &
+             (ClassConstants.INTERNAL_ACC_PRIVATE |
+              ClassConstants.INTERNAL_ACC_PUBLIC)) == 0)
+        {
+            setPackageVisibleMembers(clazz);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private static void setPackageVisibleMembers(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        if (info != null)
+        {
+            info.setContainsPackageVisibleMembers();
+        }
+    }
+
+
+    public static boolean containsPackageVisibleMembers(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        return info == null || info.containsPackageVisibleMembers();
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/PackageVisibleMemberInvokingClassMarker.java b/src/proguard/optimize/info/PackageVisibleMemberInvokingClassMarker.java
new file mode 100644
index 0000000..9ec8ec6
--- /dev/null
+++ b/src/proguard/optimize/info/PackageVisibleMemberInvokingClassMarker.java
@@ -0,0 +1,81 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This ConstantVisitor marks all classes that invoke package visible members
+ * in other classes.
+ *
+ * @author Eric Lafortune
+ */
+public class PackageVisibleMemberInvokingClassMarker
+extends      SimplifiedVisitor
+implements   ConstantVisitor
+{
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        Clazz referencedClass = refConstant.referencedClass;
+        if (referencedClass != null &&
+            (referencedClass.getAccessFlags() &
+             ClassConstants.INTERNAL_ACC_PUBLIC) == 0)
+        {
+            setInvokesPackageVisibleMembers(clazz);
+        }
+
+        Member referencedMember = refConstant.referencedMember;
+        if (referencedMember != null &&
+            (referencedMember.getAccessFlags() &
+             (ClassConstants.INTERNAL_ACC_PUBLIC |
+              ClassConstants.INTERNAL_ACC_PRIVATE)) == 0)
+        {
+            setInvokesPackageVisibleMembers(clazz);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private static void setInvokesPackageVisibleMembers(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        if (info != null)
+        {
+            info.setInvokesPackageVisibleMembers();
+        }
+    }
+
+
+    public static boolean invokesPackageVisibleMembers(Clazz clazz)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        return info == null || info.invokesPackageVisibleMembers();
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/info/ParameterUsageMarker.java b/src/proguard/optimize/info/ParameterUsageMarker.java
new file mode 100644
index 0000000..15ce88a
--- /dev/null
+++ b/src/proguard/optimize/info/ParameterUsageMarker.java
@@ -0,0 +1,285 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.attribute.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.evaluation.PartialEvaluator;
+import proguard.evaluation.value.*;
+
+/**
+ * This MemberVisitor counts the parameters and marks the used parameters
+ * of the methods that it visits. It also marks the 'this' parameters of
+ * methods that have hierarchies.
+ *
+ * @author Eric Lafortune
+ */
+public class ParameterUsageMarker
+extends      SimplifiedVisitor
+implements   MemberVisitor,
+             AttributeVisitor,
+             InstructionVisitor
+{
+    private static final boolean DEBUG = false;
+
+
+    private final boolean          markThisParameter;
+    private final boolean          markAllParameters;
+    private final PartialEvaluator partialEvaluator = new PartialEvaluator();
+
+
+    /**
+     * Creates a new ParameterUsageMarker.
+     */
+    public ParameterUsageMarker()
+    {
+        this(false, false);
+    }
+
+
+    /**
+     * Creates a new ParameterUsageMarker that optionally marks all parameters.
+     * @param markThisParameter specifies whether all 'this' parameters should
+     *                          be marked as being used.
+     * @param markAllParameters specifies whether all other parameters should
+     *                          be marked as being used.
+     */
+    public ParameterUsageMarker(boolean markThisParameter,
+                                boolean markAllParameters)
+    {
+        this.markThisParameter = markThisParameter;
+        this.markAllParameters = markAllParameters;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        int parameterSize =
+            ClassUtil.internalMethodParameterSize(programMethod.getDescriptor(programClass),
+                                                  programMethod.getAccessFlags());
+
+        if (parameterSize > 0)
+        {
+            int accessFlags = programMethod.getAccessFlags();
+
+            // Must we mark the 'this' parameter?
+            if (markThisParameter &&
+                (accessFlags & ClassConstants.INTERNAL_ACC_STATIC) == 0)
+            {
+                // Mark the 'this' parameter.
+                markParameterUsed(programMethod, 0);
+            }
+
+            // Must we mark all other parameters?
+            if (markAllParameters)
+            {
+                // Mark all parameters, without the 'this' parameter.
+                markUsedParameters(programMethod,
+                                   (accessFlags & ClassConstants.INTERNAL_ACC_STATIC) != 0 ?
+                                       -1L : -2L);
+            }
+
+            // Is it a native method?
+            if ((accessFlags & ClassConstants.INTERNAL_ACC_NATIVE) != 0)
+            {
+                // Mark all parameters.
+                markUsedParameters(programMethod, -1L);
+            }
+
+            // Is it an abstract method?
+            else if ((accessFlags & ClassConstants.INTERNAL_ACC_ABSTRACT) != 0)
+            {
+                // Mark the 'this' parameter.
+                markParameterUsed(programMethod, 0);
+            }
+
+            // Is it a non-native, concrete method?
+            else
+            {
+                // Is the method not static, but synchronized, or can it have
+                // other implementations, or is it a class instance initializer?
+                if ((accessFlags & ClassConstants.INTERNAL_ACC_STATIC) == 0 &&
+                    ((accessFlags & ClassConstants.INTERNAL_ACC_SYNCHRONIZED) != 0 ||
+                     programClass.mayHaveImplementations(programMethod)            ||
+                     programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)))
+                {
+                    // Mark the 'this' parameter.
+                    markParameterUsed(programMethod, 0);
+                }
+
+                // Mark the parameters that are used by the code.
+                programMethod.attributesAccept(programClass, this);
+            }
+
+            if (DEBUG)
+            {
+                System.out.print("ParameterUsageMarker: ["+programClass.getName() +"."+programMethod.getName(programClass)+programMethod.getDescriptor(programClass)+"]: ");
+                for (int index = 0; index < parameterSize; index++)
+                {
+                    System.out.print(isParameterUsed(programMethod, index) ? '+' : '-');
+                }
+                System.out.println();
+            }
+
+        }
+
+        // Set the parameter size.
+        setParameterSize(programMethod, parameterSize);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        // Can the method have other implementations?
+        if (libraryClass.mayHaveImplementations(libraryMethod))
+        {
+            // All implementations must keep all parameters of this method,
+            // including the 'this' parameter.
+            markUsedParameters(libraryMethod, -1L);
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Evaluate the code.
+        partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Mark the parameters that are used by the code.
+        codeAttribute.instructionsAccept(clazz, method, this);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        if (partialEvaluator.isTraced(offset) &&
+            variableInstruction.isLoad())
+        {
+            int parameterIndex = variableInstruction.variableIndex;
+            if (parameterIndex < codeAttribute.u2maxLocals)
+            {
+                Value producer =
+                    partialEvaluator.getVariablesBefore(offset).getProducerValue(parameterIndex);
+                if (producer != null &&
+                    producer.instructionOffsetValue().contains(PartialEvaluator.AT_METHOD_ENTRY))
+                {
+                    // Mark the variable.
+                    markParameterUsed(method, parameterIndex);
+
+                    // Account for Category 2 instructions, which take up two entries.
+                    if (variableInstruction.isCategory2())
+                    {
+                        markParameterUsed(method, parameterIndex + 1);
+                    }
+                }
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Sets the total size of the parameters.
+     */
+    private static void setParameterSize(Method method, int parameterSize)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setParameterSize(parameterSize);
+        }
+    }
+
+
+    /**
+     * Returns the total size of the parameters.
+     */
+    public static int getParameterSize(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info != null ? info.getParameterSize() : 0;
+    }
+
+
+    /**
+     * Marks the given parameter as being used.
+     */
+    public static void markParameterUsed(Method method, int variableIndex)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setParameterUsed(variableIndex);
+        }
+    }
+
+
+    /**
+     * Marks the given parameters as being used.
+     */
+    public static void markUsedParameters(Method method, long usedParameters)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setUsedParameters(info.getUsedParameters() | usedParameters);
+        }
+    }
+
+
+    /**
+     * Returns whether the given parameter is being used.
+     */
+    public static boolean isParameterUsed(Method method, int variableIndex)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info == null ||
+               info.isParameterUsed(variableIndex);
+    }
+
+
+    /**
+     * Returns which parameters are being used.
+     */
+    public static long getUsedParameters(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info != null ? info.getUsedParameters() : -1L;
+    }
+}
diff --git a/src/proguard/optimize/info/ReadWriteFieldMarker.java b/src/proguard/optimize/info/ReadWriteFieldMarker.java
new file mode 100644
index 0000000..57d8561
--- /dev/null
+++ b/src/proguard/optimize/info/ReadWriteFieldMarker.java
@@ -0,0 +1,163 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This InstructionVisitor marks all fields that are write-only.
+ *
+ * @author Eric Lafortune
+ */
+public class ReadWriteFieldMarker
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             MemberVisitor
+{
+    // Parameters for the visitor methods.
+    private boolean reading = true;
+    private boolean writing = true;
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        byte opcode = constantInstruction.opcode;
+
+        // Check for instructions that involve fields.
+        switch (opcode)
+        {
+            case InstructionConstants.OP_LDC:
+            case InstructionConstants.OP_LDC_W:
+                // Mark the field, if any, as being read from and written to.
+                reading = true;
+                writing = true;
+                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+                break;
+
+            case InstructionConstants.OP_GETSTATIC:
+            case InstructionConstants.OP_GETFIELD:
+                // Mark the field as being read from.
+                reading = true;
+                writing = false;
+                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+                break;
+
+            case InstructionConstants.OP_PUTSTATIC:
+            case InstructionConstants.OP_PUTFIELD:
+                // Mark the field as being written to.
+                reading = false;
+                writing = true;
+                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+                break;
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Mark the referenced field, if any.
+        stringConstant.referencedMemberAccept(this);
+    }
+
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        // Mark the referenced field.
+        fieldrefConstant.referencedMemberAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz Clazz, Member member) {}
+
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Mark the field if it is being read from.
+        if (reading)
+        {
+            markAsRead(programField);
+        }
+
+        // Mark the field if it is being written to.
+        if (writing)
+        {
+            markAsWritten(programField);
+        }
+    }
+
+
+    // Small utility methods.
+
+    private static void markAsRead(Field field)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        if (info != null)
+        {
+            info.setRead();
+        }
+    }
+
+
+    public static boolean isRead(Field field)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        return info == null ||
+               info.isRead();
+    }
+
+
+    private static void markAsWritten(Field field)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        if (info != null)
+        {
+            info.setWritten();
+        }
+    }
+
+
+    public static boolean isWritten(Field field)
+    {
+        FieldOptimizationInfo info = FieldOptimizationInfo.getFieldOptimizationInfo(field);
+        return info == null ||
+               info.isWritten();
+    }
+}
diff --git a/src/proguard/optimize/info/SideEffectInstructionChecker.java b/src/proguard/optimize/info/SideEffectInstructionChecker.java
new file mode 100644
index 0000000..8be9dc1
--- /dev/null
+++ b/src/proguard/optimize/info/SideEffectInstructionChecker.java
@@ -0,0 +1,224 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This class can tell whether an instruction has any side effects. Return
+ * instructions can be included or not.
+ *
+ * @see ReadWriteFieldMarker
+ * @see NoSideEffectMethodMarker
+ * @see SideEffectMethodMarker
+ * @author Eric Lafortune
+ */
+public class SideEffectInstructionChecker
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor,
+             MemberVisitor
+{
+    private final boolean includeReturnInstructions;
+
+    // A return value for the visitor methods.
+    private boolean hasSideEffects;
+
+
+    public SideEffectInstructionChecker(boolean includeReturnInstructions)
+    {
+        this.includeReturnInstructions = includeReturnInstructions;
+    }
+
+
+    public boolean hasSideEffects(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        hasSideEffects = false;
+
+        instruction.accept(clazz, method,  codeAttribute, offset, this);
+
+        return hasSideEffects;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        byte opcode = simpleInstruction.opcode;
+
+        // Check for instructions that might cause side effects.
+        if (opcode == InstructionConstants.OP_IASTORE      ||
+            opcode == InstructionConstants.OP_LASTORE      ||
+            opcode == InstructionConstants.OP_FASTORE      ||
+            opcode == InstructionConstants.OP_DASTORE      ||
+            opcode == InstructionConstants.OP_AASTORE      ||
+            opcode == InstructionConstants.OP_BASTORE      ||
+            opcode == InstructionConstants.OP_CASTORE      ||
+            opcode == InstructionConstants.OP_SASTORE      ||
+            opcode == InstructionConstants.OP_ATHROW       ||
+            opcode == InstructionConstants.OP_MONITORENTER ||
+            opcode == InstructionConstants.OP_MONITOREXIT  ||
+            (includeReturnInstructions &&
+             (opcode == InstructionConstants.OP_IRETURN ||
+              opcode == InstructionConstants.OP_LRETURN ||
+              opcode == InstructionConstants.OP_FRETURN ||
+              opcode == InstructionConstants.OP_DRETURN ||
+              opcode == InstructionConstants.OP_ARETURN ||
+              opcode == InstructionConstants.OP_RETURN)))
+        {
+            // These instructions always cause a side effect.
+            hasSideEffects = true;
+        }
+
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        byte opcode = variableInstruction.opcode;
+
+        // Check for instructions that might cause side effects.
+        if (includeReturnInstructions &&
+            opcode == InstructionConstants.OP_RET)
+        {
+            hasSideEffects = true;
+        }
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        byte opcode = constantInstruction.opcode;
+
+        // Check for instructions that might cause side effects.
+        if (opcode == InstructionConstants.OP_PUTSTATIC     ||
+            opcode == InstructionConstants.OP_PUTFIELD      ||
+            opcode == InstructionConstants.OP_INVOKEVIRTUAL ||
+            opcode == InstructionConstants.OP_INVOKESPECIAL ||
+            opcode == InstructionConstants.OP_INVOKESTATIC  ||
+            opcode == InstructionConstants.OP_INVOKEINTERFACE)
+        {
+            // Check if the field is write-only or volatile, or if the invoked
+            // method is causing any side effects.
+            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+        }
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        byte opcode = branchInstruction.opcode;
+
+        // Check for instructions that might cause side effects.
+        if (includeReturnInstructions &&
+            (opcode == InstructionConstants.OP_JSR ||
+             opcode == InstructionConstants.OP_JSR_W))
+        {
+            hasSideEffects = true;
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant)
+    {
+        // We'll have to assume accessing an unknown field has side effects.
+        hasSideEffects = true;
+
+        // Check the referenced field.
+        fieldrefConstant.referencedMemberAccept(this);
+    }
+
+
+    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        Member referencedMember = refConstant.referencedMember;
+
+        // Do we have a reference to the method?
+        if (referencedMember == null)
+        {
+            // We'll have to assume invoking the unknown method has side effects.
+            hasSideEffects = true;
+        }
+        else
+        {
+            // First check the referenced method itself.
+            refConstant.referencedMemberAccept(this);
+
+            // If the result isn't conclusive, check down the hierarchy.
+            if (!hasSideEffects)
+            {
+                Clazz  referencedClass  = refConstant.referencedClass;
+                Method referencedMethod = (Method)referencedMember;
+
+                // Check all other implementations of the method down the class
+                // hierarchy.
+                if ((referencedMethod.getAccessFlags() & ClassConstants.INTERNAL_ACC_PRIVATE) == 0)
+                {
+                    clazz.hierarchyAccept(false, false, false, true,
+                                          new NamedMethodVisitor(referencedMethod.getName(referencedClass),
+                                                                 referencedMethod.getDescriptor(referencedClass),
+                                          this));
+                }
+            }
+        }
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        hasSideEffects = ReadWriteFieldMarker.isRead(programField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        hasSideEffects = hasSideEffects ||
+                         SideEffectMethodMarker.hasSideEffects(programMethod);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        hasSideEffects = true;
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        hasSideEffects = hasSideEffects ||
+                         !NoSideEffectMethodMarker.hasNoSideEffects(libraryMethod);
+    }
+}
diff --git a/src/proguard/optimize/info/SideEffectMethodMarker.java b/src/proguard/optimize/info/SideEffectMethodMarker.java
new file mode 100644
index 0000000..25fda72
--- /dev/null
+++ b/src/proguard/optimize/info/SideEffectMethodMarker.java
@@ -0,0 +1,175 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassPoolVisitor marks all methods that have side effects.
+ *
+ * @see ReadWriteFieldMarker
+ * @see NoSideEffectMethodMarker
+ * @author Eric Lafortune
+ */
+public class SideEffectMethodMarker
+extends      SimplifiedVisitor
+implements   ClassPoolVisitor,
+             ClassVisitor,
+             MemberVisitor,
+             AttributeVisitor
+{
+    // A reusable object for checking whether instructions have side effects.
+    private final SideEffectInstructionChecker sideEffectInstructionChecker = new SideEffectInstructionChecker(false);
+
+    // Parameters and values for visitor methods.
+    private int     newSideEffectCount;
+    private boolean hasSideEffects;
+
+
+    // Implementations for ClassPoolVisitor.
+
+    public void visitClassPool(ClassPool classPool)
+    {
+        // Go over all classes and their methods, marking if they have side
+        // effects, until no new cases can be found.
+        do
+        {
+            newSideEffectCount = 0;
+
+            // Go over all classes and their methods once.
+            classPool.classesAccept(this);
+        }
+        while (newSideEffectCount > 0);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Go over all methods.
+        programClass.methodsAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (!hasSideEffects(programMethod) &&
+            !NoSideEffectMethodMarker.hasNoSideEffects(programMethod))
+        {
+            // Initialize the return value.
+            hasSideEffects =
+                (programMethod.getAccessFlags() &
+                 (ClassConstants.INTERNAL_ACC_NATIVE |
+                  ClassConstants.INTERNAL_ACC_SYNCHRONIZED)) != 0;
+
+            // Look further if the method hasn't been marked yet.
+            if (!hasSideEffects)
+            {
+                // Investigate the actual code.
+                programMethod.attributesAccept(programClass, this);
+            }
+
+            // Mark the method depending on the return value.
+            if (hasSideEffects)
+            {
+                markSideEffects(programMethod);
+
+                newSideEffectCount++;
+            }
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Remember whether the code has any side effects.
+        hasSideEffects = hasSideEffects(clazz, method, codeAttribute);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the given code has any side effects.
+     */
+    private boolean hasSideEffects(Clazz         clazz,
+                                   Method        method,
+                                   CodeAttribute codeAttribute)
+    {
+        byte[] code   = codeAttribute.code;
+        int    length = codeAttribute.u4codeLength;
+
+        // Go over all instructions.
+        int offset = 0;
+        do
+        {
+            // Get the current instruction.
+            Instruction instruction = InstructionFactory.create(code, offset);
+
+            // Check if it may be throwing exceptions.
+            if (sideEffectInstructionChecker.hasSideEffects(clazz,
+                                                            method,
+                                                            codeAttribute,
+                                                            offset,
+                                                            instruction))
+            {
+                return true;
+            }
+
+            // Go to the next instruction.
+            offset += instruction.length(offset);
+        }
+        while (offset < length);
+
+        return false;
+    }
+
+
+    private static void markSideEffects(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setSideEffects();
+        }
+    }
+
+
+    public static boolean hasSideEffects(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info == null ||
+               info.hasSideEffects();
+    }
+}
diff --git a/src/proguard/optimize/info/SuperInvocationMarker.java b/src/proguard/optimize/info/SuperInvocationMarker.java
new file mode 100644
index 0000000..6f3d3bd
--- /dev/null
+++ b/src/proguard/optimize/info/SuperInvocationMarker.java
@@ -0,0 +1,93 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.RefConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor marks all methods that invoke super methods (other
+ * than initializers) from the instructions that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class SuperInvocationMarker
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor
+{
+    private boolean invokesSuperMethods;
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        if (constantInstruction.opcode == InstructionConstants.OP_INVOKESPECIAL)
+        {
+            invokesSuperMethods = false;
+
+            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+
+            if (invokesSuperMethods)
+            {
+                setInvokesSuperMethods(method);
+            }
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyMethodrefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        invokesSuperMethods =
+            !clazz.equals(refConstant.referencedClass) &&
+            !refConstant.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT);
+    }
+
+
+    // Small utility methods.
+
+    private static void setInvokesSuperMethods(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        if (info != null)
+        {
+            info.setInvokesSuperMethods();
+        }
+    }
+
+
+    public static boolean invokesSuperMethods(Method method)
+    {
+        MethodOptimizationInfo info = MethodOptimizationInfo.getMethodOptimizationInfo(method);
+        return info == null || info.invokesSuperMethods();
+    }
+}
diff --git a/src/proguard/optimize/info/VariableUsageMarker.java b/src/proguard/optimize/info/VariableUsageMarker.java
new file mode 100644
index 0000000..660c4ba
--- /dev/null
+++ b/src/proguard/optimize/info/VariableUsageMarker.java
@@ -0,0 +1,95 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.info;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor marks the local variables that are used in the code
+ * attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableUsageMarker
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor
+{
+    private boolean[] variableUsed = new boolean[ClassConstants.TYPICAL_VARIABLES_SIZE];
+
+
+    /**
+     * Returns whether the given variable has been marked as being used.
+     */
+    public boolean isVariableUsed(int variableIndex)
+    {
+        return variableUsed[variableIndex];
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        int maxLocals = codeAttribute.u2maxLocals;
+
+        // Try to reuse the previous array.
+        if (variableUsed.length < maxLocals)
+        {
+            variableUsed = new boolean[maxLocals];
+        }
+        else
+        {
+            for (int index = 0; index < maxLocals; index++)
+            {
+                variableUsed[index] = false;
+            }
+        }
+
+        codeAttribute.instructionsAccept(clazz, method, this);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        // Mark the variable.
+        variableUsed[variableInstruction.variableIndex] = true;
+
+        // Account for Category 2 instructions, which take up two entries.
+        if (variableInstruction.isCategory2())
+        {
+            variableUsed[variableInstruction.variableIndex + 1] = true;
+        }
+    }
+}
diff --git a/src/proguard/optimize/info/package.html b/src/proguard/optimize/info/package.html
new file mode 100644
index 0000000..d16486e
--- /dev/null
+++ b/src/proguard/optimize/info/package.html
@@ -0,0 +1,4 @@
+<body>
+This package contains classes to collect additional information about classes
+and class members, which can then be used for optimization.
+</body>
diff --git a/src/proguard/optimize/package.html b/src/proguard/optimize/package.html
new file mode 100644
index 0000000..3ee1353
--- /dev/null
+++ b/src/proguard/optimize/package.html
@@ -0,0 +1,4 @@
+<body>
+This package contains visitors that assist with various optimizations of byte
+code.
+</body>
diff --git a/src/proguard/optimize/peephole/BranchTargetFinder.java b/src/proguard/optimize/peephole/BranchTargetFinder.java
new file mode 100644
index 0000000..8f650bb
--- /dev/null
+++ b/src/proguard/optimize/peephole/BranchTargetFinder.java
@@ -0,0 +1,691 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor finds all instruction offsets, branch targets, and
+ * exception targets in the CodeAttribute objects that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class BranchTargetFinder
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ExceptionInfoVisitor,
+             ConstantVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+    public static final int NONE            = -2;
+    public static final int AT_METHOD_ENTRY = -1;
+
+    private static final short INSTRUCTION           = 1 << 0;
+    private static final short BRANCH_ORIGIN         = 1 << 1;
+    private static final short BRANCH_TARGET         = 1 << 2;
+    private static final short AFTER_BRANCH          = 1 << 3;
+    private static final short EXCEPTION_START       = 1 << 4;
+    private static final short EXCEPTION_END         = 1 << 5;
+    private static final short EXCEPTION_HANDLER     = 1 << 6;
+    private static final short SUBROUTINE_INVOCATION = 1 << 7;
+    private static final short SUBROUTINE_RETURNING  = 1 << 8;
+
+    private static final int MAXIMUM_CREATION_OFFSETS = 32;
+
+
+    private short[] instructionMarks      = new short[ClassConstants.TYPICAL_CODE_LENGTH + 1];
+    private int[]   subroutineStarts      = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int[]   subroutineEnds        = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int[]   creationOffsets       = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int[]   initializationOffsets = new int[ClassConstants.TYPICAL_CODE_LENGTH];
+    private int     superInitializationOffset;
+
+    private int     currentSubroutineStart;
+    private int     currentSubroutineEnd;
+    private int[]   recentCreationOffsets = new int[MAXIMUM_CREATION_OFFSETS];
+    private int     recentCreationOffsetIndex;
+    private boolean isInitializer;
+
+
+    /**
+     * Returns whether there is an instruction at the given offset in the
+     * CodeAttribute that was visited most recently.
+     */
+    public boolean isInstruction(int offset)
+    {
+        return (instructionMarks[offset] & INSTRUCTION) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the target of
+     * any kind in the CodeAttribute that was visited most recently.
+     */
+    public boolean isTarget(int offset)
+    {
+        return offset == 0 ||
+               (instructionMarks[offset] & (BRANCH_TARGET   |
+                                            EXCEPTION_START |
+                                            EXCEPTION_END   |
+                                            EXCEPTION_HANDLER)) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the origin of a
+     * branch instruction in the CodeAttribute that was visited most recently.
+     */
+    public boolean isBranchOrigin(int offset)
+    {
+        return (instructionMarks[offset] & BRANCH_ORIGIN) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the target of a
+     * branch instruction in the CodeAttribute that was visited most recently.
+     */
+    public boolean isBranchTarget(int offset)
+    {
+        return (instructionMarks[offset] & BRANCH_TARGET) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset comes right after a
+     * definite branch instruction in the CodeAttribute that was visited most
+     * recently.
+     */
+    public boolean isAfterBranch(int offset)
+    {
+        return (instructionMarks[offset] & AFTER_BRANCH) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the start of an
+     * exception try block in the CodeAttribute that was visited most recently.
+     */
+    public boolean isExceptionStart(int offset)
+    {
+        return (instructionMarks[offset] & EXCEPTION_START) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the end of an
+     * exception try block in the CodeAttribute that was visited most recently.
+     */
+    public boolean isExceptionEnd(int offset)
+    {
+        return (instructionMarks[offset] & EXCEPTION_END) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the start of an
+     * exception catch block in the CodeAttribute that was visited most recently.
+     */
+    public boolean isExceptionHandler(int offset)
+    {
+        return (instructionMarks[offset] & EXCEPTION_HANDLER) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is a subroutine
+     * invocation in the CodeAttribute that was visited most recently.
+     */
+    public boolean isSubroutineInvocation(int offset)
+    {
+        return (instructionMarks[offset] & SUBROUTINE_INVOCATION) != 0;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the start of a
+     * subroutine in the CodeAttribute that was visited most recently.
+     */
+    public boolean isSubroutineStart(int offset)
+    {
+        return subroutineStarts[offset] == offset;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is part of a
+     * subroutine in the CodeAttribute that was visited most recently.
+     */
+    public boolean isSubroutine(int offset)
+    {
+        return subroutineStarts[offset] != NONE;
+    }
+
+
+    /**
+     * Returns whether the subroutine at the given offset is ever returning
+     * by means of a regular 'ret' instruction.
+     */
+    public boolean isSubroutineReturning(int offset)
+    {
+        return (instructionMarks[offset] & SUBROUTINE_RETURNING) != 0;
+    }
+
+
+    /**
+     * Returns the start offset of the subroutine at the given offset, in the
+     * CodeAttribute that was visited most recently.
+     */
+    public int subroutineStart(int offset)
+    {
+        return subroutineStarts[offset];
+    }
+
+
+    /**
+     * Returns the offset after the subroutine at the given offset, in the
+     * CodeAttribute that was visited most recently.
+     */
+    public int subroutineEnd(int offset)
+    {
+        return subroutineEnds[offset];
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is a 'new'
+     * instruction, in the CodeAttribute that was visited most recently.
+     */
+    public boolean isNew(int offset)
+    {
+        return initializationOffsets[offset] != NONE;
+    }
+
+
+    /**
+     * Returns the instruction offset at which the object instance that is
+     * created at the given 'new' instruction offset is initialized, or
+     * <code>NONE</code> if it is not being created.
+     */
+    public int initializationOffset(int offset)
+    {
+        return initializationOffsets[offset];
+    }
+
+
+    /**
+     * Returns whether the method is an instance initializer, in the
+     * CodeAttribute that was visited most recently.
+     */
+    public boolean isInitializer()
+    {
+        return superInitializationOffset != NONE;
+    }
+
+
+    /**
+     * Returns the instruction offset at which this initializer is calling
+     * the "super" or "this" initializer method, or <code>NONE</code> if it is
+     * not an initializer.
+     */
+    public int superInitializationOffset()
+    {
+        return superInitializationOffset;
+    }
+
+
+    /**
+     * Returns whether the instruction at the given offset is the special
+     * invocation of an instance initializer, in the CodeAttribute that was
+     * visited most recently.
+     */
+    public boolean isInitializer(int offset)
+    {
+        return creationOffsets[offset] != NONE;
+    }
+
+
+    /**
+     * Returns the offset of the 'new' instruction that corresponds to the
+     * invocation of the instance initializer at the given offset, or
+     * <code>AT_METHOD_ENTRY</code> if the invocation is calling the "super" or
+     * "this" initializer method, , or <code>NONE</code> if it is not a 'new'
+     * instruction.
+     */
+    public int creationOffset(int offset)
+    {
+        return creationOffsets[offset];
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // Make sure there are sufficiently large arrays.
+        int codeLength = codeAttribute.u4codeLength;
+        if (subroutineStarts.length < codeLength)
+        {
+            // Create new arrays.
+            instructionMarks      = new short[codeLength + 1];
+            subroutineStarts      = new int[codeLength];
+            subroutineEnds        = new int[codeLength];
+            creationOffsets       = new int[codeLength];
+            initializationOffsets = new int[codeLength];
+
+            // Reset the arrays.
+            for (int index = 0; index < codeLength; index++)
+            {
+                subroutineStarts[index]      = NONE;
+                subroutineEnds[index]        = NONE;
+                creationOffsets[index]       = NONE;
+                initializationOffsets[index] = NONE;
+            }
+        }
+        else
+        {
+            // Reset the arrays.
+            for (int index = 0; index < codeLength; index++)
+            {
+                instructionMarks[index]      = 0;
+                subroutineStarts[index]      = NONE;
+                subroutineEnds[index]        = NONE;
+                creationOffsets[index]       = NONE;
+                initializationOffsets[index] = NONE;
+            }
+
+            instructionMarks[codeLength] = 0;
+        }
+
+        superInitializationOffset = NONE;
+
+        // We're assuming all subroutines are contiguous blocks of code.
+        // We're not starting in a subroutine.
+        currentSubroutineStart = NONE;
+        currentSubroutineEnd   = NONE;
+
+        recentCreationOffsetIndex = 0;
+
+        // Initialize the stack of 'new' instruction offsets if this method is
+        // an instance initializer.
+        if (method.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            recentCreationOffsets[recentCreationOffsetIndex++] = AT_METHOD_ENTRY;
+        }
+
+        // The end of the code is a branch target sentinel.
+        instructionMarks[codeLength] = BRANCH_TARGET;
+
+        // Mark branch targets by going over all instructions.
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        // Mark branch targets in the exception table.
+        codeAttribute.exceptionsAccept(clazz, method, this);
+
+        // Fill out any gaps in the subroutine starts and the subroutine ends
+        // and subroutine returning flags, working backward.
+
+        // We're not starting in a subroutine.
+        int     subroutineStart     = NONE;
+        int     subroutineEnd       = codeLength;
+        boolean subroutineReturning = false;
+
+        for (int index = codeLength - 1; index >= 0; index--)
+        {
+            if (isInstruction(index))
+            {
+                // Are we inside a previously marked subroutine?
+                if (subroutineStarts[index] != NONE)
+                {
+                    // Update the current subroutine start.
+                    subroutineStart = subroutineStarts[index];
+                }
+                else if (subroutineStart != NONE)
+                {
+                    // Mark the subroutine start.
+                    subroutineStarts[index] = subroutineStart;
+                }
+
+                // Did we reach the start of the subroutine.
+                if (isSubroutineStart(index))
+                {
+                    // Stop marking it.
+                    subroutineStart = NONE;
+                }
+
+                // Are we inside a subroutine?
+                if (isSubroutine(index))
+                {
+                    // Mark the subroutine end.
+                    subroutineEnds[index] = subroutineEnd;
+
+                    // Update or mark the subroutine returning flag.
+                    if (isSubroutineReturning(index))
+                    {
+                        subroutineReturning = true;
+                    }
+                    else if (subroutineReturning)
+                    {
+                        instructionMarks[index] |= SUBROUTINE_RETURNING;
+                    }
+                }
+                else
+                {
+                    // Update the subroutine end and returning flag.
+                    subroutineEnd       = index;
+                    subroutineReturning = false;
+                }
+            }
+        }
+
+        if (DEBUG)
+        {
+            System.out.println();
+            System.out.println("Branch targets: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
+
+            for (int index = 0; index < codeLength; index++)
+            {
+                if (isInstruction(index))
+                {
+                    System.out.println("" +
+                                       (isBranchOrigin(index)         ? 'B' : '-') +
+                                       (isAfterBranch(index)          ? 'b' : '-') +
+                                       (isBranchTarget(index)         ? 'T' : '-') +
+                                       (isExceptionStart(index)       ? 'E' : '-') +
+                                       (isExceptionEnd(index)         ? 'e' : '-') +
+                                       (isExceptionHandler(index)     ? 'H' : '-') +
+                                       (isSubroutineInvocation(index) ? 'J' : '-') +
+                                       (isSubroutineStart(index)      ? 'S' : '-') +
+                                       (isSubroutineReturning(index)  ? 'r' : '-') +
+                                       (isSubroutine(index)           ? " ["+subroutineStart(index)+" -> "+subroutineEnd(index)+"]" : "") +
+                                       (isNew(index)                  ? " ["+initializationOffset(index)+"] " : " ---- ") +
+                                       InstructionFactory.create(codeAttribute.code, index).toString(index));
+                }
+            }
+        }
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        // Mark the instruction.
+        instructionMarks[offset] |= INSTRUCTION;
+
+        // Check if this is the first instruction of a subroutine.
+        checkSubroutine(offset);
+
+        byte opcode = simpleInstruction.opcode;
+        if (opcode == InstructionConstants.OP_IRETURN ||
+            opcode == InstructionConstants.OP_LRETURN ||
+            opcode == InstructionConstants.OP_FRETURN ||
+            opcode == InstructionConstants.OP_DRETURN ||
+            opcode == InstructionConstants.OP_ARETURN ||
+            opcode == InstructionConstants.OP_ATHROW)
+        {
+            // Mark the branch origin.
+            markBranchOrigin(offset);
+
+            // Mark the next instruction.
+            markAfterBranchOrigin(offset + simpleInstruction.length(offset));
+        }
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        // Mark the instruction.
+        instructionMarks[offset] |= INSTRUCTION;
+
+        // Check if this is the first instruction of a subroutine.
+        checkSubroutine(offset);
+
+        // Check if the instruction is a 'new' instruction.
+        if (constantInstruction.opcode == InstructionConstants.OP_NEW)
+        {
+            // Push the 'new' instruction offset on the stack.
+            recentCreationOffsets[recentCreationOffsetIndex++] = offset;
+        }
+        else
+        {
+            // Check if the instruction is an initializer invocation.
+            isInitializer = false;
+            clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+            if (isInitializer)
+            {
+                // Pop the 'new' instruction offset from the stack.
+                int recentCreationOffset = recentCreationOffsets[--recentCreationOffsetIndex];
+
+                // Fill it out in the creation offsets.
+                creationOffsets[offset] = recentCreationOffset;
+
+                // Fill out the initialization offsets.
+                if (recentCreationOffset == AT_METHOD_ENTRY)
+                {
+                    superInitializationOffset = offset;
+                }
+                else
+                {
+                    initializationOffsets[recentCreationOffset] = offset;
+                }
+            }
+        }
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        // Mark the instruction.
+        instructionMarks[offset] |= INSTRUCTION;
+
+        // Check if this is the first instruction of a subroutine.
+        checkSubroutine(offset);
+
+        if (variableInstruction.opcode == InstructionConstants.OP_RET)
+        {
+            // Mark the branch origin.
+            markBranchOrigin(offset);
+
+            // Mark the regular subroutine return.
+            instructionMarks[offset] |= SUBROUTINE_RETURNING;
+
+            // Mark the next instruction.
+            markAfterBranchOrigin(offset + variableInstruction.length(offset));
+        }
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        // Mark the branch origin.
+        markBranchOrigin(offset);
+
+        // Check if this is the first instruction of a subroutine.
+        checkSubroutine(offset);
+
+        // Mark the branch target.
+        markBranchTarget(offset, branchInstruction.branchOffset);
+
+        byte opcode = branchInstruction.opcode;
+        if (opcode == InstructionConstants.OP_JSR ||
+            opcode == InstructionConstants.OP_JSR_W)
+        {
+            // Mark the subroutine invocation.
+            instructionMarks[offset] |= SUBROUTINE_INVOCATION;
+
+            // Mark the subroutine start.
+            int targetOffset = offset + branchInstruction.branchOffset;
+            subroutineStarts[targetOffset] = targetOffset;
+        }
+        else if (opcode == InstructionConstants.OP_GOTO ||
+                 opcode == InstructionConstants.OP_GOTO_W)
+        {
+            // Mark the next instruction.
+            markAfterBranchOrigin(offset + branchInstruction.length(offset));
+        }
+    }
+
+
+    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
+    {
+        // Mark the branch origin.
+        markBranchOrigin(offset);
+
+        // Check if this is the first instruction of a subroutine.
+        checkSubroutine(offset);
+
+        // Mark the branch targets of the default jump offset.
+        markBranchTarget(offset, switchInstruction.defaultOffset);
+
+        // Mark the branch targets of the jump offsets.
+        markBranchTargets(offset,
+                          switchInstruction.jumpOffsets);
+
+        // Mark the next instruction.
+        markAfterBranchOrigin(offset + switchInstruction.length(offset));
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        isInitializer = methodrefConstant.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT);
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        // Mark the exception offsets.
+        instructionMarks[exceptionInfo.u2startPC]   |= EXCEPTION_START;
+        instructionMarks[exceptionInfo.u2endPC]     |= EXCEPTION_END;
+        instructionMarks[exceptionInfo.u2handlerPC] |= EXCEPTION_HANDLER;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the branch targets of the given jump offsets for the instruction
+     * at the given offset.
+     */
+    private void markBranchTargets(int offset, int[] jumpOffsets)
+    {
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            markBranchTarget(offset, jumpOffsets[index]);
+        }
+    }
+
+
+    /**
+     * Marks the branch origin at the given offset.
+     */
+    private void markBranchOrigin(int offset)
+    {
+        instructionMarks[offset] |= INSTRUCTION | BRANCH_ORIGIN;
+    }
+
+
+    /**
+     * Marks the branch target at the given offset.
+     */
+    private void markBranchTarget(int offset, int jumpOffset)
+    {
+        int targetOffset = offset + jumpOffset;
+
+        instructionMarks[targetOffset] |= BRANCH_TARGET;
+
+        // Are we inside a previously marked subroutine?
+        if (isSubroutine(offset))
+        {
+            // Mark the subroutine start of the target.
+            subroutineStarts[targetOffset] = currentSubroutineStart;
+
+            // Update the current subroutine end.
+            if (currentSubroutineEnd < targetOffset)
+            {
+                currentSubroutineEnd = targetOffset;
+            }
+        }
+    }
+
+
+    /**
+     * Marks the instruction at the given offset, after a branch.
+     */
+    private void markAfterBranchOrigin(int nextOffset)
+    {
+        instructionMarks[nextOffset] |= AFTER_BRANCH;
+
+        // Are we at the end of the current subroutine?
+        if (currentSubroutineEnd <= nextOffset)
+        {
+            // Reset the subroutine start.
+            currentSubroutineStart = NONE;
+        }
+    }
+
+
+    /**
+     * Checks if the specified instruction is inside a subroutine.
+     */
+    private void checkSubroutine(int offset)
+    {
+        // Are we inside a previously marked subroutine?
+        if (isSubroutine(offset))
+        {
+            // Update the current subroutine start.
+            currentSubroutineStart = subroutineStarts[offset];
+        }
+        else
+        {
+            // Mark the subroutine start (or NONE).
+            subroutineStarts[offset] = currentSubroutineStart;
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/ClassFinalizer.java b/src/proguard/optimize/peephole/ClassFinalizer.java
new file mode 100644
index 0000000..b5e54f8
--- /dev/null
+++ b/src/proguard/optimize/peephole/ClassFinalizer.java
@@ -0,0 +1,84 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.optimize.KeepMarker;
+
+/**
+ * This <code>ClassVisitor</code> makes the program classes that it visits
+ * final, if possible.
+ *
+ * @author Eric Lafortune
+ */
+public class ClassFinalizer
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private final ClassVisitor extraClassVisitor;
+
+
+    /**
+     * Creates a new ClassFinalizer.
+     */
+    public ClassFinalizer()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new ClassFinalizer.
+     * @param extraClassVisitor an optional extra visitor for all finalized
+     *                          classes.
+     */
+    public ClassFinalizer(ClassVisitor  extraClassVisitor)
+    {
+        this.extraClassVisitor = extraClassVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // If the class is not final/interface/abstract,
+        // and it is not being kept,
+        // and it doesn't have any subclasses,
+        // then make it final.
+        if ((programClass.u2accessFlags & (ClassConstants.INTERNAL_ACC_FINAL     |
+                                           ClassConstants.INTERNAL_ACC_INTERFACE |
+                                           ClassConstants.INTERNAL_ACC_ABSTRACT)) == 0 &&
+            !KeepMarker.isKept(programClass)                                           &&
+            programClass.subClasses == null)
+        {
+            programClass.u2accessFlags |= ClassConstants.INTERNAL_ACC_FINAL;
+
+            // Visit the class, if required.
+            if (extraClassVisitor != null)
+            {
+                extraClassVisitor.visitProgramClass(programClass);
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/ClassMerger.java b/src/proguard/optimize/peephole/ClassMerger.java
new file mode 100644
index 0000000..1e1a950
--- /dev/null
+++ b/src/proguard/optimize/peephole/ClassMerger.java
@@ -0,0 +1,541 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.visitor.*;
+import proguard.classfile.editor.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.optimize.KeepMarker;
+import proguard.optimize.info.*;
+import proguard.util.*;
+
+import java.util.*;
+
+/**
+ * This ClassVisitor inlines the classes that it visits in a given target class,
+ * whenever possible.
+ *
+ * @see RetargetedInnerClassAttributeRemover
+ * @see TargetClassChanger
+ * @see ClassReferenceFixer
+ * @see MemberReferenceFixer
+ * @see AccessFixer
+ * @author Eric Lafortune
+ */
+public class ClassMerger
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    private final ProgramClass targetClass;
+    private final boolean      allowAccessModification;
+    private final boolean      mergeInterfacesAggressively;
+    private final ClassVisitor extraClassVisitor;
+
+
+    /**
+     * Creates a new ClassMerger that will merge classes into the given target
+     * class.
+     * @param targetClass                 the class into which all visited
+     *                                    classes will be merged.
+     * @param allowAccessModification     specifies whether the access modifiers
+     *                                    of classes can be changed in order to
+     *                                    merge them.
+     * @param mergeInterfacesAggressively specifies whether interfaces may
+     *                                    be merged aggressively.
+     */
+    public ClassMerger(ProgramClass targetClass,
+                       boolean      allowAccessModification,
+                       boolean      mergeInterfacesAggressively)
+    {
+        this(targetClass, allowAccessModification, mergeInterfacesAggressively, null);
+    }
+
+
+    /**
+     * Creates a new ClassMerger that will merge classes into the given target
+     * class.
+     * @param targetClass                 the class into which all visited
+     *                                    classes will be merged.
+     * @param allowAccessModification     specifies whether the access modifiers
+     *                                    of classes can be changed in order to
+     *                                    merge them.
+     * @param mergeInterfacesAggressively specifies whether interfaces may
+     *                                    be merged aggressively.
+     * @param extraClassVisitor           an optional extra visitor for all
+     *                                    merged classes.
+     */
+    public ClassMerger(ProgramClass targetClass,
+                       boolean      allowAccessModification,
+                       boolean      mergeInterfacesAggressively,
+                       ClassVisitor extraClassVisitor)
+    {
+        this.targetClass                 = targetClass;
+        this.allowAccessModification     = allowAccessModification;
+        this.mergeInterfacesAggressively = mergeInterfacesAggressively;
+        this.extraClassVisitor           = extraClassVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        //final String CLASS_NAME = "abc/Def";
+        //DEBUG = programClass.getName().equals(CLASS_NAME) ||
+        //        targetClass.getName().equals(CLASS_NAME);
+
+        // TODO: Remove this when the class merger has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            visitProgramClass0(programClass);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while merging classes:");
+            System.err.println("  Class        = ["+programClass.getName()+"]");
+            System.err.println("  Target class = ["+targetClass.getName()+"]");
+            System.err.println("  Exception    = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+
+            if (DEBUG)
+            {
+                programClass.accept(new ClassPrinter());
+                targetClass.accept(new ClassPrinter());
+            }
+
+            throw ex;
+        }
+    }
+
+    public void visitProgramClass0(ProgramClass programClass)
+    {
+        if (!programClass.equals(targetClass) &&
+
+            // Don't merge classes that must be preserved.
+            !KeepMarker.isKept(programClass) &&
+            !KeepMarker.isKept(targetClass)  &&
+
+            // Only merge classes that haven't been retargeted yet.
+            getTargetClass(programClass) == null &&
+            getTargetClass(targetClass)  == null &&
+
+            // Don't merge annotation classes, with all their introspection and
+            // infinite recursion.
+            (programClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_ANNOTATTION) == 0 &&
+
+            // Only merge classes if we can change the access permissioms, or
+            // if they are in the same package, or
+            // if they are public and don't contain or invoke package visible
+            // class members.
+            (allowAccessModification                                                        ||
+             ((programClass.getAccessFlags() &
+               targetClass.getAccessFlags()  &
+               ClassConstants.INTERNAL_ACC_PUBLIC) != 0 &&
+              !PackageVisibleMemberContainingClassMarker.containsPackageVisibleMembers(programClass) &&
+              !PackageVisibleMemberInvokingClassMarker.invokesPackageVisibleMembers(programClass)) ||
+             ClassUtil.internalPackageName(programClass.getName()).equals(
+             ClassUtil.internalPackageName(targetClass.getName()))) &&
+
+            // Only merge two classes or two interfaces or two abstract classes,
+            // or a class into an interface with a single implementation.
+            ((programClass.getAccessFlags() &
+              (ClassConstants.INTERNAL_ACC_INTERFACE |
+               ClassConstants.INTERNAL_ACC_ABSTRACT)) ==
+             (targetClass.getAccessFlags()  &
+              (ClassConstants.INTERNAL_ACC_INTERFACE |
+               ClassConstants.INTERNAL_ACC_ABSTRACT)) ||
+             (isOnlySubClass(programClass, targetClass) &&
+              (programClass.getSuperClass().equals(targetClass) ||
+               programClass.getSuperClass().equals(targetClass.getSuperClass())))) &&
+
+            // One class must not implement the other class indirectly.
+            !indirectlyImplementedInterfaces(programClass).contains(targetClass) &&
+            !targetClass.extendsOrImplements(programClass) &&
+
+            // The two classes must have the same superclasses and interfaces
+            // with static initializers.
+            initializedSuperClasses(programClass).equals(initializedSuperClasses(targetClass))   &&
+
+            // The two classes must have the same superclasses and interfaces
+            // that are tested with 'instanceof'.
+            instanceofedSuperClasses(programClass).equals(instanceofedSuperClasses(targetClass)) &&
+
+            // The two classes must have the same superclasses that are caught
+            // as exceptions.
+            caughtSuperClasses(programClass).equals(caughtSuperClasses(targetClass)) &&
+
+            // The two classes must not both be part of a .class construct.
+            !(DotClassMarker.isDotClassed(programClass) &&
+              DotClassMarker.isDotClassed(targetClass)) &&
+
+            // The two classes must not introduce any unwanted fields.
+            !introducesUnwantedFields(programClass, targetClass) &&
+            !introducesUnwantedFields(targetClass, programClass) &&
+
+            // The classes must not have clashing constructors.
+            !haveAnyIdenticalInitializers(programClass, targetClass) &&
+
+            // The classes must not introduce abstract methods, unless
+            // explicitly allowed.
+            (mergeInterfacesAggressively ||
+             (!introducesUnwantedAbstractMethods(programClass, targetClass) &&
+              !introducesUnwantedAbstractMethods(targetClass, programClass))) &&
+
+            // The classes must not override each others concrete methods.
+            !overridesAnyMethods(programClass, targetClass) &&
+            !overridesAnyMethods(targetClass, programClass) &&
+
+            // The classes must not shadow each others non-private methods.
+            !shadowsAnyMethods(programClass, targetClass) &&
+            !shadowsAnyMethods(targetClass, programClass))
+        {
+            if (DEBUG)
+            {
+                System.out.println("ClassMerger ["+programClass.getName()+"] -> ["+targetClass.getName()+"]");
+                System.out.println("  Source interface? ["+((programClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE)!=0)+"]");
+                System.out.println("  Target interface? ["+((targetClass.getAccessFlags() & ClassConstants.INTERNAL_ACC_INTERFACE)!=0)+"]");
+                System.out.println("  Source subclasses ["+programClass.subClasses+"]");
+                System.out.println("  Target subclasses ["+targetClass.subClasses+"]");
+                System.out.println("  Source superclass ["+programClass.getSuperClass().getName()+"]");
+                System.out.println("  Target superclass ["+targetClass.getSuperClass().getName()+"]");
+            }
+
+            // Combine the access flags.
+            int targetAccessFlags = targetClass.getAccessFlags();
+            int sourceAccessFlags = programClass.getAccessFlags();
+
+            targetClass.u2accessFlags =
+                ((targetAccessFlags &
+                  sourceAccessFlags) &
+                 (ClassConstants.INTERNAL_ACC_INTERFACE  |
+                  ClassConstants.INTERNAL_ACC_ABSTRACT)) |
+                ((targetAccessFlags |
+                  sourceAccessFlags) &
+                 (ClassConstants.INTERNAL_ACC_PUBLIC     |
+                  ClassConstants.INTERNAL_ACC_ANNOTATTION |
+                  ClassConstants.INTERNAL_ACC_ENUM));
+
+            // Copy over the superclass, unless it's the target class itself.
+            //if (!targetClass.getName().equals(programClass.getSuperName()))
+            //{
+            //    targetClass.u2superClass =
+            //        new ConstantAdder(targetClass).addConstant(programClass, programClass.u2superClass);
+            //}
+
+            // Copy over the interfaces that aren't present yet and that
+            // wouldn't cause loops in the class hierarchy.
+            programClass.interfaceConstantsAccept(
+                new ExceptClassConstantFilter(targetClass.getName(),
+                new ImplementedClassConstantFilter(targetClass,
+                new ImplementingClassConstantFilter(targetClass,
+                new InterfaceAdder(targetClass)))));
+
+            // Copy over the class members.
+            MemberAdder memberAdder =
+                new MemberAdder(targetClass);
+
+            programClass.fieldsAccept(memberAdder);
+            programClass.methodsAccept(memberAdder);
+
+            // Copy over the other attributes.
+            programClass.attributesAccept(
+                new AttributeAdder(targetClass, true));
+
+            // Update the optimization information of the target class.
+            ClassOptimizationInfo info =
+                ClassOptimizationInfo.getClassOptimizationInfo(targetClass);
+            if (info != null)
+            {
+                info.merge(ClassOptimizationInfo.getClassOptimizationInfo(programClass));
+            }
+
+            // Remember to replace the inlined class by the target class.
+            setTargetClass(programClass, targetClass);
+
+            // Visit the merged class, if required.
+            if (extraClassVisitor != null)
+            {
+                extraClassVisitor.visitProgramClass(programClass);
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether a given class is the only subclass of another given class.
+     */
+    private boolean isOnlySubClass(Clazz        subClass,
+                                   ProgramClass clazz)
+    {
+        // TODO: The list of subclasses is not up to date.
+        return clazz.subClasses != null     &&
+               clazz.subClasses.length == 1 &&
+               clazz.subClasses[0].equals(subClass);
+    }
+
+
+    /**
+     * Returns the set of indirectly implemented interfaces.
+     */
+    private Set indirectlyImplementedInterfaces(Clazz clazz)
+    {
+        Set set = new HashSet();
+
+        ReferencedClassVisitor referencedInterfaceCollector =
+            new ReferencedClassVisitor(
+            new ClassHierarchyTraveler(false, false, true, false,
+            new ClassCollector(set)));
+
+        // Visit all superclasses and  collect their interfaces.
+        clazz.superClassConstantAccept(referencedInterfaceCollector);
+
+        // Visit all interfaces and collect their interfaces.
+        clazz.interfaceConstantsAccept(referencedInterfaceCollector);
+
+        return set;
+    }
+
+
+    /**
+     * Returns the set of superclasses and interfaces that are initialized.
+     */
+    private Set initializedSuperClasses(Clazz clazz)
+    {
+        Set set = new HashSet();
+
+        // Visit all superclasses and interfaces, collecting the ones that have
+        // static initializers.
+        clazz.hierarchyAccept(true, true, true, false,
+                              new NamedMethodVisitor(ClassConstants.INTERNAL_METHOD_NAME_CLINIT,
+                                                     ClassConstants.INTERNAL_METHOD_TYPE_INIT,
+                              new MemberToClassVisitor(
+                              new ClassCollector(set))));
+
+        return set;
+    }
+
+
+    /**
+     * Returns the set of superclasses and interfaces that are used in
+     * 'instanceof' tests.
+     */
+    private Set instanceofedSuperClasses(Clazz clazz)
+    {
+        Set set = new HashSet();
+
+        // Visit all superclasses and interfaces, collecting the ones that are
+        // used in an 'instanceof' test.
+        clazz.hierarchyAccept(true, true, true, false,
+                              new InstanceofClassFilter(
+                              new ClassCollector(set)));
+
+        return set;
+    }
+
+
+    /**
+     * Returns the set of superclasses that are caught as exceptions.
+     */
+    private Set caughtSuperClasses(Clazz clazz)
+    {
+        Set set = new HashSet();
+
+        // Visit all superclasses, collecting the ones that are caught.
+        clazz.hierarchyAccept(true, true, false, false,
+                              new CaughtClassFilter(
+                              new ClassCollector(set)));
+
+        return set;
+    }
+
+
+    /**
+     * Returns whether the given class would introduce any unwanted fields
+     * in the target class.
+     */
+    private boolean introducesUnwantedFields(ProgramClass programClass,
+                                             ProgramClass targetClass)
+    {
+        // The class must not have any fields, or it must not be instantiated,
+        // without any other subclasses.
+        return
+            programClass.u2fieldsCount != 0 &&
+            (InstantiationClassMarker.isInstantiated(targetClass) ||
+             (targetClass.subClasses != null &&
+              !isOnlySubClass(programClass, targetClass)));
+    }
+
+
+    /**
+     * Returns whether the two given classes have initializers with the same
+     * descriptors.
+     */
+    private boolean haveAnyIdenticalInitializers(Clazz clazz, Clazz targetClass)
+    {
+        MemberCounter counter = new MemberCounter();
+
+        // TODO: Currently checking shared methods, not just initializers.
+        // TODO: Allow identical methods.
+        // Visit all methods, counting the ones that are also present in the
+        // target class.
+        clazz.methodsAccept(//new MemberNameFilter(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT),
+                            new SimilarMemberVisitor(targetClass, true, false, false, false,
+                            new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_ABSTRACT,
+                            counter)));
+
+        return counter.getCount() > 0;
+    }
+
+
+    /**
+     * Returns whether the given class would introduce any abstract methods
+     * in the target class.
+     */
+    private boolean introducesUnwantedAbstractMethods(Clazz        clazz,
+                                                      ProgramClass targetClass)
+    {
+        // It's ok if the target class is already abstract and it has at most
+        // the class as a subclass.
+        if ((targetClass.getAccessFlags() &
+             (ClassConstants.INTERNAL_ACC_ABSTRACT |
+              ClassConstants.INTERNAL_ACC_INTERFACE)) != 0 &&
+            (targetClass.subClasses == null ||
+             isOnlySubClass(clazz, targetClass)))
+        {
+            return false;
+        }
+
+        MemberCounter counter   = new MemberCounter();
+        Set           targetSet = new HashSet();
+
+        // Collect all abstract methods, and similar abstract methods in the
+        // class hierarchy of the target class.
+        clazz.methodsAccept(new MemberAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0,
+                            new MultiMemberVisitor(new MemberVisitor[]
+                            {
+                                counter,
+                                new SimilarMemberVisitor(targetClass, true, true, true, false,
+                                                         new MemberAccessFilter(ClassConstants.INTERNAL_ACC_ABSTRACT, 0,
+                                                         new MemberCollector(targetSet)))
+                            })));
+
+        return targetSet.size() < counter.getCount();
+    }
+
+
+    /**
+     * Returns whether the given class overrides any methods in the given
+     * target class.
+     */
+    private boolean overridesAnyMethods(Clazz clazz, Clazz targetClass)
+    {
+        MemberCounter counter = new MemberCounter();
+
+        // Visit all non-private non-static methods, counting the ones that are
+        // being overridden in the class hierarchy of the target class.
+        clazz.methodsAccept(new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT,
+                            new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_CLINIT)),
+                            new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT)),
+                            new SimilarMemberVisitor(targetClass, true, true, false, false,
+                            new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT,
+                            counter))))));
+
+        return counter.getCount() > 0;
+    }
+
+
+    /**
+     * Returns whether the given class or its subclasses shadow any methods in
+     * the given target class.
+     */
+    private boolean shadowsAnyMethods(Clazz clazz, Clazz targetClass)
+    {
+        MemberCounter counter = new MemberCounter();
+
+        // Visit all private methods, counting the ones that are shadowing
+        // non-private methods in the class hierarchy of the target class.
+        clazz.hierarchyAccept(true, false, false, true,
+                              new AllMethodVisitor(
+                              new MemberAccessFilter(ClassConstants.INTERNAL_ACC_PRIVATE, 0,
+                              new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_INIT)),
+                              new SimilarMemberVisitor(targetClass, true, true, true, false,
+                              new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                              counter))))));
+
+        // Visit all static methods, counting the ones that are shadowing
+        // non-private methods in the class hierarchy of the target class.
+        clazz.hierarchyAccept(true, false, false, true,
+                              new AllMethodVisitor(
+                              new MemberAccessFilter(ClassConstants.INTERNAL_ACC_STATIC, 0,
+                              new MemberNameFilter(new NotMatcher(new FixedStringMatcher(ClassConstants.INTERNAL_METHOD_NAME_CLINIT)),
+                              new SimilarMemberVisitor(targetClass, true, true, true, false,
+                              new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
+                              counter))))));
+
+        return counter.getCount() > 0;
+    }
+
+
+    public static void setTargetClass(Clazz clazz, Clazz targetClass)
+    {
+        ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+        if (info != null)
+        {
+            info.setTargetClass(targetClass);
+        }
+    }
+
+
+    public static Clazz getTargetClass(Clazz clazz)
+    {
+        Clazz targetClass = null;
+
+        // Return the last target class, if any.
+        while (true)
+        {
+            ClassOptimizationInfo info = ClassOptimizationInfo.getClassOptimizationInfo(clazz);
+            if (info == null)
+            {
+                return targetClass;
+            }
+
+            clazz = info.getTargetClass();
+            if (clazz == null)
+            {
+                return targetClass;
+            }
+
+            targetClass = clazz;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/peephole/GotoCommonCodeReplacer.java b/src/proguard/optimize/peephole/GotoCommonCodeReplacer.java
new file mode 100644
index 0000000..4833275
--- /dev/null
+++ b/src/proguard/optimize/peephole/GotoCommonCodeReplacer.java
@@ -0,0 +1,266 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor redirects unconditional branches so any common code
+ * is shared, and the code preceding the branch can be removed, in the code
+ * attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class GotoCommonCodeReplacer
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    private final InstructionVisitor  extraInstructionVisitor;
+
+    private final BranchTargetFinder  branchTargetFinder  = new BranchTargetFinder();
+    private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
+
+
+    /**
+     * Creates a new GotoCommonCodeReplacer.
+     * @param extraInstructionVisitor an optional extra visitor for all replaced
+     *                                goto instructions.
+     */
+    public GotoCommonCodeReplacer(InstructionVisitor  extraInstructionVisitor)
+    {
+        this.extraInstructionVisitor = extraInstructionVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // Mark all branch targets.
+        branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Reset the code attribute editor.
+        codeAttributeEditor.reset(codeAttribute.u4codeLength);
+
+        // Remap the variables of the instructions.
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        // Apply the code atribute editor.
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        // Check if the instruction is an unconditional goto instruction that
+        // isn't the target of a branch itself.
+        byte opcode = branchInstruction.opcode;
+        if ((opcode == InstructionConstants.OP_GOTO ||
+             opcode == InstructionConstants.OP_GOTO_W) &&
+            !branchTargetFinder.isBranchTarget(offset))
+        {
+            int branchOffset = branchInstruction.branchOffset;
+            int targetOffset = offset + branchOffset;
+
+            // Get the number of common bytes.
+            int commonCount = commonByteCodeCount(codeAttribute, offset, targetOffset);
+
+            if (commonCount > 0 &&
+                !exceptionBoundary(codeAttribute, offset, targetOffset))
+            {
+                if (DEBUG)
+                {
+                    System.out.println("GotoCommonCodeReplacer: "+clazz.getName()+"."+method.getName(clazz)+" (["+(offset-commonCount)+"] - "+branchInstruction.toString(offset)+" -> "+targetOffset+")");
+                }
+
+                // Delete the common instructions.
+                for (int delta = 0; delta <= commonCount; delta++)
+                {
+                    int deleteOffset = offset - delta;
+                    if (branchTargetFinder.isInstruction(deleteOffset))
+                    {
+                        codeAttributeEditor.replaceInstruction(     deleteOffset, (Instruction)null);
+                        codeAttributeEditor.insertBeforeInstruction(deleteOffset, (Instruction)null);
+                        codeAttributeEditor.insertAfterInstruction( deleteOffset, (Instruction)null);
+
+                        codeAttributeEditor.deleteInstruction(deleteOffset);
+                    }
+                }
+
+                // Redirect the goto instruction, if it is still necessary.
+                int newBranchOffset = branchOffset - commonCount;
+                if (newBranchOffset != branchInstruction.length(offset))
+                {
+                    Instruction newGotoInstruction =
+                         new BranchInstruction(opcode, newBranchOffset);
+                    codeAttributeEditor.replaceInstruction(offset,
+                                                           newGotoInstruction);
+                }
+
+                // Visit the instruction, if required.
+                if (extraInstructionVisitor != null)
+                {
+                    extraInstructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction);
+                }
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns the number of common bytes preceding the given offsets,
+     * avoiding branches and exception blocks.
+     */
+    private int commonByteCodeCount(CodeAttribute codeAttribute, int offset1, int offset2)
+    {
+        // Find the block of common instructions preceding it.
+        byte[] code = codeAttribute.code;
+
+        int successfulDelta = 0;
+
+        for (int delta = 1;
+             delta <= offset1 &&
+             delta <= offset2 &&
+             offset2 - delta != offset1;
+             delta++)
+        {
+            int newOffset1 = offset1 - delta;
+            int newOffset2 = offset2 - delta;
+
+            // Is the code identical at both offsets?
+            if (code[newOffset1] != code[newOffset2])
+            {
+                break;
+            }
+
+            // Are there instructions at either offset but not both?
+            if (branchTargetFinder.isInstruction(newOffset1) ^
+                branchTargetFinder.isInstruction(newOffset2))
+            {
+                break;
+            }
+
+            // Are there instructions at both offsets?
+            if (branchTargetFinder.isInstruction(newOffset1) &&
+                branchTargetFinder.isInstruction(newOffset2))
+            {
+                // Are the offsets involved in some branches?
+                // Note that the preverifier doesn't like initializer
+                // invocations to be moved around.
+                // Also note that the preverifier doesn't like pop instructions
+                // that work on different operands.
+                if (branchTargetFinder.isBranchOrigin(newOffset1)   ||
+                    branchTargetFinder.isBranchTarget(newOffset1)   ||
+                    branchTargetFinder.isExceptionStart(newOffset1) ||
+                    branchTargetFinder.isExceptionEnd(newOffset1)   ||
+                    branchTargetFinder.isInitializer(newOffset1)    ||
+                    branchTargetFinder.isExceptionStart(newOffset2) ||
+                    branchTargetFinder.isExceptionEnd(newOffset2)   ||
+                    isPop(code[newOffset1]))
+                {
+                    break;
+                }
+
+                // Make sure the new branch target was a branch target before,
+                // in order not to introduce new entries in the stack map table.
+                if (branchTargetFinder.isBranchTarget(newOffset2))
+                {
+                    successfulDelta = delta;
+                }
+
+                if (branchTargetFinder.isBranchTarget(newOffset1))
+                {
+                    break;
+                }
+            }
+        }
+
+        return successfulDelta;
+    }
+
+
+    /**
+     * Returns whether the given opcode represents a pop instruction that must
+     * get a consistent type (pop, pop2, arraylength).
+     */
+    private boolean isPop(byte opcode)
+    {
+        return opcode == InstructionConstants.OP_POP  ||
+               opcode == InstructionConstants.OP_POP2 ||
+               opcode == InstructionConstants.OP_ARRAYLENGTH;
+    }
+
+
+    /**
+     * Returns the whether there is a boundary of an exception block between
+     * the given offsets (including both).
+     */
+    private boolean exceptionBoundary(CodeAttribute codeAttribute, int offset1, int offset2)
+    {
+        // Swap the offsets if the second one is smaller than the first one.
+        if (offset2 < offset1)
+        {
+            int offset = offset1;
+            offset1 = offset2;
+            offset2 = offset;
+        }
+
+        // Check if there is a boundary of an exception block.
+        for (int offset = offset1; offset <= offset2; offset++)
+        {
+            if (branchTargetFinder.isExceptionStart(offset) ||
+                branchTargetFinder.isExceptionEnd(offset))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/src/proguard/optimize/peephole/GotoGotoReplacer.java b/src/proguard/optimize/peephole/GotoGotoReplacer.java
new file mode 100644
index 0000000..7d7e66c
--- /dev/null
+++ b/src/proguard/optimize/peephole/GotoGotoReplacer.java
@@ -0,0 +1,114 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor simplifies unconditional branches to other
+ * unconditional branches.
+ *
+ * @author Eric Lafortune
+ */
+public class GotoGotoReplacer
+extends      SimplifiedVisitor
+implements   InstructionVisitor
+{
+    private final CodeAttributeEditor codeAttributeEditor;
+    private final InstructionVisitor  extraInstructionVisitor;
+
+
+    /**
+     * Creates a new GotoGotoReplacer.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     */
+    public GotoGotoReplacer(CodeAttributeEditor codeAttributeEditor)
+    {
+        this(codeAttributeEditor, null);
+    }
+
+
+    /**
+     * Creates a new GotoGotoReplacer.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     * @param extraInstructionVisitor an optional extra visitor for all replaced
+     *                                goto instructions.
+     */
+    public GotoGotoReplacer(CodeAttributeEditor codeAttributeEditor,
+                            InstructionVisitor  extraInstructionVisitor)
+    {
+        this.codeAttributeEditor     = codeAttributeEditor;
+        this.extraInstructionVisitor = extraInstructionVisitor;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        // Check if the instruction is an unconditional goto instruction.
+        byte opcode = branchInstruction.opcode;
+        if (opcode == InstructionConstants.OP_GOTO ||
+            opcode == InstructionConstants.OP_GOTO_W)
+        {
+            // Check if the goto instruction points to another simple goto
+            // instruction.
+            int branchOffset = branchInstruction.branchOffset;
+            int targetOffset = offset + branchOffset;
+
+            if (branchOffset != branchInstruction.length(offset) &&
+                !codeAttributeEditor.isModified(offset) &&
+                !codeAttributeEditor.isModified(targetOffset))
+            {
+                Instruction targetInstruction =
+                    InstructionFactory.create(codeAttribute.code, targetOffset);
+
+                if (targetInstruction.opcode == InstructionConstants.OP_GOTO)
+                {
+                    // Simplify the goto instruction.
+                    int targetBranchOffset = ((BranchInstruction)targetInstruction).branchOffset;
+
+                    Instruction newBranchInstruction =
+                         new BranchInstruction(opcode,
+                                               (branchOffset + targetBranchOffset));
+                    codeAttributeEditor.replaceInstruction(offset,
+                                                           newBranchInstruction);
+
+                    // Visit the instruction, if required.
+                    if (extraInstructionVisitor != null)
+                    {
+                        extraInstructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/GotoReturnReplacer.java b/src/proguard/optimize/peephole/GotoReturnReplacer.java
new file mode 100644
index 0000000..5c3eb77
--- /dev/null
+++ b/src/proguard/optimize/peephole/GotoReturnReplacer.java
@@ -0,0 +1,115 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor replaces unconditional branches to return instructions
+ * by these same return instructions.
+ *
+ * @author Eric Lafortune
+ */
+public class GotoReturnReplacer
+extends      SimplifiedVisitor
+implements   InstructionVisitor
+{
+    private final CodeAttributeEditor codeAttributeEditor;
+    private final InstructionVisitor  extraInstructionVisitor;
+
+
+    /**
+     * Creates a new GotoReturnReplacer.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     */
+    public GotoReturnReplacer(CodeAttributeEditor codeAttributeEditor)
+    {
+        this(codeAttributeEditor, null);
+    }
+
+
+    /**
+     * Creates a new GotoReturnReplacer.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     * @param extraInstructionVisitor an optional extra visitor for all replaced
+     *                                goto instructions.
+     */
+    public GotoReturnReplacer(CodeAttributeEditor codeAttributeEditor,
+                              InstructionVisitor  extraInstructionVisitor)
+    {
+        this.codeAttributeEditor     = codeAttributeEditor;
+        this.extraInstructionVisitor = extraInstructionVisitor;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        // Check if the instruction is an unconditional goto instruction.
+        byte opcode = branchInstruction.opcode;
+        if (opcode == InstructionConstants.OP_GOTO ||
+            opcode == InstructionConstants.OP_GOTO_W)
+        {
+            // Check if the goto instruction points to a return instruction.
+            int targetOffset = offset + branchInstruction.branchOffset;
+
+            if (!codeAttributeEditor.isModified(offset) &&
+                !codeAttributeEditor.isModified(targetOffset))
+            {
+                Instruction targetInstruction = InstructionFactory.create(codeAttribute.code,
+                                                                          targetOffset);
+                switch (targetInstruction.opcode)
+                {
+                    case InstructionConstants.OP_IRETURN:
+                    case InstructionConstants.OP_LRETURN:
+                    case InstructionConstants.OP_FRETURN:
+                    case InstructionConstants.OP_DRETURN:
+                    case InstructionConstants.OP_ARETURN:
+                    case InstructionConstants.OP_RETURN:
+                        // Replace the goto instruction by the return instruction.
+                        Instruction returnInstruction =
+                             new SimpleInstruction(targetInstruction.opcode);
+                        codeAttributeEditor.replaceInstruction(offset,
+                                                               returnInstruction);
+
+                        // Visit the instruction, if required.
+                        if (extraInstructionVisitor != null)
+                        {
+                            extraInstructionVisitor.visitBranchInstruction(clazz, method, codeAttribute, offset, branchInstruction);
+                        }
+
+                        break;
+                }
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/HorizontalClassMerger.java b/src/proguard/optimize/peephole/HorizontalClassMerger.java
new file mode 100644
index 0000000..a37b9a5
--- /dev/null
+++ b/src/proguard/optimize/peephole/HorizontalClassMerger.java
@@ -0,0 +1,90 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor inlines siblings in the program classes that it visits,
+ * whenever possible.
+ *
+ * @see ClassMerger
+ * @author Eric Lafortune
+ */
+public class HorizontalClassMerger
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private final boolean      allowAccessModification;
+    private final boolean      mergeInterfacesAggressively;
+    private final ClassVisitor extraClassVisitor;
+
+
+    /**
+     * Creates a new HorizontalClassMerger.
+     * @param allowAccessModification     specifies whether the access modifiers
+     *                                    of classes can be changed in order to
+     *                                    merge them.
+     * @param mergeInterfacesAggressively specifies whether interfaces may
+     *                                    be merged aggressively.
+     */
+    public HorizontalClassMerger(boolean allowAccessModification,
+                                 boolean mergeInterfacesAggressively)
+    {
+        this(allowAccessModification, mergeInterfacesAggressively, null);
+    }
+
+
+    /**
+     * Creates a new VerticalClassMerger.
+     * @param allowAccessModification     specifies whether the access modifiers
+     *                                    of classes can be changed in order to
+     *                                    merge them.
+     * @param mergeInterfacesAggressively specifies whether interfaces may
+     *                                    be merged aggressively.
+     * @param extraClassVisitor           an optional extra visitor for all
+     *                                    merged classes.
+     */
+    public HorizontalClassMerger(boolean      allowAccessModification,
+                                 boolean      mergeInterfacesAggressively,
+                                 ClassVisitor extraClassVisitor)
+    {
+        this.allowAccessModification     = allowAccessModification;
+        this.mergeInterfacesAggressively = mergeInterfacesAggressively;
+        this.extraClassVisitor           = extraClassVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.superClassConstantAccept(new ReferencedClassVisitor(
+                                              new SubclassTraveler(
+                                              new ProgramClassFilter(
+                                              new ClassMerger(programClass,
+                                                              allowAccessModification,
+                                                              mergeInterfacesAggressively,
+                                                              extraClassVisitor)))));
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/peephole/InstructionSequenceConstants.java b/src/proguard/optimize/peephole/InstructionSequenceConstants.java
new file mode 100644
index 0000000..b33204b
--- /dev/null
+++ b/src/proguard/optimize/peephole/InstructionSequenceConstants.java
@@ -0,0 +1,3351 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.constant.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.util.InstructionSequenceMatcher;
+
+/**
+ * This class contains a set of instruction sequences and their suggested
+ * replacements.
+ *
+ * @see InstructionSequencesReplacer
+ * @author Eric Lafortune
+ */
+public class InstructionSequenceConstants
+{
+    public static final int X = InstructionSequenceMatcher.X;
+    public static final int Y = InstructionSequenceMatcher.Y;
+    public static final int Z = InstructionSequenceMatcher.Z;
+
+    public static final int A = InstructionSequenceMatcher.A;
+    public static final int B = InstructionSequenceMatcher.B;
+    public static final int C = InstructionSequenceMatcher.C;
+    public static final int D = InstructionSequenceMatcher.D;
+
+
+    private static final int I_32768              =  0;
+    private static final int I_65536              =  1;
+    private static final int I_16777216           =  2;
+
+//  private static final int I_0x000000ff
+    private static final int I_0x0000ff00         =  3;
+    private static final int I_0x00ff0000         =  4;
+    private static final int I_0xff000000         =  5;
+    private static final int I_0x0000ffff         =  6;
+    private static final int I_0xffff0000         =  7;
+
+    private static final int L_M1                 =  8;
+    private static final int L_2                  =  9;
+    private static final int L_4                  = 10;
+    private static final int L_8                  = 11;
+    private static final int L_16                 = 12;
+    private static final int L_32                 = 13;
+    private static final int L_64                 = 14;
+    private static final int L_128                = 15;
+    private static final int L_256                = 16;
+    private static final int L_512                = 17;
+    private static final int L_1024               = 18;
+    private static final int L_2048               = 19;
+    private static final int L_4096               = 20;
+    private static final int L_8192               = 21;
+    private static final int L_16384              = 22;
+    private static final int L_32768              = 23;
+    private static final int L_65536              = 24;
+    private static final int L_16777216           = 25;
+    private static final int L_4294967296         = 26;
+
+    private static final int L_0x00000000ffffffff = 27;
+    private static final int L_0xffffffff00000000 = 28;
+
+    private static final int F_M1                 = 29;
+
+    private static final int D_M1                 = 30;
+
+    private static final int FIELD_I              = 31;
+    private static final int FIELD_L              = 32;
+    private static final int FIELD_F              = 33;
+    private static final int FIELD_D              = 34;
+
+    private static final int NAME_AND_TYPE_I      = 35;
+    private static final int NAME_AND_TYPE_L      = 36;
+    private static final int NAME_AND_TYPE_F      = 37;
+    private static final int NAME_AND_TYPE_D      = 38;
+
+    private static final int TYPE_I               = 39;
+    private static final int TYPE_L               = 40;
+    private static final int TYPE_F               = 41;
+    private static final int TYPE_D               = 42;
+
+
+    public static final Constant[] CONSTANTS = new Constant[]
+    {
+        new IntegerConstant(32768),
+        new IntegerConstant(65536),
+        new IntegerConstant(16777216),
+
+        new IntegerConstant(0x0000ff00),
+        new IntegerConstant(0x00ff0000),
+        new IntegerConstant(0xff000000),
+        new IntegerConstant(0x0000ffff),
+        new IntegerConstant(0xffff0000),
+
+        new LongConstant(-1L),
+        new LongConstant(2L),
+        new LongConstant(4L),
+        new LongConstant(8L),
+        new LongConstant(16L),
+        new LongConstant(32L),
+        new LongConstant(64L),
+        new LongConstant(128L),
+        new LongConstant(256L),
+        new LongConstant(512L),
+        new LongConstant(1024L),
+        new LongConstant(2048L),
+        new LongConstant(4096L),
+        new LongConstant(8192L),
+        new LongConstant(16384L),
+        new LongConstant(32768L),
+        new LongConstant(65536L),
+        new LongConstant(16777216L),
+        new LongConstant(4294967296L),
+
+        new LongConstant(0x00000000ffffffffL),
+        new LongConstant(0xffffffff00000000L),
+
+        new FloatConstant(-1f),
+
+        new DoubleConstant(-1d),
+
+        new FieldrefConstant(X, NAME_AND_TYPE_I, null, null),
+        new FieldrefConstant(X, NAME_AND_TYPE_L, null, null),
+        new FieldrefConstant(X, NAME_AND_TYPE_F, null, null),
+        new FieldrefConstant(X, NAME_AND_TYPE_D, null, null),
+
+        new NameAndTypeConstant(Y, TYPE_I),
+        new NameAndTypeConstant(Y, TYPE_L),
+        new NameAndTypeConstant(Y, TYPE_F),
+        new NameAndTypeConstant(Y, TYPE_D),
+
+        new Utf8Constant("I"),
+        new Utf8Constant("J"),
+        new Utf8Constant("F"),
+        new Utf8Constant("D"),
+    };
+
+
+    public static final Instruction[][][] VARIABLE = new Instruction[][][]
+    {
+        {   // nop = nothing
+            {
+                new SimpleInstruction(InstructionConstants.OP_NOP),
+            },{
+                // Nothing.
+            },
+        },
+        {   // iload/pop = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },{
+                // Nothing.
+            },
+        },
+        {   // lload/pop2 = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },{
+                // Nothing.
+            },
+        },
+        {   // fload/pop = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },{
+                // Nothing.
+            },
+        },
+        {   // dload/pop2 = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },{
+                // Nothing.
+            },
+        },
+        {   // aload/pop = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },{
+                // Nothing.
+            },
+        },
+        {   // i = i = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // l = l = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+                new VariableInstruction(InstructionConstants.OP_LSTORE, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // f = f = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new VariableInstruction(InstructionConstants.OP_FSTORE, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // d = d = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new VariableInstruction(InstructionConstants.OP_DSTORE, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // a = a = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },{
+                // Nothing.
+            },
+        },
+        {   // istore/istore = pop/istore
+            {
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },
+        },
+        {   // lstore/lstore = pop2/lstore
+            {
+                new VariableInstruction(InstructionConstants.OP_LSTORE, X),
+                new VariableInstruction(InstructionConstants.OP_LSTORE, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+                new VariableInstruction(InstructionConstants.OP_LSTORE, X),
+            },
+        },
+        {   // fstore/fstore = pop/fstore
+            {
+                new VariableInstruction(InstructionConstants.OP_FSTORE, X),
+                new VariableInstruction(InstructionConstants.OP_FSTORE, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+                new VariableInstruction(InstructionConstants.OP_FSTORE, X),
+            },
+        },
+        {   // dstore/dstore = pop2/dstore
+            {
+                new VariableInstruction(InstructionConstants.OP_DSTORE, X),
+                new VariableInstruction(InstructionConstants.OP_DSTORE, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+                new VariableInstruction(InstructionConstants.OP_DSTORE, X),
+            },
+        },
+        {   // astore/astore = pop/astore
+            {
+                new VariableInstruction(InstructionConstants.OP_ASTORE, X),
+                new VariableInstruction(InstructionConstants.OP_ASTORE, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+                new VariableInstruction(InstructionConstants.OP_ASTORE, X),
+            },
+        },
+        {   // istore/iload = dup/istore
+            {
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DUP),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },
+        },
+        {   // lstore/lload = dup2/lstore
+            {
+                new VariableInstruction(InstructionConstants.OP_LSTORE, X),
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DUP2),
+                new VariableInstruction(InstructionConstants.OP_LSTORE, X),
+            },
+        },
+        {   // fstore/fload = dup/fstore
+            {
+                new VariableInstruction(InstructionConstants.OP_FSTORE, X),
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DUP),
+                new VariableInstruction(InstructionConstants.OP_FSTORE, X),
+            },
+        },
+        {   // dstore/dload = dup2/dstore
+            {
+                new VariableInstruction(InstructionConstants.OP_DSTORE, X),
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DUP2),
+                new VariableInstruction(InstructionConstants.OP_DSTORE, X),
+            },
+        },
+        {   // astore/aload = dup/astore
+            {
+                new VariableInstruction(InstructionConstants.OP_ASTORE, X),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DUP),
+                new VariableInstruction(InstructionConstants.OP_ASTORE, X),
+            },
+        },
+    };
+
+    public static final Instruction[][][] ARITHMETIC = new Instruction[][][]
+    {
+        {   // c + i = i + c
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0, A),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },
+        },
+        {   // b + i = i + b
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, A),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, A),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },
+        },
+        {   // s + i = i + s
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, A),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, A),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },
+        },
+        {   // c + i = i + c
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, A),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new ConstantInstruction(InstructionConstants.OP_LDC, A),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },
+        },
+        {   // c * i = i * c
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0, A),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },
+        },
+        {   // b * i = i * b
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, A),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, A),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },
+        },
+        {   // s * i = i * s
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, A),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, A),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },
+        },
+        {   // c * i = i * c
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, A),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new ConstantInstruction(InstructionConstants.OP_LDC, A),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },
+        },
+        {   // c + l = l + c
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0, A),
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_LADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_LADD),
+            },
+        },
+        {   // c + l = l + c
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, A),
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_LADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, A),
+                new SimpleInstruction(InstructionConstants.OP_LADD),
+            },
+        },
+        {   // c * l = l * c
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0, A),
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_LLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },
+        },
+        {   // c + f = f + c
+            {
+                new SimpleInstruction(InstructionConstants.OP_FCONST_0, A),
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_FADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_FCONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_FADD),
+            },
+        },
+        {   // c + f = f + c
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, A),
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_FADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new ConstantInstruction(InstructionConstants.OP_LDC, A),
+                new SimpleInstruction(InstructionConstants.OP_FADD),
+            },
+        },
+        {   // c * f = f * c
+            {
+                new SimpleInstruction(InstructionConstants.OP_FCONST_0, A),
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_FMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_FCONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_FMUL),
+            },
+        },
+        {   // c * f = f * c
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, A),
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_FLOAD, X),
+                new ConstantInstruction(InstructionConstants.OP_LDC, A),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },
+        },
+        {   // c + d = d + c
+            {
+                new SimpleInstruction(InstructionConstants.OP_DCONST_0, A),
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_DADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_DCONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_DADD),
+            },
+        },
+        {   // c + d = d + c
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, A),
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_DADD),
+            },{
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, A),
+                new SimpleInstruction(InstructionConstants.OP_DADD),
+            },
+        },
+        {   // c * d = d * c
+            {
+                new SimpleInstruction(InstructionConstants.OP_DCONST_0, A),
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_DMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_DCONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_DMUL),
+            },
+        },
+        {   // c * d = d * c
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, A),
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_DMUL),
+            },{
+                new VariableInstruction(InstructionConstants.OP_DLOAD, X),
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, A),
+                new SimpleInstruction(InstructionConstants.OP_DMUL),
+            },
+        },
+        {   // i = i + c = i += c
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0, A),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, A),
+            },
+        },
+        {   // i = i + b = i += b
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, A),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, A),
+            },
+        },
+        {   // i = i + s = i += s
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, A),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, A),
+            },
+        },
+        {   // i = i - -1 = i++
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new SimpleInstruction(InstructionConstants.OP_ISUB),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, 1),
+            },
+        },
+        {   // i = i - 1 = i--
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new SimpleInstruction(InstructionConstants.OP_ISUB),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, -1),
+            },
+        },
+        {   // i = i - 2 = i -= 2
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+                new SimpleInstruction(InstructionConstants.OP_ISUB),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, -2),
+            },
+        },
+        {   // i = i - 3 = i -= 3
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_3),
+                new SimpleInstruction(InstructionConstants.OP_ISUB),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, -3),
+            },
+        },
+        {   // i = i - 4 = i -= 4
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_4),
+                new SimpleInstruction(InstructionConstants.OP_ISUB),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, -4),
+            },
+        },
+        {   // i = i - 5 = i -= 5
+            {
+                new VariableInstruction(InstructionConstants.OP_ILOAD, X),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_5),
+                new SimpleInstruction(InstructionConstants.OP_ISUB),
+                new VariableInstruction(InstructionConstants.OP_ISTORE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_IINC, X, -5),
+            },
+        },
+        {   // ... + 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... + 0L = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LADD),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... + 0f = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_FCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_FADD),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... + 0d = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_DCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_DADD),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... - 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_ISUB),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... - 0L = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LSUB),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... - 0f = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_FCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_FSUB),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... - 0d = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_DCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_DSUB),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... * -1 = -...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_INEG),
+            },
+        },
+        {   // ... * 0 = 0
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+            },
+        },
+        {   // ... * 1 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... * 2 = ... << 1
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 4 = ... << 2
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_4),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 8 = ... << 3
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_ICONST_3),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 16 = ... << 4
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 4),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 32 = ... << 5
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 5),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 64 = ... << 6
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 64),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 6),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 128 = ... << 7
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 128),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 7),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 256 = ... << 8
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 256),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 512 = ... << 9
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 512),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 9),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 1024 = ... << 10
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 1024),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 10),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 2048 = ... << 11
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 2048),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 11),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 4096 = ... << 12
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 4096),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 12),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 8192 = ... << 13
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 8192),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 13),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 16384 = ... << 14
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 16384),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 14),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 32768 = ... << 15
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_32768),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 15),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 65536 = ... << 16
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_65536),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * 16777216 = ... << 24
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_16777216),
+                new SimpleInstruction(InstructionConstants.OP_IMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },
+        },
+        {   // ... * -1L = -...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_M1),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_LNEG),
+            },
+        },
+        {   // ... * 0L = 0L
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+            },
+        },
+        {   // ... * 1L = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_1),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... * 2L = ... << 1
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_2),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 4L = ... << 2
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_4),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 8L = ... << 3
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_8),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_ICONST_3),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 16L = ... << 4
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_16),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 4),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 32L = ... << 5
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_32),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 5),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 64L = ... << 6
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_64),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 6),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 128L = ... << 7
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_128),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 7),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 256L = ... << 8
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_256),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 512L = ... << 9
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_512),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 9),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 1024L = ... << 10
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_1024),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 10),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 2048L = ... << 11
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_2048),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 11),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 4096L = ... << 12
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_4096),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 12),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 8192L = ... << 13
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_8192),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 13),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 16384L = ... << 14
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_16384),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 14),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 32768L = ... << 15
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_32768),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 15),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 65536LL = ... << 16
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_65536),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 16777216L = ... << 24
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_16777216),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * 4294967296L = ... << 32
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_4294967296),
+                new SimpleInstruction(InstructionConstants.OP_LMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },
+        },
+        {   // ... * -1f = -...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, F_M1),
+                new SimpleInstruction(InstructionConstants.OP_FMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_FNEG),
+            },
+        },
+//        {   // ... * 0f = 0f (or NaN)
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_FCONST_0),
+//                new SimpleInstruction(InstructionConstants.OP_FMUL),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_POP),
+//                new SimpleInstruction(InstructionConstants.OP_FCONST_0),
+//            },
+//        },
+        {   // ... * 1f = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_FCONST_1),
+                new SimpleInstruction(InstructionConstants.OP_FMUL),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... * -1d = -...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, D_M1),
+                new SimpleInstruction(InstructionConstants.OP_DMUL),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DNEG),
+            },
+        },
+//        {   // ... * 0d = 0d (or NaN)
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_DCONST_0),
+//                new SimpleInstruction(InstructionConstants.OP_DMUL),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_POP2),
+//                new SimpleInstruction(InstructionConstants.OP_DCONST_0),
+//            },
+//        },
+        {   // ... * 1d = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_DCONST_1),
+                new SimpleInstruction(InstructionConstants.OP_DMUL),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... / -1 = -...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new SimpleInstruction(InstructionConstants.OP_IDIV),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_INEG),
+            },
+        },
+        {   // ... / 1 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new SimpleInstruction(InstructionConstants.OP_IDIV),
+            },{
+                // Nothing.
+            },
+        },
+        // Not valid for negative values.
+//        {   // ... / 2 = ... >> 1
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 4 = ... >> 2
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_4),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 8 = ... >> 3
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_3),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 16 = ... >> 4
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 4),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 32 = ... >> 5
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 5),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 64 = ... >> 6
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 64),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 6),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 128 = ... >> 7
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 128),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 7),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 256 = ... >> 8
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 256),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 512 = ... >> 9
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 512),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 9),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 1024 = ... >> 10
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 1024),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 10),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 2048 = ... >> 11
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 2048),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 11),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 4096 = ... >> 12
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 4096),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 12),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 8192 = ... >> 13
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 8192),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 13),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 16384 = ... >> 14
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 16384),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 14),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 32768 = ... >> 15
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC, I_32768),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 15),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 65536 = ... >> 16
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC, I_65536),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+//        {   // ... / 16777216 = ... >> 24
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC, I_16777216),
+//                new SimpleInstruction(InstructionConstants.OP_IDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+//                new SimpleInstruction(InstructionConstants.OP_ISHR),
+//            },
+//        },
+        {   // ... / -1L = -...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_M1),
+                new SimpleInstruction(InstructionConstants.OP_LDIV),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_LNEG),
+            },
+        },
+        {   // ... / 1L = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_1),
+                new SimpleInstruction(InstructionConstants.OP_LDIV),
+            },{
+                // Nothing.
+            },
+        },
+        // Not valid for negative values.
+//        {   // ... / 2L = ... >> 1
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_2),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 4L = ... >> 2
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_4),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 8L = ... >> 3
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_8),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_3),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 16L = ... >> 4
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_16),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 4),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 32L = ... >> 5
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_32),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 5),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 64L = ... >> 6
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_64),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 6),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 128L = ... >> 7
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_128),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 7),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 256L = ... >> 8
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_256),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 512L = ... >> 9
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_512),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 9),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 1024L = ... >> 10
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_1024),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 10),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 2048L = ... >> 11
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_2048),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 11),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 4096L = ... >> 12
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_4096),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 12),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 8192L = ... >> 13
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_8192),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 13),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 16384L = ... >> 14
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_16384),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 14),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 32768L = ... >> 15
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_32768),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 15),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 65536LL = ... >> 16
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_65536),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 16777216L = ... >> 24
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_16777216),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+//        {   // ... / 4294967296L = ... >> 32
+//            {
+//                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_4294967296),
+//                new SimpleInstruction(InstructionConstants.OP_LDIV),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+//                new SimpleInstruction(InstructionConstants.OP_LSHR),
+//            },
+//        },
+        {   // ... / -1f = -...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, F_M1),
+                new SimpleInstruction(InstructionConstants.OP_FDIV),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_FNEG),
+            },
+        },
+        {   // ... / 1f = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_FCONST_1),
+                new SimpleInstruction(InstructionConstants.OP_FDIV),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... / -1d = -...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, D_M1),
+                new SimpleInstruction(InstructionConstants.OP_DDIV),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DNEG),
+            },
+        },
+        {   // ... / 1d = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_DCONST_1),
+                new SimpleInstruction(InstructionConstants.OP_DDIV),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... % 1 = 0
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new SimpleInstruction(InstructionConstants.OP_IREM),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+            },
+        },
+//        {   // ... % 2 = ... & 0x1
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_2),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 4 = ... & 0x3
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_4),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_ICONST_3),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 8 = ... & 0x07
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 0x07),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 16 = ... & 0x0f
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 0x0f),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 32 = ... & 0x1f
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 0x1f),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 64 = ... & 0x3f
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 64),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 0x3f),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 128 = ... & 0x7f
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 128),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 0x7f),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 256 = ... & 0x00ff
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 256),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0x00ff),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 512 = ... & 0x01ff
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 512),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0x01ff),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 1024 = ... & 0x03ff
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 1024),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0x03ff),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 2048 = ... & 0x07ff
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 2048),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0x07ff),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 4096 = ... & 0x0fff
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 4096),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0x0fff),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 8192 = ... & 0x1fff
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 8192),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0x1fff),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+//        {   // ... % 16384 = ... & 0x3fff
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 16384),
+//                new SimpleInstruction(InstructionConstants.OP_IREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0x3fff),
+//                new SimpleInstruction(InstructionConstants.OP_IAND),
+//            },
+//        },
+        {   // ... % 1L = 0L
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_1),
+                new SimpleInstruction(InstructionConstants.OP_LREM),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+            },
+        },
+//        {   // ... % 1f = 0f
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_FCONST_1),
+//                new SimpleInstruction(InstructionConstants.OP_FREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_POP),
+//                new SimpleInstruction(InstructionConstants.OP_FCONST_0),
+//            },
+//        },
+//        {   // ... % 1d = 0d
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_DCONST_1),
+//                new SimpleInstruction(InstructionConstants.OP_DREM),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_POP2),
+//                new SimpleInstruction(InstructionConstants.OP_DCONST_0),
+//            },
+//        },
+        {   // -(-...) = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_INEG),
+                new SimpleInstruction(InstructionConstants.OP_INEG),
+            },{
+                // Nothing.
+            },
+        },
+        {   // -(-...) = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_LNEG),
+                new SimpleInstruction(InstructionConstants.OP_LNEG),
+            },{
+                // Nothing.
+            },
+        },
+        {   // -(-...) = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_FNEG),
+                new SimpleInstruction(InstructionConstants.OP_FNEG),
+            },{
+                // Nothing.
+            },
+        },
+        {   // -(-...) = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_DNEG),
+                new SimpleInstruction(InstructionConstants.OP_DNEG),
+            },{
+                // Nothing.
+            },
+        },
+        {   // +(-...) = -...
+            {
+                new SimpleInstruction(InstructionConstants.OP_INEG),
+                new SimpleInstruction(InstructionConstants.OP_IADD),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_ISUB),
+            },
+        },
+        {   // +(-...) = -...
+            {
+                new SimpleInstruction(InstructionConstants.OP_LNEG),
+                new SimpleInstruction(InstructionConstants.OP_LADD),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_LSUB),
+            },
+        },
+        {   // +(-...) = -...
+            {
+                new SimpleInstruction(InstructionConstants.OP_FNEG),
+                new SimpleInstruction(InstructionConstants.OP_FADD),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_FSUB),
+            },
+        },
+        {   // +(-...) = -...
+            {
+                new SimpleInstruction(InstructionConstants.OP_DNEG),
+                new SimpleInstruction(InstructionConstants.OP_DADD),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DSUB),
+            },
+        },
+        {   // ... << 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... << 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... >> 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... >> 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LSHR),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... >>> 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... >>> 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LUSHR),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... & -1 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... & 0 = 0
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+            },
+        },
+        {   // ... & -1L = ...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_M1),
+                new SimpleInstruction(InstructionConstants.OP_LAND),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... & 0L = 0L
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LAND),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+            },
+        },
+        {   // ... | -1 = -1
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new SimpleInstruction(InstructionConstants.OP_IOR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+            },
+        },
+        {   // ... | 0 = ...
+            {
+               new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+               new SimpleInstruction(InstructionConstants.OP_IOR),
+           },{
+                // Nothing.
+            },
+        },
+        {   // ... | -1L = -1L
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_M1),
+                new SimpleInstruction(InstructionConstants.OP_LAND),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_M1),
+            },
+        },
+        {   // ... | 0L = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LOR),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... ^ 0 = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new SimpleInstruction(InstructionConstants.OP_IXOR),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ... ^ 0L = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_LCONST_0),
+                new SimpleInstruction(InstructionConstants.OP_LXOR),
+            },{
+                // Nothing.
+            },
+        },
+        {   // (... & 0x0000ff00) >> 8 = (... >> 8) & 0xff
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0x0000ff00),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0xff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+            },
+        },
+        {   // (... & 0x0000ff00) >>> 8 = (... >>> 8) & 0xff
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0x0000ff00),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 8),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0xff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+            },
+        },
+        {   // (... & 0x00ff0000) >> 16 = (... >> 16) & 0xff
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0x00ff0000),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0xff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+            },
+        },
+        {   // (... & 0x00ff0000) >>> 16 = (... >>> 16) & 0xff
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0x00ff0000),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0xff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+            },
+        },
+        {   // (... & 0xff000000) >> 24 = ... >> 24
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0xff000000),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },
+        },
+        {   // (... & 0xffff0000) >> 16 = ... >> 16
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0xffff0000),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },
+        },
+        {   // (... & 0xffff0000) >>> 16 = ... >>> 16
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0xffff0000),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },
+        },
+        {   // (... >> 24) & 0xff = ... >>> 24
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0xff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },
+        },
+        {   // (... >>> 24) & 0xff = ... >>> 24
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0xff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },
+        },
+        {   // (byte)(... & 0x000000ff) = (byte)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, 0xff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },
+        },
+        {   // (char)(... & 0x0000ffff) = (char)...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0x0000ffff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },
+        },
+        {   // (short)(... & 0x0000ffff) = (short)...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC, I_0x0000ffff),
+                new SimpleInstruction(InstructionConstants.OP_IAND),
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },
+        },
+        {   // (byte)(... >> 24) = ... >> 24
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },
+        },
+        {   // (byte)(... >>> 24) = ... >> 24
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },
+        },
+        {   // (char)(... >> 16) = ... >>> 16
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },
+        },
+        {   // (char)(... >>> 16) = ... >>> 16
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },
+        },
+        {   // (short)(... >> 16) = ... >> 16
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },
+        },
+        {   // (short)(... >>> 16) = ... >> 16
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },
+        },
+        {   // ... << 24 >> 24 = (byte)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 24),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },
+        },
+        {   // ... << 16 >>> 16 = (char)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_IUSHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },
+        },
+        {   // ... << 16 >> 16 = (short)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHL),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 16),
+                new SimpleInstruction(InstructionConstants.OP_ISHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },
+        },
+        {   // ... << 32 >> 32 = (long)(int)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+                new SimpleInstruction(InstructionConstants.OP_LSHL),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+                new SimpleInstruction(InstructionConstants.OP_LSHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_L2I),
+                new SimpleInstruction(InstructionConstants.OP_I2L),
+            },
+        },
+        {   // (int)(... & 0x00000000ffffffffL) = (int)...
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_0x00000000ffffffff),
+                new SimpleInstruction(InstructionConstants.OP_LAND),
+                new SimpleInstruction(InstructionConstants.OP_L2I),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_L2I),
+            },
+        },
+        {   // (... & 0xffffffff00000000L) >> 32 = ... >> 32
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_0xffffffff00000000),
+                new SimpleInstruction(InstructionConstants.OP_LAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+                new SimpleInstruction(InstructionConstants.OP_LSHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+                new SimpleInstruction(InstructionConstants.OP_LSHR),
+            },
+        },
+        {   // (... & 0xffffffff00000000L) >>> 32 = ... >>> 32
+            {
+                new ConstantInstruction(InstructionConstants.OP_LDC2_W, L_0xffffffff00000000),
+                new SimpleInstruction(InstructionConstants.OP_LAND),
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+                new SimpleInstruction(InstructionConstants.OP_LUSHR),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, 32),
+                new SimpleInstruction(InstructionConstants.OP_LUSHR),
+            },
+        },
+        {   // ... += 0 = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_IINC, X, 0),
+            },{
+                // Nothing.
+            },
+        },
+    };
+
+    public static final Instruction[][][] FIELD = new Instruction[][][]
+    {
+        {   // getfield/putfield = nothing
+            {
+                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Y),
+                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, Y),
+            },{
+                // Nothing.
+            },
+        },
+//        {   // putfield_L/putfield_L = pop2_x1/putfield
+//            {
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_L),
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_L),
+//            },{
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new SimpleInstruction(InstructionConstants.OP_POP2),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_L),
+//            },
+//        },
+//        {   // putfield_D/putfield_D = pop2_x1/putfield
+//            {
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_D),
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_D),
+//            },{
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new SimpleInstruction(InstructionConstants.OP_POP2),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_D),
+//            },
+//        },
+//        {   // putfield/putfield = pop_x1/putfield
+//            {
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, Y),
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, Y),
+//            },{
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new SimpleInstruction(InstructionConstants.OP_POP),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, Y),
+//            },
+//        },
+//        {   // putfield_L/getfield_L = dup2_x1/putfield
+//            {
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_L),
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                new ConstantInstruction(InstructionConstants.OP_GETFIELD, FIELD_L),
+//            },{
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new SimpleInstruction(InstructionConstants.OP_DUP2_X1),
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_L),
+//            },
+//        },
+//        {   // putfield_D/getfield_D = dup2_x1/putfield
+//            {
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_D),
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                new ConstantInstruction(InstructionConstants.OP_GETFIELD, FIELD_D),
+//            },{
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new SimpleInstruction(InstructionConstants.OP_DUP2_X1),
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, FIELD_D),
+//            },
+//        },
+//        {   // putfield/getfield = dup_x1/putfield
+//            {
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, Y),
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Y),
+//            },{
+//                new VariableInstruction(InstructionConstants.OP_ALOAD, X),
+//                // ...
+//                new SimpleInstruction(InstructionConstants.OP_DUP_X1),
+//                new ConstantInstruction(InstructionConstants.OP_PUTFIELD, Y),
+//            },
+//        },
+        {   // getstatic/putstatic = nothing
+            {
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, X),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // putstatic_L/putstatic_L = pop2/putstatic
+            {
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_L),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_L),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_L),
+            },
+        },
+        {   // putstatic_D/putstatic_D = pop2/putstatic
+            {
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_D),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_D),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_D),
+            },
+        },
+        {   // putstatic/putstatic = pop/putstatic
+            {
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, X),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, X),
+            },
+        },
+        {   // putstatic_L/getstatic_L = dup2/putstatic
+            {
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_L),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, FIELD_L),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DUP2),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_L),
+            },
+        },
+        {   // putstatic_D/getstatic_D = dup2/putstatic
+            {
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_D),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, FIELD_D),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DUP2),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, FIELD_D),
+            },
+        },
+        {   // putstatic/getstatic = dup/putstatic
+            {
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, X),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, X),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_DUP),
+                new ConstantInstruction(InstructionConstants.OP_PUTSTATIC, X),
+            },
+        },
+    };
+
+    public static final Instruction[][][] CAST = new Instruction[][][]
+    {
+        {   // (byte)(byte)... = (byte)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },
+        },
+        {   // (byte)(char)... = (byte)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },
+        },
+        {   // (byte)(short)... = (byte)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },
+        },
+        {   // (char)(char)... = (char)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },
+        },
+        {   // (char)(short)... = (char)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+            },
+        },
+        {   // (short)(byte)... = (byte)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2B),
+            },
+        },
+        {   // (short)(char)... = (short)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2C),
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },
+        },
+        {   // (short)(short)... = (short)...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_I2S),
+            },
+        },
+        {   // (int)(long)... = ...
+            {
+                new SimpleInstruction(InstructionConstants.OP_I2L),
+                new SimpleInstruction(InstructionConstants.OP_L2I),
+            },{
+                // Nothing.
+            },
+        },
+        {   // (X)(X)... = (X)...
+            {
+                new ConstantInstruction(InstructionConstants.OP_CHECKCAST, X),
+                new ConstantInstruction(InstructionConstants.OP_CHECKCAST, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_CHECKCAST, X),
+            },
+        },
+        // Not handled correctly in all cases by VMs prior to Java 6...
+//        {   // (byte)bytes[...] = bytes[...]
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_BALOAD),
+//                new SimpleInstruction(InstructionConstants.OP_I2B),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BALOAD),
+//            },
+//        },
+//        {   // (short)bytes[...] = bytes[...]
+//            {
+//                 new SimpleInstruction(InstructionConstants.OP_BALOAD),
+//                 new SimpleInstruction(InstructionConstants.OP_I2S),
+//             },{
+//                new SimpleInstruction(InstructionConstants.OP_BALOAD),
+//            },
+//        },
+//        {   // (char)chars[...] = chars[...]
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_CALOAD),
+//                new SimpleInstruction(InstructionConstants.OP_I2C),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_CALOAD),
+//            },
+//        },
+//        {   // (short)shorts[...] = shorts[...]
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_SALOAD),
+//                new SimpleInstruction(InstructionConstants.OP_I2S),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SALOAD),
+//            },
+//        },
+//        {   // bytes[...] = (byte)... = bytes[...] = ...
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_I2B),
+//                new SimpleInstruction(InstructionConstants.OP_BASTORE),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_BASTORE),
+//            },
+//        },
+//        {   // chars[...] = (char)... = chars[...] = ...
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_I2C),
+//                new SimpleInstruction(InstructionConstants.OP_CASTORE),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_CASTORE),
+//            },
+//        },
+//        {   // shorts[...] = (short)... = shorts[...] = ...
+//            {
+//                new SimpleInstruction(InstructionConstants.OP_I2S),
+//                new SimpleInstruction(InstructionConstants.OP_SASTORE),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SASTORE),
+//            },
+//        },
+    };
+
+    public static final Instruction[][][] BRANCH = new Instruction[][][]
+    {
+        {   // goto +3 = nothing
+            {
+                new BranchInstruction(InstructionConstants.OP_GOTO, 3),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ifeq +3 = pop
+            {
+                new BranchInstruction(InstructionConstants.OP_IFEQ, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },
+        },
+        {   // ifne +3 = pop
+            {
+                new BranchInstruction(InstructionConstants.OP_IFNE, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },
+        },
+        {   // iflt +3 = pop
+            {
+                new BranchInstruction(InstructionConstants.OP_IFLT, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },
+        },
+        {   // ifge +3 = pop
+            {
+                new BranchInstruction(InstructionConstants.OP_IFGE, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },
+        },
+        {   // ifgt +3 = pop
+            {
+                new BranchInstruction(InstructionConstants.OP_IFGT, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },
+        },
+        {   // ifle +3 = pop
+            {
+                new BranchInstruction(InstructionConstants.OP_IFLE, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },
+        },
+        {   // ificmpeq +3 = pop2
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPEQ, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },
+        },
+        {   // ificmpne +3 = pop2
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPNE, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },
+        },
+        {   // ificmplt +3 = pop2
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },
+        },
+        {   // ificmpge +3 = pop2
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },
+        },
+        {   // ificmpgt +3 = pop2
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },
+        },
+        {   // ificmple +3 = pop2
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },
+        },
+        {   // ifacmpeq +3 = pop2
+            {
+                new BranchInstruction(InstructionConstants.OP_IFACMPEQ, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },
+        },
+        {   // ifacmpne +3 = pop2
+            {
+                new BranchInstruction(InstructionConstants.OP_IFACMPNE, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP2),
+            },
+        },
+        {   // ifnull +3 = pop
+            {
+                new BranchInstruction(InstructionConstants.OP_IFNULL, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },
+        },
+        {   // ifnonnull +3 = pop
+            {
+                new BranchInstruction(InstructionConstants.OP_IFNONNULL, 3),
+            },{
+                new SimpleInstruction(InstructionConstants.OP_POP),
+            },
+        },
+        {   // if (... == 0) = ifeq
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFICMPEQ, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },
+        },
+        {   // if (0 == i) = iload/ifeq
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPEQ, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },
+        },
+        {   // if (0 == i) = getstatic/ifeq
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPEQ, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },
+        },
+        {   // if (0 == i) = getfield/ifeq
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPEQ, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },
+        },
+        {   // if (... != 0) = ifne
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFICMPNE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },
+        },
+        {   // if (0 != i) = iload/ifeq
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPNE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },
+        },
+        {   // if (0 != i) = getstatic/ifeq
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPNE, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },
+        },
+        {   // if (0 != i) = getfield/ifeq
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPNE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },
+        },
+        {   // if (... < 0) = iflt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // if (... < 1) = ifle
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // if (0 > i) = iload/iflt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // if (1 > i) = iload/ifle
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // if (0 > i) = getstatic/iflt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // if (1 > i) = getstatic/ifle
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // if (0 > i) = getfield/iflt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // if (1 > i) = getfield/ifle
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // if (... >= 0) = ifge
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // if (... >= 1) = ifgt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // if (0 <= i) = iload/ifge
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // if (1 <= i) = iload/ifgt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // if (0 <= i) = getstatic/ifge
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // if (1 <= i) = getstatic/ifgt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // if (0 <= i) = getfield/ifge
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // if (1 <= i) = getfield/ifgt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_1),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // if (... > 0) = ifgt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // if (... > -1) = ifge
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // if (0 < i) = iload/ifgt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // if (-1 < i) = iload/ifge
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // if (0 < i) = getstatic/ifgt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // if (-1 < i) = getstatic/ifge
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // if (0 < i) = getfield/ifgt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // if (-1 < i) = getfield/ifge
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // if (... <= 0) = ifle
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // if (... <= -1) = iflt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // if (0 >= i) = iload/ifle
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // if (-1 >= i) = iload/iflt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ILOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // if (0 >= i) = getstatic/ifle
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // if (-1 >= i) = getstatic/iflt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // if (0 >= i) = getfield/ifle
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // if (-1 >= i) = getfield/iflt
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_M1),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // if (... == null) = ifnull
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new BranchInstruction(InstructionConstants.OP_IFACMPEQ, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFNULL, X),
+            },
+        },
+        {   // if (null == a) = aload/ifnull
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFACMPEQ, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFNULL, X),
+            },
+        },
+        {   // if (null == a) = getstatic/ifnull
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFACMPEQ, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFNULL, X),
+            },
+        },
+        {   // if (null == a) = getfield/ifnull
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFACMPEQ, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFNULL, X),
+            },
+        },
+        {   // if (... != null) = ifnonnull
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new BranchInstruction(InstructionConstants.OP_IFACMPNE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFNONNULL, X),
+            },
+        },
+        {   // if (null != a) = aload/ifnonnull
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFACMPNE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new BranchInstruction(InstructionConstants.OP_IFNONNULL, X),
+            },
+        },
+        {   // if (null != a) = getstatic/ifnonnull
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFACMPNE, X),
+            },{
+                new ConstantInstruction(InstructionConstants.OP_GETSTATIC, Y),
+                new BranchInstruction(InstructionConstants.OP_IFNONNULL, X),
+            },
+        },
+        {   // if (null != a) = getfield/ifnonnull
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFACMPNE, X),
+            },{
+                new VariableInstruction(InstructionConstants.OP_ALOAD, Y),
+                new ConstantInstruction(InstructionConstants.OP_GETFIELD, Z),
+                new BranchInstruction(InstructionConstants.OP_IFNONNULL, X),
+            },
+        },
+        {   // iconst_0/ifeq = goto
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },
+        },
+        {   // iconst/ifeq = nothing
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0, A),
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // bipush/ifeq = nothing
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, A),
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // sipush/ifeq = nothing
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, A),
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // iconst_0/ifne = nothing
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // iconst/ifne = goto
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0, A),
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },
+        },
+        {   // bipush/ifne = goto
+            {
+                new SimpleInstruction(InstructionConstants.OP_BIPUSH, A),
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },
+        },
+        {   // sipush/ifne = goto
+            {
+                new SimpleInstruction(InstructionConstants.OP_SIPUSH, A),
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },
+        },
+        {   // iconst_0/iflt = nothing
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // iconst_0/ifge = goto
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },
+        },
+        {   // iconst_0/ifgt = nothing
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // iconst_0/ifle = goto
+            {
+                new SimpleInstruction(InstructionConstants.OP_ICONST_0),
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },
+        },
+        {   // aconst_null/ifnull = goto
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new BranchInstruction(InstructionConstants.OP_IFNULL, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },
+        },
+        {   // aconst_null/ifnonnul = nothing
+            {
+                new SimpleInstruction(InstructionConstants.OP_ACONST_NULL),
+                new BranchInstruction(InstructionConstants.OP_IFNONNULL, X),
+            },{
+                // Nothing.
+            },
+        },
+        {   // ifeq/goto = ifne
+            {
+                new BranchInstruction(InstructionConstants.OP_IFEQ, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFNE, X),
+            },
+        },
+        {   // ifne/goto = ifeq
+            {
+                new BranchInstruction(InstructionConstants.OP_IFNE, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFEQ, X),
+            },
+        },
+        {   // iflt/goto = ifge
+            {
+                new BranchInstruction(InstructionConstants.OP_IFLT, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFGE, X),
+            },
+        },
+        {   // ifge/goto = iflt
+            {
+                new BranchInstruction(InstructionConstants.OP_IFGE, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFLT, X),
+            },
+        },
+        {   // ifgt/goto = ifle
+            {
+                new BranchInstruction(InstructionConstants.OP_IFGT, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFLE, X),
+            },
+        },
+        {   // ifle/goto = ifgt
+            {
+                new BranchInstruction(InstructionConstants.OP_IFLE, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFGT, X),
+            },
+        },
+        {   // ificmpeq/goto = ificmpne
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPEQ, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFICMPNE, X),
+            },
+        },
+        {   // ificmpne/goto = ificmpeq
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPNE, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFICMPEQ, X),
+            },
+        },
+        {   // ificmplt/goto = ificmpge
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, X),
+            },
+        },
+        {   // ificmpge/goto = ificmplt
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPGE, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFICMPLT, X),
+            },
+        },
+        {   // ificmpgt/goto = ificmple
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, X),
+            },
+        },
+        {   // ificmple/goto = ificmpgt
+            {
+                new BranchInstruction(InstructionConstants.OP_IFICMPLE, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFICMPGT, X),
+            },
+        },
+        {   // ifacmpeq/goto = ifacmpne
+            {
+                new BranchInstruction(InstructionConstants.OP_IFACMPEQ, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFACMPNE, X),
+            },
+        },
+        {   // ifacmpne/goto = ifacmpeq
+            {
+                new BranchInstruction(InstructionConstants.OP_IFACMPNE, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFACMPEQ, X),
+            },
+        },
+        {   // ifnull/goto = ifnonnull
+            {
+                new BranchInstruction(InstructionConstants.OP_IFNULL, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFNONNULL, X),
+            },
+        },
+        {   // ifnonnull/goto = ifnull
+            {
+                new BranchInstruction(InstructionConstants.OP_IFNONNULL, 6),
+                new BranchInstruction(InstructionConstants.OP_GOTO, X),
+            },{
+                new BranchInstruction(InstructionConstants.OP_IFNULL, X),
+            },
+        },
+//        {   // switch (...) { default: ... } = pop/goto ...
+//            {
+//                new TableSwitchInstruction(InstructionConstants.OP_TABLESWITCH, A, X, Y, 0, new int[0]),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_POP),
+//                new BranchInstruction(InstructionConstants.OP_GOTO, A),
+//            },
+//        },
+//        {   // switch (...) { default: ... } = pop/goto ...
+//            {
+//                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, A, 0, new int[0], new int[0]),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_POP),
+//                new BranchInstruction(InstructionConstants.OP_GOTO, A),
+//            },
+//        },
+        {   // switch (...) { case/case/default: ... } = switch (...) { case/default: ... }
+            {
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, A, new int[] { X, Y }, new int[] { A, B }),
+            },{
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, A, new int[] { Y }, new int[] { B }),
+            },
+        },
+        {   // switch (...) { case/case/default: ... } = switch (...) { case/default: ... }
+            {
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, B, new int[] { X, Y }, new int[] { A, B }),
+            },{
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, B, new int[] { X }, new int[] { A }),
+            },
+        },
+        {   // switch (...) { case/case/case/default: ... } = switch (...) { case/case/default: ... }
+            {
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, A, new int[] { X, Y, Z }, new int[] { A, B, C }),
+            },{
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, A, new int[] { Y, Z }, new int[] { B, C }),
+            },
+        },
+        {   // switch (...) { case/case/case/default: ... } = switch (...) { case/case/default: ... }
+            {
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, B, new int[] { X, Y, Z }, new int[] { A, B, C }),
+            },{
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, B, new int[] { X, Z }, new int[] { A, C }),
+            },
+        },
+        {   // switch (...) { case/case/case/default: ... } = switch (...) { case/case/default: ... }
+            {
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, C, new int[] { X, Y, Z }, new int[] { A, B, C }),
+            },{
+                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, C, new int[] { X, Y }, new int[] { A, B }),
+            },
+        },
+//        {   // switch (...) { case ...: ...  default:  ... }
+//            // = if (... == ...) ... else ...
+//            {
+//                new TableSwitchInstruction(InstructionConstants.OP_TABLESWITCH, A, X, Y, 1, new int[] { B }),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, X),
+//                new BranchInstruction(InstructionConstants.OP_IFICMPNE, A),
+//                new BranchInstruction(InstructionConstants.OP_GOTO, B),
+//            },
+//        },
+//        {   // switch (...) { case ...: ...  default:  ... }
+//            // = if (... == ...) ... else ...
+//            {
+//                new LookUpSwitchInstruction(InstructionConstants.OP_LOOKUPSWITCH, A, 1, new int[] { X }, new int[] { B }),
+//            },{
+//                new SimpleInstruction(InstructionConstants.OP_SIPUSH, X),
+//                new BranchInstruction(InstructionConstants.OP_IFICMPNE, A),
+//                new BranchInstruction(InstructionConstants.OP_GOTO, B),
+//            },
+//        }
+    };
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/peephole/InstructionSequenceReplacer.java b/src/proguard/optimize/peephole/InstructionSequenceReplacer.java
new file mode 100644
index 0000000..bce06e2
--- /dev/null
+++ b/src/proguard/optimize/peephole/InstructionSequenceReplacer.java
@@ -0,0 +1,278 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.constant.Constant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.*;
+
+/**
+ * This InstructionVisitor replaces a given pattern instruction sequence by
+ * another given replacement instruction sequence. The arguments of the
+ * instruction sequences can be wildcards that are matched and replaced.
+ *
+ * @see InstructionSequenceMatcher
+ * @author Eric Lafortune
+ */
+public class InstructionSequenceReplacer
+extends      SimplifiedVisitor
+implements   InstructionVisitor,
+             ConstantVisitor
+{
+    private static final boolean DEBUG = false;
+
+
+    private final InstructionSequenceMatcher instructionSequenceMatcher;
+    private final Instruction[]              replacementInstructions;
+    private final BranchTargetFinder         branchTargetFinder;
+    private final CodeAttributeEditor        codeAttributeEditor;
+    private final InstructionVisitor         extraInstructionVisitor;
+
+    private final MyReplacementInstructionFactory replacementInstructionFactory = new MyReplacementInstructionFactory();
+
+
+    /**
+     * Creates a new InstructionSequenceReplacer.
+     * @param patternConstants        any constants referenced by the pattern
+     *                                instruction.
+     * @param patternInstructions     the pattern instruction sequence.
+     * @param replacementInstructions the replacement instruction sequence.
+     * @param branchTargetFinder      a branch target finder that has been
+     *                                initialized to indicate branch targets
+     *                                in the visited code.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     */
+    public InstructionSequenceReplacer(Constant[]          patternConstants,
+                                       Instruction[]       patternInstructions,
+                                       Instruction[]       replacementInstructions,
+                                       BranchTargetFinder  branchTargetFinder,
+                                       CodeAttributeEditor codeAttributeEditor)
+    {
+        this(patternConstants,
+             patternInstructions,
+             replacementInstructions,
+             branchTargetFinder,
+             codeAttributeEditor,
+             null);
+    }
+
+
+    /**
+     * Creates a new InstructionSequenceReplacer.
+     * @param patternConstants        any constants referenced by the pattern
+     *                                instruction.
+     * @param branchTargetFinder      a branch target finder that has been
+     *                                initialized to indicate branch targets
+     *                                in the visited code.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     * @param extraInstructionVisitor an optional extra visitor for all deleted
+     *                                load instructions.
+     */
+    public InstructionSequenceReplacer(Constant[]          patternConstants,
+                                       Instruction[]       patternInstructions,
+                                       Instruction[]       replacementInstructions,
+                                       BranchTargetFinder  branchTargetFinder,
+                                       CodeAttributeEditor codeAttributeEditor,
+                                       InstructionVisitor  extraInstructionVisitor)
+    {
+        this.instructionSequenceMatcher = new InstructionSequenceMatcher(patternConstants, patternInstructions);
+        this.replacementInstructions    = replacementInstructions;
+        this.branchTargetFinder         = branchTargetFinder;
+        this.codeAttributeEditor        = codeAttributeEditor;
+        this.extraInstructionVisitor    = extraInstructionVisitor;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        // Reset the instruction sequence matcher if the instruction is a branch
+        // target or if it has already been modified.
+        if (branchTargetFinder.isTarget(offset) ||
+            codeAttributeEditor.isModified(offset))
+        {
+            instructionSequenceMatcher.reset();
+        }
+
+        // Try to match the instruction.
+        instruction.accept(clazz, method, codeAttribute, offset, instructionSequenceMatcher);
+
+        // Did the instruction sequence match and is it still unmodified?
+        if (instructionSequenceMatcher.isMatching() &&
+            matchedInstructionsUnmodified())
+        {
+            if (DEBUG)
+            {
+                System.out.println("InstructionSequenceReplacer: ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+                System.out.println("  Matched:");
+                for (int index = 0; index < instructionSequenceMatcher.instructionCount(); index++)
+                {
+                    int matchedOffset = instructionSequenceMatcher.matchedInstructionOffset(index);
+                    System.out.println("    "+InstructionFactory.create(codeAttribute.code, matchedOffset).toString(matchedOffset));
+                }
+                System.out.println("  Replacement:");
+                for (int index = 0; index < replacementInstructions.length; index++)
+                {
+                    int matchedOffset = instructionSequenceMatcher.matchedInstructionOffset(index);
+                    System.out.println("    "+replacementInstructionFactory.create(index).shrink().toString(matchedOffset));
+                }
+            }
+
+            // Replace the instruction sequence.
+            for (int index = 0; index < replacementInstructions.length; index++)
+            {
+                codeAttributeEditor.replaceInstruction(instructionSequenceMatcher.matchedInstructionOffset(index),
+                                                       replacementInstructionFactory.create(index).shrink());
+            }
+
+            // Delete any remaining instructions in the from sequence.
+            for (int index = replacementInstructions.length; index < instructionSequenceMatcher.instructionCount(); index++)
+            {
+                codeAttributeEditor.deleteInstruction(instructionSequenceMatcher.matchedInstructionOffset(index));
+            }
+
+            // Visit the instruction, if required.
+            if (extraInstructionVisitor != null)
+            {
+                instruction.accept(clazz,
+                                   method,
+                                   codeAttribute,
+                                   offset,
+                                   extraInstructionVisitor);
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the matched pattern instructions haven't been modified
+     * before.
+     */
+    private boolean matchedInstructionsUnmodified()
+    {
+        for (int index = 0; index < instructionSequenceMatcher.instructionCount(); index++)
+        {
+            if (codeAttributeEditor.isModified(instructionSequenceMatcher.matchedInstructionOffset(index)))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * This class creates replacement instructions for matched sequences, with
+     * any matched arguments filled out.
+     */
+    private class MyReplacementInstructionFactory
+    implements    InstructionVisitor
+    {
+        private Instruction replacementInstruction;
+
+
+        /**
+         * Creates the replacement instruction for the given index in the
+         * instruction sequence.
+         */
+        public Instruction create(int index)
+        {
+            // Create the instruction.
+            replacementInstructions[index].accept(null,
+                                                  null,
+                                                  null,
+                                                  instructionSequenceMatcher.matchedInstructionOffset(index),
+                                                  this);
+
+            // Return it.
+            return replacementInstruction.shrink();
+        }
+
+
+        // Implementations for InstructionVisitor.
+
+        public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+        {
+            replacementInstruction =
+                new SimpleInstruction(simpleInstruction.opcode,
+                                      instructionSequenceMatcher.matchedArgument(simpleInstruction.constant));
+        }
+
+
+        public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+        {
+            replacementInstruction =
+                new VariableInstruction(variableInstruction.opcode,
+                                        instructionSequenceMatcher.matchedArgument(variableInstruction.variableIndex),
+                                        instructionSequenceMatcher.matchedArgument(variableInstruction.constant));
+        }
+
+
+        public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+        {
+            replacementInstruction =
+                new ConstantInstruction(constantInstruction.opcode,
+                                        instructionSequenceMatcher.matchedConstantIndex(constantInstruction.constantIndex),
+                                        instructionSequenceMatcher.matchedArgument(constantInstruction.constant));
+        }
+
+
+        public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+        {
+            replacementInstruction =
+                new BranchInstruction(branchInstruction.opcode,
+                                      instructionSequenceMatcher.matchedBranchOffset(offset, branchInstruction.branchOffset));
+        }
+
+
+        public void visitTableSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, TableSwitchInstruction tableSwitchInstruction)
+        {
+            replacementInstruction =
+                new TableSwitchInstruction(tableSwitchInstruction.opcode,
+                                           instructionSequenceMatcher.matchedBranchOffset(offset, tableSwitchInstruction.defaultOffset),
+                                           instructionSequenceMatcher.matchedArgument(tableSwitchInstruction.lowCase),
+                                           instructionSequenceMatcher.matchedArgument(tableSwitchInstruction.highCase),
+                                           instructionSequenceMatcher.matchedJumpOffsets(offset, tableSwitchInstruction.jumpOffsets));
+
+        }
+
+
+        public void visitLookUpSwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, LookUpSwitchInstruction lookUpSwitchInstruction)
+        {
+            replacementInstruction =
+                new LookUpSwitchInstruction(lookUpSwitchInstruction.opcode,
+                                            instructionSequenceMatcher.matchedBranchOffset(offset, lookUpSwitchInstruction.defaultOffset),
+                                            instructionSequenceMatcher.matchedArguments(lookUpSwitchInstruction.cases),
+                                            instructionSequenceMatcher.matchedJumpOffsets(offset, lookUpSwitchInstruction.jumpOffsets));
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/InstructionSequencesReplacer.java b/src/proguard/optimize/peephole/InstructionSequencesReplacer.java
new file mode 100644
index 0000000..f12b51a
--- /dev/null
+++ b/src/proguard/optimize/peephole/InstructionSequencesReplacer.java
@@ -0,0 +1,138 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.constant.Constant;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.Instruction;
+import proguard.classfile.instruction.visitor.*;
+
+/**
+ * This InstructionVisitor replaces multiple instruction sequences at once.
+ *
+ * @see InstructionSequenceReplacer
+ * @author Eric Lafortune
+ */
+public class InstructionSequencesReplacer
+extends      MultiInstructionVisitor
+implements   InstructionVisitor
+{
+    private static final int PATTERN_INDEX     = 0;
+    private static final int REPLACEMENT_INDEX = 1;
+
+
+    /**
+     * Creates a new InstructionSequencesReplacer.
+     * @param patternConstants        any constants referenced by the pattern
+     *                                instruction.
+     * @param instructionSequences    the instruction sequences to be replaced,
+     *                                with subsequently the sequence pair index,
+     *                                the patten/replacement index (0 or 1),
+     *                                and the instruction index in the sequence.
+     * @param branchTargetFinder      a branch target finder that has been
+     *                                initialized to indicate branch targets
+     *                                in the visited code.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     */
+    public InstructionSequencesReplacer(Constant[]          patternConstants,
+                                        Instruction[][][]   instructionSequences,
+                                        BranchTargetFinder  branchTargetFinder,
+                                        CodeAttributeEditor codeAttributeEditor)
+    {
+        this(patternConstants,
+             instructionSequences,
+             branchTargetFinder,
+             codeAttributeEditor,
+             null);
+    }
+
+
+    /**
+     * Creates a new InstructionSequenceReplacer.
+     * @param patternConstants        any constants referenced by the pattern
+     *                                instruction.
+     * @param instructionSequences    the instruction sequences to be replaced,
+     *                                with subsequently the sequence pair index,
+     *                                the patten/replacement index (0 or 1),
+     *                                and the instruction index in the sequence.
+     * @param branchTargetFinder      a branch target finder that has been
+     *                                initialized to indicate branch targets
+     *                                in the visited code.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     * @param extraInstructionVisitor an optional extra visitor for all deleted
+     *                                load instructions.
+     */
+    public InstructionSequencesReplacer(Constant[]          patternConstants,
+                                        Instruction[][][]   instructionSequences,
+                                        BranchTargetFinder  branchTargetFinder,
+                                        CodeAttributeEditor codeAttributeEditor,
+                                        InstructionVisitor  extraInstructionVisitor)
+    {
+        super(createInstructionSequenceReplacers(patternConstants,
+                                                 instructionSequences,
+                                                 branchTargetFinder,
+                                                 codeAttributeEditor,
+                                                 extraInstructionVisitor));
+    }
+
+
+    /**
+     * Creates an array of InstructionSequenceReplacer instances.
+     * @param patternConstants        any constants referenced by the pattern
+     *                                instruction.
+     * @param instructionSequences    the instruction sequences to be replaced,
+     *                                with subsequently the sequence pair index,
+     *                                the from/to index (0 or 1), and the
+     *                                instruction index in the sequence.
+     * @param branchTargetFinder      a branch target finder that has been
+     *                                initialized to indicate branch targets
+     *                                in the visited code.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     * @param extraInstructionVisitor an optional extra visitor for all deleted
+     *                                load instructions.
+     */
+    private static InstructionVisitor[] createInstructionSequenceReplacers(Constant[]          patternConstants,
+                                                                           Instruction[][][]   instructionSequences,
+                                                                           BranchTargetFinder  branchTargetFinder,
+                                                                           CodeAttributeEditor codeAttributeEditor,
+                                                                           InstructionVisitor  extraInstructionVisitor)
+    {
+        InstructionVisitor[] instructionSequenceReplacers =
+            new InstructionSequenceReplacer[instructionSequences.length];
+
+        for (int index = 0; index < instructionSequenceReplacers.length; index++)
+        {
+            Instruction[][] instructionSequencePair = instructionSequences[index];
+            instructionSequenceReplacers[index] =
+                new InstructionSequenceReplacer(patternConstants,
+                                                instructionSequencePair[PATTERN_INDEX],
+                                                instructionSequencePair[REPLACEMENT_INDEX],
+                                                branchTargetFinder,
+                                                codeAttributeEditor,
+                                                extraInstructionVisitor);
+        }
+
+        return instructionSequenceReplacers;
+    }
+}
diff --git a/src/proguard/optimize/peephole/MemberPrivatizer.java b/src/proguard/optimize/peephole/MemberPrivatizer.java
new file mode 100644
index 0000000..55b2f31
--- /dev/null
+++ b/src/proguard/optimize/peephole/MemberPrivatizer.java
@@ -0,0 +1,103 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.editor.MethodInvocationFixer;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.info.NonPrivateMemberMarker;
+
+/**
+ * This MemberVisitor makes all class members that it visits private, unless
+ * they have been marked by a NonPrivateMemberMarker. The invocations of
+ * privatized methods still have to be fixed.
+ *
+ * @see NonPrivateMemberMarker
+ * @see MethodInvocationFixer
+ * @author Eric Lafortune
+ */
+public class MemberPrivatizer
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final MemberVisitor extraMemberVisitor;
+
+
+    /**
+     * Creates a new MemberPrivatizer.
+     */
+    public MemberPrivatizer()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new MemberPrivatizer.
+     * @param extraMemberVisitor an optional extra visitor for all privatized
+     *                           class members.
+     */
+    public MemberPrivatizer(MemberVisitor extraMemberVisitor)
+    {
+        this.extraMemberVisitor = extraMemberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Is the field unmarked?
+        if (NonPrivateMemberMarker.canBeMadePrivate(programField))
+        {
+            // Make the field private.
+            programField.u2accessFlags =
+                AccessUtil.replaceAccessFlags(programField.u2accessFlags,
+                                              ClassConstants.INTERNAL_ACC_PRIVATE);
+
+            // Visit the field, if required.
+            if (extraMemberVisitor != null)
+            {
+                extraMemberVisitor.visitProgramField(programClass, programField);
+            }
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Is the method unmarked?
+        if (NonPrivateMemberMarker.canBeMadePrivate(programMethod))
+        {
+            // Make the method private.
+            programMethod.u2accessFlags =
+                AccessUtil.replaceAccessFlags(programMethod.u2accessFlags,
+                                              ClassConstants.INTERNAL_ACC_PRIVATE);
+
+            // Visit the method, if required.
+            if (extraMemberVisitor != null)
+            {
+                extraMemberVisitor.visitProgramMethod(programClass, programMethod);
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/MethodFinalizer.java b/src/proguard/optimize/peephole/MethodFinalizer.java
new file mode 100644
index 0000000..af1811b
--- /dev/null
+++ b/src/proguard/optimize/peephole/MethodFinalizer.java
@@ -0,0 +1,93 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+import proguard.optimize.KeepMarker;
+
+/**
+ * This <code>MemberVisitor</code> makes the program methods that it visits
+ * final, if possible.
+ *
+ * @author Eric Lafortune
+ */
+public class MethodFinalizer
+extends      SimplifiedVisitor
+implements   MemberVisitor
+{
+    private final MemberVisitor extraMemberVisitor;
+
+    private final MemberFinder memberFinder = new MemberFinder();
+
+
+    /**
+     * Creates a new ClassFinalizer.
+     */
+    public MethodFinalizer()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new ClassFinalizer.
+     * @param extraMemberVisitor an optional extra visitor for all finalized
+     *                           methods.
+     */
+    public MethodFinalizer(MemberVisitor extraMemberVisitor)
+    {
+        this.extraMemberVisitor = extraMemberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+    
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        String name = programMethod.getName(programClass);
+
+        // If the method is not already private/static/final/abstract,
+        // and it is not a constructor,
+        // and its class is final,
+        //     or it is not being kept and it is not overridden,
+        // then make it final.
+        if ((programMethod.u2accessFlags & (ClassConstants.INTERNAL_ACC_PRIVATE |
+                                            ClassConstants.INTERNAL_ACC_STATIC  |
+                                            ClassConstants.INTERNAL_ACC_FINAL   |
+                                            ClassConstants.INTERNAL_ACC_ABSTRACT)) == 0 &&
+            !name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)                      &&
+            ((programClass.u2accessFlags & ClassConstants.INTERNAL_ACC_FINAL) != 0 ||
+             (!KeepMarker.isKept(programMethod) &&
+              (programClass.subClasses == null ||
+               !memberFinder.isOverriden(programClass, programMethod)))))
+        {
+            programMethod.u2accessFlags |= ClassConstants.INTERNAL_ACC_FINAL;
+
+            // Visit the method, if required.
+            if (extraMemberVisitor != null)
+            {
+                extraMemberVisitor.visitProgramMethod(programClass, programMethod);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/peephole/MethodInliner.java b/src/proguard/optimize/peephole/MethodInliner.java
new file mode 100644
index 0000000..55f9ccb
--- /dev/null
+++ b/src/proguard/optimize/peephole/MethodInliner.java
@@ -0,0 +1,567 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.editor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.info.*;
+
+import java.util.Stack;
+
+/**
+ * This AttributeVisitor inlines short methods or methods that are only invoked
+ * once, in the code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class MethodInliner
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ConstantVisitor,
+             MemberVisitor
+{
+    private static final int MAXIMUM_INLINED_CODE_LENGTH       = Integer.parseInt(System.getProperty("maximum.inlined.code.length",      "8"));
+    private static final int MAXIMUM_RESULTING_CODE_LENGTH_JSE = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "8000"));
+    private static final int MAXIMUM_RESULTING_CODE_LENGTH_JME = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "2000"));
+    private static final int MAXIMUM_CODE_EXPANSION            = 2;
+    private static final int MAXIMUM_EXTRA_CODE_LENGTH         = 128;
+
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    private final boolean            microEdition;
+    private final boolean            allowAccessModification;
+    private final boolean            inlineSingleInvocations;
+    private final InstructionVisitor extraInlinedInvocationVisitor;
+
+    private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer();
+    private final AccessMethodMarker    accessMethodMarker    = new AccessMethodMarker();
+    private final CatchExceptionMarker  catchExceptionMarker  = new CatchExceptionMarker();
+    private final StackSizeComputer     stackSizeComputer     = new StackSizeComputer();
+
+    private ProgramClass       targetClass;
+    private ProgramMethod      targetMethod;
+    private ConstantAdder      constantAdder;
+    private ExceptionInfoAdder exceptionInfoAdder;
+    private int                estimatedResultingCodeLength;
+    private boolean            inlining;
+    private Stack              inliningMethods              = new Stack();
+    private boolean            emptyInvokingStack;
+    private int                uninitializedObjectCount;
+    private int                variableOffset;
+    private boolean            inlined;
+    private boolean            inlinedAny;
+
+
+    /**
+     * Creates a new MethodInliner.
+     * @param microEdition            indicates whether the resulting code is
+     *                                targeted at Java Micro Edition.
+     * @param allowAccessModification indicates whether the access modifiers of
+     *                                classes and class members can be changed
+     *                                in order to inline methods.
+     * @param inlineSingleInvocations indicates whether the single invocations
+     *                                should be inlined, or, alternatively,
+     *                                short methods.
+     */
+    public MethodInliner(boolean microEdition,
+                         boolean allowAccessModification,
+                         boolean inlineSingleInvocations)
+    {
+        this(microEdition,
+             allowAccessModification,
+             inlineSingleInvocations,
+             null);
+    }
+
+
+    /**
+     * Creates a new MethodInliner.
+     * @param microEdition            indicates whether the resulting code is
+     *                                targeted at Java Micro Edition.
+     * @param allowAccessModification indicates whether the access modifiers of
+     *                                classes and class members can be changed
+     *                                in order to inline methods.
+     * @param inlineSingleInvocations indicates whether the single invocations
+     *                                should be inlined, or, alternatively,
+     *                                short methods.
+     * @param extraInlinedInvocationVisitor an optional extra visitor for all
+     *                                      inlined invocation instructions.
+     */
+    public MethodInliner(boolean            microEdition,
+                         boolean            allowAccessModification,
+                         boolean            inlineSingleInvocations,
+                         InstructionVisitor extraInlinedInvocationVisitor)
+    {
+        this.microEdition                  = microEdition;
+        this.allowAccessModification       = allowAccessModification;
+        this.inlineSingleInvocations       = inlineSingleInvocations;
+        this.extraInlinedInvocationVisitor = extraInlinedInvocationVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (!inlining)
+        {
+//            codeAttributeComposer.DEBUG = DEBUG =
+//                clazz.getName().equals("abc/Def") &&
+//                method.getName(clazz).equals("abc");
+
+            targetClass                  = (ProgramClass)clazz;
+            targetMethod                 = (ProgramMethod)method;
+            constantAdder                = new ConstantAdder(targetClass);
+            exceptionInfoAdder           = new ExceptionInfoAdder(targetClass, codeAttributeComposer);
+            estimatedResultingCodeLength = codeAttribute.u4codeLength;
+            inliningMethods.clear();
+            uninitializedObjectCount     = method.getName(clazz).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ? 1 : 0;
+            inlinedAny                   = false;
+            codeAttributeComposer.reset();
+            stackSizeComputer.visitCodeAttribute(clazz, method, codeAttribute);
+
+            // Append the body of the code.
+            copyCode(clazz, method, codeAttribute);
+
+            targetClass   = null;
+            targetMethod  = null;
+            constantAdder = null;
+
+            // Update the code attribute if any code has been inlined.
+            if (inlinedAny)
+            {
+                codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);
+
+                // Update the accessing flags.
+                codeAttribute.instructionsAccept(clazz, method, accessMethodMarker);
+
+                // Update the exception catching flags.
+                catchExceptionMarker.visitCodeAttribute(clazz, method, codeAttribute);
+            }
+        }
+
+        // Only inline the method if it is invoked once or if it is short.
+        else if ((inlineSingleInvocations ?
+                      MethodInvocationMarker.getInvocationCount(method) == 1 :
+                      codeAttribute.u4codeLength <= MAXIMUM_INLINED_CODE_LENGTH) &&
+                 estimatedResultingCodeLength + codeAttribute.u4codeLength <
+                 (microEdition ?
+                     MAXIMUM_RESULTING_CODE_LENGTH_JME :
+                     MAXIMUM_RESULTING_CODE_LENGTH_JSE))
+        {
+            if (DEBUG)
+            {
+                System.out.println("MethodInliner: inlining ["+
+                                   clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"] in ["+
+                                   targetClass.getName()+"."+targetMethod.getName(targetClass)+targetMethod.getDescriptor(targetClass)+"]");
+            }
+
+            // Ignore the removal of the original method invocation,
+            // the addition of the parameter setup, and
+            // the modification of a few inlined instructions.
+            estimatedResultingCodeLength += codeAttribute.u4codeLength;
+
+            // Append instructions to store the parameters.
+            storeParameters(clazz, method);
+
+            // Inline the body of the code.
+            copyCode(clazz, method, codeAttribute);
+
+            inlined    = true;
+            inlinedAny = true;
+        }
+    }
+
+
+    /**
+     * Appends instructions to pop the parameters for the given method, storing
+     * them in new local variables.
+     */
+    private void storeParameters(Clazz clazz, Method method)
+    {
+        String descriptor = method.getDescriptor(clazz);
+
+        boolean isStatic =
+            (method.getAccessFlags() & ClassConstants.INTERNAL_ACC_STATIC) != 0;
+
+        // Count the number of parameters, taking into account their categories.
+        int parameterCount  = ClassUtil.internalMethodParameterCount(descriptor);
+        int parameterSize   = ClassUtil.internalMethodParameterSize(descriptor);
+        int parameterOffset = isStatic ? 0 : 1;
+
+        // Store the parameter types.
+        String[] parameterTypes = new String[parameterSize];
+
+        InternalTypeEnumeration internalTypeEnumeration =
+            new InternalTypeEnumeration(descriptor);
+
+        for (int parameterIndex = 0; parameterIndex < parameterSize; parameterIndex++)
+        {
+            String parameterType = internalTypeEnumeration.nextType();
+            parameterTypes[parameterIndex] = parameterType;
+            if (ClassUtil.internalTypeSize(parameterType) == 2)
+            {
+                parameterIndex++;
+            }
+        }
+
+        codeAttributeComposer.beginCodeFragment(parameterSize+1);
+
+        // Go over the parameter types backward, storing the stack entries
+        // in their corresponding variables.
+        for (int parameterIndex = parameterSize-1; parameterIndex >= 0; parameterIndex--)
+        {
+            String parameterType = parameterTypes[parameterIndex];
+            if (parameterType != null)
+            {
+                byte opcode;
+                switch (parameterType.charAt(0))
+                {
+                    case ClassConstants.INTERNAL_TYPE_BOOLEAN:
+                    case ClassConstants.INTERNAL_TYPE_BYTE:
+                    case ClassConstants.INTERNAL_TYPE_CHAR:
+                    case ClassConstants.INTERNAL_TYPE_SHORT:
+                    case ClassConstants.INTERNAL_TYPE_INT:
+                        opcode = InstructionConstants.OP_ISTORE;
+                        break;
+
+                    case ClassConstants.INTERNAL_TYPE_LONG:
+                        opcode = InstructionConstants.OP_LSTORE;
+                        break;
+
+                    case ClassConstants.INTERNAL_TYPE_FLOAT:
+                        opcode = InstructionConstants.OP_FSTORE;
+                        break;
+
+                    case ClassConstants.INTERNAL_TYPE_DOUBLE:
+                        opcode = InstructionConstants.OP_DSTORE;
+                        break;
+
+                    default:
+                        opcode = InstructionConstants.OP_ASTORE;
+                        break;
+                }
+
+                codeAttributeComposer.appendInstruction(parameterSize-parameterIndex-1,
+                                                        new VariableInstruction(opcode, variableOffset + parameterOffset + parameterIndex).shrink());
+            }
+        }
+
+        // Put the 'this' reference in variable 0 (plus offset).
+        if (!isStatic)
+        {
+            codeAttributeComposer.appendInstruction(parameterSize,
+                                                    new VariableInstruction(InstructionConstants.OP_ASTORE, variableOffset).shrink());
+        }
+
+        codeAttributeComposer.endCodeFragment();
+    }
+
+
+    /**
+     * Appends the code of the given code attribute.
+     */
+    private void copyCode(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // The code may expand, due to expanding constant and variable
+        // instructions.
+        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
+
+        // Copy the instructions.
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        // Copy the exceptions.
+        codeAttribute.exceptionsAccept(clazz, method, exceptionInfoAdder);
+
+        // Append a label just after the code.
+        codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
+
+        codeAttributeComposer.endCodeFragment();
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        codeAttributeComposer.appendInstruction(offset, instruction.shrink());
+    }
+
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        // Are we inlining this instruction?
+        if (inlining)
+        {
+            // Replace any return instructions by branches to the end of the code.
+            switch (simpleInstruction.opcode)
+            {
+                case InstructionConstants.OP_IRETURN:
+                case InstructionConstants.OP_LRETURN:
+                case InstructionConstants.OP_FRETURN:
+                case InstructionConstants.OP_DRETURN:
+                case InstructionConstants.OP_ARETURN:
+                case InstructionConstants.OP_RETURN:
+                    // Are we not at the last instruction?
+                    if (offset < codeAttribute.u4codeLength-1)
+                    {
+                        // Replace the return instruction by a branch instruction.
+                        Instruction branchInstruction =
+                            new BranchInstruction(InstructionConstants.OP_GOTO_W,
+                                                  codeAttribute.u4codeLength - offset);
+
+                        codeAttributeComposer.appendInstruction(offset,
+                                                                branchInstruction.shrink());
+                    }
+                    else
+                    {
+                        // Just leave out the instruction, but put in a label,
+                        // for the sake of any other branch instructions.
+                        codeAttributeComposer.appendLabel(offset);
+                    }
+
+                    return;
+            }
+        }
+
+        codeAttributeComposer.appendInstruction(offset, simpleInstruction.shrink());
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        // Are we inlining this instruction?
+        if (inlining)
+        {
+            // Update the variable index.
+            variableInstruction.variableIndex += variableOffset;
+        }
+
+        codeAttributeComposer.appendInstruction(offset, variableInstruction.shrink());
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        // Is it a method invocation?
+        switch (constantInstruction.opcode)
+        {
+            case InstructionConstants.OP_NEW:
+                uninitializedObjectCount++;
+                break;
+
+            case InstructionConstants.OP_INVOKEVIRTUAL:
+            case InstructionConstants.OP_INVOKESPECIAL:
+            case InstructionConstants.OP_INVOKESTATIC:
+            case InstructionConstants.OP_INVOKEINTERFACE:
+                // See if we can inline it.
+                inlined = false;
+
+                // Append a label, in case the invocation will be inlined.
+                codeAttributeComposer.appendLabel(offset);
+
+                emptyInvokingStack =
+                    !inlining &&
+                    stackSizeComputer.isReachable(offset) &&
+                    stackSizeComputer.getStackSize(offset) == 0;
+
+                variableOffset += codeAttribute.u2maxLocals;
+
+                clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
+
+                variableOffset -= codeAttribute.u2maxLocals;
+
+                // Was the method inlined?
+                if (inlined)
+                {
+                    if (extraInlinedInvocationVisitor != null)
+                    {
+                        extraInlinedInvocationVisitor.visitConstantInstruction(clazz, method, codeAttribute, offset, constantInstruction);
+                    }
+
+                    // The invocation itself is no longer necessary.
+                    return;
+                }
+
+                break;
+        }
+
+        // Are we inlining this instruction?
+        if (inlining)
+        {
+            // Make sure the constant is present in the constant pool of the
+            // target class.
+            constantInstruction.constantIndex =
+                constantAdder.addConstant(clazz, constantInstruction.constantIndex);
+        }
+
+        codeAttributeComposer.appendInstruction(offset, constantInstruction.shrink());
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) {}
+
+
+    public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant)
+    {
+        methodrefConstant.referencedMemberAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitAnyMember(Clazz Clazz, Member member) {}
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        int accessFlags = programMethod.getAccessFlags();
+
+        if (// Only inline the method if it is private, static, or final.
+            (accessFlags & (ClassConstants.INTERNAL_ACC_PRIVATE |
+                            ClassConstants.INTERNAL_ACC_STATIC  |
+                            ClassConstants.INTERNAL_ACC_FINAL)) != 0                               &&
+
+            // Only inline the method if it is not synchronized, etc.
+            (accessFlags & (ClassConstants.INTERNAL_ACC_SYNCHRONIZED |
+                            ClassConstants.INTERNAL_ACC_NATIVE       |
+                            ClassConstants.INTERNAL_ACC_INTERFACE    |
+                            ClassConstants.INTERNAL_ACC_ABSTRACT)) == 0                            &&
+
+            // Don't inline an <init> method, except in an <init> method in the
+            // same class.
+//            (!programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT) ||
+//             (programClass.equals(targetClass) &&
+//              targetMethod.getName(targetClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))) &&
+            !programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)  &&
+
+            // Don't inline a method into itself.
+            (!programMethod.equals(targetMethod) ||
+             !programClass.equals(targetClass))                                                    &&
+
+            // Only inline the method if it isn't recursing.
+            !inliningMethods.contains(programMethod)                                               &&
+
+            // Only inline the method if its target class has at least the
+            // same version number as the source class, in order to avoid
+            // introducing incompatible constructs.
+            targetClass.u4version >= programClass.u4version                                        &&
+
+            // Only inline the method if it doesn't invoke a super method, or if
+            // it is in the same class.
+            (!SuperInvocationMarker.invokesSuperMethods(programMethod) ||
+             programClass.equals(targetClass))                                                     &&
+
+            // Only inline the method if it doesn't branch backward while there
+            // are uninitialized objects.
+            (!BackwardBranchMarker.branchesBackward(programMethod) ||
+             uninitializedObjectCount == 0)                                                        &&
+
+            // Only inline if the code access of the inlined method allows it.
+            (allowAccessModification ||
+             ((!AccessMethodMarker.accessesPrivateCode(programMethod) ||
+               programClass.equals(targetClass)) &&
+
+              (!AccessMethodMarker.accessesPackageCode(programMethod) ||
+               ClassUtil.internalPackageName(programClass.getName()).equals(
+               ClassUtil.internalPackageName(targetClass.getName())))))                            &&
+
+//               (!AccessMethodMarker.accessesProtectedCode(programMethod) ||
+//                targetClass.extends_(programClass) ||
+//                targetClass.implements_(programClass)) ||
+            (!AccessMethodMarker.accessesProtectedCode(programMethod) ||
+             programClass.equals(targetClass))                                                     &&
+
+            // Only inline the method if it doesn't catch exceptions, or if it
+            // is invoked with an empty stack.
+            (!CatchExceptionMarker.catchesExceptions(programMethod) ||
+             emptyInvokingStack)                                                                   &&
+
+            // Only inline the method if it comes from the same class or from
+            // a class with a static initializer.
+            (programClass.equals(targetClass) ||
+             programClass.findMethod(ClassConstants.INTERNAL_METHOD_NAME_CLINIT,
+                                     ClassConstants.INTERNAL_METHOD_TYPE_CLINIT) == null))
+        {
+//            System.out.print("MethodInliner: inlining ");
+//            programMethod.accept(programClass, new SimpleClassPrinter(true));
+//            System.out.print("               in       ");
+//            targetMethod.accept(targetClass, new SimpleClassPrinter(true));
+//
+//            System.out.println("  Private:   "+
+//                               (!AccessMethodMarker.accessesPrivateCode(programMethod) ||
+//                                programClass.equals(targetClass)));
+//
+//            System.out.println("  Package:   "+
+//                               (!AccessMethodMarker.accessesPackageCode(programMethod) ||
+//                                ClassUtil.internalPackageName(programClass.getName()).equals(
+//                                ClassUtil.internalPackageName(targetClass.getName()))));
+//
+//            System.out.println("  Protected: "+
+//                               ((!AccessMethodMarker.accessesProtectedCode(programMethod) ||
+//                                 targetClass.extends_(programClass) ||
+//                                 targetClass.implements_(programClass)) ||
+//                                ClassUtil.internalPackageName(programClass.getName()).equals(
+//                                ClassUtil.internalPackageName(targetClass.getName()))));
+
+            boolean oldInlining = inlining;
+            inlining = true;
+            inliningMethods.push(programMethod);
+
+            // Inline the method body.
+            programMethod.attributesAccept(programClass, this);
+
+            // Update the optimization information of the target method.
+            MethodOptimizationInfo info =
+                MethodOptimizationInfo.getMethodOptimizationInfo(targetMethod);
+            if (info != null)
+            {
+                info.merge(MethodOptimizationInfo.getMethodOptimizationInfo(programMethod));
+            }
+
+            inlining = oldInlining;
+            inliningMethods.pop();
+        }
+        else if (programMethod.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+        {
+            uninitializedObjectCount--;
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/NopRemover.java b/src/proguard/optimize/peephole/NopRemover.java
new file mode 100644
index 0000000..69adb30
--- /dev/null
+++ b/src/proguard/optimize/peephole/NopRemover.java
@@ -0,0 +1,89 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.CodeAttribute;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor removes all nop instructions that it encounters.
+ *
+ * @author Eric Lafortune
+ */
+public class NopRemover
+extends      SimplifiedVisitor
+implements   InstructionVisitor
+{
+    private final CodeAttributeEditor codeAttributeEditor;
+    private final InstructionVisitor  extraInstructionVisitor;
+
+
+    /**
+     * Creates a new NopRemover.
+     * @param codeAttributeEditor a code editor that can be used for
+     *                            accumulating changes to the code.
+     */
+    public NopRemover(CodeAttributeEditor codeAttributeEditor)
+    {
+        this(codeAttributeEditor, null);
+    }
+
+
+    /**
+     * Creates a new NopRemover.
+     * @param codeAttributeEditor     a code editor that can be used for
+     *                                accumulating changes to the code.
+     * @param extraInstructionVisitor an optional extra visitor for all removed
+     *                                nop instructions.
+     */
+    public NopRemover(CodeAttributeEditor codeAttributeEditor,
+                      InstructionVisitor  extraInstructionVisitor)
+    {
+        this.codeAttributeEditor     = codeAttributeEditor;
+        this.extraInstructionVisitor = extraInstructionVisitor;
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        // Check if the instruction is a nop instruction.
+        if (simpleInstruction.opcode == InstructionConstants.OP_NOP &&
+            !codeAttributeEditor.isModified(offset))
+        {
+            codeAttributeEditor.deleteInstruction(offset);
+
+            // Visit the instruction, if required.
+            if (extraInstructionVisitor != null)
+            {
+                extraInstructionVisitor.visitSimpleInstruction(clazz, method, codeAttribute, offset, simpleInstruction);
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/PeepholeOptimizer.java b/src/proguard/optimize/peephole/PeepholeOptimizer.java
new file mode 100644
index 0000000..98f8e8d
--- /dev/null
+++ b/src/proguard/optimize/peephole/PeepholeOptimizer.java
@@ -0,0 +1,103 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor sets up and applies the peephole optimizations of its
+ * instruction visitor. The instruction visitor should be using the same
+ * (optional) branch target finder and code attribute editor.
+ *
+ * @author Eric Lafortune
+ */
+public class PeepholeOptimizer
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private final BranchTargetFinder  branchTargetFinder;
+    private final CodeAttributeEditor codeAttributeEditor;
+    private final InstructionVisitor  instructionVisitor;
+
+
+    /**
+     * Creates a new PeepholeOptimizer.
+     * @param codeAttributeEditor the code attribute editor that will be reset
+     *                            and then executed.
+     * @param instructionVisitor  the instruction visitor that performs
+     *                            peephole optimizations using the above code
+     *                            attribute editor.
+     */
+    public PeepholeOptimizer(CodeAttributeEditor codeAttributeEditor,
+                             InstructionVisitor  instructionVisitor)
+    {
+        this(null, codeAttributeEditor, instructionVisitor);
+    }
+
+
+    /**
+     * Creates a new PeepholeOptimizer.
+     * @param branchTargetFinder  branch target finder that will be initialized
+     *                            to indicate branch targets in the visited code.
+     * @param codeAttributeEditor the code attribute editor that will be reset
+     *                            and then executed.
+     * @param instructionVisitor  the instruction visitor that performs
+     *                            peephole optimizations using the above code
+     *                            attribute editor.
+     */
+    public PeepholeOptimizer(BranchTargetFinder  branchTargetFinder,
+                             CodeAttributeEditor codeAttributeEditor,
+                             InstructionVisitor  instructionVisitor)
+    {
+        this.branchTargetFinder  = branchTargetFinder;
+        this.codeAttributeEditor = codeAttributeEditor;
+        this.instructionVisitor  = instructionVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (branchTargetFinder != null)
+        {
+            // Set up the branch target finder.
+            branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute);
+        }
+
+        // Set up the code attribute editor.
+        codeAttributeEditor.reset(codeAttribute.u4codeLength);
+
+        // Find the peephole optimizations.
+        codeAttribute.instructionsAccept(clazz, method, instructionVisitor);
+
+        // Apply the peephole optimizations.
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+    }
+}
diff --git a/src/proguard/optimize/peephole/ReachableCodeMarker.java b/src/proguard/optimize/peephole/ReachableCodeMarker.java
new file mode 100644
index 0000000..d9dbf2d
--- /dev/null
+++ b/src/proguard/optimize/peephole/ReachableCodeMarker.java
@@ -0,0 +1,263 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This AttributeVisitor finds all instruction offsets, branch targets, and
+ * exception targets in the CodeAttribute objects that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class ReachableCodeMarker
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ExceptionInfoVisitor
+{
+    private boolean[] isReachable = new boolean[ClassConstants.TYPICAL_CODE_LENGTH];
+
+    private boolean next;
+    private boolean evaluateExceptions;
+
+
+    /**
+     * Returns whether the instruction at the given offset is reachable in
+     * the CodeAttribute that was visited most recently.
+     */
+    public boolean isReachable(int offset)
+    {
+        return isReachable[offset];
+    }
+
+
+    /**
+     * Returns whether any of the instructions at the given offsets are
+     * reachable in the CodeAttribute that was visited most recently.
+     */
+    public boolean isReachable(int startOffset, int endOffset)
+    {
+        // Check if any of the instructions is reachable.
+        for (int offset = startOffset; offset < endOffset; offset++)
+        {
+            if (isReachable[offset])
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Make sure there is a sufficiently large array.
+        int codeLength = codeAttribute.u4codeLength;
+        if (isReachable.length < codeLength)
+        {
+            // Create a new array.
+            isReachable = new boolean[codeLength];
+        }
+        else
+        {
+            // Reset the array.
+            for (int index = 0; index < codeLength; index++)
+            {
+                isReachable[index] = false;
+            }
+        }
+
+        // Mark the code, starting at the entry point.
+        markCode(clazz, method, codeAttribute, 0);
+
+        // Mark the exception handlers, iterating as long as necessary.
+        do
+        {
+            evaluateExceptions = false;
+
+            codeAttribute.exceptionsAccept(clazz, method, this);
+        }
+        while (evaluateExceptions);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction)
+    {
+        byte opcode = simpleInstruction.opcode;
+        if (opcode == InstructionConstants.OP_IRETURN ||
+            opcode == InstructionConstants.OP_LRETURN ||
+            opcode == InstructionConstants.OP_FRETURN ||
+            opcode == InstructionConstants.OP_DRETURN ||
+            opcode == InstructionConstants.OP_ARETURN ||
+            opcode == InstructionConstants.OP_RETURN  ||
+            opcode == InstructionConstants.OP_ATHROW)
+        {
+            next = false;
+        }
+    }
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        if (variableInstruction.opcode == InstructionConstants.OP_RET)
+        {
+            next = false;
+        }
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        // Mark the branch target.
+        markBranchTarget(clazz,
+                         method,
+                         codeAttribute,
+                         offset + branchInstruction.branchOffset);
+
+        byte opcode = branchInstruction.opcode;
+        if (opcode == InstructionConstants.OP_GOTO ||
+            opcode == InstructionConstants.OP_GOTO_W)
+        {
+            next = false;
+        }
+    }
+
+
+    public void visitAnySwitchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SwitchInstruction switchInstruction)
+    {
+        // Mark the branch targets of the default jump offset.
+        markBranchTarget(clazz,
+                         method,
+                         codeAttribute,
+                         offset + switchInstruction.defaultOffset);
+
+        // Mark the branch targets of the jump offsets.
+        markBranchTargets(clazz,
+                          method,
+                          codeAttribute,
+                          offset,
+                          switchInstruction.jumpOffsets);
+
+        next = false;
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        // Mark the exception handler if it's relevant.
+        if (!isReachable(exceptionInfo.u2handlerPC) &&
+            isReachable(exceptionInfo.u2startPC, exceptionInfo.u2endPC))
+        {
+            markCode(clazz, method, codeAttribute, exceptionInfo.u2handlerPC);
+
+            evaluateExceptions = true;
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the branch targets of the given jump offsets for the instruction
+     * at the given offset.
+     */
+    private void markBranchTargets(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, int[] jumpOffsets)
+    {
+        for (int index = 0; index < jumpOffsets.length; index++)
+        {
+            markCode(clazz, method, codeAttribute, offset + jumpOffsets[index]);
+        }
+    }
+
+
+    /**
+     * Marks the branch target at the given offset.
+     */
+    private void markBranchTarget(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset)
+    {
+        boolean oldNext = next;
+
+        markCode(clazz, method, codeAttribute, offset);
+
+        next = oldNext;
+    }
+
+
+    /**
+     * Marks the code starting at the given offset.
+     */
+    private void markCode(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset)
+    {
+        boolean oldNext = next;
+
+        byte[] code = codeAttribute.code;
+
+        // Continue with the current instruction as long as we haven't marked it
+        // yet.
+        while (!isReachable[offset])
+        {
+            // Get the current instruction.
+            Instruction instruction = InstructionFactory.create(code, offset);
+
+            // Mark it as reachable.
+            isReachable[offset] = true;
+
+            // By default, we'll assume we can continue with the next
+            // instruction in a moment.
+            next = true;
+
+            // Mark the branch targets, if any.
+            instruction.accept(clazz, method, codeAttribute, offset, this);
+
+            // Can we really continue with the next instruction?
+            if (!next)
+            {
+                break;
+            }
+
+            // Go to the next instruction.
+            offset += instruction.length(offset);
+        }
+
+        next = oldNext;
+    }
+}
diff --git a/src/proguard/optimize/peephole/RetargetedInnerClassAttributeRemover.java b/src/proguard/optimize/peephole/RetargetedInnerClassAttributeRemover.java
new file mode 100644
index 0000000..6707a12
--- /dev/null
+++ b/src/proguard/optimize/peephole/RetargetedInnerClassAttributeRemover.java
@@ -0,0 +1,140 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.ClassConstant;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor removes InnerClasses and EnclosingMethod attributes in
+ * classes that are retargeted or that refer to classes that are retargeted.
+ *
+ * @see ClassMerger
+ * @author Eric Lafortune
+ */
+public class RetargetedInnerClassAttributeRemover
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             AttributeVisitor,
+             InnerClassesInfoVisitor,
+             ConstantVisitor
+{
+    private boolean retargeted;
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        int         attributesCount = programClass.u2attributesCount;
+        Attribute[] attributes      = programClass.attributes;
+
+        int newAtributesCount = 0;
+
+        // Copy over all non-retargeted attributes.
+        for (int index = 0; index < attributesCount; index++)
+        {
+            Attribute attribute = attributes[index];
+
+            // Check if it's an InnerClasses or EnclosingMethod attribute in
+            // a retargeted class or referring to a retargeted class.
+            retargeted = false;
+            attribute.accept(programClass, this);
+            if (!retargeted)
+            {
+                attributes[newAtributesCount++] = attribute;
+            }
+        }
+
+        // Clean up any remaining array elements.
+        for (int index = newAtributesCount; index < attributesCount; index++)
+        {
+            attributes[index] = null;
+        }
+
+        // Update the number of attribuets.
+        programClass.u2attributesCount = newAtributesCount;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // Check whether the class itself is retargeted.
+        checkTarget(clazz);
+
+        // Check whether the referenced classes are retargeted.
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        // Check whether the class itself is retargeted.
+        checkTarget(clazz);
+
+        // Check whether the referenced class is retargeted.
+        checkTarget(enclosingMethodAttribute.referencedClass);
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        // Check whether the inner class or the outer class are retargeted.
+        innerClassesInfo.innerClassConstantAccept(clazz, this);
+        innerClassesInfo.outerClassConstantAccept(clazz, this);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Check whether the referenced class is retargeted.
+        checkTarget(classConstant.referencedClass);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Sets the global return value to true if the given class is retargeted.
+     */
+    private void checkTarget(Clazz clazz)
+    {
+        if (clazz != null &&
+            ClassMerger.getTargetClass(clazz) != null)
+        {
+            retargeted = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/peephole/TargetClassChanger.java b/src/proguard/optimize/peephole/TargetClassChanger.java
new file mode 100644
index 0000000..22fd83d
--- /dev/null
+++ b/src/proguard/optimize/peephole/TargetClassChanger.java
@@ -0,0 +1,439 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.editor.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor replaces references to classes and class members if the
+ * classes have targets that are intended to replace them.
+ *
+ * @see VerticalClassMerger
+ * @see ClassReferenceFixer
+ * @see MemberReferenceFixer
+ * @author Eric Lafortune
+ */
+public class TargetClassChanger
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor,
+             MemberVisitor,
+             AttributeVisitor,
+             LocalVariableInfoVisitor,
+             LocalVariableTypeInfoVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        Clazz   superClass       = null;
+        Clazz[] interfaceClasses = null;
+
+        // Change the references of the constant pool.
+        programClass.constantPoolEntriesAccept(this);
+
+        // Change the references of the class members.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+
+        // Change the references of the attributes.
+        programClass.attributesAccept(this);
+
+        // Is the class itself being retargeted?
+        Clazz targetClass = ClassMerger.getTargetClass(programClass);
+        if (targetClass != null)
+        {
+            // Restore the class name. We have to add a new class entry
+            // to avoid an existing entry with the same name being reused. The
+            // names have to be fixed later, based on their referenced classes.
+            programClass.u2thisClass =
+                addNewClassConstant(programClass,
+                                    programClass.getName(),
+                                    programClass);
+
+            // This class will loose all its subclasses.
+            programClass.subClasses = null;
+        }
+
+        // Remove interface classes that are pointing to this class.
+        int newInterfacesCount = 0;
+        for (int index = 0; index < programClass.u2interfacesCount; index++)
+        {
+            Clazz interfaceClass = programClass.getInterface(index);
+            if (!programClass.equals(interfaceClass))
+            {
+                programClass.u2interfaces[newInterfacesCount++] =
+                    programClass.u2interfaces[index];
+            }
+        }
+        programClass.u2interfacesCount = newInterfacesCount;
+
+        // Update the subclasses of the superclass and interfaces of the
+        // target class.
+        ConstantVisitor subclassAdder =
+            new ReferencedClassVisitor(
+            new SubclassFilter(programClass,
+            new SubclassAdder(programClass)));
+
+        programClass.superClassConstantAccept(subclassAdder);
+        programClass.interfaceConstantsAccept(subclassAdder);
+
+        // TODO: Maybe restore private method references.
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Change the references of the class members.
+        libraryClass.fieldsAccept(this);
+        libraryClass.methodsAccept(this);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Change the referenced class.
+        programField.referencedClass =
+            updateReferencedClass(programField.referencedClass);
+
+        // Change the references of the attributes.
+        programField.attributesAccept(programClass, this);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Change the referenced classes.
+        updateReferencedClasses(programMethod.referencedClasses);
+
+        // Change the references of the attributes.
+        programMethod.attributesAccept(programClass, this);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        // Change the referenced class.
+        libraryField.referencedClass =
+            updateReferencedClass(libraryField.referencedClass);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        // Change the referenced classes.
+        updateReferencedClasses(libraryMethod.referencedClasses);
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant) {}
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        // Does the string refer to a class, due to a Class.forName construct?
+        Clazz referencedClass    = stringConstant.referencedClass;
+        Clazz newReferencedClass = updateReferencedClass(referencedClass);
+        if (referencedClass != newReferencedClass)
+        {
+            // Change the referenced class.
+            stringConstant.referencedClass = newReferencedClass;
+
+            // Change the referenced class member, if applicable.
+            stringConstant.referencedMember =
+                updateReferencedMember(stringConstant.referencedMember,
+                                       stringConstant.getString(clazz),
+                                       null,
+                                       newReferencedClass);
+        }
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        Clazz referencedClass    = refConstant.referencedClass;
+        Clazz newReferencedClass = updateReferencedClass(referencedClass);
+        if (referencedClass != newReferencedClass)
+        {
+            // Change the referenced class.
+            refConstant.referencedClass  = newReferencedClass;
+
+            // Change the referenced class member.
+            refConstant.referencedMember =
+                updateReferencedMember(refConstant.referencedMember,
+                                       refConstant.getName(clazz),
+                                       refConstant.getType(clazz),
+                                       newReferencedClass);
+        }
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        // Change the referenced class.
+        classConstant.referencedClass =
+            updateReferencedClass(classConstant.referencedClass);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Change the references of the attributes.
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        // Change the references of the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        // Change the references of the local variables.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        // Change the referenced classes.
+        updateReferencedClasses(signatureAttribute.referencedClasses);
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Change the references of the annotations.
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Change the references of the annotations.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Change the references of the annotation.
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+
+   // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        // Change the referenced class.
+        localVariableInfo.referencedClass =
+            updateReferencedClass(localVariableInfo.referencedClass);
+    }
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        // Change the referenced classes.
+        updateReferencedClasses(localVariableTypeInfo.referencedClasses);
+    }
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        // Change the referenced classes.
+        updateReferencedClasses(annotation.referencedClasses);
+
+        // Change the references of the element values.
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue)
+    {
+        Clazz referencedClass    = elementValue.referencedClass;
+        Clazz newReferencedClass = updateReferencedClass(referencedClass);
+        if (referencedClass != newReferencedClass)
+        {
+            // Change the referenced annotation class.
+            elementValue.referencedClass  = newReferencedClass;
+
+            // Change the referenced method.
+            elementValue.referencedMethod =
+                (Method)updateReferencedMember(elementValue.referencedMethod,
+                                               elementValue.getMethodName(clazz),
+                                               null,
+                                               newReferencedClass);
+        }
+    }
+
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        // Change the referenced annotation class and method.
+        visitAnyElementValue(clazz, annotation, constantElementValue);
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        // Change the referenced annotation class and method.
+        visitAnyElementValue(clazz, annotation, enumConstantElementValue);
+
+        // Change the referenced classes.
+        updateReferencedClasses(enumConstantElementValue.referencedClasses);
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        // Change the referenced annotation class and method.
+        visitAnyElementValue(clazz, annotation, classElementValue);
+
+        // Change the referenced classes.
+        updateReferencedClasses(classElementValue.referencedClasses);
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        // Change the referenced annotation class and method.
+        visitAnyElementValue(clazz, annotation, annotationElementValue);
+
+        // Change the references of the annotation.
+        annotationElementValue.annotationAccept(clazz, this);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        // Change the referenced annotation class and method.
+        visitAnyElementValue(clazz, annotation, arrayElementValue);
+
+        // Change the references of the element values.
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Updates the retargeted classes in the given array of classes.
+     */
+    private void updateReferencedClasses(Clazz[] referencedClasses)
+    {
+        if (referencedClasses == null)
+        {
+            return;
+        }
+
+        for (int index = 0; index < referencedClasses.length; index++)
+        {
+            referencedClasses[index] =
+                updateReferencedClass(referencedClasses[index]);
+        }
+    }
+
+
+    /**
+     * Returns the retargeted class of the given class.
+     */
+    private Clazz updateReferencedClass(Clazz referencedClass)
+    {
+        if (referencedClass == null)
+        {
+            return null;
+        }
+
+        Clazz targetClazz = ClassMerger.getTargetClass(referencedClass);
+        return targetClazz != null ?
+            targetClazz :
+            referencedClass;
+    }
+
+
+    /**
+     * Returns the retargeted class member of the given class member.
+     */
+    private Member updateReferencedMember(Member referencedMember,
+                                          String name,
+                                          String type,
+                                          Clazz  newReferencedClass)
+    {
+        if (referencedMember == null)
+        {
+            return null;
+        }
+
+        return referencedMember instanceof Field ?
+            (Member)newReferencedClass.findField(name, type) :
+            (Member)newReferencedClass.findMethod(name, type);
+    }
+
+
+    /**
+     * Explicitly adds a new class constant for the given class in the given
+     * program class.
+     */
+    private int addNewClassConstant(ProgramClass programClass,
+                                    String       className,
+                                    Clazz        referencedClass)
+    {
+        ConstantPoolEditor constantPoolEditor =
+            new ConstantPoolEditor(programClass);
+
+        int nameIndex =
+            constantPoolEditor.addUtf8Constant(className);
+
+        int classConstantIndex =
+            constantPoolEditor.addConstant(new ClassConstant(nameIndex,
+                                                             referencedClass));
+        return classConstantIndex;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/peephole/UnreachableCodeRemover.java b/src/proguard/optimize/peephole/UnreachableCodeRemover.java
new file mode 100644
index 0000000..e8a99ab
--- /dev/null
+++ b/src/proguard/optimize/peephole/UnreachableCodeRemover.java
@@ -0,0 +1,143 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.CodeAttributeEditor;
+import proguard.classfile.instruction.Instruction;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+
+/**
+ * This InstructionVisitor deletes blocks of code that can never be reached by
+ * regular calls or branches.
+ *
+ * @author Eric Lafortune
+ */
+public class UnreachableCodeRemover
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+    private final InstructionVisitor  extraInstructionVisitor;
+
+    private final ReachableCodeMarker reachableCodeMarker = new ReachableCodeMarker();
+    private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
+
+
+    /**
+     * Creates a new UnreachableCodeRemover.
+     */
+    public UnreachableCodeRemover()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new UnreachableCodeRemover.
+     * @param extraInstructionVisitor an optional extra visitor for all
+     *                                deleted instructions.
+     */
+    public UnreachableCodeRemover(InstructionVisitor  extraInstructionVisitor)
+    {
+        this.extraInstructionVisitor = extraInstructionVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // TODO: Remove this when the code has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            // Process the code.
+            visitCodeAttribute0(clazz, method, codeAttribute);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while removing unreachable code:");
+            System.err.println("  Class       = ["+clazz.getName()+"]");
+            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+
+            throw ex;
+        }
+    }
+
+
+    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if (DEBUG)
+        {
+            System.out.println("UnreachableCodeRemover: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
+        }
+
+        reachableCodeMarker.visitCodeAttribute(clazz, method, codeAttribute);
+
+        codeAttributeEditor.reset(codeAttribute.u4codeLength);
+
+        codeAttribute.instructionsAccept(clazz, method, this);
+
+        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        if (DEBUG)
+        {
+            System.out.println("  "+(reachableCodeMarker.isReachable(offset) ? "+" : "-")+" "+instruction.toString(offset));
+        }
+
+        // Is this instruction unreachable?
+        if (!reachableCodeMarker.isReachable(offset))
+        {
+            // Then delete it.
+            codeAttributeEditor.deleteInstruction(offset);
+
+            // Visit the instruction, if required.
+            if (extraInstructionVisitor != null)
+            {
+                instruction.accept(clazz, method, codeAttribute, offset, extraInstructionVisitor);
+            }
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/UnreachableExceptionRemover.java b/src/proguard/optimize/peephole/UnreachableExceptionRemover.java
new file mode 100644
index 0000000..048f5e3
--- /dev/null
+++ b/src/proguard/optimize/peephole/UnreachableExceptionRemover.java
@@ -0,0 +1,163 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.optimize.info.ExceptionInstructionChecker;
+
+/**
+ * This AttributeVisitor removes exception handlers that are unreachable in the
+ * code attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class UnreachableExceptionRemover
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             ExceptionInfoVisitor
+{
+    private final ExceptionInfoVisitor extraExceptionInfoVisitor;
+
+
+    private final ExceptionInstructionChecker exceptionInstructionChecker = new ExceptionInstructionChecker();
+
+
+    /**
+     * Creates a new UnreachableExceptionRemover.
+     */
+    public UnreachableExceptionRemover()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new UnreachableExceptionRemover.
+     * @param extraExceptionInfoVisitor an optional extra visitor for all
+     *                                  removed exceptions.
+     */
+    public UnreachableExceptionRemover(ExceptionInfoVisitor extraExceptionInfoVisitor)
+    {
+        this.extraExceptionInfoVisitor = extraExceptionInfoVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Go over the exception table.
+        codeAttribute.exceptionsAccept(clazz, method, this);
+
+        // Remove exceptions with empty code blocks.
+        codeAttribute.u2exceptionTableLength =
+            removeEmptyExceptions(codeAttribute.exceptionTable,
+                                  codeAttribute.u2exceptionTableLength);
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        if (!mayThrowExceptions(clazz,
+                                method,
+                                codeAttribute,
+                                exceptionInfo.u2startPC,
+                                exceptionInfo.u2endPC))
+        {
+            // Make the code block empty.
+            exceptionInfo.u2endPC = exceptionInfo.u2startPC;
+
+            if (extraExceptionInfoVisitor != null)
+            {
+                extraExceptionInfoVisitor.visitExceptionInfo(clazz, method, codeAttribute, exceptionInfo);
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the specified block of code may throw exceptions.
+     */
+    private boolean mayThrowExceptions(Clazz         clazz,
+                                       Method        method,
+                                       CodeAttribute codeAttribute,
+                                       int           startOffset,
+                                       int           endOffset)
+    {
+        byte[] code = codeAttribute.code;
+
+        // Go over all instructions.
+        int offset = startOffset;
+        while (offset < endOffset)
+        {
+            // Get the current instruction.
+            Instruction instruction = InstructionFactory.create(code, offset);
+
+            // Check if it may be throwing exceptions.
+            if (exceptionInstructionChecker.mayThrowExceptions(clazz,
+                                                               method,
+                                                               codeAttribute,
+                                                               offset,
+                                                               instruction))
+            {
+                return true;
+            }
+
+            // Go to the next instruction.
+            offset += instruction.length(offset);
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Returns the given list of exceptions, without the ones that have empty
+     * code blocks.
+     */
+    private int removeEmptyExceptions(ExceptionInfo[] exceptionInfos,
+                                      int             exceptionInfoCount)
+    {
+        // Overwrite all empty exceptions.
+        int newIndex = 0;
+        for (int index = 0; index < exceptionInfoCount; index++)
+        {
+            ExceptionInfo exceptionInfo = exceptionInfos[index];
+            if (exceptionInfo.u2startPC < exceptionInfo.u2endPC)
+            {
+                exceptionInfos[newIndex++] = exceptionInfo;
+            }
+        }
+
+        return newIndex;
+    }
+}
diff --git a/src/proguard/optimize/peephole/VariableShrinker.java b/src/proguard/optimize/peephole/VariableShrinker.java
new file mode 100644
index 0000000..45b694f
--- /dev/null
+++ b/src/proguard/optimize/peephole/VariableShrinker.java
@@ -0,0 +1,129 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.VariableEditor;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.MemberVisitor;
+import proguard.optimize.*;
+import proguard.optimize.info.*;
+
+/**
+ * This MemberVisitor removes unused local variables from the code of the methods
+ * that it visits.
+ *
+ * @see ParameterUsageMarker
+ * @see MethodStaticizer
+ * @see MethodDescriptorShrinker
+ * @author Eric Lafortune
+ */
+public class VariableShrinker
+extends      SimplifiedVisitor
+implements   AttributeVisitor
+{
+    private static final boolean DEBUG = false;
+
+
+    private final MemberVisitor extraVariableMemberVisitor;
+
+    private final VariableUsageMarker variableUsageMarker = new VariableUsageMarker();
+    private final VariableEditor      variableEditor      = new VariableEditor();
+
+
+    /**
+     * Creates a new VariableShrinker.
+     */
+    public VariableShrinker()
+    {
+        this(null);
+    }
+
+
+    /**
+     * Creates a new VariableShrinker with an extra visitor.
+     * @param extraVariableMemberVisitor an optional extra visitor for all
+     *                                   removed variables.
+     */
+    public VariableShrinker(MemberVisitor extraVariableMemberVisitor)
+    {
+        this.extraVariableMemberVisitor = extraVariableMemberVisitor;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        if ((method.getAccessFlags() & ClassConstants.INTERNAL_ACC_ABSTRACT) == 0)
+        {
+            // Compute the parameter size.
+            int parameterSize =
+                ClassUtil.internalMethodParameterSize(method.getDescriptor(clazz),
+                                                      method.getAccessFlags());
+
+            // Get the total size of the local variable frame.
+            int maxLocals = codeAttribute.u2maxLocals;
+
+            if (DEBUG)
+            {
+                System.out.println("VariableShrinker: "+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz));
+                System.out.println("  Parameter size = " + parameterSize);
+                System.out.println("  Max locals     = " + maxLocals);
+            }
+
+            // Figure out the local variables that are used by the code.
+            variableUsageMarker.visitCodeAttribute(clazz, method, codeAttribute);
+
+            // Delete unused local variables from the local variable frame.
+            variableEditor.reset(maxLocals);
+
+            for (int variableIndex = parameterSize; variableIndex < maxLocals; variableIndex++)
+            {
+                // Is the variable not required?
+                if (!variableUsageMarker.isVariableUsed(variableIndex))
+                {
+                    if (DEBUG)
+                    {
+                        System.out.println("  Deleting local variable #"+variableIndex);
+                    }
+
+                    // Delete the unused variable.
+                    variableEditor.deleteVariable(variableIndex);
+
+                    // Visit the method, if required.
+                    if (extraVariableMemberVisitor != null)
+                    {
+                        method.accept(clazz, extraVariableMemberVisitor);
+                    }
+                }
+            }
+
+            // Shift all remaining parameters and variables in the byte code.
+            variableEditor.visitCodeAttribute(clazz, method, codeAttribute);
+        }
+    }
+}
diff --git a/src/proguard/optimize/peephole/VerticalClassMerger.java b/src/proguard/optimize/peephole/VerticalClassMerger.java
new file mode 100644
index 0000000..29ed6ff
--- /dev/null
+++ b/src/proguard/optimize/peephole/VerticalClassMerger.java
@@ -0,0 +1,88 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.optimize.peephole;
+
+import proguard.classfile.ProgramClass;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor inlines the direct subclasses into the program classes
+ * that it visits, whenever possible.
+ *
+ * @see ClassMerger
+ * @author Eric Lafortune
+ */
+public class VerticalClassMerger
+extends      SimplifiedVisitor
+implements   ClassVisitor
+{
+    private final boolean      allowAccessModification;
+    private final boolean      mergeInterfacesAggressively;
+    private final ClassVisitor extraClassVisitor;
+
+
+    /**
+     * Creates a new VerticalClassMerger.
+     * @param allowAccessModification     specifies whether the access modifiers
+     *                                    of classes can be changed in order to
+     *                                    merge them.
+     * @param mergeInterfacesAggressively specifies whether interfaces may
+     *                                    be merged aggressively.
+     */
+    public VerticalClassMerger(boolean allowAccessModification,
+                               boolean mergeInterfacesAggressively)
+    {
+        this(allowAccessModification, mergeInterfacesAggressively, null);
+    }
+
+
+    /**
+     * Creates a new VerticalClassMerger.
+     * @param allowAccessModification     specifies whether the access modifiers
+     *                                    of classes can be changed in order to
+     *                                    merge them.
+     * @param mergeInterfacesAggressively specifies whether interfaces may
+     *                                    be merged aggressively.
+     * @param extraClassVisitor           an optional extra visitor for all
+     *                                    merged classes.
+     */
+    public VerticalClassMerger(boolean      allowAccessModification,
+                               boolean      mergeInterfacesAggressively,
+                               ClassVisitor extraClassVisitor)
+    {
+        this.allowAccessModification     = allowAccessModification;
+        this.mergeInterfacesAggressively = mergeInterfacesAggressively;
+        this.extraClassVisitor           = extraClassVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        programClass.subclassesAccept(new ClassMerger(programClass,
+                                                      allowAccessModification,
+                                                      mergeInterfacesAggressively,
+                                                      extraClassVisitor));
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/optimize/peephole/package.html b/src/proguard/optimize/peephole/package.html
new file mode 100644
index 0000000..e0eeb51
--- /dev/null
+++ b/src/proguard/optimize/peephole/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains visitors that perform various peephole optimizations.
+</body>
diff --git a/src/proguard/package.html b/src/proguard/package.html
new file mode 100644
index 0000000..986cad8
--- /dev/null
+++ b/src/proguard/package.html
@@ -0,0 +1,5 @@
+<body>
+This package contains the main ProGuard application.
+ProGuard can read jar files, shrink and obfuscate them, and write out the
+resulting jar file.
+</body>
diff --git a/src/proguard/preverify/CodePreverifier.java b/src/proguard/preverify/CodePreverifier.java
new file mode 100644
index 0000000..fa60b9a
--- /dev/null
+++ b/src/proguard/preverify/CodePreverifier.java
@@ -0,0 +1,611 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.preverify;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.editor.*;
+import proguard.classfile.instruction.*;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+import proguard.evaluation.*;
+import proguard.evaluation.value.*;
+import proguard.optimize.evaluation.*;
+
+import java.util.*;
+
+/**
+ * This class can preverify methods in program class pools, according to a given
+ * specification.
+ *
+ * @author Eric Lafortune
+ */
+public class CodePreverifier
+extends      SimplifiedVisitor
+implements   MemberVisitor,
+             AttributeVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    private final boolean microEdition;
+
+    private final PartialEvaluator partialEvaluator = new PartialEvaluator();
+    private final LivenessAnalyzer livenessAnalyzer = new LivenessAnalyzer(partialEvaluator);
+
+
+    /**
+     * Creates a new CodePreverifier.
+     */
+    public CodePreverifier(boolean microEdition)
+    {
+        this.microEdition = microEdition;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // TODO: Remove this when the preverifier has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            // Process the code.
+            visitCodeAttribute0(clazz, method, codeAttribute);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while preverifying:");
+            System.err.println("  Class       = ["+clazz.getName()+"]");
+            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+
+            throw ex;
+        }
+    }
+
+
+    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        ProgramClass  programClass  = (ProgramClass)clazz;
+        ProgramMethod programMethod = (ProgramMethod)method;
+
+        // Evaluate the method.
+        //partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute);
+        livenessAnalyzer.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Collect the stack map frames.
+        List stackMapFrameList = new ArrayList();
+
+        for (int offset = 0; offset < codeAttribute.u4codeLength; offset++)
+        {
+            // Only store frames at the beginning of code blocks.
+            if (partialEvaluator.isTraced(offset) &&
+                partialEvaluator.isBranchOrExceptionTarget(offset))
+            {
+                // Convert the variable values to types.
+                VerificationType[] variableTypes =
+                    correspondingVerificationTypes(programClass,
+                                                   programMethod,
+                                                   codeAttribute,
+                                                   offset,
+                                                   partialEvaluator.getVariablesBefore(offset));
+
+                // Convert the stack values to types.
+                VerificationType[] stackTypes =
+                    correspondingVerificationTypes(programClass,
+                                                   programMethod,
+                                                   codeAttribute,
+                                                   offset,
+                                                   partialEvaluator.getStackBefore(offset));
+                // Create and store a new frame.
+                stackMapFrameList.add(new FullFrame(offset,
+                                                    variableTypes,
+                                                    stackTypes));
+            }
+        }
+
+        // Compress the stack map frames if the target is not Java Micro Edition.
+        if (!microEdition && !stackMapFrameList.isEmpty())
+        {
+            // Convert the initial variable values to types.
+            VerificationType[] initialVariables =
+                correspondingVerificationTypes(programClass,
+                                               programMethod,
+                                               codeAttribute,
+                                               PartialEvaluator.AT_METHOD_ENTRY,
+                                               partialEvaluator.getVariablesBefore(0));
+
+            // Special case: the <init> method.
+            if (method.getName(programClass).equals(ClassConstants.INTERNAL_METHOD_NAME_INIT))
+            {
+                initialVariables[0] = VerificationTypeFactory.createUninitializedThisType();
+            }
+
+            compressStackMapFrames(initialVariables,
+                                   stackMapFrameList);
+        }
+
+        // Get the proper name for the attribute to be added/replaced/deleted.
+        String stackMapAttributeName = microEdition ?
+             ClassConstants.ATTR_StackMap :
+             ClassConstants.ATTR_StackMapTable;
+
+        int frameCount = stackMapFrameList.size();
+
+        if (DEBUG)
+        {
+            Attribute originalStackMapAttribute = codeAttribute.getAttribute(clazz,
+                                                                             stackMapAttributeName);
+
+            if (originalStackMapAttribute != null)
+            {
+                int originalFrameCount = microEdition ?
+                    ((StackMapAttribute)originalStackMapAttribute).u2stackMapFramesCount :
+                    ((StackMapTableAttribute)originalStackMapAttribute).u2stackMapFramesCount;
+
+                StackMapFrame[] originalFrames = microEdition ?
+                    ((StackMapAttribute)originalStackMapAttribute).stackMapFrames :
+                    ((StackMapTableAttribute)originalStackMapAttribute).stackMapFrames;
+
+                if (frameCount != originalFrameCount ||
+                    !Arrays.equals(stackMapFrameList.toArray(), originalFrames))
+                {
+                    System.out.println("Original preverification ["+clazz.getName()+"]:");
+                    new ClassPrinter().visitProgramMethod(programClass, programMethod);
+                }
+            }
+            else if (frameCount != 0)
+            {
+                System.out.println("Original preverification empty ["+clazz.getName()+"."+method.getName(clazz)+"]");
+            }
+        }
+
+        if (frameCount == 0)
+        {
+            // Remove any stack map (table) attribute from the code attribute.
+            new AttributesEditor(programClass, programMethod, codeAttribute, true).deleteAttribute(stackMapAttributeName);
+        }
+        else
+        {
+            Attribute stackMapAttribute;
+
+            // Create the appropriate attribute.
+            if (microEdition)
+            {
+                // Copy the frames into an array.
+                FullFrame[] stackMapFrames = new FullFrame[frameCount];
+                stackMapFrameList.toArray(stackMapFrames);
+
+                // Put the frames into a stack map attribute.
+                stackMapAttribute = new StackMapAttribute(stackMapFrames);
+            }
+            else
+            {
+                // Copy the frames into an array.
+                StackMapFrame[] stackMapFrames = new StackMapFrame[frameCount];
+                stackMapFrameList.toArray(stackMapFrames);
+
+                // Put the frames into a stack map table attribute.
+                stackMapAttribute = new StackMapTableAttribute(stackMapFrames);
+            }
+
+            // Fill out the name of the stack map attribute.
+            stackMapAttribute.u2attributeNameIndex =
+                new ConstantPoolEditor(programClass).addUtf8Constant(stackMapAttributeName);
+
+            // Add the new stack map (table) attribute to the code attribute.
+            new AttributesEditor(programClass, programMethod, codeAttribute, true).addAttribute(stackMapAttribute);
+
+            if (DEBUG)
+            {
+                System.out.println("Preverifier ["+programClass.getName()+"."+programMethod.getName(programClass)+"]:");
+                stackMapAttribute.accept(programClass, programMethod, codeAttribute, new ClassPrinter());
+            }
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Creates and returns the verification types corresponding to the given
+     * variables. If necessary, class constants are added to the constant pool
+     * of the given class.
+     */
+    private VerificationType[] correspondingVerificationTypes(ProgramClass    programClass,
+                                                              ProgramMethod   programMethod,
+                                                              CodeAttribute   codeAttribute,
+                                                              int             offset,
+                                                              TracedVariables variables)
+    {
+        int maximumVariablesSize = variables.size();
+        int typeCount = 0;
+        int typeIndex = 0;
+
+        // Count the the number of verification types, ignoring any nulls at
+        // the end.
+        for (int index = 0; index < maximumVariablesSize; index++)
+        {
+            Value value = variables.getValue(index);
+
+            typeIndex++;
+
+            // Remember the maximum live type index.
+            if (value != null &&
+                (offset == PartialEvaluator.AT_METHOD_ENTRY ||
+                 livenessAnalyzer.isAliveBefore(offset, index)))
+            {
+                typeCount = typeIndex;
+
+                // Category 2 types that are alive are stored as single entries.
+                if (value.isCategory2())
+                {
+                    index++;
+                }
+            }
+        }
+
+        // Create and fill out the verification types.
+        VerificationType[] types = new VerificationType[typeCount];
+
+        typeIndex = 0;
+
+        // Note the slightly different terminating condition, because the
+        // types may have been truncated.
+        for (int index = 0; typeIndex < typeCount; index++)
+        {
+            Value value         = variables.getValue(index);
+            Value producerValue = variables.getProducerValue(index);
+
+            // Fill out the type.
+            VerificationType type;
+
+            if (value != null &&
+                (offset == PartialEvaluator.AT_METHOD_ENTRY ||
+                 livenessAnalyzer.isAliveBefore(offset, index)))
+            {
+                type = correspondingVerificationType(programClass,
+                                                     programMethod,
+                                                     codeAttribute,
+                                                     offset,
+                                                     index == 0,
+                                                     value,
+                                                     producerValue);
+
+                // Category 2 types that are alive are stored as single entries.
+                if (value.isCategory2())
+                {
+                    index++;
+                }
+            }
+            else
+            {
+                type = VerificationTypeFactory.createTopType();
+            }
+
+            types[typeIndex++] = type;
+        }
+
+        return types;
+    }
+
+
+    /**
+     * Creates and returns the verification types corresponding to the given
+     * stack. If necessary, class constants are added to the constant pool
+     * of the given class.
+     */
+    private VerificationType[] correspondingVerificationTypes(ProgramClass  programClass,
+                                                              ProgramMethod programMethod,
+                                                              CodeAttribute codeAttribute,
+                                                              int           offset,
+                                                              TracedStack   stack)
+    {
+        int maximumStackSize = stack.size();
+        int typeCount = 0;
+
+        // Count the the number of verification types.
+        for (int index = 0; index < maximumStackSize; index++)
+        {
+            // We have to work down from the top of the stack.
+            Value value = stack.getTop(index);
+
+            typeCount++;
+
+            // Category 2 types are stored as single entries.
+            if (value.isCategory2())
+            {
+                index++;
+            }
+        }
+
+        // Create and fill out the verification types.
+        VerificationType[] types = new VerificationType[typeCount];
+
+        int typeIndex = typeCount;
+
+        for (int index = 0; index < maximumStackSize; index++)
+        {
+            // We have to work down from the top of the stack.
+            Value value         = stack.getTop(index);
+            Value producerValue = stack.getTopProducerValue(index);
+
+            // Fill out the type.
+            types[--typeIndex] =
+                correspondingVerificationType(programClass,
+                                              programMethod,
+                                              codeAttribute,
+                                              offset,
+                                              false,
+                                              value,
+                                              producerValue);
+
+            // Category 2 types are stored as single entries.
+            if (value.isCategory2())
+            {
+                index++;
+            }
+        }
+
+        return types;
+    }
+
+
+    /**
+     * Creates and returns the verification type corresponding to the given
+     * value. If necessary, a class constant is added to the constant pool of
+     * the given class.
+     */
+    private VerificationType correspondingVerificationType(ProgramClass  programClass,
+                                                           ProgramMethod programMethod,
+                                                           CodeAttribute codeAttribute,
+                                                           int           offset,
+                                                           boolean       isVariable0,
+                                                           Value         value,
+                                                           Value         producerValue)
+    {
+        if (value == null)
+        {
+            return VerificationTypeFactory.createTopType();
+        }
+
+        int type = value.computationalType();
+
+        switch (type)
+        {
+            case Value.TYPE_INSTRUCTION_OFFSET:
+            case Value.TYPE_INTEGER:   return VerificationTypeFactory.createIntegerType();
+            case Value.TYPE_LONG:      return VerificationTypeFactory.createLongType();
+            case Value.TYPE_FLOAT:     return VerificationTypeFactory.createFloatType();
+            case Value.TYPE_DOUBLE:    return VerificationTypeFactory.createDoubleType();
+            case Value.TYPE_TOP:       return VerificationTypeFactory.createTopType();
+            case Value.TYPE_REFERENCE:
+                // Is it a Null type?
+                ReferenceValue referenceValue = value.referenceValue();
+                if (referenceValue.isNull() == Value.ALWAYS)
+                {
+                    return VerificationTypeFactory.createNullType();
+                }
+
+                // Does the reference type have a single producer?
+                if (offset != PartialEvaluator.AT_METHOD_ENTRY)
+                {
+                    InstructionOffsetValue producers = producerValue.instructionOffsetValue();
+                    if (producers.instructionOffsetCount() == 1)
+                    {
+                        int producerOffset = producers.instructionOffset(0);
+
+                        // Follow any dup or swap instructions.
+                        while (producerOffset != PartialEvaluator.AT_METHOD_ENTRY &&
+                               isDupOrSwap(codeAttribute.code[producerOffset]))
+                        {
+                            producers      = partialEvaluator.getStackBefore(producerOffset).getTopProducerValue(0).instructionOffsetValue();
+                            producerOffset = producers.instructionOffset(0);
+                        }
+
+                        // Are we in an instance initialization method,
+                        // before the super initialization, loading "this"?
+                        if (partialEvaluator.isInitializer()                       &&
+                            offset <= partialEvaluator.superInitializationOffset() &&
+                            (isVariable0 ||
+                             producerOffset > PartialEvaluator.AT_METHOD_ENTRY &&
+                             codeAttribute.code[producerOffset] == InstructionConstants.OP_ALOAD_0))
+                        {
+                            // It's an UninitializedThis type.
+                            return VerificationTypeFactory.createUninitializedThisType();
+                        }
+
+                        // Is the reference type newly created and still
+                        // uninitialized?
+                        if (producerOffset > PartialEvaluator.AT_METHOD_ENTRY &&
+                            offset <= partialEvaluator.initializationOffset(producerOffset))
+                        {
+                            // It's an Uninitialized type.
+                            return VerificationTypeFactory.createUninitializedType(producerOffset);
+                        }
+                    }
+                }
+
+                // It's an ordinary Object type.
+                return VerificationTypeFactory.createObjectType(createClassConstant(programClass, referenceValue));
+        }
+
+        throw new IllegalArgumentException("Unknown computational type ["+type+"]");
+    }
+
+
+    /**
+     * Finds or creates a class constant for the given reference value, and
+     * returns its index in the constant pool.
+     */
+    private int createClassConstant(ProgramClass   programClass,
+                                    ReferenceValue referenceValue)
+    {
+        return new ConstantPoolEditor(programClass).addClassConstant(referenceValue.getType(),
+                                                                     referenceValue.getReferencedClass());
+    }
+
+
+    /**
+     * Compresses the given list of full frames, for use in a stack map table.
+     */
+    private void compressStackMapFrames(VerificationType[] initialVariableTypes,
+                                        List               stackMapFrameList)
+    {
+        int                previousVariablesCount = initialVariableTypes.length;
+        VerificationType[] previousVariableTypes  = initialVariableTypes;
+
+        int previousOffset = -1;
+
+        for (int index = 0; index < stackMapFrameList.size(); index++)
+        {
+            FullFrame fullFrame = (FullFrame)stackMapFrameList.get(index);
+
+            int                variablesCount = fullFrame.variablesCount;
+            VerificationType[] variables      = fullFrame.variables;
+            int                stackCount     = fullFrame.stackCount;
+            VerificationType[] stack          = fullFrame.stack;
+
+            // Start computing the compressed frame.
+            // The default is the full frame.
+            StackMapFrame compressedFrame = fullFrame;
+
+            // Are all variables equal?
+            if (variablesCount == previousVariablesCount &&
+                equalVerificationTypes(variables, previousVariableTypes, variablesCount))
+            {
+                // Are the stacks equal?
+                //if (stackCount == previousStackCount &&
+                //    equalVerificationTypes(stack, previousStack, stackCount))
+                //{
+                //    // Remove the identical frame.
+                //    stackMapFrameList.remove(index--);
+                //
+                //    // Move on to the next frame (at the same index).
+                //    continue;
+                //}
+                // Is the new stack empty?
+                //else
+                if (stackCount == 0)
+                {
+                    compressedFrame = new SameZeroFrame();
+                }
+                // Does the new stack contain a single element?
+                else if (stackCount == 1)
+                {
+                    compressedFrame = new SameOneFrame(stack[0]);
+                }
+            }
+            // Is the stack empty?
+            else if (stackCount == 0)
+            {
+                int additionalVariablesCount = variablesCount - previousVariablesCount;
+
+                // Are the variables chopped?
+                if (additionalVariablesCount < 0  &&
+                    additionalVariablesCount > -4 &&
+                    equalVerificationTypes(variables, previousVariableTypes, variablesCount))
+                {
+                    compressedFrame = new LessZeroFrame((byte)-additionalVariablesCount);
+                }
+                // Are the variables extended?
+                else if (//previousVariablesCount   > 0 &&
+                         additionalVariablesCount > 0 &&
+                         additionalVariablesCount < 4 &&
+                         equalVerificationTypes(variables, previousVariableTypes, previousVariablesCount))
+                {
+                    // Copy the additional variables into an array.
+                    VerificationType[] additionalVariables = new VerificationType[additionalVariablesCount];
+                    System.arraycopy(variables, variablesCount - additionalVariablesCount,
+                                     additionalVariables, 0,
+                                     additionalVariablesCount);
+
+                    compressedFrame = new MoreZeroFrame(additionalVariables);
+                }
+            }
+
+            // Compress the instruction offset.
+            int offset = fullFrame.u2offsetDelta;
+            compressedFrame.u2offsetDelta = offset - previousOffset - 1;
+            previousOffset = offset;
+
+            // Remember this frame.
+            previousVariablesCount = fullFrame.variablesCount;
+            previousVariableTypes     = fullFrame.variables;
+
+            // Replace the full frame.
+            stackMapFrameList.set(index, compressedFrame);
+        }
+    }
+
+
+    /**
+     * Returns whether the given arrays of verification types are equal, up to
+     * the given length.
+     */
+    private boolean equalVerificationTypes(VerificationType[] types1,
+                                           VerificationType[] types2,
+                                           int                length)
+    {
+        if (length > 0 &&
+            (types1.length < length ||
+             types2.length < length))
+        {
+            return false;
+        }
+
+        for (int index = 0; index < length; index++)
+        {
+            if (!types1[index].equals(types2[index]))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Returns whether the given instruction opcode represents a dup or swap
+     * instruction (dup, dup_x1, dup_x2, dup2, dup2_x1, dup2_x2, swap).
+     */
+    private boolean isDupOrSwap(int opcode)
+    {
+        return opcode >= InstructionConstants.OP_DUP &&
+               opcode <= InstructionConstants.OP_SWAP;
+    }
+}
diff --git a/src/proguard/preverify/CodeSubroutineInliner.java b/src/proguard/preverify/CodeSubroutineInliner.java
new file mode 100644
index 0000000..603eb75
--- /dev/null
+++ b/src/proguard/preverify/CodeSubroutineInliner.java
@@ -0,0 +1,405 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.preverify;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.editor.CodeAttributeComposer;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+import proguard.optimize.peephole.BranchTargetFinder;
+
+/**
+ * This AttributeVisitor inlines local subroutines (jsr/ret) in the code
+ * attributes that it visits.
+ *
+ * @author Eric Lafortune
+ */
+public class CodeSubroutineInliner
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InstructionVisitor,
+             ExceptionInfoVisitor
+{
+    //*
+    private static final boolean DEBUG = false;
+    /*/
+    private static       boolean DEBUG = true;
+    //*/
+
+
+    private final BranchTargetFinder    branchTargetFinder    = new BranchTargetFinder();
+    private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(true);
+
+    private ExceptionInfoVisitor subroutineExceptionInliner = this;
+    private int                  clipStart                  = 0;
+    private int                  clipEnd                    = Integer.MAX_VALUE;
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+//        DEBUG =
+//            clazz.getName().equals("abc/Def") &&
+//            method.getName(clazz).equals("abc");
+
+        // TODO: Remove this when the subroutine inliner has stabilized.
+        // Catch any unexpected exceptions from the actual visiting method.
+        try
+        {
+            // Process the code.
+            visitCodeAttribute0(clazz, method, codeAttribute);
+        }
+        catch (RuntimeException ex)
+        {
+            System.err.println("Unexpected error while inlining subroutines:");
+            System.err.println("  Class       = ["+clazz.getName()+"]");
+            System.err.println("  Method      = ["+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+            System.err.println("  Exception   = ["+ex.getClass().getName()+"] ("+ex.getMessage()+")");
+
+            if (DEBUG)
+            {
+                method.accept(clazz, new ClassPrinter());
+            }
+
+            throw ex;
+        }
+    }
+
+
+    public void visitCodeAttribute0(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute);
+
+        // Don't bother if there aren't any subroutines anyway.
+        if (!containsSubroutines(codeAttribute))
+        {
+            return;
+        }
+
+        if (DEBUG)
+        {
+            System.out.println("SubroutineInliner: processing ["+clazz.getName()+"."+method.getName(clazz)+method.getDescriptor(clazz)+"]");
+        }
+
+        // Append the body of the code.
+        codeAttributeComposer.reset();
+        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
+
+        // Copy the non-subroutine instructions.
+        int offset  = 0;
+        while (offset < codeAttribute.u4codeLength)
+        {
+            Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
+            int instructionLength = instruction.length(offset);
+
+            // Is this returning subroutine?
+            if (branchTargetFinder.isSubroutine(offset) &&
+                branchTargetFinder.isSubroutineReturning(offset))
+            {
+                // Skip the subroutine.
+                if (DEBUG)
+                {
+                    System.out.println("  Skipping original subroutine instruction "+instruction.toString(offset));
+                }
+
+                // Append a label at this offset instead.
+                codeAttributeComposer.appendLabel(offset);
+            }
+            else
+            {
+                // Copy the instruction, inlining any subroutine call recursively.
+                instruction.accept(clazz, method, codeAttribute, offset, this);
+            }
+
+            offset += instructionLength;
+        }
+
+        // Copy the exceptions. Note that exceptions with empty try blocks
+        // are automatically removed.
+        codeAttribute.exceptionsAccept(clazz,
+                                       method,
+                                       subroutineExceptionInliner);
+
+        if (DEBUG)
+        {
+            System.out.println("  Appending label after code at ["+offset+"]");
+        }
+
+        // Append a label just after the code.
+        codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);
+
+        // End and update the code attribute.
+        codeAttributeComposer.endCodeFragment();
+        codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);
+    }
+
+
+    /**
+     * Returns whether the given code attribute contains any subroutines.
+     */
+    private boolean containsSubroutines(CodeAttribute codeAttribute)
+    {
+        for (int offset = 0; offset < codeAttribute.u4codeLength; offset++)
+        {
+            if (branchTargetFinder.isSubroutineInvocation(offset))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Appends the specified subroutine.
+     */
+    private void inlineSubroutine(Clazz         clazz,
+                                  Method        method,
+                                  CodeAttribute codeAttribute,
+                                  int           subroutineInvocationOffset,
+                                  int           subroutineStart)
+    {
+        int subroutineEnd = branchTargetFinder.subroutineEnd(subroutineStart);
+
+        if (DEBUG)
+        {
+            System.out.println("  Inlining subroutine ["+subroutineStart+" -> "+subroutineEnd+"] at ["+subroutineInvocationOffset+"]");
+        }
+
+        // Don't go inlining exceptions that are already applicable to this
+        // subroutine invocation.
+        ExceptionInfoVisitor oldSubroutineExceptionInliner = subroutineExceptionInliner;
+        int                  oldClipStart                  = clipStart;
+        int                  oldClipEnd                    = clipEnd;
+
+        subroutineExceptionInliner =
+            new ExceptionExcludedOffsetFilter(subroutineInvocationOffset,
+                                              subroutineExceptionInliner);
+        clipStart = subroutineStart;
+        clipEnd   = subroutineEnd;
+
+        codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);
+
+        // Copy the subroutine instructions, inlining any subroutine calls
+        // recursively.
+        codeAttribute.instructionsAccept(clazz,
+                                         method,
+                                         subroutineStart,
+                                         subroutineEnd,
+                                         this);
+
+        if (DEBUG)
+        {
+            System.out.println("    Appending label after inlined subroutine at ["+subroutineEnd+"]");
+        }
+
+        // Append a label just after the code.
+        codeAttributeComposer.appendLabel(subroutineEnd);
+
+        // Inline the subroutine exceptions.
+        codeAttribute.exceptionsAccept(clazz,
+                                       method,
+                                       subroutineStart,
+                                       subroutineEnd,
+                                       subroutineExceptionInliner);
+
+        // We can again inline exceptions that are applicable to this
+        // subroutine invocation.
+        subroutineExceptionInliner = oldSubroutineExceptionInliner;
+        clipStart                  = oldClipStart;
+        clipEnd                    = oldClipEnd;
+
+        codeAttributeComposer.endCodeFragment();
+    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction)
+    {
+        // Append the instruction.
+        codeAttributeComposer.appendInstruction(offset, instruction.shrink());
+    }
+
+
+    public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction)
+    {
+        byte opcode = variableInstruction.opcode;
+        if (opcode == InstructionConstants.OP_RET)
+        {
+            // Is the return instruction the last instruction of the subroutine?
+            if (branchTargetFinder.subroutineEnd(offset) == offset + variableInstruction.length(offset))
+            {
+                if (DEBUG)
+                {
+                    System.out.println("    Replacing subroutine return at ["+offset+"] by a label");
+                }
+
+                // Append a label at this offset instead of the subroutine return.
+                codeAttributeComposer.appendLabel(offset);
+            }
+            else
+            {
+                if (DEBUG)
+                {
+                    System.out.println("    Replacing subroutine return at ["+offset+"] by a simple branch");
+                }
+
+                // Replace the instruction by a branch.
+                Instruction replacementInstruction =
+                    new BranchInstruction(InstructionConstants.OP_GOTO,
+                                          branchTargetFinder.subroutineEnd(offset) - offset).shrink();
+
+                codeAttributeComposer.appendInstruction(offset, replacementInstruction);
+            }
+        }
+        else if (branchTargetFinder.isSubroutineStart(offset))
+        {
+            if (DEBUG)
+            {
+                System.out.println("    Replacing first subroutine instruction at ["+offset+"] by a label");
+            }
+
+            // Append a label at this offset instead of saving the subroutine
+            // return address.
+            codeAttributeComposer.appendLabel(offset);
+        }
+        else
+        {
+            // Append the instruction.
+            codeAttributeComposer.appendInstruction(offset, variableInstruction);
+        }
+    }
+
+
+    public void visitBranchInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, BranchInstruction branchInstruction)
+    {
+        byte opcode = branchInstruction.opcode;
+        if (opcode == InstructionConstants.OP_JSR ||
+            opcode == InstructionConstants.OP_JSR_W)
+        {
+            int branchOffset = branchInstruction.branchOffset;
+            int branchTarget = offset + branchOffset;
+
+            // Is the subroutine ever returning?
+            if (branchTargetFinder.isSubroutineReturning(branchTarget))
+            {
+                // Append a label at this offset instead of the subroutine invocation.
+                codeAttributeComposer.appendLabel(offset);
+
+                // Inline the invoked subroutine.
+                inlineSubroutine(clazz,
+                                 method,
+                                 codeAttribute,
+                                 offset,
+                                 branchTarget);
+            }
+            else
+            {
+                if (DEBUG)
+                {
+                    System.out.println("Replacing subroutine invocation at ["+offset+"] by a simple branch");
+                }
+
+                // Replace the subroutine invocation by a simple branch.
+                Instruction replacementInstruction =
+                    new BranchInstruction(InstructionConstants.OP_GOTO,
+                                          branchOffset).shrink();
+
+                codeAttributeComposer.appendInstruction(offset, replacementInstruction);
+            }
+        }
+        else
+        {
+            // Append the instruction.
+            codeAttributeComposer.appendInstruction(offset, branchInstruction);
+        }
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        int startPC   = Math.max(exceptionInfo.u2startPC, clipStart);
+        int endPC     = Math.min(exceptionInfo.u2endPC,   clipEnd);
+        int handlerPC = exceptionInfo.u2handlerPC;
+        int catchType = exceptionInfo.u2catchType;
+
+        // Exclude any subroutine invocations that jump out of the try block,
+        // by adding a try block before (and later on, after) each invocation.
+        for (int offset = startPC; offset < endPC; offset++)
+        {
+            if (branchTargetFinder.isSubroutineInvocation(offset))
+            {
+                Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
+                int instructionLength = instruction.length(offset);
+
+                // Is it a subroutine invocation?
+                if (!exceptionInfo.isApplicable(offset + ((BranchInstruction)instruction).branchOffset))
+                {
+                    if (DEBUG)
+                    {
+                        System.out.println("  Appending extra exception ["+startPC+" -> "+offset+"] -> "+handlerPC);
+                    }
+
+                    // Append a try block that ends before the subroutine invocation.
+                    codeAttributeComposer.appendException(new ExceptionInfo(startPC,
+                                                                            offset,
+                                                                            handlerPC,
+                                                                            catchType));
+
+                    // The next try block will start after the subroutine invocation.
+                    startPC = offset + instructionLength;
+                }
+            }
+        }
+
+        if (DEBUG)
+        {
+            if (startPC == exceptionInfo.u2startPC &&
+                endPC   == exceptionInfo.u2endPC)
+            {
+                System.out.println("  Appending exception ["+startPC+" -> "+endPC+"] -> "+handlerPC);
+            }
+            else
+            {
+                System.out.println("  Appending clipped exception ["+exceptionInfo.u2startPC+" -> "+exceptionInfo.u2endPC+"] ~> ["+startPC+" -> "+endPC+"] -> "+handlerPC);
+            }
+        }
+
+        // Append the exception. Note that exceptions with empty try blocks
+        // are automatically ignored.
+        codeAttributeComposer.appendException(new ExceptionInfo(startPC,
+                                                                endPC,
+                                                                handlerPC,
+                                                                catchType));
+    }
+}
diff --git a/src/proguard/preverify/Preverifier.java b/src/proguard/preverify/Preverifier.java
new file mode 100644
index 0000000..e071c5c
--- /dev/null
+++ b/src/proguard/preverify/Preverifier.java
@@ -0,0 +1,74 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.preverify;
+
+import proguard.Configuration;
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AllAttributeVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This class can preverify methods in program class pools, according to a given
+ * configuration.
+ *
+ * @author Eric Lafortune
+ */
+public class Preverifier
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new Preverifier.
+     */
+    public Preverifier(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Performs preverification of the given program class pool.
+     */
+    public void execute(ClassPool programClassPool)
+    {
+        // Clean up any old visitor info.
+        programClassPool.classesAccept(new ClassCleaner());
+
+        // Preverify all methods.
+        ClassVisitor preverifier =
+            new AllMethodVisitor(
+            new AllAttributeVisitor(
+            new CodePreverifier(configuration.microEdition)));
+
+        // In Java Standard Edition, only class files from Java 6 or higher
+        // should be preverified.
+        if (!configuration.microEdition)
+        {
+            preverifier =
+                new ClassVersionFilter(ClassConstants.INTERNAL_CLASS_VERSION_1_6,
+                                       Integer.MAX_VALUE,
+                                       preverifier);
+        }
+
+        programClassPool.classesAccept(preverifier);
+    }
+}
diff --git a/src/proguard/preverify/SubroutineInliner.java b/src/proguard/preverify/SubroutineInliner.java
new file mode 100644
index 0000000..e28512f
--- /dev/null
+++ b/src/proguard/preverify/SubroutineInliner.java
@@ -0,0 +1,74 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.preverify;
+
+import proguard.Configuration;
+import proguard.classfile.*;
+import proguard.classfile.attribute.visitor.AllAttributeVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This class can inline subroutines in methods. This is generally useful (i.e.
+ * required) for preverifying code.
+ *
+ * @author Eric Lafortune
+ */
+public class SubroutineInliner
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new SubroutineInliner.
+     */
+    public SubroutineInliner(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Performs subroutine inlining of the given program class pool.
+     */
+    public void execute(ClassPool programClassPool)
+    {
+        // Clean up any old visitor info.
+        programClassPool.classesAccept(new ClassCleaner());
+
+        // Inline all subroutines.
+        ClassVisitor inliner =
+            new AllMethodVisitor(
+            new AllAttributeVisitor(
+            new CodeSubroutineInliner()));
+
+        // In Java Standard Edition, only class files from Java 6 or higher
+        // should be preverified.
+        if (!configuration.microEdition)
+        {
+            inliner =
+                new ClassVersionFilter(ClassConstants.INTERNAL_CLASS_VERSION_1_6,
+                                       Integer.MAX_VALUE,
+                                       inliner);
+        }
+
+        programClassPool.classesAccept(inliner);
+    }
+}
diff --git a/src/proguard/retrace/MANIFEST.MF b/src/proguard/retrace/MANIFEST.MF
new file mode 100644
index 0000000..42c9800
--- /dev/null
+++ b/src/proguard/retrace/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: proguard.retrace.ReTrace
+Class-Path: proguard.jar
diff --git a/src/proguard/retrace/ReTrace.java b/src/proguard/retrace/ReTrace.java
new file mode 100644
index 0000000..97ab27b
--- /dev/null
+++ b/src/proguard/retrace/ReTrace.java
@@ -0,0 +1,749 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.retrace;
+
+import proguard.classfile.util.ClassUtil;
+import proguard.obfuscate.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+
+/**
+ * Tool for de-obfuscating stack traces of applications that were obfuscated
+ * with ProGuard.
+ *
+ * @author Eric Lafortune
+ */
+public class ReTrace
+implements   MappingProcessor
+{
+    private static final String REGEX_OPTION   = "-regex";
+    private static final String VERBOSE_OPTION = "-verbose";
+
+
+    public static final String STACK_TRACE_EXPRESSION = "(?:\\s*%c:.*)|(?:\\s*at\\s+%c.%m\\s*\\(.*?(?::%l)?\\)\\s*)";
+
+    private static final String REGEX_CLASS       = "\\b(?:[A-Za-z0-9_$]+\\.)*[A-Za-z0-9_$]+\\b";
+    private static final String REGEX_CLASS_SLASH = "\\b(?:[A-Za-z0-9_$]+/)*[A-Za-z0-9_$]+\\b";
+    private static final String REGEX_LINE_NUMBER = "\\b[0-9]+\\b";
+    private static final String REGEX_TYPE        = REGEX_CLASS + "(?:\\[\\])*";
+    private static final String REGEX_MEMBER      = "\\b[A-Za-z0-9_$]+\\b";
+    private static final String REGEX_ARGUMENTS   = "(?:" + REGEX_TYPE + "(?:\\s*,\\s*" + REGEX_TYPE + ")*)?";
+
+    // The class settings.
+    private final String  regularExpression;
+    private final boolean verbose;
+    private final File    mappingFile;
+    private final File    stackTraceFile;
+
+    private Map classMap       = new HashMap();
+    private Map classFieldMap  = new HashMap();
+    private Map classMethodMap = new HashMap();
+
+
+    /**
+     * Creates a new ReTrace object to process stack traces on the standard
+     * input, based on the given mapping file name.
+     * @param regularExpression the regular expression for parsing the lines in
+     *                          the stack trace.
+     * @param verbose           specifies whether the de-obfuscated stack trace
+     *                          should be verbose.
+     * @param mappingFile       the mapping file that was written out by
+     *                          ProGuard.
+     */
+    public ReTrace(String  regularExpression,
+                   boolean verbose,
+                   File    mappingFile)
+    {
+        this(regularExpression, verbose, mappingFile, null);
+    }
+
+
+    /**
+     * Creates a new ReTrace object to process a stack trace from the given file,
+     * based on the given mapping file name.
+     * @param regularExpression the regular expression for parsing the lines in
+     *                          the stack trace.
+     * @param verbose           specifies whether the de-obfuscated stack trace
+     *                          should be verbose.
+     * @param mappingFile       the mapping file that was written out by
+     *                          ProGuard.
+     * @param stackTraceFile    the optional name of the file that contains the
+     *                          stack trace.
+     */
+    public ReTrace(String  regularExpression,
+                   boolean verbose,
+                   File    mappingFile,
+                   File    stackTraceFile)
+    {
+        this.regularExpression = regularExpression;
+        this.verbose           = verbose;
+        this.mappingFile       = mappingFile;
+        this.stackTraceFile    = stackTraceFile;
+    }
+
+
+    /**
+     * Performs the subsequent ReTrace operations.
+     */
+    public void execute() throws IOException
+    {
+        // Read the mapping file.
+        MappingReader mappingReader = new MappingReader(mappingFile);
+        mappingReader.pump(this);
+
+
+        StringBuffer expressionBuffer    = new StringBuffer(regularExpression.length() + 32);
+        char[]       expressionTypes     = new char[32];
+        int          expressionTypeCount = 0;
+        int index = 0;
+        while (true)
+        {
+            int nextIndex = regularExpression.indexOf('%', index);
+            if (nextIndex < 0                             ||
+                nextIndex == regularExpression.length()-1 ||
+                expressionTypeCount == expressionTypes.length)
+            {
+                break;
+            }
+
+            expressionBuffer.append(regularExpression.substring(index, nextIndex));
+            expressionBuffer.append('(');
+
+            char expressionType = regularExpression.charAt(nextIndex + 1);
+            switch(expressionType)
+            {
+                case 'c':
+                    expressionBuffer.append(REGEX_CLASS);
+                    break;
+
+                case 'C':
+                    expressionBuffer.append(REGEX_CLASS_SLASH);
+                    break;
+
+                case 'l':
+                    expressionBuffer.append(REGEX_LINE_NUMBER);
+                    break;
+
+                case 't':
+                    expressionBuffer.append(REGEX_TYPE);
+                    break;
+
+                case 'f':
+                    expressionBuffer.append(REGEX_MEMBER);
+                    break;
+
+                case 'm':
+                    expressionBuffer.append(REGEX_MEMBER);
+                    break;
+
+                case 'a':
+                    expressionBuffer.append(REGEX_ARGUMENTS);
+                    break;
+            }
+
+            expressionBuffer.append(')');
+
+            expressionTypes[expressionTypeCount++] = expressionType;
+
+            index = nextIndex + 2;
+        }
+
+        expressionBuffer.append(regularExpression.substring(index));
+
+        Pattern pattern = Pattern.compile(expressionBuffer.toString());
+
+        // Read the stack trace file.
+        LineNumberReader reader =
+            new LineNumberReader(stackTraceFile == null ?
+                (Reader)new InputStreamReader(System.in) :
+                (Reader)new BufferedReader(new FileReader(stackTraceFile)));
+
+
+        try
+        {
+            StringBuffer outLine = new StringBuffer(256);
+            List         extraOutLines  = new ArrayList();
+
+            String className = null;
+
+            // Read the line in the stack trace.
+            while (true)
+            {
+                String line = reader.readLine();
+                if (line == null)
+                {
+                    break;
+                }
+
+                Matcher matcher = pattern.matcher(line);
+
+                if (matcher.matches())
+                {
+                    int    lineNumber = 0;
+                    String type       = null;
+                    String arguments  = null;
+
+                    // Figure out a class name, line number, type, and
+                    // arguments beforehand.
+                    for (int expressionTypeIndex = 0; expressionTypeIndex < expressionTypeCount; expressionTypeIndex++)
+                    {
+                        int startIndex = matcher.start(expressionTypeIndex + 1);
+                        if (startIndex >= 0)
+                        {
+                            String match = matcher.group(expressionTypeIndex + 1);
+
+                            char expressionType = expressionTypes[expressionTypeIndex];
+                            switch (expressionType)
+                            {
+                                case 'c':
+                                    className = originalClassName(match);
+                                    break;
+
+                                case 'C':
+                                    className = originalClassName(ClassUtil.externalClassName(match));
+                                    break;
+
+                                case 'l':
+                                    lineNumber = Integer.parseInt(match);
+                                    break;
+
+                                case 't':
+                                    type = originalType(match);
+                                    break;
+
+                                case 'a':
+                                    arguments = originalArguments(match);
+                                    break;
+                            }
+                        }
+                    }
+
+                    // Actually construct the output line.
+                    int lineIndex = 0;
+
+                    outLine.setLength(0);
+                    extraOutLines.clear();
+
+                    for (int expressionTypeIndex = 0; expressionTypeIndex < expressionTypeCount; expressionTypeIndex++)
+                    {
+                        int startIndex = matcher.start(expressionTypeIndex + 1);
+                        if (startIndex >= 0)
+                        {
+                            int    endIndex = matcher.end(expressionTypeIndex + 1);
+                            String match    = matcher.group(expressionTypeIndex + 1);
+
+                            // Copy a literal piece of input line.
+                            outLine.append(line.substring(lineIndex, startIndex));
+
+                            char expressionType = expressionTypes[expressionTypeIndex];
+                            switch (expressionType)
+                            {
+                                case 'c':
+                                    className = originalClassName(match);
+                                    outLine.append(className);
+                                    break;
+
+                                case 'C':
+                                    className = originalClassName(ClassUtil.externalClassName(match));
+                                    outLine.append(ClassUtil.internalClassName(className));
+                                    break;
+
+                                case 'l':
+                                    lineNumber = Integer.parseInt(match);
+                                    outLine.append(match);
+                                    break;
+
+                                case 't':
+                                    type = originalType(match);
+                                    outLine.append(type);
+                                    break;
+
+                                case 'f':
+                                    originalFieldName(className,
+                                                      match,
+                                                      type,
+                                                      outLine,
+                                                      extraOutLines);
+                                    break;
+
+                                case 'm':
+                                    originalMethodName(className,
+                                                       match,
+                                                       lineNumber,
+                                                       type,
+                                                       arguments,
+                                                       outLine,
+                                                       extraOutLines);
+                                    break;
+
+                                case 'a':
+                                    arguments = originalArguments(match);
+                                    outLine.append(arguments);
+                                    break;
+                            }
+
+                            // Skip the original element whose processed version
+                            // has just been appended.
+                            lineIndex = endIndex;
+                        }
+                    }
+
+                    // Copy the last literal piece of input line.
+                    outLine.append(line.substring(lineIndex));
+
+                    // Print out the main line.
+                    System.out.println(outLine);
+
+                    // Print out any additional lines.
+                    for (int extraLineIndex = 0; extraLineIndex < extraOutLines.size(); extraLineIndex++)
+                    {
+                        System.out.println(extraOutLines.get(extraLineIndex));
+                    }
+                }
+                else
+                {
+                    // Print out the original line.
+                    System.out.println(line);
+                }
+            }
+        }
+        catch (IOException ex)
+        {
+            throw new IOException("Can't read stack trace (" + ex.getMessage() + ")");
+        }
+        finally
+        {
+            if (stackTraceFile != null)
+            {
+                try
+                {
+                    reader.close();
+                }
+                catch (IOException ex)
+                {
+                    // This shouldn't happen.
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Finds the original field name(s), appending the first one to the out
+     * line, and any additional alternatives to the extra lines.
+     */
+    private void originalFieldName(String       className,
+                                   String       obfuscatedFieldName,
+                                   String       type,
+                                   StringBuffer outLine,
+                                   List         extraOutLines)
+    {
+        int extraIndent = -1;
+
+        // Class name -> obfuscated field names.
+        Map fieldMap = (Map)classFieldMap.get(className);
+        if (fieldMap != null)
+        {
+            // Obfuscated field names -> fields.
+            Set fieldSet = (Set)fieldMap.get(obfuscatedFieldName);
+            if (fieldSet != null)
+            {
+                // Find all matching fields.
+                Iterator fieldInfoIterator = fieldSet.iterator();
+                while (fieldInfoIterator.hasNext())
+                {
+                    FieldInfo fieldInfo = (FieldInfo)fieldInfoIterator.next();
+                    if (fieldInfo.matches(type))
+                    {
+                        // Is this the first matching field?
+                        if (extraIndent < 0)
+                        {
+                            extraIndent = outLine.length();
+
+                            // Append the first original name.
+                            if (verbose)
+                            {
+                                outLine.append(fieldInfo.type).append(' ');
+                            }
+                            outLine.append(fieldInfo.originalName);
+                        }
+                        else
+                        {
+                            // Create an additional line with the proper
+                            // indentation.
+                            StringBuffer extraBuffer = new StringBuffer();
+                            for (int counter = 0; counter < extraIndent; counter++)
+                            {
+                                extraBuffer.append(' ');
+                            }
+
+                            // Append the alternative name.
+                            if (verbose)
+                            {
+                                extraBuffer.append(fieldInfo.type).append(' ');
+                            }
+                            extraBuffer.append(fieldInfo.originalName);
+
+                            // Store the additional line.
+                            extraOutLines.add(extraBuffer);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Just append the obfuscated name if we haven't found any matching
+        // fields.
+        if (extraIndent < 0)
+        {
+            outLine.append(obfuscatedFieldName);
+        }
+    }
+
+
+    /**
+     * Finds the original method name(s), appending the first one to the out
+     * line, and any additional alternatives to the extra lines.
+     */
+    private void originalMethodName(String       className,
+                                    String       obfuscatedMethodName,
+                                    int          lineNumber,
+                                    String       type,
+                                    String       arguments,
+                                    StringBuffer outLine,
+                                    List         extraOutLines)
+    {
+        int extraIndent = -1;
+
+        // Class name -> obfuscated method names.
+        Map methodMap = (Map)classMethodMap.get(className);
+        if (methodMap != null)
+        {
+            // Obfuscated method names -> methods.
+            Set methodSet = (Set)methodMap.get(obfuscatedMethodName);
+            if (methodSet != null)
+            {
+                // Find all matching methods.
+                Iterator methodInfoIterator = methodSet.iterator();
+                while (methodInfoIterator.hasNext())
+                {
+                    MethodInfo methodInfo = (MethodInfo)methodInfoIterator.next();
+                    if (methodInfo.matches(lineNumber, type, arguments))
+                    {
+                        // Is this the first matching method?
+                        if (extraIndent < 0)
+                        {
+                            extraIndent = outLine.length();
+
+                            // Append the first original name.
+                            if (verbose)
+                            {
+                                outLine.append(methodInfo.type).append(' ');
+                            }
+                            outLine.append(methodInfo.originalName);
+                            if (verbose)
+                            {
+                                outLine.append('(').append(methodInfo.arguments).append(')');
+                            }
+                        }
+                        else
+                        {
+                            // Create an additional line with the proper
+                            // indentation.
+                            StringBuffer extraBuffer = new StringBuffer();
+                            for (int counter = 0; counter < extraIndent; counter++)
+                            {
+                                extraBuffer.append(' ');
+                            }
+
+                            // Append the alternative name.
+                            if (verbose)
+                            {
+                                extraBuffer.append(methodInfo.type).append(' ');
+                            }
+                            extraBuffer.append(methodInfo.originalName);
+                            if (verbose)
+                            {
+                                extraBuffer.append('(').append(methodInfo.arguments).append(')');
+                            }
+
+                            // Store the additional line.
+                            extraOutLines.add(extraBuffer);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Just append the obfuscated name if we haven't found any matching
+        // methods.
+        if (extraIndent < 0)
+        {
+            outLine.append(obfuscatedMethodName);
+        }
+    }
+
+
+    /**
+     * Returns the original argument types.
+     */
+    private String originalArguments(String obfuscatedArguments)
+    {
+        StringBuffer originalArguments = new StringBuffer();
+
+        int startIndex = 0;
+        while (true)
+        {
+            int endIndex = obfuscatedArguments.indexOf(',', startIndex);
+            if (endIndex < 0)
+            {
+                break;
+            }
+
+            originalArguments.append(originalType(obfuscatedArguments.substring(startIndex, endIndex).trim())).append(',');
+
+            startIndex = endIndex + 1;
+        }
+
+        originalArguments.append(originalType(obfuscatedArguments.substring(startIndex).trim()));
+
+        return originalArguments.toString();
+    }
+
+
+    /**
+     * Returns the original type.
+     */
+    private String originalType(String obfuscatedType)
+    {
+        int index = obfuscatedType.indexOf('[');
+
+        return index >= 0 ?
+            originalClassName(obfuscatedType.substring(0, index)) + obfuscatedType.substring(index) :
+            originalClassName(obfuscatedType);
+    }
+
+
+    /**
+     * Returns the original class name.
+     */
+    private String originalClassName(String obfuscatedClassName)
+    {
+        String originalClassName = (String)classMap.get(obfuscatedClassName);
+
+        return originalClassName != null ?
+            originalClassName :
+            obfuscatedClassName;
+    }
+
+
+    // Implementations for MappingProcessor.
+
+    public boolean processClassMapping(String className, String newClassName)
+    {
+        // Obfuscated class name -> original class name.
+        classMap.put(newClassName, className);
+
+        return true;
+    }
+
+
+    public void processFieldMapping(String className, String fieldType, String fieldName, String newFieldName)
+    {
+        // Original class name -> obfuscated field names.
+        Map fieldMap = (Map)classFieldMap.get(className);
+        if (fieldMap == null)
+        {
+            fieldMap = new HashMap();
+            classFieldMap.put(className, fieldMap);
+        }
+
+        // Obfuscated field name -> fields.
+        Set fieldSet = (Set)fieldMap.get(newFieldName);
+        if (fieldSet == null)
+        {
+            fieldSet = new LinkedHashSet();
+            fieldMap.put(newFieldName, fieldSet);
+        }
+
+        // Add the field information.
+        fieldSet.add(new FieldInfo(fieldType,
+                                   fieldName));
+    }
+
+
+    public void processMethodMapping(String className, int firstLineNumber, int lastLineNumber, String methodReturnType, String methodName, String methodArguments, String newMethodName)
+    {
+        // Original class name -> obfuscated method names.
+        Map methodMap = (Map)classMethodMap.get(className);
+        if (methodMap == null)
+        {
+            methodMap = new HashMap();
+            classMethodMap.put(className, methodMap);
+        }
+
+        // Obfuscated method name -> methods.
+        Set methodSet = (Set)methodMap.get(newMethodName);
+        if (methodSet == null)
+        {
+            methodSet = new LinkedHashSet();
+            methodMap.put(newMethodName, methodSet);
+        }
+
+        // Add the method information.
+        methodSet.add(new MethodInfo(firstLineNumber,
+                                     lastLineNumber,
+                                     methodReturnType,
+                                     methodArguments,
+                                     methodName));
+    }
+
+
+    /**
+     * A field record.
+     */
+    private static class FieldInfo
+    {
+        private String type;
+        private String originalName;
+
+
+        private FieldInfo(String type, String originalName)
+        {
+            this.type         = type;
+            this.originalName = originalName;
+        }
+
+
+        private boolean matches(String type)
+        {
+            return
+                type == null || type.equals(this.type);
+        }
+    }
+
+
+    /**
+     * A method record.
+     */
+    private static class MethodInfo
+    {
+        private int    firstLineNumber;
+        private int    lastLineNumber;
+        private String type;
+        private String arguments;
+        private String originalName;
+
+
+        private MethodInfo(int firstLineNumber, int lastLineNumber, String type, String arguments, String originalName)
+        {
+            this.firstLineNumber = firstLineNumber;
+            this.lastLineNumber  = lastLineNumber;
+            this.type            = type;
+            this.arguments       = arguments;
+            this.originalName    = originalName;
+        }
+
+
+        private boolean matches(int lineNumber, String type, String arguments)
+        {
+            return
+                (lineNumber == 0    || (firstLineNumber <= lineNumber && lineNumber <= lastLineNumber) || lastLineNumber == 0) &&
+                (type       == null || type.equals(this.type))                                                                 &&
+                (arguments  == null || arguments.equals(this.arguments));
+        }
+    }
+
+
+    /**
+     * The main program for ReTrace.
+     */
+    public static void main(String[] args)
+    {
+        if (args.length < 1)
+        {
+            System.err.println("Usage: java proguard.ReTrace [-verbose] <mapping_file> [<stacktrace_file>]");
+            System.exit(-1);
+        }
+
+        String  regularExpresssion = STACK_TRACE_EXPRESSION;
+        boolean verbose            = false;
+
+        int argumentIndex = 0;
+        while (argumentIndex < args.length)
+        {
+            String arg = args[argumentIndex];
+            if (arg.equals(REGEX_OPTION))
+            {
+                regularExpresssion = args[++argumentIndex];
+            }
+            else if (arg.equals(VERBOSE_OPTION))
+            {
+                verbose = true;
+            }
+            else
+            {
+                break;
+            }
+
+            argumentIndex++;
+        }
+
+        if (argumentIndex >= args.length)
+        {
+            System.err.println("Usage: java proguard.ReTrace [-regex <regex>] [-verbose] <mapping_file> [<stacktrace_file>]");
+            System.exit(-1);
+        }
+
+        File mappingFile    = new File(args[argumentIndex++]);
+        File stackTraceFile = argumentIndex < args.length ?
+            new File(args[argumentIndex]) :
+            null;
+
+        ReTrace reTrace = new ReTrace(regularExpresssion, verbose, mappingFile, stackTraceFile);
+
+        try
+        {
+            // Execute ReTrace with its given settings.
+            reTrace.execute();
+        }
+        catch (IOException ex)
+        {
+            if (verbose)
+            {
+                // Print a verbose stack trace.
+                ex.printStackTrace();
+            }
+            else
+            {
+                // Print just the stack trace message.
+                System.err.println("Error: "+ex.getMessage());
+            }
+
+            System.exit(1);
+        }
+
+        System.exit(0);
+    }
+}
diff --git a/src/proguard/retrace/package.html b/src/proguard/retrace/package.html
new file mode 100644
index 0000000..a35ce21
--- /dev/null
+++ b/src/proguard/retrace/package.html
@@ -0,0 +1,4 @@
+<body>
+This package contains the main ReTrace application.
+ReTrace can de-obfuscate stack traces of obfuscated programs.
+</body>
diff --git a/src/proguard/shrink/AnnotationUsageMarker.java b/src/proguard/shrink/AnnotationUsageMarker.java
new file mode 100644
index 0000000..9aaae34
--- /dev/null
+++ b/src/proguard/shrink/AnnotationUsageMarker.java
@@ -0,0 +1,359 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.Attribute;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+/**
+ * This AttributeVisitor recursively marks all necessary annotation information
+ * in the attributes that it visits.
+ *
+ * @see UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class AnnotationUsageMarker
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor,
+             ConstantVisitor,
+             ClassVisitor,
+             MemberVisitor
+{
+    private final UsageMarker usageMarker;
+
+    // Fields acting as a return parameters for several methods.
+    private boolean attributeUsed;
+    private boolean annotationUsed;
+    private boolean elementValueUsed;
+    private boolean classUsed;
+    private boolean methodUsed;
+
+
+    /**
+     * Creates a new AnnotationUsageMarker.
+     * @param usageMarker the usage marker that is used to mark the classes
+     *                    and class members.
+     */
+    public AnnotationUsageMarker(UsageMarker usageMarker)
+    {
+        this.usageMarker = usageMarker;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Mark the necessary annotation information.
+        attributeUsed = false;
+        annotationsAttribute.annotationsAccept(clazz, this);
+
+        if (attributeUsed)
+        {
+            // We got a positive used flag, so some annotation is being used.
+            // Mark this attribute as being used as well.
+            usageMarker.markAsUsed(annotationsAttribute);
+
+            markConstant(clazz, annotationsAttribute.u2attributeNameIndex);
+        }
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Mark the necessary annotation information.
+        attributeUsed = false;
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+
+        if (attributeUsed)
+        {
+            // We got a positive used flag, so some annotation is being used.
+            // Mark this attribute as being used as well.
+            usageMarker.markAsUsed(parameterAnnotationsAttribute);
+
+            markConstant(clazz, parameterAnnotationsAttribute.u2attributeNameIndex);
+        }
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Mark the necessary annotation information in any annotation elements.
+        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+
+        // Always mark annotation defaults.
+        usageMarker.markAsUsed(annotationDefaultAttribute);
+
+        markConstant(clazz, annotationDefaultAttribute.u2attributeNameIndex);
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        if (isReferencedClassUsed(annotation))
+        {
+            // Mark the annotation as being used.
+            usageMarker.markAsUsed(annotation);
+
+            markConstant(clazz, annotation.u2typeIndex);
+
+            // Mark the necessary element values.
+            annotation.elementValuesAccept(clazz, this);
+
+            // The return values.
+            annotationUsed = true;
+            attributeUsed  = true;
+        }
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+    {
+        if (isReferencedMethodUsed(constantElementValue))
+        {
+            // Mark the element value as being used.
+            usageMarker.markAsUsed(constantElementValue);
+
+            markConstant(clazz, constantElementValue.u2elementNameIndex);
+            markConstant(clazz, constantElementValue.u2constantValueIndex);
+
+            // The return value.
+            elementValueUsed = true;
+        }
+    }
+
+
+    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+    {
+        if (isReferencedMethodUsed(enumConstantElementValue))
+        {
+            // Check the referenced classes.
+            classUsed = true;
+            enumConstantElementValue.referencedClassesAccept(usageMarker);
+
+            if (classUsed)
+            {
+                // Mark the element value as being used.
+                usageMarker.markAsUsed(enumConstantElementValue);
+
+                markConstant(clazz, enumConstantElementValue.u2elementNameIndex);
+                markConstant(clazz, enumConstantElementValue.u2typeNameIndex);
+                markConstant(clazz, enumConstantElementValue.u2constantNameIndex);
+
+                // The return value.
+                elementValueUsed = true;
+            }
+        }
+    }
+
+
+    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+    {
+        if (isReferencedMethodUsed(classElementValue))
+        {
+            // Check the referenced classes.
+            classUsed = true;
+            classElementValue.referencedClassesAccept(usageMarker);
+
+            if (classUsed)
+            {
+                // Mark the element value as being used.
+                usageMarker.markAsUsed(classElementValue);
+
+                markConstant(clazz, classElementValue.u2elementNameIndex);
+                markConstant(clazz, classElementValue.u2classInfoIndex);
+
+                // The return value.
+                elementValueUsed = true;
+            }
+        }
+    }
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        if (isReferencedMethodUsed(annotationElementValue))
+        {
+            boolean oldAnnotationUsed = annotationUsed;
+
+            // Check and mark the contained annotation.
+            annotationUsed = false;
+            annotationElementValue.annotationAccept(clazz, this);
+
+            if (annotationUsed)
+            {
+                // Mark the element value as being used.
+                usageMarker.markAsUsed(annotationElementValue);
+
+                markConstant(clazz, annotationElementValue.u2elementNameIndex);
+
+                // The return value.
+                elementValueUsed = true;
+            }
+
+            annotationUsed = oldAnnotationUsed;
+        }
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        if (isReferencedMethodUsed(arrayElementValue))
+        {
+            boolean oldelementValueUsed = elementValueUsed;
+
+            // Check and mark the contained element values.
+            elementValueUsed = false;
+            arrayElementValue.elementValuesAccept(clazz, annotation, this);
+
+            if (elementValueUsed)
+            {
+                // Mark the element value as being used.
+                usageMarker.markAsUsed(arrayElementValue);
+
+                markConstant(clazz, arrayElementValue.u2elementNameIndex);
+
+                // The return value.
+                //elementValueUsed = true;
+            }
+            else
+            {
+                elementValueUsed = oldelementValueUsed;
+            }
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitAnyConstant(Clazz clazz, Constant constant)
+    {
+        usageMarker.markAsUsed(constant);
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classUsed = usageMarker.isUsed(classConstant);
+
+        // Is the class constant marked as being used?
+        if (!classUsed)
+        {
+            // Check the referenced class.
+            classUsed = true;
+            classConstant.referencedClassAccept(this);
+
+            // Is the referenced class marked as being used?
+            if (classUsed)
+            {
+                // Mark the class constant and its Utf8 constant.
+                usageMarker.markAsUsed(classConstant);
+
+                markConstant(clazz, classConstant.u2nameIndex);
+            }
+        }
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        classUsed = usageMarker.isUsed(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        classUsed = true;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        methodUsed = usageMarker.isUsed(programMethod);
+    }
+
+
+    public void visitLibraryMethod(LibraryClass LibraryClass, LibraryMethod libraryMethod)
+    {
+        classUsed = true;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the annotation class has been marked as being used.
+     */
+    private boolean isReferencedClassUsed(Annotation annotation)
+    {
+        // Check if the referenced class is being used.
+        classUsed = true;
+        annotation.referencedClassAccept(this);
+
+        return classUsed;
+    }
+
+
+    /**
+     * Returns whether the annotation method has been marked as being used.
+     */
+    private boolean isReferencedMethodUsed(ElementValue elementValue)
+    {
+        // Check if the referenced method is being used.
+        methodUsed = true;
+        elementValue.referencedMethodAccept(this);
+
+        return methodUsed;
+    }
+
+
+    /**
+     * Marks the specified constant pool entry.
+     */
+    private void markConstant(Clazz clazz, int index)
+    {
+        if (index > 0)
+        {
+            clazz.constantPoolEntryAccept(index, this);
+        }
+    }
+}
diff --git a/src/proguard/shrink/ClassShrinker.java b/src/proguard/shrink/ClassShrinker.java
new file mode 100644
index 0000000..0b5c5b7
--- /dev/null
+++ b/src/proguard/shrink/ClassShrinker.java
@@ -0,0 +1,448 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.annotation.visitor.*;
+import proguard.classfile.attribute.visitor.AttributeVisitor;
+import proguard.classfile.constant.*;
+import proguard.classfile.editor.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+/**
+ * This ClassVisitor removes constant pool entries and class members that
+ * are not marked as being used.
+ *
+ * @see UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class ClassShrinker
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor,
+             AttributeVisitor,
+             AnnotationVisitor,
+             ElementValueVisitor
+{
+    private final UsageMarker usageMarker;
+
+    private int[] constantIndexMap = new int[ClassConstants.TYPICAL_CONSTANT_POOL_SIZE];
+
+    private final ConstantPoolRemapper constantPoolRemapper = new ConstantPoolRemapper();
+
+
+    /**
+     * Creates a new ClassShrinker.
+     * @param usageMarker the usage marker that is used to mark the classes
+     *                    and class members.
+     */
+    public ClassShrinker(UsageMarker usageMarker)
+    {
+        this.usageMarker = usageMarker;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Shrink the arrays for constant pool, interfaces, fields, methods,
+        // and class attributes.
+        programClass.u2interfacesCount =
+            shrinkConstantIndexArray(programClass.constantPool,
+                                     programClass.u2interfaces,
+                                     programClass.u2interfacesCount);
+
+        // Shrinking the constant pool also sets up an index map.
+        programClass.u2constantPoolCount =
+            shrinkConstantPool(programClass.constantPool,
+                               programClass.u2constantPoolCount);
+
+        programClass.u2fieldsCount =
+            shrinkArray(programClass.fields,
+                        programClass.u2fieldsCount);
+
+        programClass.u2methodsCount =
+            shrinkArray(programClass.methods,
+                        programClass.u2methodsCount);
+
+        programClass.u2attributesCount =
+            shrinkArray(programClass.attributes,
+                        programClass.u2attributesCount);
+
+        // Compact the remaining fields, methods, and attributes,
+        // and remap their references to the constant pool.
+        programClass.fieldsAccept(this);
+        programClass.methodsAccept(this);
+        programClass.attributesAccept(this);
+
+        // Remap all constant pool references.
+        constantPoolRemapper.setConstantIndexMap(constantIndexMap);
+        constantPoolRemapper.visitProgramClass(programClass);
+
+        // Remove the unused interfaces from the class signature.
+        programClass.attributesAccept(new SignatureShrinker());
+
+        // Compact the extra field pointing to the subclasses of this class.
+        programClass.subClasses =
+            shrinkToNewArray(programClass.subClasses);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Library classes are left unchanged.
+
+        // Compact the extra field pointing to the subclasses of this class.
+        libraryClass.subClasses =
+            shrinkToNewArray(libraryClass.subClasses);
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramMember(ProgramClass programClass, ProgramMember programMember)
+    {
+        // Shrink the attributes array.
+        programMember.u2attributesCount =
+            shrinkArray(programMember.attributes,
+                        programMember.u2attributesCount);
+
+        // Shrink any attributes.
+        programMember.attributesAccept(programClass, this);
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // Shrink the array of InnerClassesInfo objects.
+        innerClassesAttribute.u2classesCount =
+            shrinkArray(innerClassesAttribute.classes,
+                        innerClassesAttribute.u2classesCount);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        // Sometimes, a class is still referenced (apparently as a dummy class),
+        // but its enclosing method is not. Then remove the reference to
+        // the enclosing method.
+        // E.g. the anonymous inner class javax.swing.JList$1 is defined inside
+        // a constructor of javax.swing.JList, but it is also referenced as a
+        // dummy argument in a constructor of javax.swing.JList$ListSelectionHandler.
+        if (enclosingMethodAttribute.referencedMethod != null &&
+            !usageMarker.isUsed(enclosingMethodAttribute.referencedMethod))
+        {
+            enclosingMethodAttribute.u2nameAndTypeIndex = 0;
+
+            enclosingMethodAttribute.referencedMethod = null;
+        }
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        // Shrink the attributes array.
+        codeAttribute.u2attributesCount =
+            shrinkArray(codeAttribute.attributes,
+                        codeAttribute.u2attributesCount);
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Shrink the annotations array.
+        annotationsAttribute.u2annotationsCount =
+            shrinkArray(annotationsAttribute.annotations,
+                        annotationsAttribute.u2annotationsCount);
+
+        // Shrink the annotations themselves.
+        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Loop over all parameters.
+        for (int parameterIndex = 0; parameterIndex < parameterAnnotationsAttribute.u2parametersCount; parameterIndex++)
+        {
+            // Shrink the parameter annotations array.
+            parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex] =
+                shrinkArray(parameterAnnotationsAttribute.parameterAnnotations[parameterIndex],
+                            parameterAnnotationsAttribute.u2parameterAnnotationsCount[parameterIndex]);
+        }
+
+        // Shrink the annotations themselves.
+        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    // Implementations for AnnotationVisitor.
+
+    public void visitAnnotation(Clazz clazz, Annotation annotation)
+    {
+        // Shrink the element values array.
+        annotation.u2elementValuesCount =
+            shrinkArray(annotation.elementValues,
+                        annotation.u2elementValuesCount);
+
+        // Shrink the element values themselves.
+        annotation.elementValuesAccept(clazz, this);
+    }
+
+
+    /**
+     * This AttributeVisitor updates the Utf8 constants of class signatures,
+     * removing any unused interfaces.
+     */
+    private class SignatureShrinker
+    extends       SimplifiedVisitor
+    implements    AttributeVisitor
+    {
+        public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+        public void visitSignatureAttribute(Clazz clazz, SignatureAttribute  signatureAttribute)
+        {
+            Clazz[] referencedClasses = signatureAttribute.referencedClasses;
+            if (referencedClasses != null)
+            {
+                // Go over the generic definitions, superclass and implemented interfaces.
+                String signature = clazz.getString(signatureAttribute.u2signatureIndex);
+
+                InternalTypeEnumeration internalTypeEnumeration =
+                    new InternalTypeEnumeration(signature);
+
+                StringBuffer newSignatureBuffer = new StringBuffer();
+
+                int referencedClassIndex    = 0;
+                int newReferencedClassIndex = 0;
+
+                while (internalTypeEnumeration.hasMoreTypes())
+                {
+                    // Consider the classes referenced by this signature.
+                    String type       = internalTypeEnumeration.nextType();
+                    int    classCount = new DescriptorClassEnumeration(type).classCount();
+
+                    Clazz referencedClass = referencedClasses[referencedClassIndex];
+                    if (referencedClass == null ||
+                        usageMarker.isUsed(referencedClass))
+                    {
+                        // Append the superclass or interface.
+                        newSignatureBuffer.append(type);
+
+                        // Copy the referenced classes.
+                        for (int counter = 0; counter < classCount; counter++)
+                        {
+                            referencedClasses[newReferencedClassIndex++] =
+                                referencedClasses[referencedClassIndex++];
+                        }
+                    }
+                    else
+                    {
+                        // Skip the referenced classes.
+                        referencedClassIndex += classCount;
+                    }
+                }
+
+                if (newReferencedClassIndex < referencedClassIndex)
+                {
+                    // Update the signature.
+                    ((Utf8Constant)((ProgramClass)clazz).constantPool[signatureAttribute.u2signatureIndex]).setString(newSignatureBuffer.toString());
+
+                    // Clear the unused entries.
+                    while (newReferencedClassIndex < referencedClassIndex)
+                    {
+                        referencedClasses[newReferencedClassIndex++] = null;
+                    }
+                }
+            }
+        }
+    }
+
+
+    // Implementations for ElementValueVisitor.
+
+    public void visitAnyElementValue(Clazz clazz, Annotation annotation, ElementValue elementValue) {}
+
+
+    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+    {
+        // Shrink the contained annotation.
+        annotationElementValue.annotationAccept(clazz, this);
+    }
+
+
+    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+    {
+        // Shrink the element values array.
+        arrayElementValue.u2elementValuesCount =
+            shrinkArray(arrayElementValue.elementValues,
+                        arrayElementValue.u2elementValuesCount);
+
+        // Shrink the element values themselves.
+        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Removes all entries that are not marked as being used from the given
+     * constant pool.
+     * @return the new number of entries.
+     */
+    private int shrinkConstantPool(Constant[] constantPool, int length)
+    {
+        if (constantIndexMap.length < length)
+        {
+            constantIndexMap = new int[length];
+        }
+
+        int     counter = 1;
+        boolean isUsed  = false;
+
+        // Shift the used constant pool entries together.
+        for (int index = 1; index < length; index++)
+        {
+            constantIndexMap[index] = counter;
+
+            Constant constant = constantPool[index];
+
+            // Don't update the flag if this is the second half of a long entry.
+            if (constant != null)
+            {
+                isUsed = usageMarker.isUsed(constant);
+            }
+
+            if (isUsed)
+            {
+                constantPool[counter++] = constant;
+            }
+        }
+
+        // Clear the remaining constant pool elements.
+        for (int index = counter; index < length; index++)
+        {
+            constantPool[index] = null;
+        }
+
+        return counter;
+    }
+
+
+    /**
+     * Removes all indices that point to unused constant pool entries
+     * from the given array.
+     * @return the new number of indices.
+     */
+    private int shrinkConstantIndexArray(Constant[] constantPool, int[] array, int length)
+    {
+        int counter = 0;
+
+        // Shift the used objects together.
+        for (int index = 0; index < length; index++)
+        {
+            if (usageMarker.isUsed(constantPool[array[index]]))
+            {
+                array[counter++] = array[index];
+            }
+        }
+
+        // Clear the remaining array elements.
+        for (int index = counter; index < length; index++)
+        {
+            array[index] = 0;
+        }
+
+        return counter;
+    }
+
+
+    /**
+     * Removes all Clazz objects that are not marked as being used
+     * from the given array and returns the remaining objects in a an array
+     * of the right size.
+     * @return the new array.
+     */
+    private Clazz[] shrinkToNewArray(Clazz[] array)
+    {
+        if (array == null)
+        {
+            return null;
+        }
+
+        // Shrink the given array in-place.
+        int length = shrinkArray(array, array.length);
+        if (length == 0)
+        {
+            return null;
+        }
+
+        // Return immediately if the array is of right size already.
+        if (length == array.length)
+        {
+            return array;
+        }
+
+        // Copy the remaining elements into a new array of the right size.
+        Clazz[] newArray = new Clazz[length];
+        System.arraycopy(array, 0, newArray, 0, length);
+        return newArray;
+    }
+
+
+    /**
+     * Removes all VisitorAccepter objects that are not marked as being used
+     * from the given array.
+     * @return the new number of VisitorAccepter objects.
+     */
+    private int shrinkArray(VisitorAccepter[] array, int length)
+    {
+        int counter = 0;
+
+        // Shift the used objects together.
+        for (int index = 0; index < length; index++)
+        {
+            if (usageMarker.isUsed(array[index]))
+            {
+                array[counter++] = array[index];
+            }
+        }
+
+        // Clear the remaining array elements.
+        for (int index = counter; index < length; index++)
+        {
+            array[index] = null;
+        }
+
+        return counter;
+    }
+}
diff --git a/src/proguard/shrink/InnerUsageMarker.java b/src/proguard/shrink/InnerUsageMarker.java
new file mode 100644
index 0000000..b8ca801
--- /dev/null
+++ b/src/proguard/shrink/InnerUsageMarker.java
@@ -0,0 +1,174 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This AttributeVisitor recursively marks all necessary inner class information
+ * in the attributes that it visits.
+ *
+ * @see UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class InnerUsageMarker
+extends      SimplifiedVisitor
+implements   AttributeVisitor,
+             InnerClassesInfoVisitor,
+             ConstantVisitor,
+             ClassVisitor
+{
+    private final UsageMarker usageMarker;
+
+    // Fields acting as a return parameters for several methods.
+    private boolean attributeUsed;
+    private boolean classUsed;
+
+
+    /**
+     * Creates a new InnerUsageMarker.
+     * @param usageMarker the usage marker that is used to mark the classes
+     *                    and class members.
+     */
+    public InnerUsageMarker(UsageMarker usageMarker)
+    {
+        this.usageMarker = usageMarker;
+    }
+
+
+    // Implementations for AttributeVisitor.
+
+    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // Mark the necessary inner classes information.
+        attributeUsed = false;
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+
+        if (attributeUsed)
+        {
+            // We got a positive used flag, so some inner class is being used.
+            // Mark this attribute as being used as well.
+            usageMarker.markAsUsed(innerClassesAttribute);
+
+            markConstant(clazz, innerClassesAttribute.u2attributeNameIndex);
+        }
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        boolean innerClassesInfoUsed = usageMarker.isUsed(innerClassesInfo);
+
+        if (!innerClassesInfoUsed)
+        {
+            // Check if the inner class (if any) is marked as being used.
+            classUsed = true;
+            innerClassesInfo.innerClassConstantAccept(clazz, this);
+            innerClassesInfoUsed = classUsed;
+
+            // Check if the outer class (if any) is marked as being used.
+            classUsed = true;
+            innerClassesInfo.outerClassConstantAccept(clazz, this);
+            innerClassesInfoUsed &= classUsed;
+
+            // If both the inner class and the outer class are marked as being
+            // used, then mark this InnerClassesInfo as well.
+            if (innerClassesInfoUsed)
+            {
+                usageMarker.markAsUsed(innerClassesInfo);
+
+                innerClassesInfo.innerNameConstantAccept(clazz, this);
+            }
+        }
+
+        // The return value.
+        attributeUsed |= innerClassesInfoUsed;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        classUsed = usageMarker.isUsed(classConstant);
+
+        // Is the class constant marked as being used?
+        if (!classUsed)
+        {
+            // Check the referenced class.
+            classUsed = true;
+            classConstant.referencedClassAccept(this);
+
+            // Is the referenced class marked as being used?
+            if (classUsed)
+            {
+                // Mark the class constant and its Utf8 constant.
+                usageMarker.markAsUsed(classConstant);
+
+                markConstant(clazz, classConstant.u2nameIndex);
+            }
+        }
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        usageMarker.markAsUsed(utf8Constant);
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        classUsed = usageMarker.isUsed(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        classUsed = true;
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the given constant pool entry of the given class. This includes
+     * visiting any other referenced constant pool entries.
+     */
+    private void markConstant(Clazz clazz, int index)
+    {
+         clazz.constantPoolEntryAccept(index, this);
+    }
+}
diff --git a/src/proguard/shrink/InterfaceUsageMarker.java b/src/proguard/shrink/InterfaceUsageMarker.java
new file mode 100644
index 0000000..7599898
--- /dev/null
+++ b/src/proguard/shrink/InterfaceUsageMarker.java
@@ -0,0 +1,152 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.ClassVisitor;
+
+
+/**
+ * This ClassVisitor recursively marks all interface
+ * classes that are being used in the visited class.
+ *
+ * @see UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class InterfaceUsageMarker
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             ConstantVisitor
+{
+    private final UsageMarker usageMarker;
+
+    // Fields acting as a return parameters for several methods.
+    private boolean used;
+    private boolean anyUsed;
+
+
+    /**
+     * Creates a new InterfaceUsageMarker.
+     * @param usageMarker the usage marker that is used to mark the classes
+     *                    and class members.
+     */
+    public InterfaceUsageMarker(UsageMarker usageMarker)
+    {
+        this.usageMarker = usageMarker;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        boolean classUsed         = usageMarker.isUsed(programClass);
+        boolean classPossiblyUsed = usageMarker.isPossiblyUsed(programClass);
+
+        if (classUsed || classPossiblyUsed)
+        {
+            // Check if any interfaces are being used.
+            boolean oldAnyUsed = anyUsed;
+            anyUsed = false;
+
+            programClass.interfaceConstantsAccept(this);
+
+            classUsed |= anyUsed;
+            anyUsed = oldAnyUsed;
+
+            // Is this an interface with a preliminary mark?
+            if (classPossiblyUsed)
+            {
+                // Should it be included now?
+                if (classUsed)
+                {
+                    // At least one if this interface's interfaces is being used.
+                    // Mark this interface as well.
+                    usageMarker.markAsUsed(programClass);
+
+                    // Mark this interface's name.
+                    programClass.thisClassConstantAccept(this);
+
+                    // Mark the superclass (java/lang/Object).
+                    programClass.superClassConstantAccept(this);
+                }
+                else
+                {
+                    // Unmark this interface, so we don't bother looking at it again.
+                    usageMarker.markAsUnused(programClass);
+                }
+            }
+        }
+
+        // The return value.
+        used = classUsed;
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // The return values.
+        used    = true;
+        anyUsed = true;
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        boolean classUsed = usageMarker.isUsed(classConstant);
+
+        if (!classUsed)
+        {
+            // The ClassConstant isn't marked as being used yet. But maybe it
+            // should be included as an interface, so check the actual class.
+            classConstant.referencedClassAccept(this);
+            classUsed = used;
+
+            if (classUsed)
+            {
+                // The class is being used. Mark the ClassConstant as being used
+                // as well.
+                usageMarker.markAsUsed(classConstant);
+
+                clazz.constantPoolEntryAccept(classConstant.u2nameIndex, this);
+            }
+        }
+
+        // The return values.
+        used    =  classUsed;
+        anyUsed |= classUsed;
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        if (!usageMarker.isUsed(utf8Constant))
+        {
+            usageMarker.markAsUsed(utf8Constant);
+        }
+    }
+}
diff --git a/src/proguard/shrink/ShortestUsageMark.java b/src/proguard/shrink/ShortestUsageMark.java
new file mode 100644
index 0000000..757c713
--- /dev/null
+++ b/src/proguard/shrink/ShortestUsageMark.java
@@ -0,0 +1,183 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.*;
+
+
+/**
+ * This class can be used as a mark when keeping classes, class members, and
+ * other elements. It can be certain or preliminary. It also contains additional
+ * information about the reasons why an element is being kept.
+ *
+ * @see ClassShrinker
+ *
+ * @author Eric Lafortune
+ */
+final class ShortestUsageMark
+{
+    private final boolean certain;
+    private final String  reason;
+    private final int     depth;
+    private       Clazz   clazz;
+    private       Member  member;
+
+
+    /**
+     * Creates a new certain ShortestUsageMark.
+     * @param reason the reason for this mark.
+     */
+    public ShortestUsageMark(String reason)
+    {
+        this.certain = true;
+        this.reason  = reason;
+        this.depth   = 0;
+    }
+
+
+    /**
+     * Creates a new certain ShortestUsageMark.
+     * @param previousUsageMark the previous mark to which this one is linked.
+     * @param reason            the reason for this mark.
+     * @param clazz             the class causing this mark.
+     */
+    public ShortestUsageMark(ShortestUsageMark previousUsageMark,
+                             String            reason,
+                             int               cost,
+                             Clazz             clazz)
+    {
+        this(previousUsageMark, reason, cost, clazz, null);
+    }
+
+
+    /**
+     * Creates a new certain ShortestUsageMark.
+     * @param previousUsageMark the previous mark to which this one is linked.
+     * @param reason            the reason for this mark.
+     * @param clazz             the class causing this mark.
+     * @param member            the member in the above class causing this mark.
+     * @param cost              the added cost of following this path.
+     */
+    public ShortestUsageMark(ShortestUsageMark previousUsageMark,
+                             String            reason,
+                             int               cost,
+                             Clazz             clazz,
+                             Member            member)
+    {
+        this.certain = true;
+        this.reason  = reason;
+        this.depth   = previousUsageMark.depth + cost;
+        this.clazz   = clazz;
+        this.member  = member;
+    }
+
+
+    /**
+     * Creates a new ShortestUsageMark, based on another mark.
+     * @param otherUsageMark the other mark, whose properties will be copied.
+     * @param certain        specifies whether this is a certain mark.
+     */
+    public ShortestUsageMark(ShortestUsageMark otherUsageMark,
+                             boolean           certain)
+    {
+        this.certain = certain;
+        this.reason  = otherUsageMark.reason;
+        this.depth   = otherUsageMark.depth;
+        this.clazz   = otherUsageMark.clazz;
+        this.member  = otherUsageMark.member;
+    }
+
+
+    /**
+     * Returns whether this is a certain mark.
+     */
+    public boolean isCertain()
+    {
+        return certain;
+    }
+
+
+    /**
+     * Returns the reason for this mark.
+     */
+    public String getReason()
+    {
+        return reason;
+    }
+
+
+    /**
+     * Returns whether this mark has a shorter chain of reasons than the
+     * given mark.
+     */
+    public boolean isShorter(ShortestUsageMark otherUsageMark)
+    {
+        return this.depth < otherUsageMark.depth;
+    }
+
+
+    /**
+     * Returns whether this is mark is caused by the given class.
+     */
+    public boolean isCausedBy(Clazz clazz)
+    {
+        return clazz.equals(this.clazz);
+    }
+
+
+    /**
+     * Applies the given class visitor to this mark's class, if any,
+     * and if this mark doesn't have a member.
+     */
+    public void acceptClassVisitor(ClassVisitor classVisitor)
+    {
+        if (clazz  != null &&
+            member == null)
+        {
+            clazz.accept(classVisitor);
+        }
+    }
+
+
+    /**
+     * Applies the given class visitor to this mark's member, if any.
+     */
+    public void acceptMemberVisitor(MemberVisitor memberVisitor)
+    {
+        if (clazz  != null &&
+            member != null)
+        {
+            member.accept(clazz, memberVisitor);
+        }
+    }
+
+
+    // Implementations for Object.
+
+    public String toString()
+    {
+        return "certain=" + certain + ", depth="+depth+": " +
+               reason +
+               (clazz      != null ? clazz.getName() : "(none)") + ": " +
+               (member     != null ? member.getName(clazz) : "(none)");
+    }
+}
diff --git a/src/proguard/shrink/ShortestUsageMarker.java b/src/proguard/shrink/ShortestUsageMarker.java
new file mode 100644
index 0000000..da8fad3
--- /dev/null
+++ b/src/proguard/shrink/ShortestUsageMarker.java
@@ -0,0 +1,277 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.*;
+
+
+/**
+ * This ClassVisitor and MemberVisitor recursively marks all classes
+ * and class elements that are being used. For each element, it finds the
+ * shortest chain of dependencies.
+ *
+ * @see ClassShrinker
+ *
+ * @author Eric Lafortune
+ */
+public class ShortestUsageMarker extends UsageMarker
+{
+    private static final ShortestUsageMark INITIAL_MARK =
+        new ShortestUsageMark("is kept by a directive in the configuration.\n\n");
+
+
+    // A field acting as a parameter to the visitor methods.
+    private ShortestUsageMark currentUsageMark = INITIAL_MARK;
+
+    // A utility object to check for recursive causes.
+    private final MyRecursiveCauseChecker recursiveCauseChecker = new MyRecursiveCauseChecker();
+
+
+    // Overriding implementations for UsageMarker.
+
+    protected void markProgramClassBody(ProgramClass programClass)
+    {
+        ShortestUsageMark previousUsageMark = currentUsageMark;
+
+        currentUsageMark = new ShortestUsageMark(getShortestUsageMark(programClass),
+                                                 "is extended by   ",
+                                                 10000,
+                                                 programClass);
+
+        super.markProgramClassBody(programClass);
+
+        currentUsageMark = previousUsageMark;
+    }
+
+
+    protected void markProgramFieldBody(ProgramClass programClass, ProgramField programField)
+    {
+        ShortestUsageMark previousUsageMark = currentUsageMark;
+
+        currentUsageMark = new ShortestUsageMark(getShortestUsageMark(programField),
+                                                 "is referenced by ",
+                                                 1,
+                                                 programClass,
+                                                 programField);
+
+        super.markProgramFieldBody(programClass, programField);
+
+        currentUsageMark = previousUsageMark;
+    }
+
+
+    protected void markProgramMethodBody(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        ShortestUsageMark previousUsageMark = currentUsageMark;
+
+        currentUsageMark = new ShortestUsageMark(getShortestUsageMark(programMethod),
+                                                 "is invoked by    ",
+                                                 1,
+                                                 programClass,
+                                                 programMethod);
+
+        super.markProgramMethodBody(programClass, programMethod);
+
+        currentUsageMark = previousUsageMark;
+    }
+
+
+    protected void markMethodHierarchy(Clazz clazz, Method method)
+    {
+        ShortestUsageMark previousUsageMark = currentUsageMark;
+
+        currentUsageMark = new ShortestUsageMark(getShortestUsageMark(method),
+                                                 "implements       ",
+                                                 100,
+                                                 clazz,
+                                                 method);
+
+        super.markMethodHierarchy(clazz, method);
+
+        currentUsageMark = previousUsageMark;
+    }
+
+
+    // Small utility methods.
+
+    protected void markAsUsed(VisitorAccepter visitorAccepter)
+    {
+        Object visitorInfo = visitorAccepter.getVisitorInfo();
+
+        ShortestUsageMark shortestUsageMark =
+            visitorInfo != null                           &&
+            visitorInfo instanceof ShortestUsageMark      &&
+            !((ShortestUsageMark)visitorInfo).isCertain() &&
+            !currentUsageMark.isShorter((ShortestUsageMark)visitorInfo) ?
+                new ShortestUsageMark((ShortestUsageMark)visitorInfo, true):
+                currentUsageMark;
+
+        visitorAccepter.setVisitorInfo(shortestUsageMark);
+    }
+
+
+    protected boolean shouldBeMarkedAsUsed(VisitorAccepter visitorAccepter)
+    {
+        Object visitorInfo = visitorAccepter.getVisitorInfo();
+
+        return //!(visitorAccepter instanceof Clazz &&
+               //  isCausedBy(currentUsageMark, (Clazz)visitorAccepter)) &&
+               (visitorInfo == null                           ||
+               !(visitorInfo instanceof ShortestUsageMark)   ||
+               !((ShortestUsageMark)visitorInfo).isCertain() ||
+               currentUsageMark.isShorter((ShortestUsageMark)visitorInfo));
+    }
+
+
+    protected boolean isUsed(VisitorAccepter visitorAccepter)
+    {
+        Object visitorInfo = visitorAccepter.getVisitorInfo();
+
+        return visitorInfo != null                      &&
+               visitorInfo instanceof ShortestUsageMark &&
+               ((ShortestUsageMark)visitorInfo).isCertain();
+    }
+
+
+    protected void markAsPossiblyUsed(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(new ShortestUsageMark(currentUsageMark, false));
+    }
+
+
+    protected boolean shouldBeMarkedAsPossiblyUsed(VisitorAccepter visitorAccepter)
+    {
+        Object visitorInfo = visitorAccepter.getVisitorInfo();
+
+        return visitorInfo == null                         ||
+               !(visitorInfo instanceof ShortestUsageMark) ||
+               (!((ShortestUsageMark)visitorInfo).isCertain() &&
+                currentUsageMark.isShorter((ShortestUsageMark)visitorInfo));
+    }
+
+
+    protected boolean isPossiblyUsed(VisitorAccepter visitorAccepter)
+    {
+        Object visitorInfo = visitorAccepter.getVisitorInfo();
+
+        return visitorInfo != null                      &&
+               visitorInfo instanceof ShortestUsageMark &&
+               !((ShortestUsageMark)visitorInfo).isCertain();
+    }
+
+
+    protected ShortestUsageMark getShortestUsageMark(VisitorAccepter visitorAccepter)
+    {
+        Object visitorInfo = visitorAccepter.getVisitorInfo();
+
+        return (ShortestUsageMark)visitorInfo;
+    }
+
+
+    // Small utility methods.
+
+    private boolean isCausedBy(ShortestUsageMark shortestUsageMark,
+                               Clazz             clazz)
+    {
+        return recursiveCauseChecker.check(shortestUsageMark, clazz);
+    }
+
+
+    private class MyRecursiveCauseChecker implements ClassVisitor, MemberVisitor
+    {
+        private Clazz   checkClass;
+        private boolean isRecursing;
+
+
+        public boolean check(ShortestUsageMark shortestUsageMark,
+                             Clazz             clazz)
+        {
+            checkClass  = clazz;
+            isRecursing = false;
+
+            shortestUsageMark.acceptClassVisitor(this);
+            shortestUsageMark.acceptMemberVisitor(this);
+
+            return isRecursing;
+        }
+
+        // Implementations for ClassVisitor.
+
+        public void visitProgramClass(ProgramClass programClass)
+        {
+            checkCause(programClass);
+        }
+
+
+        public void visitLibraryClass(LibraryClass libraryClass)
+        {
+            checkCause(libraryClass);
+        }
+
+
+        // Implementations for MemberVisitor.
+
+        public void visitProgramField(ProgramClass programClass, ProgramField programField)
+        {
+            checkCause(programField);
+        }
+
+
+        public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+        {
+            checkCause(programMethod);
+        }
+
+
+        public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+        {
+             checkCause(libraryField);
+       }
+
+
+        public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+        {
+            checkCause(libraryMethod);
+        }
+
+
+        // Small utility methods.
+
+        private void checkCause(VisitorAccepter visitorAccepter)
+        {
+            if (ShortestUsageMarker.this.isUsed(visitorAccepter))
+            {
+                ShortestUsageMark shortestUsageMark = ShortestUsageMarker.this.getShortestUsageMark(visitorAccepter);
+
+                // Check the class of this mark, if any
+                isRecursing = shortestUsageMark.isCausedBy(checkClass);
+
+                // Check the causing class or method, if still necessary.
+                if (!isRecursing)
+                {
+                    shortestUsageMark.acceptClassVisitor(this);
+                    shortestUsageMark.acceptMemberVisitor(this);
+                }
+            }
+        }
+    }
+}
diff --git a/src/proguard/shrink/ShortestUsagePrinter.java b/src/proguard/shrink/ShortestUsagePrinter.java
new file mode 100644
index 0000000..db42fe1
--- /dev/null
+++ b/src/proguard/shrink/ShortestUsagePrinter.java
@@ -0,0 +1,210 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.util.ClassUtil;
+import proguard.classfile.visitor.*;
+
+import java.io.PrintStream;
+
+
+/**
+ * This ClassVisitor     and MemberVisitor prints out the reasons why
+ * classes and class members have been marked as being used.
+ *
+ * @see UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class ShortestUsagePrinter
+implements   ClassVisitor,
+             MemberVisitor
+{
+    private final ShortestUsageMarker shortestUsageMarker;
+    private final boolean             verbose;
+    private final PrintStream         ps;
+
+
+    /**
+     * Creates a new UsagePrinter that prints verbosely to <code>System.out</code>.
+     * @param shortestUsageMarker the usage marker that was used to mark the
+     *                            classes and class members.
+     */
+    public ShortestUsagePrinter(ShortestUsageMarker shortestUsageMarker)
+    {
+        this(shortestUsageMarker, true);
+    }
+
+
+    /**
+     * Creates a new UsagePrinter that prints to the given stream.
+     * @param shortestUsageMarker the usage marker that was used to mark the
+     *                            classes and class members.
+     * @param verbose             specifies whether the output should be verbose.
+     */
+    public ShortestUsagePrinter(ShortestUsageMarker shortestUsageMarker,
+                                boolean             verbose)
+    {
+        this(shortestUsageMarker, verbose, System.out);
+    }
+
+    /**
+     * Creates a new UsagePrinter that prints to the given stream.
+     * @param shortestUsageMarker the usage marker that was used to mark the
+     *                            classes and class members.
+     * @param verbose             specifies whether the output should be verbose.
+     * @param printStream         the stream to which to print.
+     */
+    public ShortestUsagePrinter(ShortestUsageMarker shortestUsageMarker,
+                                boolean             verbose,
+                                PrintStream         printStream)
+    {
+        this.shortestUsageMarker = shortestUsageMarker;
+        this.verbose             = verbose;
+        this.ps                  = printStream;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        // Print the name of this class.
+        ps.println(ClassUtil.externalClassName(programClass.getName()));
+
+        // Print the reason for keeping this class.
+        printReason(programClass);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        // Print the name of this class.
+        ps.println(ClassUtil.externalClassName(libraryClass.getName()));
+
+        // Print the reason for keeping this class.
+        ps.println("  is a library class.\n");
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        // Print the name of this field.
+        String name = programField.getName(programClass);
+        String type = programField.getDescriptor(programClass);
+
+        ps.println(ClassUtil.externalClassName(programClass.getName()) +
+                   (verbose ?
+                        ": " + ClassUtil.externalFullFieldDescription(0, name, type):
+                        "."  + name) +
+                   lineNumberRange(programClass, programField));
+
+        // Print the reason for keeping this method.
+        printReason(programField);
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Print the name of this method.
+        String name = programMethod.getName(programClass);
+        String type = programMethod.getDescriptor(programClass);
+
+        ps.println(ClassUtil.externalClassName(programClass.getName()) +
+                   (verbose ?
+                        ": " + ClassUtil.externalFullMethodDescription(programClass.getName(), 0, name, type):
+                        "."  + name) +
+                   lineNumberRange(programClass, programMethod));
+
+        // Print the reason for keeping this method.
+        printReason(programMethod);
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        // Print the name of this field.
+        String name = libraryField.getName(libraryClass);
+        String type = libraryField.getDescriptor(libraryClass);
+
+        ps.println(ClassUtil.externalClassName(libraryClass.getName()) +
+                   (verbose ?
+                        ": " + ClassUtil.externalFullFieldDescription(0, name, type):
+                        "."  + name));
+
+        // Print the reason for keeping this field.
+        ps.println("  is a library field.\n");
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        // Print the name of this method.
+        String name = libraryMethod.getName(libraryClass);
+        String type = libraryMethod.getDescriptor(libraryClass);
+
+        ps.println(ClassUtil.externalClassName(libraryClass.getName()) +
+                   (verbose ?
+                        ": " + ClassUtil.externalFullMethodDescription(libraryClass.getName(), 0, name, type):
+                        "."  + name));
+
+        // Print the reason for keeping this method.
+        ps.println("  is a library method.\n");
+    }
+
+
+    // Small utility methods.
+
+    private void printReason(VisitorAccepter visitorAccepter)
+    {
+        if (shortestUsageMarker.isUsed(visitorAccepter))
+        {
+            ShortestUsageMark shortestUsageMark = shortestUsageMarker.getShortestUsageMark(visitorAccepter);
+
+            // Print the reason for keeping this class.
+            ps.print("  " + shortestUsageMark.getReason());
+
+            // Print the class or method that is responsible, with its reasons.
+            shortestUsageMark.acceptClassVisitor(this);
+            shortestUsageMark.acceptMemberVisitor(this);
+        }
+        else
+        {
+            ps.println("  is not being kept.\n");
+        }
+    }
+
+
+    /**
+     * Returns the line number range of the given class member, followed by a
+     * colon, or just an empty String if no range is available.
+     */
+    private static String lineNumberRange(ProgramClass programClass, ProgramMember programMember)
+    {
+        String range = programMember.getLineNumberRange(programClass);
+        return range != null ?
+            (" (" + range + ")") :
+            "";
+    }
+}
diff --git a/src/proguard/shrink/Shrinker.java b/src/proguard/shrink/Shrinker.java
new file mode 100644
index 0000000..edbc27f
--- /dev/null
+++ b/src/proguard/shrink/Shrinker.java
@@ -0,0 +1,170 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.*;
+import proguard.classfile.ClassPool;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.visitor.*;
+
+import java.io.*;
+
+/**
+ * This class shrinks class pools according to a given configuration.
+ *
+ * @author Eric Lafortune
+ */
+public class Shrinker
+{
+    private final Configuration configuration;
+
+
+    /**
+     * Creates a new Shrinker.
+     */
+    public Shrinker(Configuration configuration)
+    {
+        this.configuration = configuration;
+    }
+
+
+    /**
+     * Performs shrinking of the given program class pool.
+     */
+    public ClassPool execute(ClassPool programClassPool,
+                             ClassPool libraryClassPool) throws IOException
+    {
+        // Check if we have at least some keep commands.
+        if (configuration.keep == null)
+        {
+            throw new IOException("You have to specify '-keep' options for the shrinking step.");
+        }
+
+        // Clean up any old visitor info.
+        programClassPool.classesAccept(new ClassCleaner());
+        libraryClassPool.classesAccept(new ClassCleaner());
+
+        // Create a visitor for marking the seeds.
+        UsageMarker usageMarker = configuration.whyAreYouKeeping == null ?
+            new UsageMarker() :
+            new ShortestUsageMarker();
+
+        ClassPoolVisitor classPoolvisitor =
+            ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
+                                                                    usageMarker,
+                                                                    usageMarker,
+                                                                    true,
+                                                                    false,
+                                                                    false);
+        // Mark the seeds.
+        programClassPool.accept(classPoolvisitor);
+        libraryClassPool.accept(classPoolvisitor);
+
+        // Mark interfaces that have to be kept.
+        programClassPool.classesAccept(new InterfaceUsageMarker(usageMarker));
+
+        // Mark the inner class and annotation information that has to be kept.
+        programClassPool.classesAccept(
+            new UsedClassFilter(usageMarker,
+            new AllAttributeVisitor(true,
+            new MultiAttributeVisitor(new AttributeVisitor[]
+            {
+                new InnerUsageMarker(usageMarker),
+                new AnnotationUsageMarker(usageMarker),
+            }))));
+
+        // Should we explain ourselves?
+        if (configuration.whyAreYouKeeping != null)
+        {
+            System.out.println();
+
+            // Create a visitor for explaining classes and class members.
+            ShortestUsagePrinter shortestUsagePrinter =
+                new ShortestUsagePrinter((ShortestUsageMarker)usageMarker,
+                                         configuration.verbose);
+
+            ClassPoolVisitor whyClassPoolvisitor =
+                ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.whyAreYouKeeping,
+                                                                        shortestUsagePrinter,
+                                                                        shortestUsagePrinter);
+
+            // Mark the seeds.
+            programClassPool.accept(whyClassPoolvisitor);
+            libraryClassPool.accept(whyClassPoolvisitor);
+        }
+
+        if (configuration.printUsage != null)
+        {
+            PrintStream ps = isFile(configuration.printUsage) ?
+                new PrintStream(new BufferedOutputStream(new FileOutputStream(configuration.printUsage))) :
+                System.out;
+
+            // Print out items that will be removed.
+            programClassPool.classesAcceptAlphabetically(
+                new UsagePrinter(usageMarker, true, ps));
+
+            if (ps != System.out)
+            {
+                ps.close();
+            }
+        }
+
+        // Discard unused program classes.
+        int originalProgramClassPoolSize = programClassPool.size();
+
+        ClassPool newProgramClassPool = new ClassPool();
+        programClassPool.classesAccept(
+            new UsedClassFilter(usageMarker,
+            new MultiClassVisitor(
+            new ClassVisitor[] {
+                new ClassShrinker(usageMarker),
+                new ClassPoolFiller(newProgramClassPool)
+            })));
+
+        programClassPool.clear();
+
+        // Check if we have at least some output classes.
+        int newProgramClassPoolSize = newProgramClassPool.size();
+        if (newProgramClassPoolSize == 0)
+        {
+            throw new IOException("The output jar is empty. Did you specify the proper '-keep' options?");
+        }
+
+        if (configuration.verbose)
+        {
+            System.out.println("Removing unused program classes and class elements...");
+            System.out.println("  Original number of program classes: " + originalProgramClassPoolSize);
+            System.out.println("  Final number of program classes:    " + newProgramClassPoolSize);
+        }
+
+        return newProgramClassPool;
+    }
+
+
+    /**
+     * Returns whether the given file is actually a file, or just a placeholder
+     * for the standard output.
+     */
+    private boolean isFile(File file)
+    {
+        return file.getPath().length() > 0;
+    }
+}
diff --git a/src/proguard/shrink/UsageMarker.java b/src/proguard/shrink/UsageMarker.java
new file mode 100644
index 0000000..e913046
--- /dev/null
+++ b/src/proguard/shrink/UsageMarker.java
@@ -0,0 +1,920 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.attribute.*;
+import proguard.classfile.attribute.annotation.*;
+import proguard.classfile.attribute.preverification.*;
+import proguard.classfile.attribute.preverification.visitor.*;
+import proguard.classfile.attribute.visitor.*;
+import proguard.classfile.constant.*;
+import proguard.classfile.constant.visitor.ConstantVisitor;
+import proguard.classfile.instruction.*;
+import proguard.classfile.instruction.visitor.InstructionVisitor;
+import proguard.classfile.util.SimplifiedVisitor;
+import proguard.classfile.visitor.*;
+
+
+/**
+ * This ClassVisitor and MemberVisitor recursively marks all classes and class
+ * elements that are being used.
+ *
+ * @see ClassShrinker
+ *
+ * @author Eric Lafortune
+ */
+class      UsageMarker
+extends    SimplifiedVisitor
+implements ClassVisitor,
+           MemberVisitor,
+           ConstantVisitor,
+           AttributeVisitor,
+           InnerClassesInfoVisitor,
+           ExceptionInfoVisitor,
+           StackMapFrameVisitor,
+           VerificationTypeVisitor,
+           LocalVariableInfoVisitor,
+           LocalVariableTypeInfoVisitor,
+//         AnnotationVisitor,
+//         ElementValueVisitor,
+           InstructionVisitor
+{
+    // A visitor info flag to indicate the ProgramMember object is being used,
+    // if its Clazz can be determined as being used as well.
+    private static final Object POSSIBLY_USED = new Object();
+    // A visitor info flag to indicate the visitor accepter is being used.
+    private static final Object USED          = new Object();
+
+
+    private final MyInterfaceUsageMarker          interfaceUsageMarker          = new MyInterfaceUsageMarker();
+    private final MyPossiblyUsedMemberUsageMarker possiblyUsedMemberUsageMarker = new MyPossiblyUsedMemberUsageMarker();
+//    private ClassVisitor       dynamicClassMarker   =
+//        new MultiClassVisitor(
+//        new ClassVisitor[]
+//        {
+//            this,
+//            new NamedMethodVisitor(ClassConstants.INTERNAL_METHOD_NAME_INIT,
+//                                   ClassConstants.INTERNAL_METHOD_TYPE_INIT,
+//                                   this)
+//        });
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (shouldBeMarkedAsUsed(programClass))
+        {
+            // Mark this class.
+            markAsUsed(programClass);
+
+            markProgramClassBody(programClass);
+        }
+    }
+
+
+    protected void markProgramClassBody(ProgramClass programClass)
+    {
+        // Mark this class's name.
+        markConstant(programClass, programClass.u2thisClass);
+
+        // Mark the superclass.
+        if (programClass.u2superClass != 0)
+        {
+            markConstant(programClass, programClass.u2superClass);
+        }
+
+        // Give the interfaces preliminary marks.
+        programClass.hierarchyAccept(false, false, true, false,
+                                     interfaceUsageMarker);
+
+        // Explicitly mark the <clinit> method.
+        programClass.methodAccept(ClassConstants.INTERNAL_METHOD_NAME_CLINIT,
+                                  ClassConstants.INTERNAL_METHOD_TYPE_CLINIT,
+                                  this);
+
+        // Explicitly mark the parameterless <init> method.
+        programClass.methodAccept(ClassConstants.INTERNAL_METHOD_NAME_INIT,
+                                  ClassConstants.INTERNAL_METHOD_TYPE_INIT,
+                                  this);
+
+        // Process all class members that have already been marked as possibly used.
+        programClass.fieldsAccept(possiblyUsedMemberUsageMarker);
+        programClass.methodsAccept(possiblyUsedMemberUsageMarker);
+
+        // Mark the attributes.
+        programClass.attributesAccept(this);
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (shouldBeMarkedAsUsed(libraryClass))
+        {
+            markAsUsed(libraryClass);
+
+            // We're not going to analyze all library code. We're assuming that
+            // if this class is being used, all of its methods will be used as
+            // well. We'll mark them as such (here and in all subclasses).
+
+            // Mark the superclass.
+            Clazz superClass = libraryClass.superClass;
+            if (superClass != null)
+            {
+                superClass.accept(this);
+            }
+
+            // Mark the interfaces.
+            Clazz[] interfaceClasses = libraryClass.interfaceClasses;
+            if (interfaceClasses != null)
+            {
+                for (int index = 0; index < interfaceClasses.length; index++)
+                {
+                    if (interfaceClasses[index] != null)
+                    {
+                        interfaceClasses[index].accept(this);
+                    }
+                }
+            }
+
+            // Mark all methods.
+            libraryClass.methodsAccept(this);
+        }
+    }
+
+
+    /**
+     * This ClassVisitor marks ProgramClass objects as possibly used,
+     * and it visits LibraryClass objects with its outer UsageMarker.
+     */
+    private class MyInterfaceUsageMarker
+    implements    ClassVisitor
+    {
+        public void visitProgramClass(ProgramClass programClass)
+        {
+            if (shouldBeMarkedAsPossiblyUsed(programClass))
+            {
+                // We can't process the interface yet, because it might not
+                // be required. Give it a preliminary mark.
+                markAsPossiblyUsed(programClass);
+            }
+        }
+
+        public void visitLibraryClass(LibraryClass libraryClass)
+        {
+            // Make sure all library interface methods are marked.
+            UsageMarker.this.visitLibraryClass(libraryClass);
+        }
+    }
+
+
+    private class MyPossiblyUsedMemberUsageMarker
+    extends       SimplifiedVisitor
+    implements    MemberVisitor
+    {
+        // Implementations for MemberVisitor.
+
+        public void visitProgramField(ProgramClass programClass, ProgramField programField)
+        {
+            // Has the method already been referenced?
+            if (isPossiblyUsed(programField))
+            {
+                markAsUsed(programField);
+
+                // Mark the name and descriptor.
+                markConstant(programClass, programField.u2nameIndex);
+                markConstant(programClass, programField.u2descriptorIndex);
+
+                // Mark the attributes.
+                programField.attributesAccept(programClass, UsageMarker.this);
+
+                // Mark the classes referenced in the descriptor string.
+                programField.referencedClassesAccept(UsageMarker.this);
+            }
+        }
+
+
+        public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+        {
+            // Has the method already been referenced?
+            if (isPossiblyUsed(programMethod))
+            {
+                markAsUsed(programMethod);
+
+                // Mark the method body.
+                markProgramMethodBody(programClass, programMethod);
+
+                // Note that, if the method has been marked as possibly used,
+                // the method hierarchy has already been marked (cfr. below).
+            }
+        }
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (shouldBeMarkedAsUsed(programField))
+        {
+            // Is the field's class used?
+            if (isUsed(programClass))
+            {
+                markAsUsed(programField);
+
+                // Mark the field body.
+                markProgramFieldBody(programClass, programField);
+            }
+
+            // Hasn't the field been marked as possibly being used yet?
+            else if (shouldBeMarkedAsPossiblyUsed(programField))
+            {
+                // We can't process the field yet, because the class isn't
+                // marked as being used (yet). Give it a preliminary mark.
+                markAsPossiblyUsed(programField);
+            }
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (shouldBeMarkedAsUsed(programMethod))
+        {
+            // Is the method's class used?
+            if (isUsed(programClass))
+            {
+                markAsUsed(programMethod);
+
+                // Mark the method body.
+                markProgramMethodBody(programClass, programMethod);
+
+                // Mark the method hierarchy.
+                markMethodHierarchy(programClass, programMethod);
+            }
+
+            // Hasn't the method been marked as possibly being used yet?
+            else if (shouldBeMarkedAsPossiblyUsed(programMethod))
+            {
+                // We can't process the method yet, because the class isn't
+                // marked as being used (yet). Give it a preliminary mark.
+                markAsPossiblyUsed(programMethod);
+
+                // Mark the method hierarchy.
+                markMethodHierarchy(programClass, programMethod);
+            }
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass programClass, LibraryField programField) {}
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (shouldBeMarkedAsUsed(libraryMethod))
+        {
+            markAsUsed(libraryMethod);
+
+            // Mark the method hierarchy.
+            markMethodHierarchy(libraryClass, libraryMethod);
+        }
+    }
+
+
+    protected void markProgramFieldBody(ProgramClass programClass, ProgramField programField)
+    {
+        // Mark the name and descriptor.
+        markConstant(programClass, programField.u2nameIndex);
+        markConstant(programClass, programField.u2descriptorIndex);
+
+        // Mark the attributes.
+        programField.attributesAccept(programClass, this);
+
+        // Mark the classes referenced in the descriptor string.
+        programField.referencedClassesAccept(this);
+    }
+
+
+    protected void markProgramMethodBody(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        // Mark the name and descriptor.
+        markConstant(programClass, programMethod.u2nameIndex);
+        markConstant(programClass, programMethod.u2descriptorIndex);
+
+        // Mark the attributes.
+        programMethod.attributesAccept(programClass, this);
+
+        // Mark the classes referenced in the descriptor string.
+        programMethod.referencedClassesAccept(this);
+    }
+
+
+    /**
+     * Marks the hierarchy of implementing or overriding methods corresponding
+     * to the given method, if any.
+     */
+    protected void markMethodHierarchy(Clazz clazz, Method method)
+    {
+        if ((method.getAccessFlags() &
+             (ClassConstants.INTERNAL_ACC_PRIVATE |
+              ClassConstants.INTERNAL_ACC_STATIC)) == 0)
+        {
+            clazz.accept(new ConcreteClassDownTraveler(
+                         new ClassHierarchyTraveler(true, true, false, true,
+                         new NamedMethodVisitor(method.getName(clazz),
+                                                method.getDescriptor(clazz),
+                         new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE | ClassConstants.INTERNAL_ACC_STATIC | ClassConstants.INTERNAL_ACC_ABSTRACT,
+                         this)))));
+        }
+    }
+
+
+    // Implementations for ConstantVisitor.
+
+    public void visitIntegerConstant(Clazz clazz, IntegerConstant integerConstant)
+    {
+        if (shouldBeMarkedAsUsed(integerConstant))
+        {
+            markAsUsed(integerConstant);
+        }
+    }
+
+
+    public void visitLongConstant(Clazz clazz, LongConstant longConstant)
+    {
+        if (shouldBeMarkedAsUsed(longConstant))
+        {
+            markAsUsed(longConstant);
+        }
+    }
+
+
+    public void visitFloatConstant(Clazz clazz, FloatConstant floatConstant)
+    {
+        if (shouldBeMarkedAsUsed(floatConstant))
+        {
+            markAsUsed(floatConstant);
+        }
+    }
+
+
+    public void visitDoubleConstant(Clazz clazz, DoubleConstant doubleConstant)
+    {
+        if (shouldBeMarkedAsUsed(doubleConstant))
+        {
+            markAsUsed(doubleConstant);
+        }
+    }
+
+
+    public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
+    {
+        if (shouldBeMarkedAsUsed(stringConstant))
+        {
+            markAsUsed(stringConstant);
+
+            markConstant(clazz, stringConstant.u2stringIndex);
+
+            // Mark the referenced class and its parameterless constructor,
+            // if the string is being used in a Class.forName construct.
+            //stringConstant.referencedClassAccept(dynamicClassMarker);
+
+            // Mark the referenced class or class member, if any.
+            stringConstant.referencedClassAccept(this);
+            stringConstant.referencedMemberAccept(this);
+        }
+    }
+
+
+    public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
+    {
+        if (shouldBeMarkedAsUsed(utf8Constant))
+        {
+            markAsUsed(utf8Constant);
+        }
+    }
+
+
+    public void visitAnyRefConstant(Clazz clazz, RefConstant refConstant)
+    {
+        if (shouldBeMarkedAsUsed(refConstant))
+        {
+            markAsUsed(refConstant);
+
+            markConstant(clazz, refConstant.u2classIndex);
+            markConstant(clazz, refConstant.u2nameAndTypeIndex);
+
+            // When compiled with "-target 1.2" or higher, the class or
+            // interface actually containing the referenced class member may
+            // be higher up the hierarchy. Make sure it's marked, in case it
+            // isn't used elsewhere.
+            refConstant.referencedClassAccept(this);
+
+            // Mark the referenced class member itself.
+            refConstant.referencedMemberAccept(this);
+        }
+    }
+
+
+    public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
+    {
+        if (shouldBeMarkedAsUsed(classConstant))
+        {
+            markAsUsed(classConstant);
+
+            markConstant(clazz, classConstant.u2nameIndex);
+
+            // Mark the referenced class itself.
+            classConstant.referencedClassAccept(this);
+        }
+    }
+
+
+    public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant)
+    {
+        if (shouldBeMarkedAsUsed(nameAndTypeConstant))
+        {
+            markAsUsed(nameAndTypeConstant);
+
+            markConstant(clazz, nameAndTypeConstant.u2nameIndex);
+            markConstant(clazz, nameAndTypeConstant.u2descriptorIndex);
+        }
+    }
+
+
+    // Implementations for AttributeVisitor.
+    // Note that attributes are typically only referenced once, so we don't
+    // test if they have been marked already.
+
+    public void visitUnknownAttribute(Clazz clazz, UnknownAttribute unknownAttribute)
+    {
+        // This is the best we can do for unknown attributes.
+        markAsUsed(unknownAttribute);
+
+        markConstant(clazz, unknownAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitSourceFileAttribute(Clazz clazz, SourceFileAttribute sourceFileAttribute)
+    {
+        markAsUsed(sourceFileAttribute);
+
+        markConstant(clazz, sourceFileAttribute.u2attributeNameIndex);
+        markConstant(clazz, sourceFileAttribute.u2sourceFileIndex);
+    }
+
+
+    public void visitSourceDirAttribute(Clazz clazz, SourceDirAttribute sourceDirAttribute)
+    {
+        markAsUsed(sourceDirAttribute);
+
+        markConstant(clazz, sourceDirAttribute.u2attributeNameIndex);
+        markConstant(clazz, sourceDirAttribute.u2sourceDirIndex);
+    }
+
+
+    public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute)
+    {
+        // Don't mark the attribute and its name yet. We may mark it later, in
+        // InnerUsageMarker.
+        //markAsUsed(innerClassesAttribute);
+
+        //markConstant(clazz, innerClassesAttribute.u2attrNameIndex);
+
+        // Do mark the outer class entries.
+        innerClassesAttribute.innerClassEntriesAccept(clazz, this);
+    }
+
+
+    public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute)
+    {
+        markAsUsed(enclosingMethodAttribute);
+
+        markConstant(clazz, enclosingMethodAttribute.u2attributeNameIndex);
+        markConstant(clazz, enclosingMethodAttribute.u2classIndex);
+
+        if (enclosingMethodAttribute.u2nameAndTypeIndex != 0)
+        {
+            markConstant(clazz, enclosingMethodAttribute.u2nameAndTypeIndex);
+        }
+    }
+
+
+    public void visitDeprecatedAttribute(Clazz clazz, DeprecatedAttribute deprecatedAttribute)
+    {
+        markAsUsed(deprecatedAttribute);
+
+        markConstant(clazz, deprecatedAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitSyntheticAttribute(Clazz clazz, SyntheticAttribute syntheticAttribute)
+    {
+        markAsUsed(syntheticAttribute);
+
+        markConstant(clazz, syntheticAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitSignatureAttribute(Clazz clazz, SignatureAttribute signatureAttribute)
+    {
+        markAsUsed(signatureAttribute);
+
+        markConstant(clazz, signatureAttribute.u2attributeNameIndex);
+        markConstant(clazz, signatureAttribute.u2signatureIndex);
+    }
+
+
+    public void visitConstantValueAttribute(Clazz clazz, Field field, ConstantValueAttribute constantValueAttribute)
+    {
+        markAsUsed(constantValueAttribute);
+
+        markConstant(clazz, constantValueAttribute.u2attributeNameIndex);
+        markConstant(clazz, constantValueAttribute.u2constantValueIndex);
+    }
+
+
+    public void visitExceptionsAttribute(Clazz clazz, Method method, ExceptionsAttribute exceptionsAttribute)
+    {
+        markAsUsed(exceptionsAttribute);
+
+        markConstant(clazz, exceptionsAttribute.u2attributeNameIndex);
+
+        // Mark the constant pool entries referenced by the exceptions.
+        exceptionsAttribute.exceptionEntriesAccept((ProgramClass)clazz, this);
+    }
+
+
+    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
+    {
+        markAsUsed(codeAttribute);
+
+        markConstant(clazz, codeAttribute.u2attributeNameIndex);
+
+        // Mark the constant pool entries referenced by the instructions,
+        // by the exceptions, and by the attributes.
+        codeAttribute.instructionsAccept(clazz, method, this);
+        codeAttribute.exceptionsAccept(clazz, method, this);
+        codeAttribute.attributesAccept(clazz, method, this);
+    }
+
+
+    public void visitStackMapAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapAttribute stackMapAttribute)
+    {
+        markAsUsed(stackMapAttribute);
+
+        markConstant(clazz, stackMapAttribute.u2attributeNameIndex);
+
+        // Mark the constant pool entries referenced by the stack map frames.
+        stackMapAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitStackMapTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, StackMapTableAttribute stackMapTableAttribute)
+    {
+        markAsUsed(stackMapTableAttribute);
+
+        markConstant(clazz, stackMapTableAttribute.u2attributeNameIndex);
+
+        // Mark the constant pool entries referenced by the stack map frames.
+        stackMapTableAttribute.stackMapFramesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
+    {
+        markAsUsed(lineNumberTableAttribute);
+
+        markConstant(clazz, lineNumberTableAttribute.u2attributeNameIndex);
+    }
+
+
+    public void visitLocalVariableTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTableAttribute localVariableTableAttribute)
+    {
+        markAsUsed(localVariableTableAttribute);
+
+        markConstant(clazz, localVariableTableAttribute.u2attributeNameIndex);
+
+        // Mark the constant pool entries referenced by the local variables.
+        localVariableTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitLocalVariableTypeTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeTableAttribute localVariableTypeTableAttribute)
+    {
+        markAsUsed(localVariableTypeTableAttribute);
+
+        markConstant(clazz, localVariableTypeTableAttribute.u2attributeNameIndex);
+
+        // Mark the constant pool entries referenced by the local variable types.
+        localVariableTypeTableAttribute.localVariablesAccept(clazz, method, codeAttribute, this);
+    }
+
+
+    public void visitAnyAnnotationsAttribute(Clazz clazz, AnnotationsAttribute annotationsAttribute)
+    {
+        // Don't mark the attribute and its contents yet. We may mark them later,
+        // in AnnotationUsageMarker.
+//        markAsUsed(annotationsAttribute);
+//
+//        markConstant(clazz, annotationsAttribute.u2attributeNameIndex);
+//
+//        // Mark the constant pool entries referenced by the annotations.
+//        annotationsAttribute.annotationsAccept(clazz, this);
+    }
+
+
+    public void visitAnyParameterAnnotationsAttribute(Clazz clazz, Method method, ParameterAnnotationsAttribute parameterAnnotationsAttribute)
+    {
+        // Don't mark the attribute and its contents yet. We may mark them later,
+        // in AnnotationUsageMarker.
+//        markAsUsed(parameterAnnotationsAttribute);
+//
+//        markConstant(clazz, parameterAnnotationsAttribute.u2attributeNameIndex);
+//
+//        // Mark the constant pool entries referenced by the annotations.
+//        parameterAnnotationsAttribute.annotationsAccept(clazz, method, this);
+    }
+
+
+    public void visitAnnotationDefaultAttribute(Clazz clazz, Method method, AnnotationDefaultAttribute annotationDefaultAttribute)
+    {
+        // Don't mark the attribute and its contents yet. We may mark them later,
+        // in AnnotationUsageMarker.
+//        markAsUsed(annotationDefaultAttribute);
+//
+//        markConstant(clazz, annotationDefaultAttribute.u2attributeNameIndex);
+//
+//        // Mark the constant pool entries referenced by the element value.
+//        annotationDefaultAttribute.defaultValueAccept(clazz, this);
+    }
+
+
+    // Implementations for ExceptionInfoVisitor.
+
+    public void visitExceptionInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo)
+    {
+        markAsUsed(exceptionInfo);
+
+        if (exceptionInfo.u2catchType != 0)
+        {
+            markConstant(clazz, exceptionInfo.u2catchType);
+        }
+    }
+
+
+    // Implementations for InnerClassesInfoVisitor.
+
+    public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo)
+    {
+        // At this point, we only mark outer classes of this class.
+        // Inner class can be marked later, by InnerUsageMarker.
+        if (innerClassesInfo.u2innerClassIndex != 0 &&
+            clazz.getName().equals(clazz.getClassName(innerClassesInfo.u2innerClassIndex)))
+        {
+            markAsUsed(innerClassesInfo);
+
+            innerClassesInfo.innerClassConstantAccept(clazz, this);
+            innerClassesInfo.outerClassConstantAccept(clazz, this);
+            innerClassesInfo.innerNameConstantAccept(clazz, this);
+        }
+    }
+
+
+    // Implementations for StackMapFrameVisitor.
+
+    public void visitAnyStackMapFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, StackMapFrame stackMapFrame) {}
+
+
+    public void visitSameOneFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SameOneFrame sameOneFrame)
+    {
+        // Mark the constant pool entries referenced by the verification types.
+        sameOneFrame.stackItemAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitMoreZeroFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, MoreZeroFrame moreZeroFrame)
+    {
+        // Mark the constant pool entries referenced by the verification types.
+        moreZeroFrame.additionalVariablesAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    public void visitFullFrame(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, FullFrame fullFrame)
+    {
+        // Mark the constant pool entries referenced by the verification types.
+        fullFrame.variablesAccept(clazz, method, codeAttribute, offset, this);
+        fullFrame.stackAccept(clazz, method, codeAttribute, offset, this);
+    }
+
+
+    // Implementations for VerificationTypeVisitor.
+
+    public void visitAnyVerificationType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VerificationType verificationType) {}
+
+
+    public void visitObjectType(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ObjectType objectType)
+    {
+        markConstant(clazz, objectType.u2classIndex);
+    }
+
+
+    // Implementations for LocalVariableInfoVisitor.
+
+    public void visitLocalVariableInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableInfo localVariableInfo)
+    {
+        markConstant(clazz, localVariableInfo.u2nameIndex);
+        markConstant(clazz, localVariableInfo.u2descriptorIndex);
+    }
+
+
+    // Implementations for LocalVariableTypeInfoVisitor.
+
+    public void visitLocalVariableTypeInfo(Clazz clazz, Method method, CodeAttribute codeAttribute, LocalVariableTypeInfo localVariableTypeInfo)
+    {
+        markConstant(clazz, localVariableTypeInfo.u2nameIndex);
+        markConstant(clazz, localVariableTypeInfo.u2signatureIndex);
+    }
+
+
+//    // Implementations for AnnotationVisitor.
+//
+//    public void visitAnnotation(Clazz clazz, Annotation annotation)
+//    {
+//        markConstant(clazz, annotation.u2typeIndex);
+//
+//        // Mark the constant pool entries referenced by the element values.
+//        annotation.elementValuesAccept(clazz, this);
+//    }
+//
+//
+//    // Implementations for ElementValueVisitor.
+//
+//    public void visitConstantElementValue(Clazz clazz, Annotation annotation, ConstantElementValue constantElementValue)
+//    {
+//        if (constantElementValue.u2elementNameIndex != 0)
+//        {
+//            markConstant(clazz, constantElementValue.u2elementNameIndex);
+//        }
+//
+//        markConstant(clazz, constantElementValue.u2constantValueIndex);
+//    }
+//
+//
+//    public void visitEnumConstantElementValue(Clazz clazz, Annotation annotation, EnumConstantElementValue enumConstantElementValue)
+//    {
+//        if (enumConstantElementValue.u2elementNameIndex != 0)
+//        {
+//            markConstant(clazz, enumConstantElementValue.u2elementNameIndex);
+//        }
+//
+//        markConstant(clazz, enumConstantElementValue.u2typeNameIndex);
+//        markConstant(clazz, enumConstantElementValue.u2constantNameIndex);
+//    }
+//
+//
+//    public void visitClassElementValue(Clazz clazz, Annotation annotation, ClassElementValue classElementValue)
+//    {
+//        if (classElementValue.u2elementNameIndex != 0)
+//        {
+//            markConstant(clazz, classElementValue.u2elementNameIndex);
+//        }
+//
+//        // Mark the referenced class constant pool entry.
+//        markConstant(clazz, classElementValue.u2classInfoIndex);
+//    }
+//
+//
+//    public void visitAnnotationElementValue(Clazz clazz, Annotation annotation, AnnotationElementValue annotationElementValue)
+//    {
+//        if (annotationElementValue.u2elementNameIndex != 0)
+//        {
+//            markConstant(clazz, annotationElementValue.u2elementNameIndex);
+//        }
+//
+//        // Mark the constant pool entries referenced by the annotation.
+//        annotationElementValue.annotationAccept(clazz, this);
+//    }
+//
+//
+//    public void visitArrayElementValue(Clazz clazz, Annotation annotation, ArrayElementValue arrayElementValue)
+//    {
+//        if (arrayElementValue.u2elementNameIndex != 0)
+//        {
+//            markConstant(clazz, arrayElementValue.u2elementNameIndex);
+//        }
+//
+//        // Mark the constant pool entries referenced by the element values.
+//        arrayElementValue.elementValuesAccept(clazz, annotation, this);
+//    }
+
+
+    // Implementations for InstructionVisitor.
+
+    public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
+
+
+    public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction)
+    {
+        markConstant(clazz, constantInstruction.constantIndex);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Marks the given visitor accepter as being used.
+     */
+    protected void markAsUsed(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(USED);
+    }
+
+
+    /**
+     * Returns whether the given visitor accepter should still be marked as
+     * being used.
+     */
+    protected boolean shouldBeMarkedAsUsed(VisitorAccepter visitorAccepter)
+    {
+        return visitorAccepter.getVisitorInfo() != USED;
+    }
+
+
+    /**
+     * Returns whether the given visitor accepter has been marked as being used.
+     */
+    protected boolean isUsed(VisitorAccepter visitorAccepter)
+    {
+        return visitorAccepter.getVisitorInfo() == USED;
+    }
+
+
+    /**
+     * Marks the given visitor accepter as possibly being used.
+     */
+    protected void markAsPossiblyUsed(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(POSSIBLY_USED);
+    }
+
+
+    /**
+     * Returns whether the given visitor accepter should still be marked as
+     * possibly being used.
+     */
+    protected boolean shouldBeMarkedAsPossiblyUsed(VisitorAccepter visitorAccepter)
+    {
+        return visitorAccepter.getVisitorInfo() != USED &&
+               visitorAccepter.getVisitorInfo() != POSSIBLY_USED;
+    }
+
+
+    /**
+     * Returns whether the given visitor accepter has been marked as possibly
+     * being used.
+     */
+    protected boolean isPossiblyUsed(VisitorAccepter visitorAccepter)
+    {
+        return visitorAccepter.getVisitorInfo() == POSSIBLY_USED;
+    }
+
+
+    /**
+     * Clears any usage marks from the given visitor accepter.
+     */
+    protected void markAsUnused(VisitorAccepter visitorAccepter)
+    {
+        visitorAccepter.setVisitorInfo(null);
+    }
+
+
+    /**
+     * Marks the given constant pool entry of the given class. This includes
+     * visiting any referenced objects.
+     */
+    private void markConstant(Clazz clazz, int index)
+    {
+         clazz.constantPoolEntryAccept(index, this);
+    }
+}
diff --git a/src/proguard/shrink/UsagePrinter.java b/src/proguard/shrink/UsagePrinter.java
new file mode 100644
index 0000000..294b9e1
--- /dev/null
+++ b/src/proguard/shrink/UsagePrinter.java
@@ -0,0 +1,177 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.util.*;
+import proguard.classfile.visitor.*;
+
+import java.io.PrintStream;
+
+
+/**
+ * This ClassVisitor prints out the classes and class members that have been
+ * marked as being used (or not used).
+ *
+ * @see UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class UsagePrinter
+extends      SimplifiedVisitor
+implements   ClassVisitor,
+             MemberVisitor
+{
+    private final UsageMarker usageMarker;
+    private final boolean     printUnusedItems;
+    private final PrintStream ps;
+
+    // A field to remember the class name, if a header is needed for class members.
+    private String      className;
+
+
+    /**
+     * Creates a new UsagePrinter that prints to <code>System.out</code>.
+     * @param usageMarker      the usage marker that was used to mark the
+     *                         classes and class members.
+     * @param printUnusedItems a flag that indicates whether only unused items
+     *                         should be printed, or alternatively, only used
+     *                         items.
+     */
+    public UsagePrinter(UsageMarker usageMarker,
+                        boolean     printUnusedItems)
+    {
+        this(usageMarker, printUnusedItems, System.out);
+    }
+
+
+    /**
+     * Creates a new UsagePrinter that prints to the given stream.
+     * @param usageMarker      the usage marker that was used to mark the
+     *                         classes and class members.
+     * @param printUnusedItems a flag that indicates whether only unused items
+     *                         should be printed, or alternatively, only used
+     *                         items.
+     * @param printStream      the stream to which to print.
+     */
+    public UsagePrinter(UsageMarker usageMarker,
+                        boolean     printUnusedItems,
+                        PrintStream printStream)
+    {
+        this.usageMarker      = usageMarker;
+        this.printUnusedItems = printUnusedItems;
+        this.ps               = printStream;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (usageMarker.isUsed(programClass))
+        {
+            if (printUnusedItems)
+            {
+                className = programClass.getName();
+
+                programClass.fieldsAccept(this);
+                programClass.methodsAccept(this);
+
+                className = null;
+            }
+            else
+            {
+                ps.println(ClassUtil.externalClassName(programClass.getName()));
+            }
+        }
+        else
+        {
+            if (printUnusedItems)
+            {
+                ps.println(ClassUtil.externalClassName(programClass.getName()));
+            }
+        }
+    }
+
+
+    // Implementations for MemberVisitor.
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (usageMarker.isUsed(programField) ^ printUnusedItems)
+        {
+            printClassNameHeader();
+
+            ps.println("    " +
+                       lineNumberRange(programClass, programField) +
+                       ClassUtil.externalFullFieldDescription(
+                           programField.getAccessFlags(),
+                           programField.getName(programClass),
+                           programField.getDescriptor(programClass)));
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (usageMarker.isUsed(programMethod) ^ printUnusedItems)
+        {
+            printClassNameHeader();
+
+            ps.println("    " +
+                       lineNumberRange(programClass, programMethod) +
+                       ClassUtil.externalFullMethodDescription(
+                           programClass.getName(),
+                           programMethod.getAccessFlags(),
+                           programMethod.getName(programClass),
+                           programMethod.getDescriptor(programClass)));
+        }
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Prints the class name field. The field is then cleared, so it is not
+     * printed again.
+     */
+    private void printClassNameHeader()
+    {
+        if (className != null)
+        {
+            ps.println(ClassUtil.externalClassName(className) + ":");
+            className = null;
+        }
+    }
+
+
+    /**
+     * Returns the line number range of the given class member, followed by a
+     * colon, or just an empty String if no range is available.
+     */
+    private static String lineNumberRange(ProgramClass programClass, ProgramMember programMember)
+    {
+        String range = programMember.getLineNumberRange(programClass);
+        return range != null ?
+            (range + ":") :
+            "";
+    }
+}
diff --git a/src/proguard/shrink/UsedClassFilter.java b/src/proguard/shrink/UsedClassFilter.java
new file mode 100644
index 0000000..ec180bd
--- /dev/null
+++ b/src/proguard/shrink/UsedClassFilter.java
@@ -0,0 +1,74 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.ClassVisitor;
+
+/**
+ * This ClassVisitor delegates all its method calls to another ClassVisitor,
+ * but only for Clazz objects that are marked as used.
+ *
+ * @see UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class UsedClassFilter
+implements   ClassVisitor
+{
+    private final UsageMarker  usageMarker;
+    private final ClassVisitor classVisitor;
+
+
+    /**
+     * Creates a new UsedClassFilter.
+     * @param usageMarker  the usage marker that is used to mark the classes
+     *                     and class members.
+     * @param classVisitor the class visitor to which the visiting will be
+     *                     delegated.
+     */
+    public UsedClassFilter(UsageMarker  usageMarker,
+                           ClassVisitor classVisitor)
+    {
+        this.usageMarker  = usageMarker;
+        this.classVisitor = classVisitor;
+    }
+
+
+    // Implementations for ClassVisitor.
+
+    public void visitProgramClass(ProgramClass programClass)
+    {
+        if (usageMarker.isUsed(programClass))
+        {
+            classVisitor.visitProgramClass(programClass);
+        }
+    }
+
+
+    public void visitLibraryClass(LibraryClass libraryClass)
+    {
+        if (usageMarker.isUsed(libraryClass))
+        {
+            classVisitor.visitLibraryClass(libraryClass);
+        }
+    }
+}
diff --git a/src/proguard/shrink/UsedMemberFilter.java b/src/proguard/shrink/UsedMemberFilter.java
new file mode 100644
index 0000000..755cfd1
--- /dev/null
+++ b/src/proguard/shrink/UsedMemberFilter.java
@@ -0,0 +1,93 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.shrink;
+
+import proguard.classfile.*;
+import proguard.classfile.visitor.MemberVisitor;
+
+/**
+ * This MemberVisitor delegates all its method calls to another MemberVisitor,
+ * but only for Member objects that are marked as used.
+ *
+ * @see UsageMarker
+ *
+ * @author Eric Lafortune
+ */
+public class UsedMemberFilter
+implements   MemberVisitor
+{
+    private final UsageMarker   usageMarker;
+    private final MemberVisitor memberVisitor;
+
+
+    /**
+     * Creates a new UsedClassFilter.
+     * @param usageMarker   the usage marker that is used to mark the classes
+     *                      and class members.
+     * @param memberVisitor the member visitor to which the visiting will be
+     *                      delegated.
+     */
+    public UsedMemberFilter(UsageMarker   usageMarker,
+                            MemberVisitor memberVisitor)
+    {
+        this.usageMarker   = usageMarker;
+        this.memberVisitor = memberVisitor;
+    }
+
+
+    // Implementations for MemberVisitor.
+
+
+    public void visitProgramField(ProgramClass programClass, ProgramField programField)
+    {
+        if (usageMarker.isUsed(programField))
+        {
+            memberVisitor.visitProgramField(programClass, programField);
+        }
+    }
+
+
+    public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
+    {
+        if (usageMarker.isUsed(programMethod))
+        {
+            memberVisitor.visitProgramMethod(programClass, programMethod);
+        }
+    }
+
+
+    public void visitLibraryField(LibraryClass libraryClass, LibraryField libraryField)
+    {
+        if (usageMarker.isUsed(libraryField))
+        {
+            memberVisitor.visitLibraryField(libraryClass, libraryField);
+        }
+    }
+
+
+    public void visitLibraryMethod(LibraryClass libraryClass, LibraryMethod libraryMethod)
+    {
+        if (usageMarker.isUsed(libraryMethod))
+        {
+            memberVisitor.visitLibraryMethod(libraryClass, libraryMethod);
+        }
+    }
+}
diff --git a/src/proguard/shrink/package.html b/src/proguard/shrink/package.html
new file mode 100644
index 0000000..8973198
--- /dev/null
+++ b/src/proguard/shrink/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains classes to perform shrinking of class files.
+</body>
diff --git a/src/proguard/util/AndMatcher.java b/src/proguard/util/AndMatcher.java
new file mode 100644
index 0000000..94a37e5
--- /dev/null
+++ b/src/proguard/util/AndMatcher.java
@@ -0,0 +1,49 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher tests whether strings matches both given StringMatcher
+ * instances.
+ *
+ * @author Eric Lafortune
+ */
+public class AndMatcher implements StringMatcher
+{
+    private final StringMatcher matcher1;
+    private final StringMatcher matcher2;
+
+
+    public AndMatcher(StringMatcher matcher1, StringMatcher matcher2)
+    {
+        this.matcher1 = matcher1;
+        this.matcher2 = matcher2;
+    }
+
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        return matcher1.matches(string) &&
+               matcher2.matches(string);
+    }
+}
diff --git a/src/proguard/util/ClassNameParser.java b/src/proguard/util/ClassNameParser.java
new file mode 100644
index 0000000..ee972f0
--- /dev/null
+++ b/src/proguard/util/ClassNameParser.java
@@ -0,0 +1,216 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+import proguard.classfile.ClassConstants;
+
+/**
+ * This StringParser can create StringMatcher instances for regular expressions
+ * matching internal class names (or descriptors containing class names).
+ * The regular expressions can contain the following wildcards:
+ * '%'     for a single internal primitive type character (V, Z, B, C, S, I, F,
+ *         J, or D),
+ * '?'     for a single regular class name character,
+ * '*'     for any number of regular class name characters,
+ * '**'    for any number of regular class name characters or package separator
+ *         characters ('/'),
+ * 'L***;' for a single internal type (class name or primitive type,
+ *         array or non-array), and
+ * 'L///;' for any number of internal types (class names and primitive
+ *         types).
+ *
+ * @author Eric Lafortune
+ */
+public class ClassNameParser implements StringParser
+{
+    private static final char[] INTERNAL_PRIMITIVE_TYPES = new char[]
+    {
+        ClassConstants.INTERNAL_TYPE_VOID,
+        ClassConstants.INTERNAL_TYPE_BOOLEAN,
+        ClassConstants.INTERNAL_TYPE_BYTE,
+        ClassConstants.INTERNAL_TYPE_CHAR,
+        ClassConstants.INTERNAL_TYPE_SHORT,
+        ClassConstants.INTERNAL_TYPE_INT,
+        ClassConstants.INTERNAL_TYPE_LONG,
+        ClassConstants.INTERNAL_TYPE_FLOAT,
+        ClassConstants.INTERNAL_TYPE_DOUBLE,
+    };
+
+
+    // Implementations for StringParser.
+
+    public StringMatcher parse(String regularExpression)
+    {
+        int           index;
+        StringMatcher nextMatcher = new EmptyStringMatcher();
+
+        // Look for wildcards.
+        for (index = 0; index < regularExpression.length(); index++)
+        {
+            // Is there an 'L///;' wildcard?
+            if (regularExpression.regionMatches(index, "L///;", 0, 5))
+            {
+                SettableMatcher settableMatcher = new SettableMatcher();
+
+                // Create a matcher, recursively, for the remainder of the
+                // string, optionally preceded by any type.
+                nextMatcher =
+                    new OrMatcher(parse(regularExpression.substring(index + 5)),
+                                  createAnyTypeMatcher(settableMatcher));
+
+                settableMatcher.setMatcher(nextMatcher);
+
+                break;
+            }
+
+            // Is there an 'L***;' wildcard?
+            if (regularExpression.regionMatches(index, "L***;", 0, 5))
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    createAnyTypeMatcher(parse(regularExpression.substring(index + 5)));
+                break;
+            }
+
+            // Is there a '**' wildcard?
+            if (regularExpression.regionMatches(index, "**", 0, 2))
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(null,
+                                              new char[] { ClassConstants.INTERNAL_TYPE_CLASS_END },
+                                              0,
+                                              Integer.MAX_VALUE,
+                                              parse(regularExpression.substring(index + 2)));
+                break;
+            }
+
+            // Is there a '*' wildcard?
+            else if (regularExpression.charAt(index) == '*')
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(null,
+                                              new char[] { ClassConstants.INTERNAL_TYPE_CLASS_END, ClassConstants.INTERNAL_PACKAGE_SEPARATOR },
+                                              0,
+                                              Integer.MAX_VALUE,
+                                              parse(regularExpression.substring(index + 1)));
+                break;
+            }
+
+            // Is there a '?' wildcard?
+            else if (regularExpression.charAt(index) == '?')
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(null,
+                                              new char[] { ClassConstants.INTERNAL_TYPE_CLASS_END, ClassConstants.INTERNAL_PACKAGE_SEPARATOR },
+                                              1,
+                                              1,
+                                              parse(regularExpression.substring(index + 1)));
+                break;
+            }
+
+            // Is there a '%' wildcard?
+            else if (regularExpression.charAt(index) == '%')
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(INTERNAL_PRIMITIVE_TYPES,
+                                              null,
+                                              1,
+                                              1,
+                                              parse(regularExpression.substring(index + 1)));
+                break;
+            }
+        }
+
+        // Return a matcher for the fixed first part of the regular expression,
+        // if any, and the remainder.
+        return index != 0 ?
+            (StringMatcher)new FixedStringMatcher(regularExpression.substring(0, index), nextMatcher) :
+            (StringMatcher)nextMatcher;
+    }
+
+
+    // Small utility methods.
+
+
+    /**
+     * Creates a StringMatcher that matches any type (class or primitive type,
+     * array or non-array) and then the given matcher.
+     */
+    private VariableStringMatcher createAnyTypeMatcher(StringMatcher nextMatcher)
+    {
+        return new VariableStringMatcher(new char[] { ClassConstants.INTERNAL_TYPE_ARRAY },
+                                  null,
+                                  0,
+                                  255,
+        new OrMatcher(
+        new VariableStringMatcher(INTERNAL_PRIMITIVE_TYPES,
+                                  null,
+                                  1,
+                                  1,
+                                  nextMatcher),
+        new VariableStringMatcher(new char[] { ClassConstants.INTERNAL_TYPE_CLASS_START },
+                                  null,
+                                  1,
+                                  1,
+        new VariableStringMatcher(null,
+                                  new char[] { ClassConstants.INTERNAL_TYPE_CLASS_END },
+                                  0,
+                                  Integer.MAX_VALUE,
+        new VariableStringMatcher(new char[] { ClassConstants.INTERNAL_TYPE_CLASS_END },
+                                  null,
+                                  1,
+                                  1,
+                                  nextMatcher)))));
+    }
+
+
+    /**
+     * A main method for testing class name matching.
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            System.out.println("Regular expression ["+args[0]+"]");
+            ClassNameParser parser  = new ClassNameParser();
+            StringMatcher  matcher = parser.parse(args[0]);
+            for (int index = 1; index < args.length; index++)
+            {
+                String string = args[index];
+                System.out.print("String             ["+string+"]");
+                System.out.println(" -> match = "+matcher.matches(args[index]));
+            }
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/util/ConstantMatcher.java b/src/proguard/util/ConstantMatcher.java
new file mode 100644
index 0000000..1764caa
--- /dev/null
+++ b/src/proguard/util/ConstantMatcher.java
@@ -0,0 +1,48 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher matches any string or no string at all.
+ *
+ * @author Eric Lafortune
+ */
+public class ConstantMatcher implements StringMatcher
+{
+    private boolean matches;
+
+
+    /**
+     * Creates a new ConstantMatcher that always returns the given result.
+     */
+    public ConstantMatcher(boolean matches)
+    {
+        this.matches = matches;
+    }
+
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        return matches;
+    }
+}
\ No newline at end of file
diff --git a/src/proguard/util/EmptyStringMatcher.java b/src/proguard/util/EmptyStringMatcher.java
new file mode 100644
index 0000000..543f446
--- /dev/null
+++ b/src/proguard/util/EmptyStringMatcher.java
@@ -0,0 +1,36 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher tests whether strings are empty.
+ *
+ * @author Eric Lafortune
+ */
+public class EmptyStringMatcher implements StringMatcher
+{
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        return string.length() == 0;
+    }
+}
diff --git a/src/proguard/util/ExtensionMatcher.java b/src/proguard/util/ExtensionMatcher.java
new file mode 100644
index 0000000..5a9f658
--- /dev/null
+++ b/src/proguard/util/ExtensionMatcher.java
@@ -0,0 +1,63 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher tests whether strings end in a given extension, ignoring
+ * its case.
+ *
+ * @author Eric Lafortune
+ */
+public class ExtensionMatcher implements StringMatcher
+{
+    private final String extension;
+
+
+    /**
+     * Creates a new StringMatcher.
+     * @param extension the extension against which strings will be matched.
+     */
+    public ExtensionMatcher(String extension)
+    {
+        this.extension = extension;
+    }
+
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        return endsWithIgnoreCase(string, extension);
+    }
+
+
+    /**
+     * Returns whether the given string ends with the given suffix, ignoring its
+     * case.
+     */
+    private static boolean endsWithIgnoreCase(String string, String suffix)
+    {
+        int stringLength = string.length();
+        int suffixLength = suffix.length();
+
+        return string.regionMatches(true, stringLength - suffixLength, suffix, 0, suffixLength);
+    }
+}
diff --git a/src/proguard/util/FileNameParser.java b/src/proguard/util/FileNameParser.java
new file mode 100644
index 0000000..913f22d
--- /dev/null
+++ b/src/proguard/util/FileNameParser.java
@@ -0,0 +1,121 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+import java.io.File;
+
+/**
+ * This StringParser can create StringMatcher instances for regular expressions
+ * matching file names. The regular expressions can contain the following
+ * wildcards:
+ * '?'  for a single regular file name character,
+ * '*'  for any number of regular file name characters, and
+ * '**' for any number of regular file name characters or directory separator
+ *      characters (always including '/').
+ *
+ * @author Eric Lafortune
+ */
+public class FileNameParser implements StringParser
+{
+    // Implementations for StringParser.
+
+    public StringMatcher parse(String regularExpression)
+    {
+        int           index;
+        StringMatcher nextMatcher = new EmptyStringMatcher();
+
+        // Look for wildcards.
+        for (index = 0; index < regularExpression.length(); index++)
+        {
+            // Is there a '**' wildcard?
+            if (regularExpression.regionMatches(index, "**", 0, 2))
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(null,
+                                              null,
+                                              0,
+                                              Integer.MAX_VALUE,
+                                              parse(regularExpression.substring(index + 2)));
+                break;
+            }
+
+            // Is there a '*' wildcard?
+            else if (regularExpression.charAt(index) == '*')
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(null,
+                                              new char[] { File.pathSeparatorChar, '/' },
+                                              0,
+                                              Integer.MAX_VALUE,
+                                              parse(regularExpression.substring(index + 1)));
+                break;
+            }
+
+            // Is there a '?' wildcard?
+            else if (regularExpression.charAt(index) == '?')
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(null,
+                                              new char[] { File.pathSeparatorChar, '/' },
+                                              1,
+                                              1,
+                                              parse(regularExpression.substring(index + 1)));
+                break;
+            }
+        }
+
+        // Return a matcher for the fixed first part of the regular expression,
+        // if any, and the remainder.
+        return index != 0 ?
+            (StringMatcher)new FixedStringMatcher(regularExpression.substring(0, index), nextMatcher) :
+            (StringMatcher)nextMatcher;
+    }
+
+
+    /**
+     * A main method for testing file name matching.
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            System.out.println("Regular expression ["+args[0]+"]");
+            FileNameParser parser  = new FileNameParser();
+            StringMatcher  matcher = parser.parse(args[0]);
+            for (int index = 1; index < args.length; index++)
+            {
+                String string = args[index];
+                System.out.print("String             ["+string+"]");
+                System.out.println(" -> match = "+matcher.matches(args[index]));
+            }
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/util/FixedStringMatcher.java b/src/proguard/util/FixedStringMatcher.java
new file mode 100644
index 0000000..c1eb3f4
--- /dev/null
+++ b/src/proguard/util/FixedStringMatcher.java
@@ -0,0 +1,56 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher tests whether strings start with a given fixed string
+ * and then match another optional given StringMatcher.
+ *
+ * @author Eric Lafortune
+ */
+public class FixedStringMatcher implements StringMatcher
+{
+    private final String        fixedString;
+    private final StringMatcher nextMatcher;
+
+
+    public FixedStringMatcher(String fixedString)
+    {
+        this(fixedString, null);
+    }
+
+
+    public FixedStringMatcher(String fixedString, StringMatcher nextMatcher)
+    {
+        this.fixedString = fixedString;
+        this.nextMatcher = nextMatcher;
+    }
+
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        return string.startsWith(fixedString) &&
+               (nextMatcher == null ||
+                nextMatcher.matches(string.substring(fixedString.length())));
+    }
+}
diff --git a/src/proguard/util/ListMatcher.java b/src/proguard/util/ListMatcher.java
new file mode 100644
index 0000000..b2559c8
--- /dev/null
+++ b/src/proguard/util/ListMatcher.java
@@ -0,0 +1,69 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher tests whether strings match a given list of StringMatcher
+ * instances. The instances are considered sequentially. Each instance in the
+ * list can optionally be negated, meaning that a match makes the entire
+ * remaining match fail.
+ *
+ * @author Eric Lafortune
+ */
+public class ListMatcher implements StringMatcher
+{
+    private final StringMatcher[] matchers;
+    private final boolean[]       negate;
+
+
+    public ListMatcher(StringMatcher[] matchers)
+    {
+        this(matchers, null);
+    }
+
+
+    public ListMatcher(StringMatcher[] matchers, boolean[] negate)
+    {
+        this.matchers = matchers;
+        this.negate   = negate;
+    }
+
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        // Check the list of matchers.
+        for (int index = 0; index < matchers.length; index++)
+        {
+            StringMatcher matcher = matchers[index];
+            if (matcher.matches(string))
+            {
+                return negate == null ||
+                       !negate[index];
+            }
+        }
+
+        return negate != null &&
+               negate[negate.length - 1];
+
+    }
+}
diff --git a/src/proguard/util/ListParser.java b/src/proguard/util/ListParser.java
new file mode 100644
index 0000000..cec803b
--- /dev/null
+++ b/src/proguard/util/ListParser.java
@@ -0,0 +1,137 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+import java.util.List;
+
+/**
+ * This StringParser can create StringMatcher instances for regular expressions.
+ * The regular expressions are either presented as a list, or they are
+ * interpreted as comma-separated lists, optionally prefixed with '!' negators.
+ * If an entry with a negator matches, a negative match is returned, without
+ * considering any subsequent entries in the list. The creation of StringMatcher
+ * instances  for the entries is delegated to the given StringParser.
+ *
+ * @author Eric Lafortune
+ */
+public class ListParser implements StringParser
+{
+    private final StringParser stringParser;
+
+
+    /**
+     * Creates a new ListParser that parses individual elements in the
+     * comma-separated list with the given StringParser.
+     */
+    public ListParser(StringParser stringParser)
+    {
+        this.stringParser = stringParser;
+    }
+
+
+    // Implementations for StringParser.
+
+    public StringMatcher parse(String regularExpression)
+    {
+        // Does the regular expression contain a ',' list separator?
+        return parse(ListUtil.commaSeparatedList(regularExpression));
+    }
+
+
+    /**
+     * Creates a StringMatcher for the given regular expression, which can
+     * be a list of optionally negated simple entries.
+     * <p>
+     * An empty list results in a StringMatcher that matches any string.
+     */
+    public StringMatcher parse(List regularExpressions)
+    {
+        StringMatcher listMatcher = null;
+
+        // Loop over all simple regular expressions, backward, creating a
+        // linked list of matchers.
+        for (int index = regularExpressions.size()-1; index >=0; index--)
+        {
+            String regularExpression = (String)regularExpressions.get(index);
+
+            StringMatcher entryMatcher = parseEntry(regularExpression);
+
+            // Prepend the entry matcher.
+            listMatcher =
+                listMatcher == null ?
+                    (StringMatcher)entryMatcher :
+                isNegated(regularExpression) ?
+                    (StringMatcher)new AndMatcher(entryMatcher, listMatcher) :
+                    (StringMatcher)new OrMatcher(entryMatcher, listMatcher);
+        }
+
+        return listMatcher != null ? listMatcher : new ConstantMatcher(true);
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Creates a StringMatcher for the given regular expression, which is a
+     * an optionally negated simple expression.
+     */
+    private StringMatcher parseEntry(String regularExpression)
+    {
+        // Wrap the matcher if the regular expression starts with a '!' negator.
+        return isNegated(regularExpression) ?
+          new NotMatcher(stringParser.parse(regularExpression.substring(1))) :
+          stringParser.parse(regularExpression);
+    }
+
+
+    /**
+     * Returns whether the given simple regular expression is negated.
+     */
+    private boolean isNegated(String regularExpression)
+    {
+        return regularExpression.length() > 0 &&
+               regularExpression.charAt(0) == '!';
+    }
+
+
+    /**
+     * A main method for testing name matching.
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            System.out.println("Regular expression ["+args[0]+"]");
+            ListParser parser  = new ListParser(new NameParser());
+            StringMatcher  matcher = parser.parse(args[0]);
+            for (int index = 1; index < args.length; index++)
+            {
+                String string = args[index];
+                System.out.print("String             ["+string+"]");
+                System.out.println(" -> match = "+matcher.matches(args[index]));
+            }
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/util/ListUtil.java b/src/proguard/util/ListUtil.java
new file mode 100644
index 0000000..570dbe8
--- /dev/null
+++ b/src/proguard/util/ListUtil.java
@@ -0,0 +1,173 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+import java.util.*;
+
+
+/**
+ * This class provides some utility methods for working with
+ * <code>java.util.List</code> objects.
+ *
+ * @author Eric Lafortune
+ */
+public class ListUtil
+{
+    /**
+     * Creates a comma-separated String from the given List of String objects.
+     */
+    public static String commaSeparatedString(List list)
+    {
+        if (list == null)
+        {
+            return null;
+        }
+
+        StringBuffer buffer = new StringBuffer();
+
+        for (int index = 0; index < list.size(); index++)
+        {
+            if (index > 0)
+            {
+                buffer.append(',');
+            }
+
+            buffer.append(quotedString((String)list.get(index)));
+        }
+
+        return buffer.toString();
+    }
+
+
+    /**
+     * Creates a List of String objects from the given comma-separated String.
+     */
+    public static List commaSeparatedList(String string)
+    {
+        if (string == null)
+        {
+            return null;
+        }
+
+        List list = new ArrayList();
+        int index = 0;
+        while ((index = skipWhitespace(string, index)) < string.length())
+        {
+            int nextIndex;
+
+            // Do we have an opening quote?
+            if (string.charAt(index) == '\'')
+            {
+                // Parse a quoted string.
+                nextIndex = string.indexOf('\'', index + 1);
+                if (nextIndex < 0)
+                {
+                    nextIndex = string.length();
+                }
+
+                list.add(string.substring(index + 1, nextIndex));
+            }
+            else
+            {
+                // Parse a non-quoted string.
+                nextIndex = string.indexOf(',', index);
+                if (nextIndex < 0)
+                {
+                    nextIndex = string.length();
+                }
+
+                String substring = string.substring(index, nextIndex).trim();
+                if (substring.length() > 0)
+                {
+                    list.add(substring);
+                }
+            }
+
+            index = nextIndex + 1;
+        }
+
+        return list;
+    }
+
+
+    /**
+     * Skips any whitespace characters.
+     */
+    private static int skipWhitespace(String string, int index)
+    {
+        while (index < string.length() &&
+               Character.isWhitespace(string.charAt(index)))
+        {
+            index++;
+        }
+        return index;
+    }
+
+
+    /**
+     * Returns a quoted version of the given string, if necessary.
+     */
+    private static String quotedString(String string)
+    {
+        return string.length()     == 0 ||
+               string.indexOf(' ') >= 0 ||
+               string.indexOf('@') >= 0 ||
+               string.indexOf('{') >= 0 ||
+               string.indexOf('}') >= 0 ||
+               string.indexOf('(') >= 0 ||
+               string.indexOf(')') >= 0 ||
+               string.indexOf(':') >= 0 ||
+               string.indexOf(';') >= 0 ||
+               string.indexOf(',') >= 0  ? ("'" + string + "'") :
+                                           (      string      );
+    }
+
+
+    public static void main(String[] args)
+    {
+        if (args.length == 1)
+        {
+            System.out.println("Input string: ["+args[0]+"]");
+
+            List list = commaSeparatedList(args[0]);
+
+            System.out.println("Resulting list:");
+            for (int index = 0; index < list.size(); index++)
+            {
+                System.out.println("["+list.get(index)+"]");
+            }
+        }
+        else
+        {
+            List list = Arrays.asList(args);
+
+            System.out.println("Input list:");
+            for (int index = 0; index < list.size(); index++)
+            {
+                System.out.println("["+list.get(index)+"]");
+            }
+
+            String string = commaSeparatedString(list);
+
+            System.out.println("Resulting string: ["+string+"]");
+        }
+    }
+}
diff --git a/src/proguard/util/NameParser.java b/src/proguard/util/NameParser.java
new file mode 100644
index 0000000..e311fbf
--- /dev/null
+++ b/src/proguard/util/NameParser.java
@@ -0,0 +1,106 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringParser can create StringMatcher instances for regular expressions
+ * matching names. The regular expressions are interpreted as comma-separated
+ * lists of names, optionally prefixed with '!' negators.
+ * If a name with a negator matches, a negative match is returned, without
+ * considering any subsequent entries in the list.
+ * Names can contain the following wildcards:
+ * '?'  for a single character, and
+ * '*'  for any number of characters.
+ *
+ * @author Eric Lafortune
+ */
+public class NameParser implements StringParser
+{
+    // Implementations for StringParser.
+
+    public StringMatcher parse(String regularExpression)
+    {
+        int           index;
+        StringMatcher nextMatcher = new EmptyStringMatcher();
+
+        // Look for wildcards.
+        for (index = 0; index < regularExpression.length(); index++)
+        {
+            // Is there a '*' wildcard?
+            if (regularExpression.charAt(index) == '*')
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(null,
+                                              null,
+                                              0,
+                                              Integer.MAX_VALUE,
+                                              parse(regularExpression.substring(index + 1)));
+                break;
+            }
+
+            // Is there a '?' wildcard?
+            else if (regularExpression.charAt(index) == '?')
+            {
+                // Create a matcher for the wildcard and, recursively, for the
+                // remainder of the string.
+                nextMatcher =
+                    new VariableStringMatcher(null,
+                                              null,
+                                              1,
+                                              1,
+                                              parse(regularExpression.substring(index + 1)));
+                break;
+            }
+        }
+
+        // Return a matcher for the fixed first part of the regular expression,
+        // if any, and the remainder.
+        return index != 0 ?
+            (StringMatcher)new FixedStringMatcher(regularExpression.substring(0, index), nextMatcher) :
+            (StringMatcher)nextMatcher;
+    }
+
+
+    /**
+     * A main method for testing name matching.
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            System.out.println("Regular expression ["+args[0]+"]");
+            NameParser parser  = new NameParser();
+            StringMatcher  matcher = parser.parse(args[0]);
+            for (int index = 1; index < args.length; index++)
+            {
+                String string = args[index];
+                System.out.print("String             ["+string+"]");
+                System.out.println(" -> match = "+matcher.matches(args[index]));
+            }
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}
diff --git a/src/proguard/util/NotMatcher.java b/src/proguard/util/NotMatcher.java
new file mode 100644
index 0000000..f2a9a51
--- /dev/null
+++ b/src/proguard/util/NotMatcher.java
@@ -0,0 +1,46 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher tests whether strings does not match the given
+ * StringMatcher.
+ *
+ * @author Eric Lafortune
+ */
+public class NotMatcher implements StringMatcher
+{
+    private final StringMatcher matcher;
+
+
+    public NotMatcher(StringMatcher matcher)
+    {
+        this.matcher = matcher;
+    }
+
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        return !matcher.matches(string);
+    }
+}
diff --git a/src/proguard/util/OrMatcher.java b/src/proguard/util/OrMatcher.java
new file mode 100644
index 0000000..097c6c6
--- /dev/null
+++ b/src/proguard/util/OrMatcher.java
@@ -0,0 +1,49 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher tests whether strings matches either of the given
+ *  StringMatcher instances.
+ *
+ * @author Eric Lafortune
+ */
+public class OrMatcher implements StringMatcher
+{
+    private final StringMatcher matcher1;
+    private final StringMatcher matcher2;
+
+
+    public OrMatcher(StringMatcher matcher1, StringMatcher matcher2)
+    {
+        this.matcher1 = matcher1;
+        this.matcher2 = matcher2;
+    }
+
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        return matcher1.matches(string) ||
+               matcher2.matches(string);
+    }
+}
diff --git a/src/proguard/util/SettableMatcher.java b/src/proguard/util/SettableMatcher.java
new file mode 100644
index 0000000..9557f62
--- /dev/null
+++ b/src/proguard/util/SettableMatcher.java
@@ -0,0 +1,46 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher delegates to a another StringMatcher that can be set
+ * after this StringMatcher has been constructed.
+ *
+ * @author Eric Lafortune
+ */
+public class SettableMatcher implements StringMatcher
+{
+    private StringMatcher matcher;
+
+
+    public void setMatcher(StringMatcher matcher)
+    {
+        this.matcher = matcher;
+    }
+
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        return matcher.matches(string);
+    }
+}
diff --git a/src/proguard/util/StringMatcher.java b/src/proguard/util/StringMatcher.java
new file mode 100644
index 0000000..bd66dcc
--- /dev/null
+++ b/src/proguard/util/StringMatcher.java
@@ -0,0 +1,38 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+
+/**
+ * This interface provides a method to determine whether strings match a given
+ * criterion, which is specified by the implementation.
+ *
+ * @author Eric Lafortune
+ */
+public interface StringMatcher
+{
+    /**
+     * Checks whether the given string matches.
+     * @param string the string to match.
+     * @return a boolean indicating whether the string matches the criterion.
+     */
+    public boolean matches(String string);
+}
diff --git a/src/proguard/util/StringParser.java b/src/proguard/util/StringParser.java
new file mode 100644
index 0000000..29f4f16
--- /dev/null
+++ b/src/proguard/util/StringParser.java
@@ -0,0 +1,35 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This interface provides a method to create a SringMatcher for a given
+ * regular expression.
+ *
+ * @author Eric Lafortune
+ */
+public interface StringParser
+{
+    /**
+     * Creates a StringMatcher for the given regular expression.
+     */
+    public StringMatcher parse(String regularExpression);
+}
diff --git a/src/proguard/util/VariableStringMatcher.java b/src/proguard/util/VariableStringMatcher.java
new file mode 100644
index 0000000..1e41323
--- /dev/null
+++ b/src/proguard/util/VariableStringMatcher.java
@@ -0,0 +1,126 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.util;
+
+/**
+ * This StringMatcher tests whether strings start with a specified variable
+ * string and then match another given StringMatcher.
+ *
+ * @author Eric Lafortune
+ */
+public class VariableStringMatcher implements StringMatcher
+{
+    private final char[]        allowedCharacters;
+    private final char[]        disallowedCharacters;
+    private final int           minimumLength;
+    private final int           maximumLength;
+    private final StringMatcher nextMatcher;
+
+
+    public VariableStringMatcher(char[]        allowedCharacters,
+                                 char[]        disallowedCharacters,
+                                 int           minimumLength,
+                                 int           maximumLength,
+                                 StringMatcher nextMatcher)
+    {
+        this.allowedCharacters    = allowedCharacters;
+        this.disallowedCharacters = disallowedCharacters;
+        this.minimumLength        = minimumLength;
+        this.maximumLength        = maximumLength;
+        this.nextMatcher          = nextMatcher;
+    }
+
+    // Implementations for StringMatcher.
+
+    public boolean matches(String string)
+    {
+        if (string.length() < minimumLength)
+        {
+            return false;
+        }
+
+        // Check the first minimum number of characters.
+        for (int index = 0; index < minimumLength; index++)
+        {
+            if (!isAllowedCharacter(string.charAt(index)))
+            {
+                return false;
+            }
+        }
+
+        int maximumLength = Math.min(this.maximumLength, string.length());
+
+        // Check the remaining characters, up to the maximum number.
+        for (int index = minimumLength; index < maximumLength; index++)
+        {
+            if (nextMatcher.matches(string.substring(index)))
+            {
+                return true;
+            }
+
+            if (!isAllowedCharacter(string.charAt(index)))
+            {
+                return false;
+            }
+        }
+
+        // Check the remaining characters in the string.
+        return nextMatcher.matches(string.substring(maximumLength));
+    }
+
+
+    // Small utility methods.
+
+    /**
+     * Returns whether the given character is allowed in the variable string.
+     */
+    private boolean isAllowedCharacter(char character)
+    {
+        // Check the allowed characters.
+        if (allowedCharacters != null)
+        {
+            for (int index = 0; index < allowedCharacters.length; index++)
+            {
+                if (allowedCharacters[index] == character)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // Check the disallowed characters.
+        if (disallowedCharacters != null)
+        {
+            for (int index = 0; index < disallowedCharacters.length; index++)
+            {
+                if (disallowedCharacters[index] == character)
+                {
+                    return false;
+                }
+            }
+        }
+
+        // Any remaining character is allowed.
+        return true;
+    }
+}
diff --git a/src/proguard/util/package.html b/src/proguard/util/package.html
new file mode 100644
index 0000000..cb14fdc
--- /dev/null
+++ b/src/proguard/util/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains utility classes for regular expression matching,...
+</body>
diff --git a/src/proguard/wtk/ProGuardObfuscator.java b/src/proguard/wtk/ProGuardObfuscator.java
new file mode 100644
index 0000000..4618437
--- /dev/null
+++ b/src/proguard/wtk/ProGuardObfuscator.java
@@ -0,0 +1,141 @@
+/*
+ * ProGuard -- shrinking, optimization, obfuscation, and preverification
+ *             of Java bytecode.
+ *
+ * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program 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 for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package proguard.wtk;
+
+import com.sun.kvem.environment.Obfuscator;
+import proguard.*;
+
+import java.io.*;
+
+
+/**
+ * ProGuard plug-in for the J2ME Wireless Toolkit.
+ * <p>
+ * In order to integrate this plug-in in the toolkit, you'll have to put the
+ * following lines in the file
+ * {j2mewtk.dir}<code>/wtklib/Linux/ktools.properties</code> or
+ * {j2mewtk.dir}<code>\wtklib\Windows\ktools.properties</code> (whichever is
+ * applicable).
+ * <p>
+ * <pre>
+ * obfuscator.runner.class.name: proguard.wtk.ProGuardObfuscator
+ * obfuscator.runner.classpath: /usr/local/java/proguard1.6/lib/proguard.jar
+ * </pre>
+ * Please make sure the class path is set correctly for your system.
+ *
+ * @author Eric Lafortune
+ */
+public class ProGuardObfuscator implements Obfuscator
+{
+    private static final String DEFAULT_CONFIGURATION = "default.pro";
+
+
+    // Implementations for Obfuscator.
+
+    public void createScriptFile(File jadFile,
+                                 File projectDir)
+    {
+        // We don't really need to create a script file;
+        // we'll just fill out all options in the run method.
+    }
+
+
+    public void run(File   obfuscatedJarFile,
+                    String wtkBinDir,
+                    String wtkLibDir,
+                    String jarFileName,
+                    String projectDirName,
+                    String classPath,
+                    String emptyAPI)
+    throws IOException
+    {
+        // Create the ProGuard configuration.
+        Configuration configuration = new Configuration();
+
+        // Parse the default configuration file.
+        ConfigurationParser parser = new ConfigurationParser(this.getClass().getResource(DEFAULT_CONFIGURATION));
+
+        try
+        {
+            parser.parse(configuration);
+
+            // Fill out the library class path.
+            configuration.libraryJars = classPath(classPath);
+
+            // Fill out the program class path (input and output).
+            configuration.programJars = new ClassPath();
+            configuration.programJars.add(new ClassPathEntry(new File(jarFileName), false));
+            configuration.programJars.add(new ClassPathEntry(obfuscatedJarFile, true));
+
+            // The preverify tool seems to unpack the resulting classes,
+            // so we must not use mixed-case class names on Windows.
+            configuration.useMixedCaseClassNames =
+                !System.getProperty("os.name").regionMatches(true, 0, "windows", 0, 7);
+
+            // Run ProGuard with these options.
+            ProGuard proGuard = new ProGuard(configuration);
+            proGuard.execute();
+
+        }
+        catch (ParseException ex)
+        {
+            throw new IOException(ex.getMessage());
+        }
+        finally
+        {
+            parser.close();
+        }
+    }
+
+
+    /**
+     * Converts the given class path String into a ClassPath object.
+     */
+    private ClassPath classPath(String classPathString)
+    {
+        ClassPath classPath = new ClassPath();
+
+        String separator = System.getProperty("path.separator");
+
+        int index = 0;
+        while (index < classPathString.length())
+        {
+            // Find the next separator, or the end of the String.
+            int next_index = classPathString.indexOf(separator, index);
+            if (next_index < 0)
+            {
+                next_index = classPathString.length();
+            }
+
+            // Create and add the found class path entry.
+            ClassPathEntry classPathEntry =
+                new ClassPathEntry(new File(classPathString.substring(index, next_index)),
+                                   false);
+
+            classPath.add(classPathEntry);
+
+            // Continue after the separator.
+            index = next_index + 1;
+        }
+
+        return classPath;
+    }
+}
diff --git a/src/proguard/wtk/default.pro b/src/proguard/wtk/default.pro
new file mode 100644
index 0000000..d31714f
--- /dev/null
+++ b/src/proguard/wtk/default.pro
@@ -0,0 +1,114 @@
+-dontnote
+-microedition
+-mergeinterfacesaggressively
+-overloadaggressively
+-repackageclasses ''
+-allowaccessmodification
+
+# Keep all extensions of javax.microedition.midlet.MIDlet.
+-keep public class * extends javax.microedition.midlet.MIDlet
+
+# Keep all native class/method names.
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+# Remove all invocations of System methods without side effects
+# whose return values are not used.
+-assumenosideeffects public class java.lang.System {
+    public static native long currentTimeMillis();
+    static java.lang.Class getCallerClass();
+    public static native int identityHashCode(java.lang.Object);
+    public static java.lang.SecurityManager getSecurityManager();
+    public static java.util.Properties getProperties();
+    public static java.lang.String getProperty(java.lang.String);
+    public static java.lang.String getenv(java.lang.String);
+    public static native java.lang.String mapLibraryName(java.lang.String);
+    public static java.lang.String getProperty(java.lang.String,java.lang.String);
+}
+
+# Remove all invocations of String methods without side effects
+# whose return values are not used.
+-assumenosideeffects public class java.lang.String {
+    public java.lang.String();
+    public java.lang.String(byte[]);
+    public java.lang.String(byte[],int);
+    public java.lang.String(byte[],int,int);
+    public java.lang.String(byte[],int,int,int);
+    public java.lang.String(byte[],int,int,java.lang.String);
+    public java.lang.String(byte[],java.lang.String);
+    public java.lang.String(char[]);
+    public java.lang.String(char[],int,int);
+    public java.lang.String(java.lang.String);
+    public java.lang.String(java.lang.StringBuffer);
+    public static java.lang.String copyValueOf(char[]);
+    public static java.lang.String copyValueOf(char[],int,int);
+    public static java.lang.String valueOf(boolean);
+    public static java.lang.String valueOf(char);
+    public static java.lang.String valueOf(char[]);
+    public static java.lang.String valueOf(char[],int,int);
+    public static java.lang.String valueOf(double);
+    public static java.lang.String valueOf(float);
+    public static java.lang.String valueOf(int);
+    public static java.lang.String valueOf(java.lang.Object);
+    public static java.lang.String valueOf(long);
+    public boolean contentEquals(java.lang.StringBuffer);
+    public boolean endsWith(java.lang.String);
+    public boolean equalsIgnoreCase(java.lang.String);
+    public boolean equals(java.lang.Object);
+    public boolean matches(java.lang.String);
+    public boolean regionMatches(boolean,int,java.lang.String,int,int);
+    public boolean regionMatches(int,java.lang.String,int,int);
+    public boolean startsWith(java.lang.String);
+    public boolean startsWith(java.lang.String,int);
+    public byte[] getBytes();
+    public byte[] getBytes(java.lang.String);
+    public char charAt(int);
+    public char[] toCharArray();
+    public int compareToIgnoreCase(java.lang.String);
+    public int compareTo(java.lang.Object);
+    public int compareTo(java.lang.String);
+    public int hashCode();
+    public int indexOf(int);
+    public int indexOf(int,int);
+    public int indexOf(java.lang.String);
+    public int indexOf(java.lang.String,int);
+    public int lastIndexOf(int);
+    public int lastIndexOf(int,int);
+    public int lastIndexOf(java.lang.String);
+    public int lastIndexOf(java.lang.String,int);
+    public int length();
+    public java.lang.CharSequence subSequence(int,int);
+    public java.lang.String concat(java.lang.String);
+    public java.lang.String replaceAll(java.lang.String,java.lang.String);
+    public java.lang.String replace(char,char);
+    public java.lang.String replaceFirst(java.lang.String,java.lang.String);
+    public java.lang.String[] split(java.lang.String);
+    public java.lang.String[] split(java.lang.String,int);
+    public java.lang.String substring(int);
+    public java.lang.String substring(int,int);
+    public java.lang.String toLowerCase();
+    public java.lang.String toLowerCase(java.util.Locale);
+    public java.lang.String toString();
+    public java.lang.String toUpperCase();
+    public java.lang.String toUpperCase(java.util.Locale);
+    public java.lang.String trim();
+}
+
+
+# Remove all invocations of StringBuffer methods without side effects
+# whose return values are not used.
+-assumenosideeffects public class java.lang.StringBuffer {
+    public java.lang.StringBuffer();
+    public java.lang.StringBuffer(int);
+    public java.lang.StringBuffer(java.lang.String);
+    public java.lang.String toString();
+    public char charAt(int);
+    public int capacity();
+    public int indexOf(java.lang.String,int);
+    public int lastIndexOf(java.lang.String);
+    public int lastIndexOf(java.lang.String,int);
+    public int length();
+    public java.lang.String substring(int);
+    public java.lang.String substring(int,int);
+}
diff --git a/src/proguard/wtk/package.html b/src/proguard/wtk/package.html
new file mode 100644
index 0000000..6efc644
--- /dev/null
+++ b/src/proguard/wtk/package.html
@@ -0,0 +1,3 @@
+<body>
+This package contains the J2ME Wireless Toolkit plug-in for ProGuard.
+</body>