Vendor jarjar instead of using a binary version of it.

It appears that jarjar is not actually used except for renaming classes
in the coverage collecting Java test runner as verified by

`find . -name BUILD | xargs grep jarjar`

Yak shaving for #2820.

This is necessary because the current version of jarjar does not work
with Java 8 classes because it embeds a version of ASM that doesn't
support that yet.

Change-Id: I6ac59b84bbbc1e85fe8e7f4f4876b98fc6129df0
diff --git a/src/java_tools/junitrunner/java/com/google/testing/coverage/BUILD b/src/java_tools/junitrunner/java/com/google/testing/coverage/BUILD
index d4d7508..1714f62 100644
--- a/src/java_tools/junitrunner/java/com/google/testing/coverage/BUILD
+++ b/src/java_tools/junitrunner/java/com/google/testing/coverage/BUILD
@@ -105,11 +105,11 @@
         "if [[ $$(uname -a) =~ MSYS ]] || [[ $$(uname -a) =~ CYGWIN ]] || [[ $$(uname -a) =~ freebsd ]]; then",
         "  cp \"$(location :JacocoCoverage_deploy.jar)\" \"$@\";",
         "else",
-        "  \"$(JAVA)\" -jar \"$(location //third_party/java/jarjar:jarjar_bin_deploy.jar)\" process \"$(location :JacocoCoverage.jarjar)\" \"$(location :JacocoCoverage_deploy.jar)\" \"$@\"",
+        "  \"$(JAVA)\" -jar \"$(location //third_party/java/jarjar:jarjar_command_deploy.jar)\" --rules \"$(location :JacocoCoverage.jarjar)\" --output \"$@\" \"$(location :JacocoCoverage_deploy.jar)\"",
         "fi",
     ]),
     tools = [
-        "//third_party/java/jarjar:jarjar_bin_deploy.jar",
+        "//third_party/java/jarjar:jarjar_command_deploy.jar",
         "//tools/defaults:jdk",
     ],
 )
diff --git a/third_party/java/jarjar/BUILD b/third_party/java/jarjar/BUILD
index 8f31f16..a13054f 100644
--- a/third_party/java/jarjar/BUILD
+++ b/third_party/java/jarjar/BUILD
@@ -7,13 +7,25 @@
     srcs = glob(["**"]),
 )
 
-java_import(
-    name = "jarjar",
-    jars = ["jarjar-1.4.jar"],
+java_library(
+    name = "jarjar_core",
+    srcs = glob(["jarjar-core/src/main/**/*.java"]),
+    deps = [
+        "//third_party:asm",
+        "//third_party:asm-commons",
+        "//third_party:asm-util",
+        "//third_party:jsr305",
+        "//third_party:slf4j",
+    ],
 )
 
 java_binary(
-    name = "jarjar_bin",
+    name = "jarjar_command",
+    srcs = glob(["jarjar-command/src/main/**/*.java"]),
     main_class = "com.tonicsystems.jarjar.Main",
-    runtime_deps = [":jarjar"],
+    deps = [
+        ":jarjar_core",
+        "//third_party:jsr305",
+        "//third_party/java/jopt-simple",
+    ],
 )
diff --git a/third_party/java/jarjar/COPYING b/third_party/java/jarjar/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/third_party/java/jarjar/COPYING
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/third_party/java/jarjar/README.bazel.md b/third_party/java/jarjar/README.bazel.md
new file mode 100644
index 0000000..8a71e81
--- /dev/null
+++ b/third_party/java/jarjar/README.bazel.md
@@ -0,0 +1,6 @@
+This is jarjar. They don't seem to tag their releases, so I just used whatever was at HEAD. Reproduction:
+
+1. `git clone https://github.com/shevek/jarjar`
+2. `git checkout 69d2972ea10eefa66cdc2c1c283cb6ef79b3c5ba`
+3. Copy the git tree to `third_party/java/jarjar`
+4. Keep the existing `BUILD` file
diff --git a/third_party/java/jarjar/README.md b/third_party/java/jarjar/README.md
new file mode 100644
index 0000000..6d7097c
--- /dev/null
+++ b/third_party/java/jarjar/README.md
@@ -0,0 +1,184 @@
+Jar Jar Links is a utility that makes it easy to repackage Java
+libraries and embed them into your own distribution. This is useful
+for two reasons:
+
+You can easily ship a single jar file with no external dependencies.
+
+You can avoid problems where your library depends on a specific
+version of a library, which may conflict with the dependencies of
+another library.
+
+How does it work?
+
+Jar Jar Links includes an Ant task that extends the built-in jar
+task. The normal zipfileset element is used to embed jar files. A
+new rule element is added which uses wildcards patterns to rename
+the embedded class files. Bytecode transformation (via ASM) is used
+to change references to the renamed classes, and special handling is
+provided for moving resource files and transforming string literals.
+
+Using with ant
+--------------
+
+In our imaginary project, the Ant "jar" target looks like:
+
+```
+<target name="jar" depends="compile">
+    <jar jarfile="dist/example.jar">
+        <fileset dir="build/main"/>
+    </jar>
+</target>
+```
+
+To use Jar Jar Links, we define a new task named "jarjar", and
+substitute it wherever we used the jar task. Because the JarJarTask
+class extends the normal Ant Jar task, you can use jarjar without
+any of its additional features, if you want:
+
+```
+<target name="jar" depends="compile">
+    <taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask"
+        classpath="lib/jarjar.jar"/>
+    <jarjar jarfile="dist/example.jar">
+        <fileset dir="build/main"/>
+    </jarjar>
+</target>
+```
+
+Just like with the "jar" task, we can include the contents of another
+jar file using the "zipfileset" element. But simply including another
+projects classes is not good enough to avoid jar hell, since the class
+names remain unchanged and can still conflict with other versions.
+
+To rename the classes, JarJarTask adds a new "rule" element. The
+rule takes a "pattern" attribute, which uses wildcards to match
+against class names, and a "result" attribute, which describes how
+to transform the matched names.
+
+In this example we include classes from jaxen.jar and add a rule
+that changes any class name starting with "org.jaxen" to start with
+"org.example.jaxen" instead (in our imaginary world we control the
+example.org domain):
+
+```
+<target name="jar" depends="compile">
+    <taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask"
+        classpath="lib/jarjar.jar"/>
+    <jarjar jarfile="dist/example.jar">
+        <fileset dir="build/main"/>
+        <zipfileset src="lib/jaxen.jar"/>
+        <rule pattern="org.jaxen.**" result="org.example.@1"/>
+    </jarjar>
+</target>
+```
+
+The ** in the pattern means to match against any valid package
+substring. To match against a single package component (by excluding
+dots (.) from the match), a single * may be used instead.
+
+The @1 in the result is a reference to the ** portion of the rule. For
+every * or ** in the rule, a numbered reference is available for use
+in the result. References are numbered from left to right, starting
+with @1, then @2, and so on.
+
+The special @0 reference refers to the entire class name.
+
+Using with gradle
+-----------------
+
+```
+	dependencies {
+		// Use jarjar.repackage in place of a dependency notation.
+		compile jarjar.repackage {
+			from 'com.google.guava:guava:18.0'
+
+			classDelete "com.google.common.base.**"
+
+			classRename "com.google.**" "org.private.google.@1"
+		}
+	}
+```
+
+See (jarjar-gradle/example/build.gradle) for some complete examples.
+
+Using from the command line
+---------------------------
+
+From the command-line
+
+```
+java -jar jarjar.jar [help]
+```
+
+Prints this help message.
+
+```
+java -jar jarjar.jar strings <cp>
+```
+
+Dumps all string literals in classpath `<cp>`. Line numbers will be
+included if the classes have debug information.
+
+```
+java -jar jarjar.jar find <level> <cp1> [<cp2>]
+```
+
+Prints dependencies on classpath `<cp2>` in classpath `<cp1>`. If `<cp2>`
+is omitted, `<cp1>` is used for both arguments.
+
+The level argument must be class or jar. The former prints dependencies
+between individual classes, while the latter only prints jar->jar
+dependencies. A "jar" in this context is actually any classpath
+component, which can be a jar file, a zip file, or a parent directory
+(see below).
+
+```
+java -jar jarjar.jar process <rulesFile> <inJar> <outJar>
+```
+
+Transform the `<inJar>` jar file, writing a new jar file to `<outJar>`. Any
+existing file named by `<outJar>` will be deleted.
+
+The transformation is defined by a set of rules in the file specified
+by the rules argument (see below).  Classpath format
+
+The classpath argument is a colon or semi-colon delimited
+set (depending on platform) of directories, jar files,
+or zip files. See the following page for more details:
+http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/classpath.html
+
+Mustang-style wildcards are also supported:
+http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6268383 Rules
+file format
+
+The rules file is a text file, one rule per line. Leading and trailing
+whitespace is ignored. There are three types of rules:
+
+```
+rule <pattern> <result>
+zap <pattern>
+keep <pattern>
+```
+
+The standard rule (rule) is used to rename classes. All references to
+the renamed classes will also be updated. If a class name is matched
+by more than one rule, only the first one will apply.
+
+`<pattern>` is a class name with optional wildcards. `**` will match
+against any valid class name substring. To match a single package
+component (by excluding . from the match), a single `*` may be used
+instead.
+
+`<result>` is a class name which can optionally reference the substrings
+matched by the wildcards. A numbered reference is available for every
+`*` or `**` in the `<pattern>`, starting from left to right: @1, @2, etc. A
+special @0 reference contains the entire matched class name.
+
+The zap rule causes any matched class to be removed from the resulting
+jar file. All zap rules are processed before renaming rules.
+
+The keep rule marks all matched classes as "roots". If any keep rules
+are defined all classes which are not reachable from the roots via
+dependency analysis are discarded when writing the output jar. This
+is the last step in the process, after renaming and zapping.
+
diff --git a/third_party/java/jarjar/build.gradle b/third_party/java/jarjar/build.gradle
new file mode 100644
index 0000000..4789994
--- /dev/null
+++ b/third_party/java/jarjar/build.gradle
@@ -0,0 +1,148 @@
+buildscript {
+	apply from: file('gradle/buildscript.gradle'), to: buildscript
+}
+
+apply plugin: 'org.anarres.stdproject'
+stdproject {
+}
+
+subprojects {
+	apply plugin: 'org.anarres.stdmodule'
+	stdmodule {
+		description "Jar Jar Links is a utility that makes it easy to repackage Java libraries and embed them into your own distribution."
+		author id: 'shevek', name: 'Shevek', email: 'github@anarres.org'
+		license 'Apache-2.0'
+	}
+
+	group = "org.anarres.jarjar"
+
+	dependencies {
+		compile 'com.google.code.findbugs:jsr305:2.0.2'
+		compile 'org.slf4j:slf4j-api:1.7.12'
+	}
+
+	sourceCompatibility = 1.5
+	animalsniffer {
+		signature = "org.codehaus.mojo.signature:java15:+@signature"
+	}
+}
+
+project(':jarjar-testdata') {
+	dependencies {
+		compile 'com.google.guava:guava:17.0'
+	}
+
+	for (int i = 0; i < 4; i++) {
+		String name = "jar$i";
+		Task t = task(name, type: Jar) {
+			classifier = name
+			from(sourceSets.main.output) {
+				include "**/pkg$i/**";
+			}
+		}
+
+		artifacts {
+			archives t
+		}
+	}
+
+/*
+	assemble << {
+		println configurations.archives.dump()
+		println "\nArtifacts:\n" + configurations.archives.artifacts.dump()
+		println "\nFiles:\n" + configurations.archives.files.dump()
+		println "\nEach:\n"
+		for (Object o : configurations.archives.artifacts.files) {
+			println o.dump()
+		}
+		// println configurations.archives
+		println jar0.dump()
+		println jar0.archivePath
+	}
+*/
+
+	uploadArchives.enabled = false
+}
+
+project(':jarjar-core') {
+	dependencies {
+		compile 'org.ow2.asm:asm-commons:5.0.3'
+		compile 'org.ow2.asm:asm-util:5.0.3'
+		// compile 'org.ow2.asm:asm-commons:4.0'
+
+		// testCompile project(':jarjar-testdata')
+	}
+
+	def samplesProject = project(':jarjar-testdata')
+	test.dependsOn samplesProject.tasks['assemble']
+	test {
+		systemProperty "jar", samplesProject.jar.archivePath
+		for (int i = 0; i < 4; i++) {
+			systemProperty "jar$i", samplesProject."jar$i".archivePath
+		}
+	}
+}
+
+project(':jarjar-ant') {
+	dependencies {
+		compile project(':jarjar-core')
+		compile 'org.apache.ant:ant:1.7.0'
+
+		testCompile project(':jarjar-core').sourceSets.test.output
+	}
+}
+
+project(':jarjar-maven') {
+	dependencies {
+		compile project(':jarjar-core')
+		compile 'org.apache.maven:maven-plugin-api:2.0'
+
+		testCompile project(':jarjar-core').sourceSets.test.output
+	}
+}
+
+project(':jarjar-gradle') {
+	apply plugin: 'org.anarres.stdplugin'
+
+	stdplugin {
+		implementation "org.anarres.jarjar", "org.anarres.gradle.plugin.jarjar.JarjarPlugin"
+	}
+
+/*
+	tasks.removeAll {
+		// println it.dump();
+		// println it.name;
+		return it.name == "sourcesJar";
+	}
+
+	// apply plugin: 'java-gradle-plugin'
+	// apply plugin: 'nu.studer.plugindev'
+	// apply from: file('../gradle/plugin.gradle')
+*/
+
+	dependencies {
+		compile project(':jarjar-core')
+		compile 'org.apache.commons:commons-compress:1.9'
+		compile gradleApi()
+		compile 'oro:oro:2.0.8'
+
+		testCompile project(':jarjar-core').sourceSets.test.output
+	}
+
+	animalsniffer {
+		skip = true
+	}
+}
+
+project(':jarjar-command') {
+	dependencies {
+		compile project(':jarjar-core')
+		compile 'net.sf.jopt-simple:jopt-simple:4.8'
+
+		testCompile project(':jarjar-core').sourceSets.test.output
+	}
+
+	apply plugin: 'application'
+
+	mainClassName = "com.tonicsystems.jarjar.Main"
+}
diff --git a/third_party/java/jarjar/codequality/HEADER b/third_party/java/jarjar/codequality/HEADER
new file mode 100644
index 0000000..169c3d1
--- /dev/null
+++ b/third_party/java/jarjar/codequality/HEADER
@@ -0,0 +1,13 @@
+Copyright ${year} Shevek.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/third_party/java/jarjar/codequality/checkstyle.xml b/third_party/java/jarjar/codequality/checkstyle.xml
new file mode 100644
index 0000000..47c01a2
--- /dev/null
+++ b/third_party/java/jarjar/codequality/checkstyle.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+
+<module name="Checker">
+
+    <!-- Checks that a package-info.java file exists for each package.     -->
+    <!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->
+    <!--
+    <module name="JavadocPackage">
+      <property name="allowLegacy" value="true"/>
+    </module>
+    -->
+
+    <!-- Checks whether files end with a new line.                        -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
+    <module name="NewlineAtEndOfFile"/>
+
+    <!-- Checks that property files contain the same keys.         -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
+    <module name="Translation"/>
+
+    <!-- Checks for Size Violations.                    -->
+    <!-- See http://checkstyle.sf.net/config_sizes.html -->
+    <module name="FileLength"/>
+
+    <!-- Checks for whitespace                               -->
+    <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+    <module name="FileTabCharacter"/>
+
+    <!-- Miscellaneous other checks.                   -->
+    <!-- See http://checkstyle.sf.net/config_misc.html -->
+    <module name="RegexpSingleline">
+       <property name="format" value="\s+$"/>
+       <property name="minimum" value="0"/>
+       <property name="maximum" value="0"/>
+       <property name="message" value="Line has trailing spaces."/>
+       <property name="severity" value="info"/>
+    </module>
+
+    <module name="TreeWalker">
+
+        <!-- Checks for Javadoc comments.                     -->
+        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+        <module name="JavadocMethod">
+          <property name="scope" value="package"/>
+          <property name="allowMissingParamTags" value="true"/>
+          <property name="allowMissingThrowsTags" value="true"/>
+          <property name="allowMissingReturnTag" value="true"/>
+          <property name="allowThrowsTagsForSubclasses" value="true"/>
+          <property name="allowUndeclaredRTE" value="true"/>
+          <property name="allowMissingPropertyJavadoc" value="true"/>
+        </module>
+        <module name="JavadocType">
+          <property name="scope" value="package"/>
+        </module>
+        <module name="JavadocVariable">
+          <property name="scope" value="package"/>
+        </module>
+        <module name="JavadocStyle">
+          <property name="checkEmptyJavadoc" value="true"/>
+        </module>
+
+        <!-- Checks for Naming Conventions.                  -->
+        <!-- See http://checkstyle.sf.net/config_naming.html -->
+        <module name="ConstantName"/>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName"/>
+        <module name="MethodName"/>
+        <module name="PackageName"/>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName"/>
+        <module name="TypeName"/>
+
+        <!-- Checks for imports                              -->
+        <!-- See http://checkstyle.sf.net/config_import.html -->
+        <module name="AvoidStarImport"/>
+        <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
+        <module name="RedundantImport"/>
+        <module name="UnusedImports"/>
+
+
+        <!-- Checks for Size Violations.                    -->
+        <!-- See http://checkstyle.sf.net/config_sizes.html -->
+        <module name="LineLength">
+          <!-- what is a good max value? -->
+          <property name="max" value="120"/>
+          <!-- ignore lines like "$File: //depot/... $" -->
+          <property name="ignorePattern" value="\$File.*\$"/>
+          <property name="severity" value="info"/>
+        </module>
+        <module name="MethodLength"/>
+        <module name="ParameterNumber"/>
+
+
+        <!-- Checks for whitespace                               -->
+        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+        <module name="EmptyForIteratorPad"/>
+        <module name="GenericWhitespace"/>
+        <module name="MethodParamPad"/>
+        <module name="NoWhitespaceAfter"/>
+        <module name="NoWhitespaceBefore"/>
+        <module name="OperatorWrap"/>
+        <module name="ParenPad"/>
+        <module name="TypecastParenPad"/>
+        <module name="WhitespaceAfter"/>
+        <module name="WhitespaceAround"/>
+
+        <!-- Modifier Checks                                    -->
+        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+        <module name="ModifierOrder"/>
+        <module name="RedundantModifier"/>
+
+
+        <!-- Checks for blocks. You know, those {}'s         -->
+        <!-- See http://checkstyle.sf.net/config_blocks.html -->
+        <module name="AvoidNestedBlocks"/>
+        <module name="EmptyBlock">
+          <property name="option" value="text"/>
+        </module>
+        <module name="LeftCurly"/>
+        <module name="NeedBraces"/>
+        <module name="RightCurly"/>
+
+
+        <!-- Checks for common coding problems               -->
+        <!-- See http://checkstyle.sf.net/config_coding.html -->
+        <!-- <module name="AvoidInlineConditionals"/> -->
+        <module name="EmptyStatement"/>
+        <module name="EqualsHashCode"/>
+        <module name="HiddenField">
+          <property name="ignoreConstructorParameter" value="true"/>
+          <property name="ignoreSetter" value="true"/>
+          <property name="severity" value="warning"/>
+        </module>
+        <module name="IllegalInstantiation"/>
+        <module name="InnerAssignment"/>
+        <module name="MagicNumber">
+          <property name="severity" value="warning"/>
+        </module>
+        <module name="MissingSwitchDefault"/>
+        <!-- Problem with finding exception types... -->
+        <module name="RedundantThrows">
+          <property name="allowUnchecked" value="true"/>
+          <property name="suppressLoadErrors" value="true"/>
+          <property name="severity" value="info"/>
+        </module>
+        <module name="SimplifyBooleanExpression"/>
+        <module name="SimplifyBooleanReturn"/>
+
+        <!-- Checks for class design                         -->
+        <!-- See http://checkstyle.sf.net/config_design.html -->
+        <!-- <module name="DesignForExtension"/> -->
+        <module name="FinalClass"/>
+        <module name="HideUtilityClassConstructor"/>
+        <module name="InterfaceIsType"/>
+        <module name="VisibilityModifier"/>
+
+
+        <!-- Miscellaneous other checks.                   -->
+        <!-- See http://checkstyle.sf.net/config_misc.html -->
+        <module name="ArrayTypeStyle"/>
+        <!-- <module name="FinalParameters"/> -->
+        <module name="TodoComment">
+          <property name="format" value="TODO"/>
+          <property name="severity" value="info"/>
+        </module>
+        <module name="UpperEll"/>
+
+        <module name="FileContentsHolder"/> <!-- Required by comment suppression filters -->
+
+    </module>
+
+    <!-- Enable suppression comments -->
+    <module name="SuppressionCommentFilter">
+      <property name="offCommentFormat" value="CHECKSTYLE IGNORE\s+(\S+)"/>
+      <property name="onCommentFormat" value="CHECKSTYLE END IGNORE\s+(\S+)"/>
+      <property name="checkFormat" value="$1"/>
+    </module>
+    <module name="SuppressWithNearbyCommentFilter">
+      <!-- Syntax is "SUPPRESS CHECKSTYLE name" -->
+      <property name="commentFormat" value="SUPPRESS CHECKSTYLE (\w+)"/>
+      <property name="checkFormat" value="$1"/>
+      <property name="influenceFormat" value="1"/>
+    </module>
+</module>
diff --git a/third_party/java/jarjar/gradle.properties b/third_party/java/jarjar/gradle.properties
new file mode 100644
index 0000000..aef125e
--- /dev/null
+++ b/third_party/java/jarjar/gradle.properties
@@ -0,0 +1 @@
+version=1.0.0
diff --git a/third_party/java/jarjar/gradle/buildscript.gradle b/third_party/java/jarjar/gradle/buildscript.gradle
new file mode 100644
index 0000000..c808309
--- /dev/null
+++ b/third_party/java/jarjar/gradle/buildscript.gradle
@@ -0,0 +1,11 @@
+// Executed in context of buildscript
+repositories {
+	mavenLocal()
+	mavenCentral()
+	jcenter()
+	// maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
+}
+
+dependencies {
+	classpath 'org.anarres.gradle:gradle-stdproject-plugin:1.0.2'
+}
diff --git a/third_party/java/jarjar/gradle/plugin.gradle b/third_party/java/jarjar/gradle/plugin.gradle
new file mode 100644
index 0000000..666d077
--- /dev/null
+++ b/third_party/java/jarjar/gradle/plugin.gradle
@@ -0,0 +1,20 @@
+plugindev {
+	pluginImplementationClass 'org.anarres.gradle.plugin.jarjar.JarjarPlugin'
+	pluginDescription "Jarjar Links makes it easy to repackage Java libraries."
+	pluginLicenses 'Apache-2.0'
+	pluginTags 'gradle', 'plugin', 'jarjar', 'links', 'asm'
+	authorId 'shevek'
+	authorName 'Shevek'
+	authorEmail 'github@anarres.org'
+	projectUrl "https://github.com/shevek/${rootProject.githubProjectName}"
+	projectIssuesUrl "https://github.com/shevek/${rootProject.githubProjectName}/issues"
+	projectVcsUrl "https://github.com/shevek/${rootProject.githubProjectName}.git"
+	projectInceptionYear '2014'
+	done()
+}
+
+bintray {
+	user = rootProject.hasProperty('bintrayUsername')?rootProject.bintrayUsername:''
+	key = rootProject.hasProperty('bintrayApiKey')?rootProject.bintrayApiKey:''
+	pkg.repo = 'gradle-plugins'
+}
diff --git a/third_party/java/jarjar/gradle/wrapper/gradle-wrapper.jar b/third_party/java/jarjar/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..c97a8bd
--- /dev/null
+++ b/third_party/java/jarjar/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/third_party/java/jarjar/gradle/wrapper/gradle-wrapper.properties b/third_party/java/jarjar/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ca384f3
--- /dev/null
+++ b/third_party/java/jarjar/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Mar 29 22:39:26 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip
diff --git a/third_party/java/jarjar/gradlew b/third_party/java/jarjar/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/third_party/java/jarjar/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/third_party/java/jarjar/gradlew.bat b/third_party/java/jarjar/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/third_party/java/jarjar/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/third_party/java/jarjar/jarjar-ant/build.gradle b/third_party/java/jarjar/jarjar-ant/build.gradle
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-ant/build.gradle
diff --git a/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/JarJarTask.java b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/JarJarTask.java
new file mode 100644
index 0000000..2f54ad0
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/JarJarTask.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar;
+
+import com.tonicsystems.jarjar.transform.config.ClassDelete;
+import com.tonicsystems.jarjar.transform.config.ClassKeepTransitive;
+import com.tonicsystems.jarjar.transform.config.ClassRename;
+import com.tonicsystems.jarjar.transform.jar.DefaultJarProcessor;
+import com.tonicsystems.jarjar.util.AntJarProcessor;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import org.apache.tools.ant.BuildException;
+
+public class JarJarTask extends AntJarProcessor {
+
+    private DefaultJarProcessor processor = new DefaultJarProcessor();
+
+    @Nonnull
+    private static String checkNotNull(@CheckForNull String in, @Nonnull String msg) {
+        if (in == null)
+            throw new IllegalArgumentException(msg);
+        return in;
+    }
+
+    public void addConfiguredRule(Rule rule) {
+        processor.addClassRename(new ClassRename(
+                checkNotNull(rule.getPattern(), "The <rule> element requires the \"pattern\" attribute."),
+                checkNotNull(rule.getResult(), "The <rule> element requires the \"result\" attribute.")
+        ));
+    }
+
+    public void addConfiguredZap(Zap zap) {
+        processor.addClassDelete(new ClassDelete(
+                checkNotNull(zap.getPattern(), "The <zap> element requires a \"pattern\" attribute.")
+        ));
+    }
+
+    public void addConfiguredKeep(Keep keep) {
+        processor.addClassKeepTransitive(new ClassKeepTransitive(
+                checkNotNull(keep.getPattern(), "The <keep> element requires a \"pattern\" attribute.")
+        ));
+    }
+
+    @Override
+    public void execute() throws BuildException {
+        execute(processor);
+    }
+
+    @Override
+    protected void cleanHelper() {
+        super.cleanHelper();
+        processor = new DefaultJarProcessor();
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Keep.java b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Keep.java
new file mode 100644
index 0000000..70f407e
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Keep.java
@@ -0,0 +1,13 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar;
+
+/**
+ *
+ * @author shevek
+ */
+public class Keep extends PatternElement {
+}
diff --git a/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/PatternElement.java b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/PatternElement.java
new file mode 100644
index 0000000..de6722f
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/PatternElement.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar;
+
+import javax.annotation.Nonnull;
+
+/**
+ * This object and its subclasses are also exposed to ant, so need setters for XML.
+ *
+ * @author shevek
+ */
+public abstract class PatternElement {
+
+    private String pattern;
+
+    @Nonnull
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(@Nonnull String pattern) {
+        this.pattern = pattern;
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Rule.java b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Rule.java
new file mode 100644
index 0000000..886d431
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Rule.java
@@ -0,0 +1,23 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar;
+
+/**
+ *
+ * @author shevek
+ */
+public class Rule extends PatternElement {
+
+    private String result;
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+    public String getResult() {
+        return result;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Zap.java b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Zap.java
new file mode 100644
index 0000000..3628857
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/Zap.java
@@ -0,0 +1,13 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar;
+
+/**
+ *
+ * @author shevek
+ */
+public class Zap extends PatternElement {
+}
diff --git a/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/util/AntJarProcessor.java b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/util/AntJarProcessor.java
new file mode 100644
index 0000000..afa229f
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-ant/src/main/java/com/tonicsystems/jarjar/util/AntJarProcessor.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.util;
+
+import com.tonicsystems.jarjar.transform.Transformable;
+import com.tonicsystems.jarjar.transform.jar.JarProcessor;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.taskdefs.Jar;
+import org.apache.tools.ant.types.ZipFileSet;
+import org.apache.tools.zip.JarMarker;
+import org.apache.tools.zip.ZipExtraField;
+import org.apache.tools.zip.ZipOutputStream;
+
+public abstract class AntJarProcessor extends Jar {
+
+    private final Transformable struct = new Transformable();
+    private JarProcessor proc;
+    private final byte[] buf = new byte[0x2000];
+
+    private final Set<String> dirs = new HashSet<String>();
+    private boolean filesOnly;
+
+    protected boolean verbose;
+
+    private static final ZipExtraField[] JAR_MARKER = new ZipExtraField[]{
+        JarMarker.getInstance()
+    };
+
+    public void setVerbose(boolean verbose) {
+        this.verbose = verbose;
+    }
+
+    @Override
+    public abstract void execute() throws BuildException;
+
+    public void execute(JarProcessor proc) throws BuildException {
+        this.proc = proc;
+        super.execute();
+    }
+
+    @Override
+    public void setFilesonly(boolean f) {
+        super.setFilesonly(f);
+        filesOnly = f;
+    }
+
+    @Override
+    protected void zipDir(File dir, ZipOutputStream zOut, String vPath, int mode)
+            throws IOException {
+    }
+
+    // TODO: Rewrite this entirely.
+    @Override
+    protected void zipFile(InputStream is, ZipOutputStream zOut, String vPath,
+            long lastModified, File fromArchive, int mode) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        IoUtil.copy(is, baos, buf);
+        struct.data = baos.toByteArray();
+        struct.name = vPath;
+        struct.time = lastModified;
+        if (proc.process(struct) != JarProcessor.Result.DISCARD) {
+            if (mode == 0)
+                mode = ZipFileSet.DEFAULT_FILE_MODE;
+            if (!filesOnly) {
+                addParentDirs(struct.name, zOut);
+            }
+            super.zipFile(new ByteArrayInputStream(struct.data),
+                    zOut, struct.name, struct.time, fromArchive, mode);
+        }
+    }
+
+    private void addParentDirs(String file, ZipOutputStream zOut) throws IOException {
+        int slash = file.lastIndexOf('/');
+        if (slash >= 0) {
+            String dir = file.substring(0, slash);
+            if (dirs.add(dir)) {
+                addParentDirs(dir, zOut);
+                super.zipDir((File) null, zOut, dir + "/", ZipFileSet.DEFAULT_DIR_MODE, JAR_MARKER);
+            }
+        }
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        cleanHelper();
+    }
+
+    @Override
+    protected void cleanUp() {
+        super.cleanUp();
+        cleanHelper();
+    }
+
+    protected void cleanHelper() {
+        verbose = false;
+        filesOnly = false;
+        dirs.clear();
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-command/build.gradle b/third_party/java/jarjar/jarjar-command/build.gradle
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-command/build.gradle
diff --git a/third_party/java/jarjar/jarjar-command/src/main/java/com/tonicsystems/jarjar/Main.java b/third_party/java/jarjar/jarjar-command/src/main/java/com/tonicsystems/jarjar/Main.java
new file mode 100644
index 0000000..985af60
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-command/src/main/java/com/tonicsystems/jarjar/Main.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar;
+
+import com.tonicsystems.jarjar.classpath.ClassPath;
+import com.tonicsystems.jarjar.transform.jar.DefaultJarProcessor;
+import com.tonicsystems.jarjar.transform.config.RulesFileParser;
+import com.tonicsystems.jarjar.dependencies.TextDependencyHandler;
+import com.tonicsystems.jarjar.dependencies.DependencyFinder;
+import com.tonicsystems.jarjar.dependencies.DependencyHandler;
+import com.tonicsystems.jarjar.strings.StringDumper;
+import com.tonicsystems.jarjar.transform.config.AbstractPattern;
+import com.tonicsystems.jarjar.transform.JarTransformer;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
+
+public class Main {
+
+    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+    private static final String PATH_SEPARATOR = System.getProperty("path.separator");
+
+    public static enum Mode {
+
+        strings, find, process;
+    }
+
+    private final OptionParser parser = new OptionParser();
+    private final OptionSpec<Void> helpOption = parser.accepts("help")
+            .forHelp();
+    private final OptionSpec<Mode> modeOption = parser.accepts("mode")
+            .withRequiredArg().ofType(Mode.class).defaultsTo(Mode.process).describedAs("Mode to run (strings, find, process)");
+    private final OptionSpec<DependencyHandler.Level> levelOption = parser.accepts("level")
+            .withRequiredArg().ofType(DependencyHandler.Level.class).defaultsTo(DependencyHandler.Level.CLASS).describedAs("Level for DepHandler.");
+    private final OptionSpec<File> fromFilesOption = parser.accepts("from")
+            .withRequiredArg().ofType(File.class).withValuesSeparatedBy(PATH_SEPARATOR).describedAs("Classpath for strings, find.");
+    private final OptionSpec<File> rulesOption = parser.accepts("rules")
+            .withRequiredArg().ofType(File.class).describedAs("Rules file.");
+    private final OptionSpec<File> outputOption = parser.accepts("output")
+            .withRequiredArg().ofType(File.class).describedAs("Output JAR file.");
+    private final OptionSpec<File> filesOption = parser.nonOptions()
+            .ofType(File.class).describedAs("JAR files or directories to process.");
+
+    public void run(@Nonnull String[] args) throws Exception {
+        OptionSet options = parser.parse(args);
+        if (options.has(helpOption)) {
+            parser.printHelpOn(System.err);
+            System.exit(1);
+        }
+
+        Mode mode = options.valueOf(modeOption);
+        switch (mode) {
+            case find:
+                find(options);
+                break;
+            case process:
+                process(options);
+                break;
+            case strings:
+                strings(options);
+                break;
+            default:
+                throw new IllegalArgumentException("Illegal mode " + mode);
+        }
+    }
+
+    private static boolean isEmpty(@CheckForNull List<?> values) {
+        if (values == null)
+            return true;
+        return values.isEmpty();
+    }
+
+    @Nonnull
+    private <T> T valueOf(@Nonnull OptionSet options, @Nonnull OptionSpec<T> option) {
+        T value = options.valueOf(option);
+        if (value == null)
+            throw new IllegalArgumentException(option + " is required.");
+        return value;
+    }
+
+    @Nonnull
+    private <T> List<T> valuesOf(@Nonnull OptionSet options, @Nonnull OptionSpec<T> option) {
+        List<T> values = options.valuesOf(option);
+        if (isEmpty(values))
+            throw new IllegalArgumentException(option + " is required.");
+        return values;
+    }
+
+    private static ClassPath newClassPath(Iterable<? extends File> files) {
+        return new ClassPath(new File(System.getProperty("user.dir")), files);
+    }
+
+    public void strings(@Nonnull OptionSet options) throws IOException {
+        List<File> files = options.valuesOf(filesOption);
+        File parent = new File(System.getProperty("user.dir"));
+        new StringDumper().run(System.out, newClassPath(files));
+        System.out.flush();
+    }
+
+    public void find(@Nonnull OptionSet options) throws IOException {
+        List<File> toFiles = valuesOf(options, filesOption);
+        List<File> fromFiles = options.valuesOf(fromFilesOption);
+        if (isEmpty(fromFiles))
+            fromFiles = toFiles;
+        DependencyHandler.Level level = valueOf(options, levelOption);
+        DependencyHandler handler = new TextDependencyHandler(System.out, level);
+        new DependencyFinder().run(handler, newClassPath(fromFiles), newClassPath(toFiles));
+        System.out.flush();
+    }
+
+    public void process(@Nonnull OptionSet options) throws IOException {
+        File outputFile = valueOf(options, outputOption);
+        File rulesFile = valueOf(options, rulesOption);
+        List<File> files = valuesOf(options, filesOption);
+
+        DefaultJarProcessor processor = new DefaultJarProcessor();
+        RulesFileParser.parse(processor, rulesFile);
+        processor.setSkipManifest(Boolean.getBoolean("skipManifest"));
+
+        JarTransformer transformer = new JarTransformer(outputFile, processor);
+        transformer.transform(newClassPath(files));
+    }
+
+    public static void main(String[] args) throws Exception {
+        Main main = new Main();
+        main.run(args);
+        // MainUtil.runMain(new Main(), args, "help");
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-command/src/test/resources/com/tonicsystems/jarjar/help.txt b/third_party/java/jarjar/jarjar-command/src/test/resources/com/tonicsystems/jarjar/help.txt
new file mode 100644
index 0000000..8410909
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-command/src/test/resources/com/tonicsystems/jarjar/help.txt
@@ -0,0 +1,75 @@
+Jar Jar Links - A utility to repackage and embed Java libraries
+Copyright 2007 Google Inc.
+
+Command-line usage:
+
+  java -jar jarjar.jar [help]
+
+    Prints this help message.
+
+  java -jar jarjar.jar strings <cp>
+
+    Dumps all string literals in classpath <cp>. Line numbers will be
+    included if the classes have debug information.
+
+  java -jar jarjar.jar find <level> <cp1> [<cp2>]
+
+    Prints dependencies on classpath <cp2> in classpath <cp1>. If <cp2>
+    is omitted, <cp1> is used for both arguments.
+
+    The level argument must be "class" or "jar". The former prints
+    dependencies between individual classes, while the latter only
+    prints jar->jar dependencies. A "jar" in this context is actually
+    any classpath component, which can be a jar file, a zip file, or a
+    parent directory (see below).
+
+  java -jar jarjar.jar process <rulesFile> <inJar> <outJar>
+
+    Transform the <inJar> jar file, writing a new jar file to <outJar>.
+    Any existing file named by <outJar> will be deleted.
+
+    The transformation is defined by a set of rules in the file specified
+    by the rules argument (see below).
+
+Classpath format:
+
+  The classpath argument is a colon or semi-colon delimited set
+  (depending on platform) of directories, jar files, or zip files. See
+  the following page for more details:
+  http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/classpath.html
+  
+  Mustang-style wildcards are also supported:
+  http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6268383
+
+Rules file format:
+
+  The rules file is a text file, one rule per line. Leading and trailing
+  whitespace is ignored. There are three types of rules:
+
+    rule <pattern> <result>
+    zap <pattern>
+    keep <pattern>
+
+  The standard rule ("rule") is used to rename classes. All references
+  to the renamed classes will also be updated. If a class name is
+  matched by more than one rule, only the first one will apply.
+
+  <pattern> is a class name with optional wildcards. "**" will
+  match against any valid class name substring. To match a single
+  package component (by excluding "." from the match), a single "*" may
+  be used instead.
+
+  <result> is a class name which can optionally reference the
+  substrings matched by the wildcards. A numbered reference is available
+  for every "*" or "**" in the <pattern>, starting from left to
+  right: "@1", "@2", etc. A special "@0" reference contains the entire
+  matched class name.
+
+  The "zap" rule causes any matched class to be removed from the resulting
+  jar file. All zap rules are processed before renaming rules.
+
+  The "keep" rule marks all matched classes as "roots". If any keep
+  rules are defined all classes which are not reachable from the roots
+  via dependency analysis are discarded when writing the output
+  jar. This is the last step in the process, after renaming and zapping.
+
diff --git a/third_party/java/jarjar/jarjar-core/build.gradle b/third_party/java/jarjar/jarjar-core/build.gradle
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/build.gradle
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPath.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPath.java
new file mode 100644
index 0000000..c12929b
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPath.java
@@ -0,0 +1,71 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.classpath;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Iterator;
+import javax.annotation.Nonnull;
+
+/**
+ *
+ * @author shevek
+ */
+public class ClassPath implements Iterable<ClassPathArchive> {
+
+    private final File root;
+    private final Iterable<? extends File> entries;
+
+    public ClassPath(@Nonnull File root, @Nonnull Iterable<? extends File> entries) {
+        this.root = root;
+        this.entries = entries;
+    }
+
+    public ClassPath(@Nonnull File root, @Nonnull File[] entries) {
+        this(root, Arrays.asList(entries));
+    }
+
+    @Nonnull
+    public File getRoot() {
+        return root;
+    }
+
+    @Override
+    public Iterator<ClassPathArchive> iterator() {
+        return new PathIterator();
+    }
+
+    private class PathIterator implements Iterator<ClassPathArchive> {
+
+        private final Iterator<? extends File> entryIterator;
+
+        public PathIterator() {
+            this.entryIterator = entries.iterator();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return entryIterator.hasNext();
+        }
+
+        @Override
+        public ClassPathArchive next() {
+            File entryFile = entryIterator.next();
+            if (!entryFile.isAbsolute())
+                entryFile = new File(root, entryFile.getPath());
+            if (entryFile.isDirectory())
+                return new ClassPathArchive.DirectoryArchive(entryFile);
+            else
+                return new ClassPathArchive.ZipArchive(entryFile);
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPathArchive.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPathArchive.java
new file mode 100644
index 0000000..9c3a4bf
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPathArchive.java
@@ -0,0 +1,200 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.classpath;
+
+import com.tonicsystems.jarjar.util.ClassNameUtils;
+import com.tonicsystems.jarjar.util.RuntimeIOException;
+import java.io.BufferedInputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import javax.annotation.Nonnull;
+
+/**
+ *
+ * @author shevek
+ */
+public abstract class ClassPathArchive implements Iterable<ClassPathResource> {
+
+    protected final File root;
+
+    public ClassPathArchive(@Nonnull File root) {
+        this.root = root;
+    }
+
+    @Nonnull
+    public String getArchiveName() {
+        return root.getPath();
+    }
+
+    public static class ZipArchive extends ClassPathArchive {
+
+        public ZipArchive(@Nonnull File root) {
+            super(root);
+        }
+
+        @Override
+        public Iterator<ClassPathResource> iterator() {
+            try {
+                return new ZipIterator(root);
+            } catch (IOException e) {
+                throw new RuntimeIOException(e);
+            }
+        }
+
+    }
+
+    private static class ZipIterator implements Iterator<ClassPathResource>, Closeable {
+
+        private final ZipFile zipFile;
+        private Enumeration<? extends ZipEntry> zipEntries;
+
+        ZipIterator(@Nonnull File file) throws IOException {
+            this.zipFile = new ZipFile(file);
+            this.zipEntries = zipFile.entries();
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (!zipEntries.hasMoreElements()) {
+                // close();
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        @Override
+        public ClassPathResource next() {
+            final ZipEntry entry = zipEntries.nextElement();
+            if (entry == null)
+                throw new NoSuchElementException();
+            return new ClassPathResource() {
+                @Override
+                public String getArchiveName() {
+                    return zipFile.getName();
+                }
+
+                @Override
+                public String getName() {
+                    return entry.getName();
+                }
+
+                @Override
+                public long getLastModifiedTime() {
+                    return entry.getTime();
+                }
+
+                @Override
+                public InputStream openStream() throws IOException {
+                    return zipFile.getInputStream(entry);
+                }
+            };
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void close() throws IOException {
+            zipFile.close();
+        }
+    }
+
+    public static class DirectoryArchive extends ClassPathArchive {
+
+        public DirectoryArchive(@Nonnull File root) {
+            super(root);
+        }
+
+        @Override
+        public Iterator<ClassPathResource> iterator() {
+            return new DirectoryIterator(root);
+        }
+
+    }
+
+    private static class DirectoryIterator implements Iterator<ClassPathResource> {
+
+        @Nonnull
+        private static void findClassFiles(@Nonnull Collection<? super File> out, @Nonnull File dir) {
+            for (File file : dir.listFiles()) {
+                if (file.isDirectory()) {
+                    findClassFiles(out, file);
+                } else if (file.isFile()) {
+                    if (ClassNameUtils.isClass(file.getName())) {
+                        out.add(file);
+                    }
+                }
+            }
+        }
+
+        private final File root;
+        private final Iterator<File> entries;
+
+        DirectoryIterator(@Nonnull File root) {
+            this.root = root;
+            List<File> files = new ArrayList<File>();
+            findClassFiles(files, root);
+            this.entries = files.iterator();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return entries.hasNext();
+        }
+
+        @Override
+        public ClassPathResource next() {
+            final File file = entries.next();
+            return new ClassPathResource() {
+                @Override
+                public String getArchiveName() {
+                    return root.getPath();
+                }
+
+                @Override
+                public String getName() {
+                    return file.getName();
+                }
+
+                @Override
+                public long getLastModifiedTime() {
+                    return file.lastModified();
+                }
+
+                @Override
+                public InputStream openStream() throws IOException {
+                    return new BufferedInputStream(new FileInputStream(file));
+                }
+            };
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "(" + root + ")";
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPathResource.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPathResource.java
new file mode 100644
index 0000000..587bfda
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/classpath/ClassPathResource.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.classpath;
+
+import java.io.InputStream;
+import java.io.IOException;
+import javax.annotation.Nonnull;
+
+public abstract class ClassPathResource {
+
+    @Nonnull
+    public abstract String getArchiveName();
+
+    @Nonnull
+    public abstract String getName();
+
+    public abstract long getLastModifiedTime();
+
+    @Nonnull
+    public abstract InputStream openStream() throws IOException;
+
+    @Override
+    public String toString() {
+        return getArchiveName() + "!" + getName();
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/AbstractDependencyHandler.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/AbstractDependencyHandler.java
new file mode 100644
index 0000000..17e38d1
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/AbstractDependencyHandler.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.dependencies;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.Nonnull;
+
+public abstract class AbstractDependencyHandler implements DependencyHandler {
+
+    protected final Level level;
+    private final Set<Pair<String>> seen = new HashSet<Pair<String>>();
+
+    protected AbstractDependencyHandler(Level level) {
+        this.level = level;
+    }
+
+    @Override
+    public void handle(Dependency from, Dependency to) throws IOException {
+        Pair<String> pair;
+        if (level == Level.JAR) {
+            pair = new Pair<String>(from.getClassPath(), to.getClassPath());
+        } else {
+            pair = new Pair<String>(from.getClassName(), to.getClassName());
+        }
+        if (seen.add(pair))
+            handle(pair.getLeft(), pair.getRight());
+    }
+
+    protected abstract void handle(@Nonnull String from, @Nonnull String to) throws IOException;
+
+    @Override
+    public void handleStart() throws IOException {
+    }
+
+    @Override
+    public void handleEnd() throws IOException {
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/ClassHeaderReader.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/ClassHeaderReader.java
new file mode 100644
index 0000000..25a8a74
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/ClassHeaderReader.java
@@ -0,0 +1,188 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.dependencies;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Array;
+
+/* pp */ class ClassHeaderReader {
+
+    private int access;
+    private String thisClass;
+    private String superClass;
+    private String[] interfaces;
+
+    private InputStream in;
+    private byte[] b = new byte[0x2000];
+    private int[] items = new int[1000];
+    private int bsize = 0;
+    private MyByteArrayInputStream bin = new MyByteArrayInputStream();
+    private DataInputStream data = new DataInputStream(bin);
+
+    public int getAccess() {
+        return access;
+    }
+
+    public String getClassName() {
+        return thisClass;
+    }
+
+    public String getSuperName() {
+        return superClass;
+    }
+
+    public String[] getInterfaces() {
+        return interfaces;
+    }
+
+    public void read(InputStream in) throws IOException {
+        try {
+            this.in = in;
+            bsize = 0;
+            access = 0;
+            thisClass = superClass = null;
+            interfaces = null;
+
+            try {
+                buffer(4);
+            } catch (IOException e) {
+                // ignore
+            }
+            if (b[0] != (byte) 0xCA || b[1] != (byte) 0xFE || b[2] != (byte) 0xBA || b[3] != (byte) 0xBE)
+                throw new ClassFormatError("Bad magic number");
+
+            buffer(6);
+            readUnsignedShort(4); // minorVersion
+            readUnsignedShort(6); // majorVersion
+            // TODO: check version
+            int constant_pool_count = readUnsignedShort(8);
+            items = (int[]) resizeArray(items, constant_pool_count);
+
+            int index = 10;
+            for (int i = 1; i < constant_pool_count; i++) {
+                int size;
+                buffer(index + 3); // TODO: reduce calls to buffer
+                int tag = b[index];
+                items[i] = index + 1;
+                switch (tag) {
+                    case 9:  // Fieldref
+                    case 10: // Methodref
+                    case 11: // InterfaceMethodref
+                    case 3:  // Integer
+                    case 4:  // Float
+                    case 12: // NameAndType
+                        size = 4;
+                        break;
+                    case 5:  // Long
+                    case 6:  // Double
+                        size = 8;
+                        i++;
+                        break;
+                    case 1:  // Utf8
+                        size = 2 + readUnsignedShort(index + 1);
+                        break;
+                    case 7:  // Class
+                    case 8:  // String
+                        size = 2;
+                        break;
+                    default:
+                        throw new IllegalStateException("Unknown constant pool tag " + tag);
+                }
+                index += size + 1;
+            }
+            buffer(index + 8);
+            access = readUnsignedShort(index);
+            thisClass = readClass(index + 2);
+            superClass = readClass(index + 4);
+            int interfaces_count = readUnsignedShort(index + 6);
+
+            index += 8;
+            buffer(index + interfaces_count * 2);
+            interfaces = new String[interfaces_count];
+            for (int i = 0; i < interfaces_count; i++) {
+                interfaces[i] = readClass(index);
+                index += 2;
+            }
+        } finally {
+            in.close();
+        }
+    }
+
+    private String readClass(int index) throws IOException {
+        index = readUnsignedShort(index);
+        if (index == 0)
+            return null;
+        index = readUnsignedShort(items[index]);
+        bin.readFrom(b, items[index]);
+        return data.readUTF();
+    }
+
+    private int readUnsignedShort(int index) {
+        byte[] b = this.b;
+        return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
+    }
+
+    private static final int CHUNK = 2048;
+
+    private void buffer(int amount) throws IOException {
+        if (amount > b.length)
+            b = (byte[]) resizeArray(b, b.length * 2);
+        if (amount > bsize) {
+            int rounded = (int) (CHUNK * Math.ceil((float) amount / CHUNK));
+            bsize += read(in, b, bsize, rounded - bsize);
+            if (amount > bsize)
+                throw new EOFException();
+        }
+    }
+
+    private static int read(InputStream in, byte[] b, int off, int len) throws IOException {
+        int total = 0;
+        while (total < len) {
+            int result = in.read(b, off + total, len - total);
+            if (result == -1)
+                break;
+            total += result;
+        }
+        return total;
+    }
+
+    private static Object resizeArray(Object array, int length) {
+        if (Array.getLength(array) < length) {
+            Object newArray = Array.newInstance(array.getClass().getComponentType(), length);
+            System.arraycopy(array, 0, newArray, 0, Array.getLength(array));
+            return newArray;
+        } else {
+            return array;
+        }
+    }
+
+    private static class MyByteArrayInputStream extends ByteArrayInputStream {
+
+        public MyByteArrayInputStream() {
+            super(new byte[0]);
+        }
+
+        public void readFrom(byte[] buf, int pos) {
+            this.buf = buf;
+            this.pos = pos;
+            this.count = buf.length;
+        }
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/Dependency.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/Dependency.java
new file mode 100644
index 0000000..77f0041
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/Dependency.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.dependencies;
+
+import javax.annotation.Nonnull;
+
+public class Dependency {
+
+    private final String classPath;
+    private final String className;
+
+    public Dependency(@Nonnull String classPath, @Nonnull String className) {
+        this.classPath = classPath;
+        this.className = className;
+    }
+
+    @Nonnull
+    public String getClassPath() {
+        return classPath;
+    }
+
+    @Nonnull
+    public String getClassName() {
+        return className;
+    }
+
+    @Override
+    public String toString() {
+        return classPath + "!" + className;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyFinder.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyFinder.java
new file mode 100644
index 0000000..964b23d
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyFinder.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.dependencies;
+
+import com.tonicsystems.jarjar.classpath.ClassPath;
+import com.tonicsystems.jarjar.classpath.ClassPathArchive;
+import com.tonicsystems.jarjar.classpath.ClassPathResource;
+import com.tonicsystems.jarjar.util.RuntimeIOException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import org.objectweb.asm.ClassReader;
+
+public class DependencyFinder {
+
+    private File curDir = new File(System.getProperty("user.dir"));
+
+    public void setCurrentDirectory(File curDir) {
+        this.curDir = curDir;
+    }
+
+    public void run(DependencyHandler handler, ClassPath from, ClassPath to) throws IOException {
+        try {
+            ClassHeaderReader header = new ClassHeaderReader();
+            Map<String, String> classToArchiveMap = new HashMap<String, String>();
+            for (ClassPathArchive toArchive : to) {
+                for (ClassPathResource toResource : toArchive) {
+                    InputStream in = toResource.openStream();
+                    try {
+                        header.read(in);
+                        classToArchiveMap.put(header.getClassName(), toArchive.getArchiveName());
+                    } catch (Exception e) {
+                        System.err.println("Error reading " + toResource.getName() + ": " + e.getMessage());
+                    } finally {
+                        in.close();
+                    }
+                }
+            }
+
+            handler.handleStart();
+            for (ClassPathArchive fromArchive : from) {
+                for (ClassPathResource fromResource : fromArchive) {
+                    InputStream in = fromResource.openStream();
+                    try {
+                        new ClassReader(in).accept(new DependencyFinderClassVisitor(classToArchiveMap, fromArchive.getArchiveName(), handler),
+                                ClassReader.SKIP_DEBUG);
+                    } catch (Exception e) {
+                        System.err.println("Error reading " + fromResource.getName() + ": " + e.getMessage());
+                    } finally {
+                        in.close();
+                    }
+                }
+            }
+            handler.handleEnd();
+        } catch (RuntimeIOException e) {
+            throw (IOException) e.getCause();
+        }
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyFinderClassVisitor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyFinderClassVisitor.java
new file mode 100644
index 0000000..968a4c5
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyFinderClassVisitor.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.dependencies;
+
+import com.tonicsystems.jarjar.util.RuntimeIOException;
+import java.io.IOException;
+import java.util.Map;
+import org.objectweb.asm.commons.Remapper;
+import org.objectweb.asm.commons.RemappingClassAdapter;
+
+class DependencyFinderClassVisitor extends RemappingClassAdapter {
+
+    public DependencyFinderClassVisitor(Map<String, String> classToArchiveMap, String archiveName, DependencyHandler handler) throws IOException {
+        super(null, new DependencyFinderRemapper(classToArchiveMap, archiveName, handler));
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+        ((DependencyFinderRemapper) remapper).setClassName(name);
+        super.visit(version, access, name, signature, superName, interfaces);
+    }
+
+    private static class DependencyFinderRemapper extends Remapper {
+
+        private final Map<String, String> classes;
+        private final String archiveName;
+        private final DependencyHandler handler;
+        private Dependency curPathClass;
+
+        public DependencyFinderRemapper(Map<String, String> classes, String archiveName, DependencyHandler handler) throws IOException {
+            this.classes = classes;
+            this.archiveName = archiveName;
+            this.handler = handler;
+        }
+
+        public void setClassName(String name) {
+            curPathClass = new Dependency(archiveName, name);
+        }
+
+        @Override
+        public String map(String key) {
+            try {
+                if (classes.containsKey(key)) {
+                    String otherSource = classes.get(key);
+                    if (!archiveName.equals(otherSource)) {
+                        // TODO: some escape mechanism?
+                        handler.handle(curPathClass, new Dependency(otherSource, key));
+                    }
+                }
+            } catch (IOException e) {
+                throw new RuntimeIOException(e);
+            }
+            return null;
+        }
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyHandler.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyHandler.java
new file mode 100644
index 0000000..7d01748
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/DependencyHandler.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.dependencies;
+
+import java.io.IOException;
+
+public interface DependencyHandler {
+
+    public enum Level {
+
+        CLASS, JAR;
+    }
+
+    void handleStart() throws IOException;
+
+    void handle(Dependency from, Dependency to) throws IOException;
+
+    void handleEnd() throws IOException;
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/Pair.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/Pair.java
new file mode 100644
index 0000000..8682bf1
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/Pair.java
@@ -0,0 +1,57 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.dependencies;
+
+import javax.annotation.Nonnull;
+
+/**
+ *
+ * @author shevek
+ */
+public class Pair<T> {
+
+    private final T left;
+    private final T right;
+
+    public Pair(@Nonnull T left, @Nonnull T right) {
+        this.left = left;
+        this.right = right;
+    }
+
+    @Nonnull
+    public T getLeft() {
+        return left;
+    }
+
+    @Nonnull
+    public T getRight() {
+        return right;
+    }
+
+    @Override
+    public int hashCode() {
+        return (left.hashCode() << 8) ^ right.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (null == obj)
+            return false;
+        if (!getClass().equals(obj.getClass()))
+            return false;
+        Pair o = (Pair) obj;
+        return left.equals(o.left)
+                && right.equals(o.right);
+    }
+
+    @Override
+    public String toString() {
+        return left + " : " + right;
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/TextDependencyHandler.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/TextDependencyHandler.java
new file mode 100644
index 0000000..205b12e
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/dependencies/TextDependencyHandler.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.dependencies;
+
+import java.io.IOException;
+
+public class TextDependencyHandler extends AbstractDependencyHandler {
+
+    private final Appendable out;
+
+    public TextDependencyHandler(Appendable out, Level level) {
+        super(level);
+        this.out = out;
+    }
+
+    @Override
+    protected void handle(String from, String to) throws IOException {
+        out.append(from + " -> " + to + "\n");
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/strings/StringDumper.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/strings/StringDumper.java
new file mode 100644
index 0000000..589764c
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/strings/StringDumper.java
@@ -0,0 +1,111 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.strings;
+
+import com.tonicsystems.jarjar.classpath.ClassPath;
+import com.tonicsystems.jarjar.classpath.ClassPathArchive;
+import com.tonicsystems.jarjar.classpath.ClassPathResource;
+import com.tonicsystems.jarjar.util.IoUtil;
+import com.tonicsystems.jarjar.util.RuntimeIOException;
+import java.io.IOException;
+import java.io.InputStream;
+import javax.annotation.Nonnull;
+import org.objectweb.asm.ClassReader;
+
+public class StringDumper {
+
+    public void run(Appendable out, ClassPath classPath) throws IOException {
+        StringReader stringReader = new DumpStringReader(out);
+        for (ClassPathArchive classPathArchive : classPath) {
+            for (ClassPathResource classPathResource : classPathArchive) {
+                InputStream in = classPathResource.openStream();
+                try {
+                    new ClassReader(in).accept(stringReader, 0);
+                } catch (Exception e) {
+                    System.err.println("Error reading " + classPathResource + ": " + e.getMessage());
+                } finally {
+                    in.close();
+                }
+                IoUtil.flush(out);
+            }
+        }
+    }
+
+    private static class DumpStringReader extends StringReader {
+
+        private final Appendable out;
+        private String className;
+
+        public DumpStringReader(@Nonnull Appendable out) {
+            this.out = out;
+        }
+
+        @Override
+        public void visitString(String className, String value, int line) {
+            if (value.length() > 0) {
+                try {
+                    if (!className.equals(this.className)) {
+                        this.className = className;
+                        out.append(className.replace('/', '.'));
+                    }
+                    out.append("\t");
+                    if (line >= 0)
+                        out.append(line + ": ");
+                    out.append(escapeStringLiteral(value));
+                    out.append("\n");
+                } catch (IOException e) {
+                    throw new RuntimeIOException(e);
+                }
+            }
+        }
+    };
+
+    @Nonnull
+    private static String escapeStringLiteral(@Nonnull String value) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("\"");
+        for (int i = 0, size = value.length(); i < size; i++) {
+            char ch = value.charAt(i);
+            switch (ch) {
+                case '\n':
+                    sb.append("\\n");
+                    break;
+                case '\r':
+                    sb.append("\\r");
+                    break;
+                case '\b':
+                    sb.append("\\b");
+                    break;
+                case '\f':
+                    sb.append("\\f");
+                    break;
+                case '\t':
+                    sb.append("\\t");
+                    break;
+                case '\"':
+                    sb.append("\\\"");
+                    break;
+                case '\\':
+                    sb.append("\\\\");
+                    break;
+                default:
+                    sb.append(ch);
+            }
+        }
+        sb.append("\"");
+        return sb.toString();
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/strings/StringReader.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/strings/StringReader.java
new file mode 100644
index 0000000..b4bf18d
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/strings/StringReader.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.strings;
+
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public abstract class StringReader extends ClassVisitor {
+
+    private int line = -1;
+    private String className;
+
+    public StringReader() {
+        super(Opcodes.ASM5);
+    }
+
+    public abstract void visitString(@Nonnull String className, @Nonnull String value, @Nonnegative int line);
+
+    private void handleObject(Object value) {
+        if (value instanceof String)
+            visitString(className, (String) value, line);
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+        className = name;
+        line = -1;
+    }
+
+    @Override
+    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+        handleObject(value);
+        return new FieldVisitor(Opcodes.ASM5) {
+            @Override
+            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+                return StringReader.this.visitAnnotation(desc, visible);
+            }
+        };
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+        return new AnnotationVisitor(Opcodes.ASM5) {
+            @Override
+            public void visit(String name, Object value) {
+                handleObject(value);
+            }
+
+            @Override
+            public void visitEnum(String name, String desc, String value) {
+                handleObject(value);
+            }
+
+            @Override
+            public AnnotationVisitor visitAnnotation(String name, String desc) {
+                return this;
+            }
+        };
+    }
+
+    @Override
+    public MethodVisitor visitMethod(int access, String name, String desc,
+            String signature, String[] exceptions) {
+        return new MethodVisitor(Opcodes.ASM5) {
+            @Override
+            public void visitLdcInsn(Object cst) {
+                handleObject(cst);
+            }
+
+            @Override
+            public void visitLineNumber(int line, Label start) {
+                StringReader.this.line = line;
+            }
+
+            @Override
+            public void visitInvokeDynamicInsn(String name, String desc,
+                    Handle bsm, Object... bsmArgs) {
+                for (Object bsmArg : bsmArgs)
+                    handleObject(bsmArg);
+            }
+
+            @Override
+            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+                return StringReader.this.visitAnnotation(desc, visible);
+            }
+
+            @Override
+            public AnnotationVisitor visitParameterAnnotation(int parameter,
+                    String desc, boolean visible) {
+                return StringReader.this.visitAnnotation(desc, visible);
+            }
+        };
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/JarTransformer.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/JarTransformer.java
new file mode 100644
index 0000000..2fc5b3d
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/JarTransformer.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform;
+
+import com.tonicsystems.jarjar.classpath.ClassPath;
+import com.tonicsystems.jarjar.classpath.ClassPathArchive;
+import com.tonicsystems.jarjar.classpath.ClassPathResource;
+import com.tonicsystems.jarjar.transform.jar.JarProcessor;
+import com.tonicsystems.jarjar.util.IoUtil;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import javax.annotation.Nonnull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JarTransformer {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JarTransformer.class);
+
+    public static enum DuplicatePolicy {
+
+        DISCARD, ERROR;
+    }
+    private final File outputFile;
+    private final JarProcessor processor;
+    private DuplicatePolicy duplicatePolicy = DuplicatePolicy.DISCARD;
+    private final byte[] buf = new byte[0x2000];
+    private final Set<String> dirs = new HashSet<String>();
+    private final Set<String> files = new HashSet<String>();
+
+    public JarTransformer(@Nonnull File outputFile, @Nonnull JarProcessor processor) {
+        this.outputFile = outputFile;
+        this.processor = processor;
+    }
+
+    @Nonnull
+    private Transformable newTransformable(@Nonnull ClassPathResource inputResource)
+            throws IOException {
+        Transformable struct = new Transformable();
+        struct.name = inputResource.getName();
+        struct.time = inputResource.getLastModifiedTime();
+
+        InputStream in = inputResource.openStream();
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            IoUtil.copy(in, out, buf);
+            struct.data = out.toByteArray();
+        } finally {
+            in.close();
+        }
+
+        return struct;
+    }
+
+    private void addDirs(JarOutputStream outputJarStream, String name) throws IOException {
+        int dirIdx = name.lastIndexOf('/');
+        if (dirIdx == -1)
+            return;
+        String dirName = name.substring(0, dirIdx + 1);
+        if (dirs.add(dirName)) {
+            JarEntry dirEntry = new JarEntry(dirName);
+            outputJarStream.putNextEntry(dirEntry);
+        }
+    }
+
+    public void transform(@Nonnull ClassPath inputPath) throws IOException {
+
+        SCAN:
+        {
+            for (ClassPathArchive inputArchive : inputPath) {
+                LOG.debug("Scanning archive {}", inputArchive);
+                for (ClassPathResource inputResource : inputArchive) {
+                    Transformable struct = newTransformable(inputResource);
+                    processor.scan(struct);
+                }
+            }
+        }
+
+        OUT:
+        {
+            Set<String> dirs = new HashSet<String>();
+
+            JarOutputStream outputJarStream = new JarOutputStream(new FileOutputStream(outputFile));
+            for (ClassPathArchive inputArchive : inputPath) {
+                LOG.info("Transforming archive {}", inputArchive);
+                for (ClassPathResource inputResource : inputArchive) {
+                    Transformable struct = newTransformable(inputResource);
+                    if (processor.process(struct) == JarProcessor.Result.DISCARD)
+                        continue;
+
+                    addDirs(outputJarStream, struct.name);
+
+                    if (DuplicatePolicy.DISCARD.equals(duplicatePolicy)) {
+                        if (!files.add(struct.name)) {
+                            LOG.debug("Discarding duplicate {}", struct.name);
+                            continue;
+                        }
+                    }
+
+                    LOG.debug("Writing {}", struct.name);
+                    JarEntry outputEntry = new JarEntry(struct.name);
+                    outputEntry.setTime(struct.time);
+                    outputEntry.setCompressedSize(-1);
+                    outputJarStream.putNextEntry(outputEntry);
+                    outputJarStream.write(struct.data);
+                }
+            }
+            outputJarStream.close();
+        }
+
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/Transformable.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/Transformable.java
new file mode 100644
index 0000000..765724e
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/Transformable.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform;
+
+public class Transformable {
+
+    public byte[] data;
+    public String name;
+    public long time;
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/ClassTransformer.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/ClassTransformer.java
new file mode 100644
index 0000000..6e99cb9
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/ClassTransformer.java
@@ -0,0 +1,19 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.transform.asm;
+
+import javax.annotation.Nonnull;
+import org.objectweb.asm.ClassVisitor;
+
+/**
+ *
+ * @author shevek
+ */
+public interface ClassTransformer {
+
+    @Nonnull
+    public ClassVisitor transform(@Nonnull ClassVisitor v);
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/GetNameClassWriter.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/GetNameClassWriter.java
new file mode 100644
index 0000000..d62f8a3
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/GetNameClassWriter.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.asm;
+
+import javax.annotation.Nonnull;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class GetNameClassWriter extends ClassVisitor {
+
+    private String className;
+
+    /**
+     * Constructs a new GetNameClassWriter.
+     */
+     // * @param flags may include {@link ClassWriter#COMPUTE_FRAMES} * or {@link ClassWriter#COMPUTE_MAXS}.
+    public GetNameClassWriter(ClassVisitor cv) {
+        super(Opcodes.ASM5, cv);
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+        className = name;
+        super.visit(version, access, name, signature, superName, interfaces);
+    }
+
+    @Nonnull
+    public String getClassName() {
+        return className;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/PackageRemapper.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/PackageRemapper.java
new file mode 100644
index 0000000..1b2aa02
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/PackageRemapper.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.asm;
+
+import com.tonicsystems.jarjar.transform.config.ClassRename;
+import com.tonicsystems.jarjar.transform.config.PatternUtils;
+import com.tonicsystems.jarjar.util.ClassNameUtils;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.objectweb.asm.commons.Remapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PackageRemapper extends Remapper {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PackageRemapper.class);
+    private static final String RESOURCE_SUFFIX = "RESOURCE";
+
+    private final List<ClassRename> patterns;
+    private final Map<String, String> typeCache = new HashMap<String, String>();
+    private final Map<String, String> pathCache = new HashMap<String, String>();
+    private final Map<Object, String> valueCache = new HashMap<Object, String>();
+
+    public PackageRemapper(@Nonnull Iterable<? extends ClassRename> patterns) {
+        this.patterns = PatternUtils.toList(patterns);
+    }
+
+    public PackageRemapper(@Nonnull ClassRename... patterns) {
+        this(Arrays.asList(patterns));
+    }
+
+    public void addRule(@Nonnull ClassRename pattern) {
+        this.patterns.add(pattern);
+    }
+
+    @Override
+    public String map(String key) {
+        String s = typeCache.get(key);
+        if (s == null) {
+            s = replaceHelper(key);
+            if (key.equals(s))
+                s = null;
+            typeCache.put(key, s);
+        }
+        return s;
+    }
+
+    public String mapPath(String path) {
+        String s = pathCache.get(path);
+        if (s == null) {
+            s = path;
+            int slash = s.lastIndexOf('/');
+            String end;
+            if (slash < 0) {
+                end = s;
+                s = RESOURCE_SUFFIX;
+            } else {
+                end = s.substring(slash + 1);
+                s = s.substring(0, slash + 1) + RESOURCE_SUFFIX;
+            }
+            boolean absolute = s.startsWith("/");
+            if (absolute)
+                s = s.substring(1);
+
+            s = replaceHelper(s);
+
+            if (absolute)
+                s = "/" + s;
+            if (s.indexOf(RESOURCE_SUFFIX) < 0)
+                return path;
+            s = s.substring(0, s.length() - RESOURCE_SUFFIX.length()) + end;
+            pathCache.put(path, s);
+        }
+        return s;
+    }
+
+    @Override
+    public Object mapValue(Object value) {
+        if (value instanceof String) {
+            String s = valueCache.get(value);
+            if (s == null) {
+                s = (String) value;
+                if (ClassNameUtils.isArrayForName(s)) {
+                    String desc1 = s.replace('.', '/');
+                    String desc2 = mapDesc(desc1);
+                    if (!desc2.equals(desc1))
+                        return desc2.replace('/', '.');
+                } else {
+                    s = mapPath(s);
+                    if (s.equals(value)) {
+                        boolean hasDot = s.indexOf('.') >= 0;
+                        boolean hasSlash = s.indexOf('/') >= 0;
+                        if (!(hasDot && hasSlash)) {
+                            if (hasDot) {
+                                s = replaceHelper(s.replace('.', '/')).replace('/', '.');
+                            } else {
+                                s = replaceHelper(s);
+                            }
+                        }
+                    }
+                }
+                valueCache.put(value, s);
+            }
+            // TODO: add back class name to verbose message
+            if (!s.equals(value))
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Changed \"" + value + "\" -> \"" + s + "\"");
+            return s;
+        } else {
+            return super.mapValue(value);
+        }
+    }
+
+    private String replaceHelper(String value) {
+        for (ClassRename pattern : patterns) {
+            String result = pattern.replace(value);
+            if (result != null)
+                return result;
+        }
+        return value;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/RemappingClassTransformer.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/RemappingClassTransformer.java
new file mode 100644
index 0000000..a30341c
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/asm/RemappingClassTransformer.java
@@ -0,0 +1,30 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.transform.asm;
+
+import javax.annotation.Nonnull;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.commons.Remapper;
+import org.objectweb.asm.commons.RemappingClassAdapter;
+
+/**
+ *
+ * @author shevek
+ */
+public class RemappingClassTransformer implements ClassTransformer {
+
+    private final Remapper remapper;
+
+    public RemappingClassTransformer(@Nonnull Remapper remapper) {
+        this.remapper = remapper;
+    }
+
+    @Override
+    public ClassVisitor transform(ClassVisitor v) {
+        return new RemappingClassAdapter(v, remapper);
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractClassPattern.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractClassPattern.java
new file mode 100644
index 0000000..d76d9e4
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractClassPattern.java
@@ -0,0 +1,26 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import javax.annotation.Nonnull;
+
+/**
+ *
+ * @author shevek
+ */
+public abstract class AbstractClassPattern extends AbstractPattern {
+
+    private static String check(String patternText) {
+        if (patternText.indexOf('/') >= 0)
+            throw new IllegalArgumentException("Class patterns cannot contain slashes");
+        return patternText.replace('.', '/');
+    }
+
+    public AbstractClassPattern(@Nonnull String patternText) {
+        super(check(patternText));
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractPattern.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractPattern.java
new file mode 100644
index 0000000..f88a809
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractPattern.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * This object and its subclasses are also exposed to ant, so need setters for XML.
+ *
+ * @author shevek
+ */
+public abstract class AbstractPattern {
+
+    private final String patternText;
+    private final Pattern pattern;
+
+    public AbstractPattern(@Nonnull String patternText) {
+        if (patternText == null)
+            throw new IllegalArgumentException("Pattern text may not be null.");
+        this.patternText = patternText;
+        this.pattern = PatternUtils.newPattern(patternText);
+    }
+
+    @Nonnull
+    public String getPatternText() {
+        return patternText;
+    }
+
+    @Nonnull
+    public Pattern getPattern() {
+        return pattern;
+    }
+
+    @CheckForNull
+    protected Matcher getMatcher(@Nonnull String value) {
+        if (!PatternUtils.isPossibleQualifiedName(value, "/"))
+            return null;
+        Matcher matcher = pattern.matcher(value);
+        if (matcher.matches())
+            return matcher;
+        return null;
+    }
+
+    public boolean matches(@Nonnull String value) {
+        return getMatcher(value) != null;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "{" + pattern + ")";
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractResourcePattern.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractResourcePattern.java
new file mode 100644
index 0000000..1810a81
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/AbstractResourcePattern.java
@@ -0,0 +1,20 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import javax.annotation.Nonnull;
+
+/**
+ *
+ * @author shevek
+ */
+public abstract class AbstractResourcePattern extends AbstractPattern {
+
+    public AbstractResourcePattern(@Nonnull String patternText) {
+        super(patternText);
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassDelete.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassDelete.java
new file mode 100644
index 0000000..503d630
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassDelete.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Requires that any matched class is removed.
+ *
+ * @author shevek
+ */
+public class ClassDelete extends AbstractClassPattern {
+
+    public ClassDelete(@Nonnull String pattern) {
+        super(pattern);
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassKeep.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassKeep.java
new file mode 100644
index 0000000..1d5cca4
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassKeep.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Requires that any matched class is kept.
+ *
+ * @author shevek
+ */
+public class ClassKeep extends AbstractClassPattern {
+
+    public ClassKeep(@Nonnull String pattern) {
+        super(pattern);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassKeepTransitive.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassKeepTransitive.java
new file mode 100644
index 0000000..175fe7f
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassKeepTransitive.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Requires that any matched class and all classes directly or indirectly reachable from it are kept.
+ *
+ * @author shevek
+ */
+public class ClassKeepTransitive extends AbstractClassPattern {
+
+    public ClassKeepTransitive(@Nonnull String pattern) {
+        super(pattern);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassRename.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassRename.java
new file mode 100644
index 0000000..02a500e
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ClassRename.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Requires that any matched class is renamed.
+ *
+ * @author shevek
+ */
+public class ClassRename extends AbstractClassPattern {
+
+    // private final String replaceText;
+    private final List<Object> replace;
+
+    public ClassRename(@Nonnull String patternText, @Nonnull String replaceText) {
+        super(patternText);
+        if (replaceText == null)
+            throw new IllegalArgumentException("Result may not be null.");
+        // this.replaceText = replaceText;
+        this.replace = PatternUtils.newReplace(getPattern(), replaceText);
+    }
+
+    @CheckForNull
+    public String replace(@Nonnull String value) {
+        return PatternUtils.replace(this, replace, value);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/PatternUtils.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/PatternUtils.java
new file mode 100644
index 0000000..cd88ed4
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/PatternUtils.java
@@ -0,0 +1,245 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nonnull;
+
+public class PatternUtils {
+
+    private PatternUtils() {
+    }
+
+    private static final Pattern dstar = Pattern.compile("\\*\\*");
+    private static final Pattern star = Pattern.compile("\\*");
+    private static final Pattern estar = Pattern.compile("\\+\\??\\)\\Z");
+
+    @Nonnull
+    private static String replaceAllLiteral(@Nonnull String value, @Nonnull Pattern pattern, @Nonnull String replace) {
+        return pattern.matcher(value).replaceAll(Matcher.quoteReplacement(replace));
+    }
+
+    @Nonnull
+    public static Pattern newPattern(@Nonnull String pattern) {
+        if (pattern.equals("**"))
+            throw new IllegalArgumentException("'**' is not a valid pattern");
+        if (!isPossibleQualifiedName(pattern, "/*"))
+            throw new IllegalArgumentException("Not a valid package pattern: " + pattern);
+        if (pattern.indexOf("***") >= 0)
+            throw new IllegalArgumentException("The sequence '***' is invalid in a package pattern");
+
+        String regex = pattern;
+        regex = replaceAllLiteral(regex, dstar, "(.+?)");   // One wildcard test requires the argument to be allowably empty.
+        regex = replaceAllLiteral(regex, star, "([^/]+)");
+        regex = replaceAllLiteral(regex, estar, "*\\??)");  // Although we replaced with + above, we mean *
+        return Pattern.compile("\\A" + regex + "\\Z");
+        // this.count = this.pattern.matcher("foo").groupCount();
+    }
+
+    private static enum State {
+
+        NORMAL, ESCAPE;
+    }
+
+    @Nonnull
+    public static List<Object> newReplace(@Nonnull Pattern pattern, @Nonnull String result) {
+        List<Object> parts = new ArrayList<Object>(16);
+        // TODO: check for illegal characters
+        int max = 0;
+        State state = State.NORMAL;
+        for (int i = 0, mark = 0, len = result.length(); i < len + 1; i++) {
+            char ch = (i == len) ? '@' : result.charAt(i);
+            switch (state) {
+                case NORMAL:
+                    if (ch == '@') {
+                        parts.add(result.substring(mark, i).replace('.', '/'));
+                        mark = i + 1;
+                        state = State.ESCAPE;
+                    }
+                    break;
+                case ESCAPE:
+                    switch (ch) {
+                        case '0':
+                        case '1':
+                        case '2':
+                        case '3':
+                        case '4':
+                        case '5':
+                        case '6':
+                        case '7':
+                        case '8':
+                        case '9':
+                            break;
+                        default:
+                            if (i == mark)
+                                throw new IllegalArgumentException("Backslash not followed by a digit");
+                            int n = Integer.parseInt(result.substring(mark, i));
+                            if (n > max)
+                                max = n;
+                            parts.add(Integer.valueOf(n));
+                            mark = i--;
+                            state = State.NORMAL;
+                            break;
+                    }
+                    break;
+            }
+        }
+
+        int count = pattern.matcher("foo").groupCount();
+        if (count < max)
+            throw new IllegalArgumentException("Result includes impossible placeholder \"@" + max + "\": " + result);
+        // System.err.println(this);
+        return parts;
+    }
+
+    public static String replace(@Nonnull AbstractPattern pattern, @Nonnull List<Object> replace, String value) {
+        Matcher matcher = pattern.getMatcher(value);
+        if (matcher == null)
+            return null;
+        StringBuilder sb = new StringBuilder();
+        for (Object part : replace) {
+            if (part instanceof String)
+                sb.append((String) part);
+            else
+                sb.append(matcher.group((Integer) part));
+        }
+        return sb.toString();
+    }
+
+    public static final String PACKAGE_INFO = "package-info";
+
+    /* pp */ static boolean isPossibleQualifiedName(@Nonnull String value, @Nonnull String extraAllowedCharacters) {
+        // package-info violates the spec for Java Identifiers.
+        // Nevertheless, expressions that end with this string are still legal.
+        // See 7.4.1.1 of the Java language spec for discussion.
+        if (value.endsWith(PACKAGE_INFO)) {
+            value = value.substring(0, value.length() - PACKAGE_INFO.length());
+        }
+        for (int i = 0, len = value.length(); i < len; i++) {
+            char c = value.charAt(i);
+            if (Character.isJavaIdentifierPart(c))
+                continue;
+            if (extraAllowedCharacters.indexOf(c) >= 0)
+                continue;
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Copies the given {@link Iterable} into a new {@link List}.
+     *
+     * @param <T> The free parameter for the element type.
+     * @param in The Iterable to copy.
+     * @return A new, mutable {@link ArrayList}.
+     */
+    @Nonnull
+    public static <T extends AbstractPattern> List<T> toList(@Nonnull Iterable<? extends T> in) {
+        List<T> out = new ArrayList<T>();
+        for (T i : in)
+            out.add(i);
+        return out;
+    }
+
+    // Adapted from http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns
+    @Nonnull
+    public static String convertGlobToRegEx(@Nonnull String line) {
+        line = line.trim();
+        int strLen = line.length();
+        StringBuilder sb = new StringBuilder(strLen);
+        // Remove beginning and ending * globs because they're useless
+        if (line.startsWith("*")) {
+            line = line.substring(1);
+            strLen--;
+        }
+        if (line.endsWith("*")) {
+            line = line.substring(0, strLen - 1);
+            strLen--;
+        }
+        boolean escaping = false;
+        int inCurlies = 0;
+        CHAR:
+        for (char currentChar : line.toCharArray()) {
+            switch (currentChar) {
+                case '*':
+                    if (escaping)
+                        sb.append("\\*");
+                    else
+                        sb.append(".*");
+                    break;
+                case '?':
+                    if (escaping)
+                        sb.append("\\?");
+                    else
+                        sb.append('.');
+                    break;
+                case '.':
+                case '(':
+                case ')':
+                case '+':
+                case '|':
+                case '^':
+                case '$':
+                case '@':
+                case '%':
+                    sb.append('\\');
+                    sb.append(currentChar);
+                    break;
+                case '\\':
+                    if (escaping)
+                        sb.append("\\\\");
+                    else {
+                        escaping = true;
+                        continue CHAR;
+                    }
+                    break;
+                case '{':
+                    if (escaping)
+                        sb.append("\\{");
+                    else {
+                        sb.append('(');
+                        inCurlies++;
+                    }
+                    break;
+                case '}':
+                    if (escaping)
+                        sb.append("\\}");
+                    else if (inCurlies > 0) {
+                        sb.append(')');
+                        inCurlies--;
+                    } else
+                        sb.append("}");
+                    break;
+                case ',':
+                    if (escaping)
+                        sb.append("\\,");
+                    else if (inCurlies > 0)
+                        sb.append('|');
+                    else
+                        sb.append(",");
+                    break;
+                default:
+                    sb.append(currentChar);
+                    break;
+            }
+            escaping = false;
+        }
+        return sb.toString();
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ResourceRename.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ResourceRename.java
new file mode 100644
index 0000000..91cfc40
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/ResourceRename.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Requires that any matched resource is renamed.
+ *
+ * @author shevek
+ */
+public class ResourceRename extends AbstractResourcePattern {
+
+    // private final String replaceText;
+    private final List<Object> replace;
+
+    public ResourceRename(@Nonnull String patternText, @Nonnull String replaceText) {
+        super(patternText);
+        if (replaceText == null)
+            throw new IllegalArgumentException("Result may not be null.");
+        // this.replaceText = replaceText;
+        this.replace = PatternUtils.newReplace(getPattern(), replaceText);
+    }
+
+    @CheckForNull
+    public String replace(@Nonnull String value) {
+        return PatternUtils.replace(this, replace, value);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/RulesFileParser.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/RulesFileParser.java
new file mode 100644
index 0000000..59377ad
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/config/RulesFileParser.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.config;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import javax.annotation.WillClose;
+
+public class RulesFileParser {
+
+    public interface Output {
+
+        public void addClassDelete(@Nonnull ClassDelete classDelete);
+
+        public void addClassRename(@Nonnull ClassRename classRename);
+
+        public void addClassKeep(@Nonnull ClassKeep classKeep);
+
+        public void addClassKeepTransitive(@Nonnull ClassKeepTransitive classKeepTransitive);
+    }
+
+    private RulesFileParser() {
+    }
+
+    @Nonnull
+    public static void parse(@Nonnull Output output, @Nonnull File file) throws IOException {
+        parse(output, new FileReader(file));
+    }
+
+    @Nonnull
+    public static void parse(@Nonnull Output output, @Nonnull String value) throws IOException {
+        parse(output, new StringReader(value));
+    }
+
+    @Nonnull
+    private static List<String> split(@Nonnull String line) {
+        StringTokenizer tok = new StringTokenizer(line);
+        List<String> out = new ArrayList<String>();
+        while (tok.hasMoreTokens()) {
+            String token = tok.nextToken();
+            if (token.startsWith("#"))
+                break;
+            out.add(token);
+        }
+        return out;
+    }
+
+    @Nonnull
+    private static void parse(@Nonnull Output output, @Nonnull @WillClose Reader r) throws IOException {
+        try {
+            BufferedReader br = new BufferedReader(r);
+            int lineNumber = 1;
+            String line;
+            while ((line = br.readLine()) != null) {
+                List<String> words = split(line);
+                if (words.isEmpty())
+                    continue;
+                if (words.size() < 2)
+                    throw error(lineNumber, words, "not enough words on line.");
+                String type = words.get(0);
+                if (type.equals("rule")) {
+                    if (words.size() < 3)
+                        throw error(lineNumber, words, "'rule' requires 2 arguments.");
+                    output.addClassRename(new ClassRename(words.get(1), words.get(2)));
+                } else if (type.equals("zap")) {
+                    output.addClassDelete(new ClassDelete(words.get(1)));
+                } else if (type.equals("keep")) {
+                    output.addClassKeepTransitive(new ClassKeepTransitive(words.get(1)));
+                } else {
+                    throw error(lineNumber, words, "Unrecognized keyword " + type);
+                }
+                lineNumber++;
+            }
+        } finally {
+            r.close();
+        }
+    }
+
+    @Nonnull
+    private static IllegalArgumentException error(@Nonnegative int lineNumber, @Nonnull List<String> words, @Nonnull String reason) {
+        throw new IllegalArgumentException("Error on line " + lineNumber + ": " + words + ": " + reason);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/AbstractFilterJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/AbstractFilterJarProcessor.java
new file mode 100644
index 0000000..dc9ce41
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/AbstractFilterJarProcessor.java
@@ -0,0 +1,47 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import com.tonicsystems.jarjar.transform.Transformable;
+import java.io.IOException;
+import javax.annotation.Nonnull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author shevek
+ */
+public abstract class AbstractFilterJarProcessor implements JarProcessor {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractFilterJarProcessor.class);
+
+    protected abstract boolean isFiltered(@Nonnull String name);
+
+    protected boolean isVerbose() {
+        return true;
+    }
+
+    @Override
+    public Result scan(Transformable struct) throws IOException {
+        if (isFiltered(struct.name)) {
+            if (isVerbose())
+                LOG.debug("{}.scan discarded {}", getClass().getSimpleName(), struct.name);
+            return Result.DISCARD;
+        }
+        return Result.KEEP;
+    }
+
+    @Override
+    public Result process(Transformable struct) throws IOException {
+        if (isFiltered(struct.name)) {
+            if (isVerbose())
+                LOG.debug("{}.process discarded {}", getClass().getSimpleName(), struct.name);
+            return Result.DISCARD;
+        }
+        return Result.KEEP;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassClosureJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassClosureJarProcessor.java
new file mode 100644
index 0000000..c5f9318
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassClosureJarProcessor.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import com.tonicsystems.jarjar.transform.config.PatternUtils;
+import com.tonicsystems.jarjar.transform.config.ClassKeepTransitive;
+import com.tonicsystems.jarjar.transform.Transformable;
+import com.tonicsystems.jarjar.util.ClassNameUtils;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.commons.Remapper;
+import org.objectweb.asm.commons.RemappingClassAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Keeps all classes reachable from a given set of roots.
+ *
+ * Put this early in the chain as it does not honour renames.
+ */
+public class ClassClosureJarProcessor extends AbstractFilterJarProcessor {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ClassClosureJarProcessor.class);
+
+    private static class DependencyCollector extends Remapper {
+
+        private final Set<String> dependencies = new HashSet<String>();
+
+        @Override
+        public String map(String key) {
+            if (key.startsWith("java/") || key.startsWith("javax/"))
+                return null;
+            dependencies.add(key);
+            return null;
+        }
+
+        @Override
+        public Object mapValue(Object value) {
+            if (value instanceof String) {
+                String s = (String) value;
+                if (ClassNameUtils.isArrayForName(s)) {
+                    mapDesc(s.replace('.', '/'));
+                } else if (ClassNameUtils.isForName(s)) {
+                    map(s.replace('.', '/'));
+                }
+                return value;
+            } else {
+                return super.mapValue(value);
+            }
+        }
+    }
+
+    private final List<ClassKeepTransitive> patterns;
+    private final List<String> roots = new ArrayList<String>();
+    private final Map<String, Set<String>> dependencies = new HashMap<String, Set<String>>();
+    private Set<String> closure;
+
+    public ClassClosureJarProcessor(@Nonnull Iterable<? extends ClassKeepTransitive> patterns) {
+        this.patterns = PatternUtils.toList(patterns);
+    }
+
+    public ClassClosureJarProcessor(@Nonnull ClassKeepTransitive... patterns) {
+        this(Arrays.asList(patterns));
+    }
+
+    public void addKeep(@Nonnull ClassKeepTransitive pattern) {
+        patterns.add(pattern);
+    }
+
+    private boolean isEnabled() {
+        return !patterns.isEmpty();
+    }
+
+    @Override
+    public Result scan(Transformable struct) throws IOException {
+        if (!isEnabled())
+            return Result.KEEP;
+        try {
+            if (ClassNameUtils.isClass(struct.name)) {
+                String name = struct.name.substring(0, struct.name.length() - 6);
+                for (ClassKeepTransitive pattern : patterns)
+                    if (pattern.matches(name))
+                        roots.add(name);
+                DependencyCollector collector = new DependencyCollector();
+                dependencies.put(name, collector.dependencies);
+                new ClassReader(new ByteArrayInputStream(struct.data)).accept(new RemappingClassAdapter(null, collector), ClassReader.EXPAND_FRAMES);
+                collector.dependencies.remove(name);
+            }
+        } catch (Exception e) {
+            LOG.warn("Error reading " + struct.name + ": " + e.getMessage());
+        }
+        return Result.KEEP;
+    }
+
+    private void addTransitiveClosure(Collection<? super String> out, Collection<String> itemDependencies) {
+        if (itemDependencies == null)
+            return;
+        for (String name : itemDependencies)
+            if (out.add(name))
+                addTransitiveClosure(out, dependencies.get(name));
+    }
+
+    @Override
+    protected boolean isFiltered(String name) {
+        if (closure == null) {
+            closure = new HashSet<String>();
+            addTransitiveClosure(closure, roots);
+        }
+        return !closure.contains(name);
+    }
+
+    @Override
+    public Result process(Transformable struct) throws IOException {
+        if (!isEnabled())
+            return Result.KEEP;
+        if (!ClassNameUtils.isClass(struct.name))
+            return Result.KEEP;
+        return super.process(struct);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassFilterJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassFilterJarProcessor.java
new file mode 100644
index 0000000..79798ba
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassFilterJarProcessor.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import com.tonicsystems.jarjar.transform.config.AbstractPattern;
+import com.tonicsystems.jarjar.transform.config.ClassDelete;
+import com.tonicsystems.jarjar.transform.config.ClassKeep;
+import com.tonicsystems.jarjar.util.ClassNameUtils;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Filters classes by name.
+ *
+ * Keeps all classes specified by ClassKeep (default all classes).
+ * Then removes all classes specified by ClassDelete (default no classes).
+ * Ignores non-class resources.
+ *
+ * @see ClassNameUtils#isClass(String)
+ * @author shevek
+ */
+public class ClassFilterJarProcessor extends AbstractFilterJarProcessor {
+
+    // private static final Logger LOG = LoggerFactory.getLogger(ClassFilterJarProcessor.class);
+    private final List<ClassKeep> keepPatterns = new ArrayList<ClassKeep>();
+    private final List<ClassDelete> deletePatterns = new ArrayList<ClassDelete>();
+
+    public void addClassKeep(@Nonnull ClassKeep pattern) {
+        keepPatterns.add(pattern);
+    }
+
+    public void addClassDelete(@Nonnull ClassDelete pattern) {
+        deletePatterns.add(pattern);
+    }
+
+    @CheckForNull
+    protected <T extends AbstractPattern> T getMatchingPattern(@Nonnull List<? extends T> patterns, @Nonnull String name) {
+        for (T pattern : patterns) {
+            if (pattern.matches(name)) {
+                // LOG.debug(pattern + " matches " + name);
+                return pattern;
+            }
+        }
+        // LOG.debug("No pattern matches " + name);
+        return null;
+    }
+
+    @Override
+    protected boolean isFiltered(@Nonnull String name) {
+        if (!ClassNameUtils.isClass(name))
+            return false;
+        name = name.substring(0, name.length() - 6);
+        // LOG.debug("Looking to include " + name);
+        INCLUDE:
+        {
+            if (keepPatterns.isEmpty())
+                break INCLUDE;
+            if (getMatchingPattern(keepPatterns, name) != null)
+                break INCLUDE;
+            // We have include patterns, but none matched. Filter it.
+            return true;
+        }
+        // LOG.debug("Looking to exclude " + name);
+        return getMatchingPattern(deletePatterns, name) != null;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassTransformerJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassTransformerJarProcessor.java
new file mode 100644
index 0000000..1dfc805
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ClassTransformerJarProcessor.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import com.tonicsystems.jarjar.transform.asm.ClassTransformer;
+import com.tonicsystems.jarjar.transform.asm.GetNameClassWriter;
+import com.tonicsystems.jarjar.transform.Transformable;
+import com.tonicsystems.jarjar.util.ClassNameUtils;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import javax.annotation.Nonnull;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A JarProcessor which applies a list of {@link ClassTransformer ClassTransformers}
+ * to any files ending in .class.
+ */
+public class ClassTransformerJarProcessor implements JarProcessor {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ClassTransformerJarProcessor.class);
+    private final List<ClassTransformer> classProcessors;
+
+    public ClassTransformerJarProcessor(@Nonnull List<ClassTransformer> classProcessors) {
+        this.classProcessors = classProcessors;
+    }
+
+    public ClassTransformerJarProcessor(@Nonnull ClassTransformer... classProcessors) {
+        this(Arrays.asList(classProcessors));
+    }
+
+    @Override
+    public Result scan(Transformable struct) throws IOException {
+        return Result.KEEP;
+    }
+
+    @Override
+    public Result process(Transformable struct) throws IOException {
+        if (ClassNameUtils.isClass(struct.name)) {
+            try {
+                ClassReader reader = new ClassReader(struct.data);
+                ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+                GetNameClassWriter namer = new GetNameClassWriter(writer);
+                ClassVisitor cv = namer;
+                for (ClassTransformer classProcessor : classProcessors)
+                    cv = classProcessor.transform(cv);
+                reader.accept(cv, ClassReader.EXPAND_FRAMES);
+                struct.name = ClassNameUtils.javaNameToPath(namer.getClassName());
+                struct.data = writer.toByteArray();
+            } catch (Exception e) {
+                LOG.warn("Failed to read class " + struct.name + ": " + e);
+            }
+        }
+        return Result.KEEP;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/DefaultJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/DefaultJarProcessor.java
new file mode 100644
index 0000000..c53757c
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/DefaultJarProcessor.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import com.tonicsystems.jarjar.transform.asm.PackageRemapper;
+import com.tonicsystems.jarjar.transform.config.ClassDelete;
+import com.tonicsystems.jarjar.transform.config.ClassKeepTransitive;
+import com.tonicsystems.jarjar.transform.config.ClassRename;
+import com.tonicsystems.jarjar.transform.asm.RemappingClassTransformer;
+import com.tonicsystems.jarjar.transform.config.ClassKeep;
+import com.tonicsystems.jarjar.transform.config.RulesFileParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultJarProcessor extends JarProcessorChain implements RulesFileParser.Output {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DefaultJarProcessor.class);
+    // private final Map<String, String> renames = new HashMap<String, String>();
+
+    private final ManifestFilterJarProcessor manifestFilterJarProcessor = new ManifestFilterJarProcessor();
+    private final ClassFilterJarProcessor classFilterJarProcessor = new ClassFilterJarProcessor();
+    private final ClassClosureJarProcessor classClosureFilterJarProcessor = new ClassClosureJarProcessor();
+    private final PackageRemapper packageRemapper = new PackageRemapper();
+    private final RemappingClassTransformer remappingClassTransformer = new RemappingClassTransformer(packageRemapper);
+    private final ResourceRenamerJarProcessor resourceRenamerJarProcessor = new ResourceRenamerJarProcessor(packageRemapper);
+
+    public DefaultJarProcessor() {
+        add(new DirectoryFilterJarProcessor());
+        add(manifestFilterJarProcessor);
+        add(classFilterJarProcessor);
+        add(classClosureFilterJarProcessor);
+        add(new ClassTransformerJarProcessor(remappingClassTransformer));
+        add(resourceRenamerJarProcessor);
+    }
+
+    @Override
+    public void addClassDelete(ClassDelete classDelete) {
+        classFilterJarProcessor.addClassDelete(classDelete);
+    }
+
+    @Override
+    public void addClassRename(ClassRename classRename) {
+        packageRemapper.addRule(classRename);
+    }
+
+    @Override
+    public void addClassKeep(ClassKeep classKeep) {
+        classFilterJarProcessor.addClassKeep(classKeep);
+    }
+
+    @Override
+    public void addClassKeepTransitive(ClassKeepTransitive classKeepTransitive) {
+        classClosureFilterJarProcessor.addKeep(classKeepTransitive);
+    }
+
+    public void setSkipManifest(boolean value) {
+        manifestFilterJarProcessor.setEnabled(value);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/DirectoryFilterJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/DirectoryFilterJarProcessor.java
new file mode 100644
index 0000000..30fdb2a
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/DirectoryFilterJarProcessor.java
@@ -0,0 +1,24 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+/**
+ *
+ * @author shevek
+ */
+public class DirectoryFilterJarProcessor extends AbstractFilterJarProcessor {
+
+    @Override
+    protected boolean isFiltered(String name) {
+        return name.endsWith("/");
+    }
+
+    @Override
+    protected boolean isVerbose() {
+        return false;
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/JarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/JarProcessor.java
new file mode 100644
index 0000000..5c652da
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/JarProcessor.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import com.tonicsystems.jarjar.transform.Transformable;
+import java.io.IOException;
+import javax.annotation.Nonnull;
+
+public interface JarProcessor {
+
+    public static enum Result {
+
+        KEEP,
+        DISCARD;
+    }
+
+    // public boolean isEnabled();
+
+    @Nonnull
+    public Result scan(@Nonnull Transformable struct) throws IOException;
+
+    /**
+     * Process the entry (e.g. rename the file)
+     * <p>
+     * Returns <code>true</code> if the processor wants to retain the entry. In this case, the entry can be removed
+     * from the jar file in a future time. Return <code>false</code> for the entries which do not have been changed and
+     * there fore are not to be deleted
+     *
+     * @param struct The archive entry to be transformed.
+     * @return <code>true</code> if he process chain can continue after this process
+     * @throws IOException if it all goes upside down
+     */
+    @Nonnull
+    public Result process(@Nonnull Transformable struct) throws IOException;
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/JarProcessorChain.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/JarProcessorChain.java
new file mode 100644
index 0000000..397aba6
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/JarProcessorChain.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import com.tonicsystems.jarjar.transform.Transformable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.annotation.Nonnull;
+
+public class JarProcessorChain extends ArrayList<JarProcessor> implements JarProcessor {
+
+    public JarProcessorChain(@Nonnull Iterable<? extends JarProcessor> processors) {
+        for (JarProcessor processor : processors)
+            add(processor);
+    }
+
+    public JarProcessorChain(@Nonnull JarProcessor... processors) {
+        this(Arrays.asList(processors));
+    }
+
+    @Override
+    public Result scan(Transformable struct) throws IOException {
+        for (JarProcessor processor : this)
+            if (processor.scan(struct) == Result.DISCARD)
+                return Result.DISCARD;
+        return Result.KEEP;
+    }
+
+    @Override
+    public Result process(Transformable struct) throws IOException {
+        for (JarProcessor processor : this)
+            if (processor.process(struct) == Result.DISCARD)
+                return Result.DISCARD;
+        return Result.KEEP;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ManifestFilterJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ManifestFilterJarProcessor.java
new file mode 100644
index 0000000..d5711fd
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ManifestFilterJarProcessor.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import java.util.Collections;
+
+/**
+ * Excludes the manifest.
+ */
+public class ManifestFilterJarProcessor extends PathFilterJarProcessor {
+
+    public static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
+
+    private boolean enabled = false;
+
+    public ManifestFilterJarProcessor() {
+        super(Collections.singleton(MANIFEST_PATH));
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @Override
+    protected boolean isFiltered(String name) {
+        if (!isEnabled())
+            return false;
+        return super.isFiltered(name);
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/PathFilterJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/PathFilterJarProcessor.java
new file mode 100644
index 0000000..c54cd7c
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/PathFilterJarProcessor.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import java.util.Set;
+import javax.annotation.Nonnull;
+
+/**
+ * Excludes resources by exact name.
+ */
+public class PathFilterJarProcessor extends AbstractFilterJarProcessor {
+
+    private final Set<? extends String> excludes;
+
+    public PathFilterJarProcessor(@Nonnull Set<? extends String> excludes) {
+        this.excludes = excludes;
+    }
+
+    @Override
+    protected boolean isFiltered(String name) {
+        return excludes.contains(name);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ResourceRenamerJarProcessor.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ResourceRenamerJarProcessor.java
new file mode 100644
index 0000000..42c018d
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/transform/jar/ResourceRenamerJarProcessor.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.transform.jar;
+
+import com.tonicsystems.jarjar.transform.asm.PackageRemapper;
+import com.tonicsystems.jarjar.transform.Transformable;
+import com.tonicsystems.jarjar.util.ClassNameUtils;
+import java.io.IOException;
+import javax.annotation.Nonnull;
+
+/**
+ * Allows any file which is NOT a JAR file.
+ */
+public class ResourceRenamerJarProcessor implements JarProcessor {
+
+    private final PackageRemapper pr;
+
+    public ResourceRenamerJarProcessor(@Nonnull PackageRemapper pr) {
+        this.pr = pr;
+    }
+
+    @Override
+    public Result scan(Transformable struct) throws IOException {
+        return Result.KEEP;
+    }
+
+    @Override
+    public Result process(Transformable struct) throws IOException {
+        if (!ClassNameUtils.isClass(struct.name))
+            struct.name = pr.mapPath(struct.name);
+        return Result.KEEP;
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/ClassNameUtils.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/ClassNameUtils.java
new file mode 100644
index 0000000..7b133aa
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/ClassNameUtils.java
@@ -0,0 +1,79 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.util;
+
+import java.io.File;
+import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
+
+/**
+ *
+ * @author shevek
+ */
+public class ClassNameUtils {
+
+    private static final Pattern ARRAY_FOR_NAME_PATTERN
+            = Pattern.compile("\\[L[\\p{javaJavaIdentifierPart}\\.]+?;");
+
+    /**
+     * Returns true if the given string looks like a Java array name.
+     * @param value The name to inspect.
+     * @return true if the given string looks like a Java array name.
+     */
+    // also used by KeepProcessor
+    public static boolean isArrayForName(String value) {
+        // Type type = Type.getType(value);
+        // type.getSort() == ARRAY;
+        // type.getElementType();
+        return ARRAY_FOR_NAME_PATTERN.matcher(value).matches();
+    }
+
+    // TODO: use this for package remapping too?
+    /**
+     * Returns true if the String looks like a Java type name.
+     * @param value The name to inspect.
+     * @return true if the String looks like a Java type name.
+     */
+    public static boolean isForName(@Nonnull String value) {
+        if (value.equals(""))
+            return false;
+        for (int i = 0, len = value.length(); i < len; i++) {
+            char c = value.charAt(i);
+            if (c != '.' && !Character.isJavaIdentifierPart(c))
+                return false;
+        }
+        return true;
+    }
+
+    @Nonnull
+    public static String javaNameToPath(@Nonnull String className) {
+        return className.replace('.', '/') + ClassNameUtils.EXT_CLASS;
+    }
+
+    @Nonnull
+    public static String pathToJavaName(@Nonnull String path) {
+        if (isClass(path))
+            path = path.substring(0, path.length() - EXT_CLASS.length());
+        return path.replace('/', '.');
+    }
+
+    public static final String EXT_CLASS = ".class";
+
+    public static boolean isClass(@Nonnull String name) {
+        return hasExtension(name, EXT_CLASS);
+    }
+
+    public static boolean hasExtension(@Nonnull File file, @Nonnull String ext) {
+        return hasExtension(file.getName(), ext);
+    }
+
+    public static boolean hasExtension(@Nonnull String name, @Nonnull String ext) {
+        if (name.length() < ext.length())
+            return false;
+        String actual = name.substring(name.length() - ext.length());
+        return actual.equalsIgnoreCase(ext);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/IoUtil.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/IoUtil.java
new file mode 100644
index 0000000..53d630e
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/IoUtil.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.util;
+
+import java.io.*;
+import javax.annotation.Nonnull;
+import javax.annotation.WillNotClose;
+
+public class IoUtil {
+
+    private IoUtil() {
+    }
+
+    public static void copy(@Nonnull @WillNotClose InputStream is, @Nonnull @WillNotClose OutputStream out, @Nonnull byte[] buf) throws IOException {
+        for (;;) {
+            int amt = is.read(buf);
+            if (amt < 0)
+                break;
+            out.write(buf, 0, amt);
+        }
+    }
+
+    public static void copy(@Nonnull File from, @Nonnull File to, @Nonnull byte[] buf) throws IOException {
+        InputStream in = new FileInputStream(from);
+        try {
+            OutputStream out = new FileOutputStream(to);
+            try {
+                copy(in, out, buf);
+            } finally {
+                out.close();
+            }
+        } finally {
+            in.close();
+        }
+    }
+
+    public static void flush(Object o) throws IOException {
+        if (o instanceof Flushable)
+            ((Flushable) o).flush();
+    }
+
+    public static void close(Object o) throws IOException {
+        if (o instanceof Closeable)
+            ((Closeable) o).close();
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/RuntimeIOException.java b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/RuntimeIOException.java
new file mode 100644
index 0000000..b666385
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/main/java/com/tonicsystems/jarjar/util/RuntimeIOException.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar.util;
+
+import java.io.IOException;
+import javax.annotation.Nonnull;
+
+public class RuntimeIOException extends RuntimeException {
+
+    private static final long serialVersionUID = 0L;
+
+    public RuntimeIOException(@Nonnull IOException e) {
+        super(e);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/GenericsTest.java b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/GenericsTest.java
new file mode 100644
index 0000000..a707947
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/GenericsTest.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar;
+
+import com.tonicsystems.jarjar.transform.asm.PackageRemapper;
+import com.tonicsystems.jarjar.transform.config.ClassRename;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.commons.RemappingClassAdapter;
+
+public class GenericsTest {
+
+    @Test
+    public void testTransform() throws Exception {
+        ClassRename rule = new ClassRename("java.lang.String", "com.tonicsystems.String");
+        RemappingClassAdapter t = new RemappingClassAdapter(null, new PackageRemapper(rule));
+        ClassReader reader = new ClassReader(getClass().getResourceAsStream("/Generics.class"));
+        reader.accept(t, 0);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/PackageRemapperTest.java b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/PackageRemapperTest.java
new file mode 100644
index 0000000..5e36533
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/PackageRemapperTest.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar;
+
+import com.tonicsystems.jarjar.transform.asm.PackageRemapper;
+import com.tonicsystems.jarjar.transform.config.ClassRename;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PackageRemapperTest {
+
+    protected PackageRemapper remapper;
+
+    @Before
+    public void setUp() {
+        ClassRename rule = new ClassRename("org.**", "foo.@1");
+        remapper = new PackageRemapper(Collections.singletonList(rule));
+    }
+
+    @Test
+    public void testMapValue() {
+        assertUnchangedValue("[^\\s;/@&=,.?:+$]");
+        assertUnchangedValue("[Ljava/lang/Object;");
+        assertUnchangedValue("[Lorg/example/Object;");
+        assertUnchangedValue("[Ljava.lang.Object;");
+        assertUnchangedValue("[Lorg.example/Object;");
+        assertUnchangedValue("[L;");
+        assertUnchangedValue("[Lorg.example.Object;;");
+        assertUnchangedValue("[Lorg.example.Obj ct;");
+        assertUnchangedValue("org.example/Object");
+
+        assertEquals("[Lfoo.example.Object;", remapper.mapValue("[Lorg.example.Object;"));
+        assertEquals("foo.example.Object", remapper.mapValue("org.example.Object"));
+        assertEquals("foo/example/Object", remapper.mapValue("org/example/Object"));
+        assertEquals("foo/example.Object", remapper.mapValue("org/example.Object")); // path match
+
+        assertEquals("foo.example.package-info", remapper.mapValue("org.example.package-info"));
+        assertEquals("foo/example/package-info", remapper.mapValue("org/example/package-info"));
+        assertEquals("foo/example.package-info", remapper.mapValue("org/example.package-info"));
+    }
+
+    private void assertUnchangedValue(String value) {
+        assertEquals(value, remapper.mapValue(value));
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/RulesFileParserTest.java b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/RulesFileParserTest.java
new file mode 100644
index 0000000..06b1c42
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/RulesFileParserTest.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar;
+
+import org.junit.Test;
+
+public class RulesFileParserTest {
+
+    @Test
+    public void testSimple() throws Exception {
+        // TODO
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/WildcardTest.java b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/WildcardTest.java
new file mode 100644
index 0000000..7253a2c
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/WildcardTest.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar;
+
+import com.tonicsystems.jarjar.transform.config.ResourceRename;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import static org.junit.Assert.*;
+
+public class WildcardTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(WildcardTest.class);
+
+    @Test
+    public void testWildcards() {
+        wildcard("net/sf/cglib/**", "foo/@1", "net/sf/cglib/proxy/Mixin$Generator",
+                "foo/proxy/Mixin$Generator");
+        wildcard("net/sf/cglib/**", "foo/@1", "net/sf/cglib/Bar", "foo/Bar");
+        wildcard("net/sf/cglib/**", "foo/@1", "net/sf/cglib/Bar/Baz", "foo/Bar/Baz");
+        wildcard("net/sf/cglib/**", "foo/@1", "net/sf/cglib/", "foo/");
+        wildcard("net/sf/cglib/**", "foo/@1", "net/sf/cglib/!", null);
+        wildcard("net/sf/cglib/*", "foo/@1", "net/sf/cglib/Bar", "foo/Bar");
+        wildcard("net/sf/cglib/*/*", "foo/@2/@1", "net/sf/cglib/Bar/Baz", "foo/Baz/Bar");
+    }
+
+    private static void wildcard(String pattern, String result, String value, String expect) {
+        ResourceRename wc = new ResourceRename(pattern, result);
+        LOG.info("Compile: " + pattern + " -> " + wc);
+        String actual = wc.replace(value);
+        LOG.info("Replace: " + value + " -> " + actual + " (expected " + expect + ")");
+        assertEquals(expect, actual);
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/transform/AbstractJarTransformerTest.java b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/transform/AbstractJarTransformerTest.java
new file mode 100644
index 0000000..a58eb86
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/transform/AbstractJarTransformerTest.java
@@ -0,0 +1,52 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.transform;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import javax.annotation.Nonnull;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author shevek
+ */
+public class AbstractJarTransformerTest {
+
+    @Nonnull
+    protected static File newJar(String propertyName) {
+        return new File(System.getProperty(propertyName));
+    }
+    protected final File jar = newJar("jar");
+    protected final File[] jars = new File[]{
+        newJar("jar0"),
+        newJar("jar1"),
+        newJar("jar2"),
+        newJar("jar3")
+    };
+
+    @Nonnull
+    protected Method getMethod(@Nonnull File file, @Nonnull String className, @Nonnull String methodName, @Nonnull Class<?>... parameterTypes) throws Exception {
+        URLClassLoader loader = new URLClassLoader(new URL[]{file.toURI().toURL()}, getClass().getClassLoader());
+        Class<?> c = loader.loadClass(className);
+        return c.getMethod("main", parameterTypes);
+    }
+
+    protected static void assertContains(@Nonnull JarFile jarFile, @Nonnull String resourceName) {
+        JarEntry jarEntry = jarFile.getJarEntry(resourceName);
+        assertNotNull("JarFile " + jarFile + " does not contain entry " + resourceName, jarEntry);
+    }
+
+    protected static void assertNotContains(@Nonnull JarFile jarFile, @Nonnull String resourceName) {
+        JarEntry jarEntry = jarFile.getJarEntry(resourceName);
+        assertNull("JarFile " + jarFile + " does contains unexpected entry " + resourceName, jarEntry);
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/transform/JarTransformerTest.java b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/transform/JarTransformerTest.java
new file mode 100644
index 0000000..79a533f
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/test/java/com/tonicsystems/jarjar/transform/JarTransformerTest.java
@@ -0,0 +1,54 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.tonicsystems.jarjar.transform;
+
+import com.tonicsystems.jarjar.classpath.ClassPath;
+import com.tonicsystems.jarjar.transform.jar.DefaultJarProcessor;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.jar.JarFile;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author shevek
+ */
+public class JarTransformerTest extends AbstractJarTransformerTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JarTransformerTest.class);
+
+    private final File outputFile = new File("build/tmp/output.jar");
+    private final DefaultJarProcessor processor = new DefaultJarProcessor();
+    private final JarTransformer transformer = new JarTransformer(outputFile, processor);
+    private final ClassPath classPath = new ClassPath(new File("/"), jars);
+
+    /*
+     @Test
+     public void testTransformNoop() throws Exception {
+     processor.setSkipManifest(true);
+     processor.add(new PathFilterJarProcessor(Collections.singleton("META-INF/jarjar-testdata.properties")));
+     transformer.transform(classPath);
+     }
+     */
+    @Test
+    public void testTransformRename() throws Exception {
+        processor.setSkipManifest(true);
+        // processor.add(new PathFilterJarProcessor(Collections.singleton("META-INF/jarjar-testdata.properties")));
+        transformer.transform(classPath);
+
+        Method m = getMethod(outputFile, "org.anarres.jarjar.testdata.pkg0.Main", "main", String[].class);
+        m.invoke(null, (Object) new String[]{});
+
+        JarFile jarFile = new JarFile(outputFile);
+        assertContains(jarFile, "org/anarres/jarjar/testdata/pkg0/Main.class");
+        assertContains(jarFile, "org/anarres/jarjar/testdata/pkg1/Cls1.class");
+        assertContains(jarFile, "org/anarres/jarjar/testdata/pkg2/Cls2.class");
+        assertContains(jarFile, "org/anarres/jarjar/testdata/pkg3/Cls3.class");
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-core/src/test/resources/Generics.class b/third_party/java/jarjar/jarjar-core/src/test/resources/Generics.class
new file mode 100644
index 0000000..827519a
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/test/resources/Generics.class
Binary files differ
diff --git a/third_party/java/jarjar/jarjar-core/src/test/resources/enumtest.jar b/third_party/java/jarjar/jarjar-core/src/test/resources/enumtest.jar
new file mode 100644
index 0000000..df34d16
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-core/src/test/resources/enumtest.jar
Binary files differ
diff --git a/third_party/java/jarjar/jarjar-gradle/build.gradle b/third_party/java/jarjar/jarjar-gradle/build.gradle
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/build.gradle
diff --git a/third_party/java/jarjar/jarjar-gradle/example/build.gradle b/third_party/java/jarjar/jarjar-gradle/example/build.gradle
new file mode 100644
index 0000000..4bc3010f
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/example/build.gradle
@@ -0,0 +1,130 @@
+buildscript {
+	repositories {
+		mavenLocal()
+		mavenCentral()
+	}
+
+	dependencies {
+		def properties = new Properties();
+		file('../../gradle.properties').withReader { properties.load(it); }
+		classpath "org.anarres.jarjar:jarjar-gradle:${properties.version}"
+	}
+}
+
+subprojects {
+	apply plugin: 'java'
+	apply plugin: 'application'
+	apply plugin: 'org.anarres.jarjar'
+
+	mainClassName = "org.anarres.jarjar.test.Main"
+
+	repositories {
+		mavenCentral()
+		maven { url 'http://repo.hortonworks.com/content/repositories/releases/' }
+	}
+
+	sourceSets {
+		main {
+			java { srcDir rootProject.file('src/main/java') }
+		}
+	}
+}
+
+project(':sub0') {
+
+	configurations {
+		upstream
+		literal {
+			extendsFrom upstream
+			resolutionStrategy {
+				// http://gradle.org/docs/current/javadoc/org/gradle/api/artifacts/ResolutionStrategy.html
+				eachDependency { DependencyResolveDetails details ->
+					println "DependencyResolveDetails " + details.dump()
+				}
+				// http://gradle.org/docs/current/javadoc/org/gradle/api/artifacts/ComponentSelectionRules.html
+				componentSelection {
+					withModule("org.apache.hive:hive-metastore") { ComponentSelection selection ->
+						selection.reject("Rejecting all hive-metastore")
+						println "ComponentSelection: " + selection.dump()
+					}
+				}
+			}
+		}
+		compile {
+			extendsFrom literal
+		}
+	}
+
+	dependencies {
+		upstream('org.apache.hive:hive-exec:0.13.0.2.1.5.0-695') {
+			exclude group: 'net.hydromatic', module: 'optiq-core'
+			// transitive = false
+		}
+
+		compile jarjar.repackage {
+			from configurations.upstream
+
+			archiveBypass "commons*.jar"
+			archiveExclude "slf4j*.jar"
+
+			classDelete "org.apache.thrift.**"
+
+			classRename 'org.json.**', 'org.anarres.hive.json.@1'
+			classRename 'org.iq80.**', 'org.anarres.hive.iq80.@1'
+			classRename 'org.codehaus.jackson.**', 'org.anarres.hive.jackson.@1'
+			classRename 'com.google.**', 'org.anarres.hive.google.@1'
+			classRename 'javolution.**', 'org.anarres.hive.javolution.@1'
+			classRename 'com.esotericsoftware.kryo.**', 'org.anarres.hive.kryo.@1'
+		}
+	}
+
+}
+
+project(':sub1') {
+
+	dependencies {
+		compile jarjar.repackage {
+			from ('org.apache.hive:hive-exec:0.13.0.2.1.5.0-695') {
+				exclude group: 'net.hydromatic', module: 'optiq-core'
+				// transitive = false
+			}
+
+			archiveBypass "commons*.jar"
+			archiveExclude "slf4j*.jar"
+
+			classDelete "org.apache.thrift.**"
+
+			classRename 'org.json.**', 'org.anarres.hive.json.@1'
+			classRename 'org.iq80.**', 'org.anarres.hive.iq80.@1'
+			classRename 'org.codehaus.jackson.**', 'org.anarres.hive.jackson.@1'
+			classRename 'com.google.**', 'org.anarres.hive.google.@1'
+			classRename 'javolution.**', 'org.anarres.hive.javolution.@1'
+			classRename 'com.esotericsoftware.kryo.**', 'org.anarres.hive.kryo.@1'
+		}
+	}
+
+}
+
+project(':sub2') {
+
+	dependencies {
+		compile jarjar.repackage {
+			from gradleApi()
+
+			archiveBypass "commons*.jar"
+			archiveExclude "slf4j*.jar"
+
+		}
+	}
+
+}
+
+/*
+project(':sub3') {
+	dependencies {
+		compile jarjar.dependency('org.apache.hive:hive-exec:0.13.0.2.1.5.0-695') {
+		}
+	}
+}
+*/
+
diff --git a/third_party/java/jarjar/jarjar-gradle/example/settings.gradle b/third_party/java/jarjar/jarjar-gradle/example/settings.gradle
new file mode 100644
index 0000000..c0fb16e
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/example/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name='jarjar-gradle-example'
+include 'sub0', 'sub1', 'sub2' // , 'sub3'
diff --git a/third_party/java/jarjar/jarjar-gradle/example/src/main/java/org/anarres/jarjar/test/Main.java b/third_party/java/jarjar/jarjar-gradle/example/src/main/java/org/anarres/jarjar/test/Main.java
new file mode 100644
index 0000000..6d88240
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/example/src/main/java/org/anarres/jarjar/test/Main.java
@@ -0,0 +1,7 @@
+package org.anarres.jarjar.test;
+
+public class Main {
+	public static void main(String[] args) {
+		System.out.println("Hello, world.");
+	}
+}
diff --git a/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarArchiveTask.java b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarArchiveTask.java
new file mode 100644
index 0000000..8824b4a
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarArchiveTask.java
@@ -0,0 +1,32 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.gradle.plugin.jarjar;
+
+import groovy.lang.Closure;
+import javax.annotation.Nonnull;
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.file.copy.CopyAction;
+import org.gradle.api.tasks.bundling.Jar;
+
+/**
+ *
+ * @author shevek
+ */
+public class JarjarArchiveTask extends Jar {
+
+    @Override
+    protected CopyAction createCopyAction() {
+        DocumentationRegistry documentationRegistry = getServices().get(DocumentationRegistry.class);
+        return new JarjarCopyAction(getArchivePath(), getCompressor(), documentationRegistry);
+    }
+
+    public void fromJar(@Nonnull Object... sourcePaths) {
+    }
+
+    public void fromJar(@Nonnull Object sourcePath, @Nonnull Closure c) {
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarController.java b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarController.java
new file mode 100644
index 0000000..e7913ca
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarController.java
@@ -0,0 +1,60 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.gradle.plugin.jarjar;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyObjectSupport;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Nonnull;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This object appears as 'jarjar' in the project extensions.
+ *
+ * @author shevek
+ */
+public class JarjarController extends GroovyObjectSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JarjarController.class);
+    private static final AtomicInteger SEQ = new AtomicInteger(0);
+    private final Project project;
+
+    public JarjarController(@Nonnull Project project) {
+        this.project = project;
+    }
+
+    @Nonnull
+    public Dependency dependency(Object dependencyNotation, Closure configurationClosure) {
+        Dependency d = project.getDependencies().create(dependencyNotation, configurationClosure);
+        LOG.info("sub is " + d);
+        return new JarjarDependency(d);
+    }
+
+    @Nonnull
+    public Dependency dependency(Object dependencyNotation) {
+        return dependency(dependencyNotation, Closure.IDENTITY);
+    }
+
+    @Nonnull
+    public FileCollection repackage(@Nonnull String name, @Nonnull Closure<?> c) {
+        JarjarTask jarjar = project.getTasks().create(
+                name,
+                JarjarTask.class,
+                new ClosureBackedAction<JarjarTask>(c));
+        return jarjar.getOutputs().getFiles();
+    }
+
+    @Nonnull
+    public FileCollection repackage(@Nonnull Closure<?> c) {
+        return repackage("jarjar-" + SEQ.getAndIncrement(), c);
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarCopyAction.java b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarCopyAction.java
new file mode 100644
index 0000000..8dd0263
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarCopyAction.java
@@ -0,0 +1,137 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.gradle.plugin.jarjar;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import javax.annotation.Nonnull;
+import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
+import org.apache.commons.compress.archivers.jar.JarArchiveOutputStream;
+import org.apache.commons.compress.archivers.zip.UnixStat;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
+import org.gradle.api.internal.file.copy.CopyAction;
+import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
+import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
+import org.gradle.api.internal.file.copy.ZipCompressor;
+import org.gradle.api.internal.tasks.SimpleWorkResult;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.bundling.Zip;
+import org.gradle.api.tasks.bundling.internal.Zip64RequiredException;
+import org.gradle.internal.IoActions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Based on ZipCopyAction from Gradle sources.
+ *
+ * @author shevek
+ */
+public class JarjarCopyAction implements CopyAction {
+
+    private static final Logger LOG = LoggerFactory.getLogger(JarjarCopyAction.class);
+    private final File zipFile;
+    // private final ZipCompressor compressor;
+    private final DocumentationRegistry documentationRegistry;
+
+    public JarjarCopyAction(@Nonnull File zipFile, @Nonnull ZipCompressor compressor, @Nonnull DocumentationRegistry documentationRegistry) {
+        this.zipFile = zipFile;
+        // this.compressor = compressor;
+        this.documentationRegistry = documentationRegistry;
+    }
+
+    @Nonnull
+    @Override
+    public WorkResult execute(@Nonnull final CopyActionProcessingStream stream) {
+        LOG.info("CopyAction Executing  " + stream);
+
+        stream.process(new ScanAction());
+
+        final JarArchiveOutputStream zipOutStr;
+
+        try {
+            zipOutStr = new JarArchiveOutputStream(new FileOutputStream(zipFile));
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not create ZIP '%s'.", zipFile), e);
+        }
+
+        try {
+            IoActions.withResource(zipOutStr, new Action<JarArchiveOutputStream>() {
+                @Override
+                public void execute(@Nonnull JarArchiveOutputStream outputStream) {
+                    stream.process(new ProcessAction(outputStream));
+                }
+            });
+        } catch (UncheckedIOException e) {
+            if (e.getCause() instanceof Zip64RequiredException) {
+                throw new org.gradle.api.tasks.bundling.internal.Zip64RequiredException(
+                        String.format("%s\n\nTo build this archive, please enable the zip64 extension.\nSee: %s", e.getCause().getMessage(), documentationRegistry.getDslRefForProperty(Zip.class, "zip64"))
+                );
+            }
+        }
+
+        return new SimpleWorkResult(true);
+    }
+
+    private class ScanAction implements CopyActionProcessingStreamAction {
+
+        @Override
+        public void processFile(FileCopyDetailsInternal details) {
+            LOG.info("CopyAction Scanning " + details);
+        }
+    }
+
+    private class ProcessAction implements CopyActionProcessingStreamAction {
+
+        private final JarArchiveOutputStream zipOutStr;
+
+        public ProcessAction(@Nonnull JarArchiveOutputStream zipOutStr) {
+            this.zipOutStr = zipOutStr;
+        }
+
+        @Override
+        public void processFile(@Nonnull FileCopyDetailsInternal details) {
+            LOG.info("CopyAction Processing " + details);
+
+            if (details.isDirectory()) {
+                visitDir(details);
+            } else {
+                visitFile(details);
+            }
+        }
+
+        private void visitFile(@Nonnull FileCopyDetails fileDetails) {
+            try {
+                JarArchiveEntry archiveEntry = new JarArchiveEntry(fileDetails.getRelativePath().getPathString());
+                archiveEntry.setTime(fileDetails.getLastModified());
+                archiveEntry.setUnixMode(UnixStat.FILE_FLAG | fileDetails.getMode());
+                zipOutStr.putArchiveEntry(archiveEntry);
+                fileDetails.copyTo(zipOutStr);
+                zipOutStr.closeArchiveEntry();
+            } catch (Exception e) {
+                throw new GradleException(String.format("Could not add %s to ZIP '%s'.", fileDetails, zipFile), e);
+            }
+        }
+
+        private void visitDir(@Nonnull FileCopyDetails dirDetails) {
+            try {
+                // Trailing slash in name indicates that entry is a directory
+                JarArchiveEntry archiveEntry = new JarArchiveEntry(dirDetails.getRelativePath().getPathString() + '/');
+                archiveEntry.setTime(dirDetails.getLastModified());
+                archiveEntry.setUnixMode(UnixStat.DIR_FLAG | dirDetails.getMode());
+                zipOutStr.putArchiveEntry(archiveEntry);
+                zipOutStr.closeArchiveEntry();
+            } catch (Exception e) {
+                throw new GradleException(String.format("Could not add %s to ZIP '%s'.", dirDetails, zipFile), e);
+            }
+        }
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarDependency.java b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarDependency.java
new file mode 100644
index 0000000..50e292f
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarDependency.java
@@ -0,0 +1,67 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.gradle.plugin.jarjar;
+
+import java.util.Collections;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.gradle.api.Buildable;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.artifacts.ResolvableDependency;
+import org.gradle.api.tasks.TaskDependency;
+
+/**
+ *
+ * @author shevek
+ */
+public class JarjarDependency implements /* ResolvableDependency, */ Dependency, Buildable {
+
+    private final Dependency delegate;
+
+    public JarjarDependency(@Nonnull Dependency delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public String getGroup() {
+        return delegate.getGroup();
+    }
+
+    @Override
+    public String getName() {
+        return delegate.getName();
+    }
+
+    @Override
+    public String getVersion() {
+        return delegate.getVersion();
+    }
+
+    @Override
+    public boolean contentEquals(Dependency d) {
+        while (d instanceof JarjarDependency)
+            d = ((JarjarDependency) d).delegate;
+        return delegate.contentEquals(d);
+    }
+
+    @Override
+    public Dependency copy() {
+        return new JarjarDependency(delegate);
+    }
+
+    @Override
+    public TaskDependency getBuildDependencies() {
+        if (delegate instanceof Buildable)
+            return ((Buildable) delegate).getBuildDependencies();
+        return new TaskDependency() {
+            @Override
+            public Set<? extends Task> getDependencies(Task task) {
+                return Collections.emptySet();
+            }
+        };
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarPlugin.java b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarPlugin.java
new file mode 100644
index 0000000..93c3903
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarPlugin.java
@@ -0,0 +1,41 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.gradle.plugin.jarjar;
+
+import groovy.lang.Closure;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ClosureBackedAction;
+import static org.bouncycastle.asn1.x500.style.RFC4519Style.c;
+import static org.gradle.internal.Transformers.name;
+
+/**
+ *
+ * @author shevek
+ */
+public class JarjarPlugin implements Plugin<Project> {
+
+    @Override
+    public void apply(final Project project) {
+        project.getLogger().info("Applying " + this);
+        // project.getExtensions().getExtraProperties().set("Jarjar", JarjarTask.class);
+        /*
+         project.getExtensions().getExtraProperties().set("jarjarDependency", new Closure<FileCollection>(JarjarPlugin.this) {
+         @Override
+         public FileCollection call(Object... args) {
+         JarjarTask jarjar = project.getTasks().create(
+         name,
+         JarjarTask.class,
+         new ClosureBackedAction<JarjarTask>(c));
+         return jarjar.getOutputs().getFiles();
+         }
+         });
+         */
+        project.getExtensions().create("jarjar", JarjarController.class, project);
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarTask.java b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarTask.java
new file mode 100644
index 0000000..9d863ca
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/src/main/java/org/anarres/gradle/plugin/jarjar/JarjarTask.java
@@ -0,0 +1,232 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.gradle.plugin.jarjar;
+
+import com.tonicsystems.jarjar.classpath.ClassPath;
+import com.tonicsystems.jarjar.transform.JarTransformer;
+import com.tonicsystems.jarjar.transform.config.ClassKeepTransitive;
+import com.tonicsystems.jarjar.transform.config.ClassDelete;
+import com.tonicsystems.jarjar.transform.config.ClassRename;
+import com.tonicsystems.jarjar.transform.jar.DefaultJarProcessor;
+import groovy.lang.Closure;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.apache.oro.text.GlobCompiler;
+import org.apache.oro.text.regex.MalformedPatternException;
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.Perl5Matcher;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.OutputFiles;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.TaskOutputs;
+
+/**
+ *
+ * @author shevek
+ */
+public class JarjarTask extends ConventionTask {
+
+    private class FilterSpec implements Spec<File> {
+
+        private final String message;
+        private final Iterable<? extends Pattern> patterns;
+        private final boolean result;
+
+        public FilterSpec(@Nonnull String message, @Nonnull Iterable<? extends Pattern> patterns, boolean result) {
+            this.message = message;
+            this.patterns = patterns;
+            this.result = result;
+        }
+
+        @Override
+        public boolean isSatisfiedBy(File t) {
+            if (matchesAny(patterns, t.getName())) {
+                getLogger().info(message + " " + t);
+                return result;
+            }
+            return !result;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + "(patterns=" + patterns + ")";
+        }
+    }
+
+    private static final Perl5Matcher globMatcher = new Perl5Matcher();
+
+    private static boolean matchesAny(@Nonnull Iterable<? extends Pattern> patterns, @Nonnull String text) {
+        for (Pattern pattern : patterns) {
+            if (globMatcher.matches(text, pattern)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Nonnull
+    private static Iterable<Pattern> toPatterns(@Nonnull Iterable<? extends String>... patterns) throws MalformedPatternException {
+        GlobCompiler compiler = new GlobCompiler();
+        List<Pattern> out = new ArrayList<Pattern>();
+        for (Iterable<? extends String> in : patterns)
+            for (String pattern : in)
+                out.add(compiler.compile(pattern));
+        return out;
+    }
+
+    private final ConfigurableFileCollection sourceFiles;
+    private final Set<String> archiveBypasses = new HashSet<String>();
+    private final Set<String> archiveExcludes = new HashSet<String>();
+    private File destinationDir;
+    private String destinationName;
+
+    private final DefaultJarProcessor processor = new DefaultJarProcessor();
+
+    public JarjarTask() {
+        sourceFiles = getProject().files();
+    }
+
+    @InputFiles
+    public FileCollection getSourceFiles() {
+        return sourceFiles;
+    }
+
+    /**
+     * Returns the directory where the archive is generated into.
+     *
+     * @return the directory
+     */
+    public File getDestinationDir() {
+        File out = destinationDir;
+        if (out == null)
+            out = new File(getProject().getBuildDir(), "jarjar");
+        return out;
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    /**
+     * Returns the file name of the generated archive.
+     *
+     * @return the name
+     */
+    public String getDestinationName() {
+        String out = destinationName;
+        if (out == null)
+            out = getName() + ".jar";
+        return out;
+    }
+
+    public void setDestinationName(String destinationName) {
+        this.destinationName = destinationName;
+    }
+
+    /**
+     * The path where the archive is constructed.
+     * The path is simply the {@code destinationDir} plus the {@code destinationName}.
+     *
+     * @return a File object with the path to the archive
+     */
+    @OutputFile
+    public File getDestinationPath() {
+        return new File(getDestinationDir(), getDestinationName());
+    }
+
+    @OutputFiles
+    public FileCollection getBypassedArchives() throws MalformedPatternException {
+        return sourceFiles.filter(new FilterSpec("Bypassing archive", toPatterns(archiveBypasses), true));
+    }
+
+    /**
+     * Processes a FileCollection, which may be simple, a {@link Configuration},
+     * or derived from a {@link TaskOutputs}.
+     *
+     * @param files The input FileCollection to consume.
+     */
+    public void from(@Nonnull FileCollection files) {
+        sourceFiles.from(files);
+    }
+
+    /**
+     * Processes a Dependency directly, which may be derived from
+     * {@link DependencyHandler#create(java.lang.Object)},
+     * {@link DependencyHandler#project(java.util.Map)},
+     * {@link DependencyHandler#module(java.lang.Object)},
+     * {@link DependencyHandler#gradleApi()}, etc.
+     *
+     * @param dependency The dependency to process.
+     */
+    public void from(@Nonnull Dependency dependency) {
+        Configuration configuration = getProject().getConfigurations().detachedConfiguration(dependency);
+        from(configuration);
+    }
+
+    /**
+     * Processes a dependency specified by name.
+     *
+     * @param dependencyNotation The dependency, in a notation described in {@link DependencyHandler}.
+     * @param configClosure The closure to use to configure the dependency.
+     * @see DependencyHandler
+     */
+    public void from(@Nonnull String dependencyNotation, Closure configClosure) {
+        from(getProject().getDependencies().create(dependencyNotation, configClosure));
+    }
+
+    /**
+     * Processes a dependency specified by name.
+     *
+     * @param dependencyNotation The dependency, in a notation described in {@link DependencyHandler}.
+     */
+    public void from(@Nonnull String dependencyNotation) {
+        from(getProject().getDependencies().create(dependencyNotation));
+    }
+
+    public void archiveBypass(@Nonnull String pattern) throws MalformedPatternException {
+        archiveBypasses.add(pattern);
+    }
+
+    public void archiveExclude(@Nonnull String pattern) throws MalformedPatternException {
+        archiveExcludes.add(pattern);
+    }
+
+    public void classRename(@Nonnull String pattern, @Nonnull String replacement) {
+        processor.addClassRename(new ClassRename(pattern, replacement));
+    }
+
+    public void classDelete(@Nonnull String pattern) {
+        processor.addClassDelete(new ClassDelete(pattern));
+    }
+
+    public void classClosureRoot(@Nonnull String pattern) {
+        processor.addClassKeepTransitive(new ClassKeepTransitive(pattern));
+    }
+
+    @TaskAction
+    public void run() throws Exception {
+        FileCollection inputFiles = sourceFiles.filter(new FilterSpec("Excluding archive", toPatterns(archiveBypasses, archiveExcludes), false));
+        final File outputFile = getDestinationPath();
+        outputFile.getParentFile().mkdirs();
+        getLogger().info("Running jarjar for {}", outputFile);
+        getLogger().info("Inputs are {}", inputFiles);
+
+        JarTransformer transformer = new JarTransformer(outputFile, processor);
+        transformer.transform(new ClassPath(getProject().getProjectDir(), inputFiles));
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-gradle/src/test/java/org/anarres/gradle/plugin/jarjar/JarjarDependencyTest.java b/third_party/java/jarjar/jarjar-gradle/src/test/java/org/anarres/gradle/plugin/jarjar/JarjarDependencyTest.java
new file mode 100644
index 0000000..b6a8271
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-gradle/src/test/java/org/anarres/gradle/plugin/jarjar/JarjarDependencyTest.java
@@ -0,0 +1,21 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.gradle.plugin.jarjar;
+
+import org.junit.Test;
+// import static org.junit.Assert.*;
+
+/**
+ *
+ * @author shevek
+ */
+public class JarjarDependencyTest {
+
+    @Test
+    public void testTask() {
+    }
+
+}
diff --git a/third_party/java/jarjar/jarjar-maven/build.gradle b/third_party/java/jarjar/jarjar-maven/build.gradle
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-maven/build.gradle
diff --git a/third_party/java/jarjar/jarjar-maven/src/main/java/com/tonicsystems/jarjar/JarJarMojo.java b/third_party/java/jarjar/jarjar-maven/src/main/java/com/tonicsystems/jarjar/JarJarMojo.java
new file mode 100644
index 0000000..140cb1f
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-maven/src/main/java/com/tonicsystems/jarjar/JarJarMojo.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.tonicsystems.jarjar;
+
+import com.tonicsystems.jarjar.classpath.ClassPath;
+import com.tonicsystems.jarjar.transform.jar.DefaultJarProcessor;
+import com.tonicsystems.jarjar.transform.config.RulesFileParser;
+import com.tonicsystems.jarjar.transform.JarTransformer;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+
+public class JarJarMojo extends AbstractMojo {
+
+    private File fromJar;
+    private File toJar;
+    private File rulesFile;
+    private String rules;
+    @Deprecated // Maven might need this for compatibility.
+    private boolean verbose;
+
+    @Override
+    public void execute() throws MojoExecutionException {
+        if (!((rulesFile == null || !rulesFile.exists()) ^ (rules == null)))
+            throw new MojoExecutionException("Exactly one of rules or rulesFile is required");
+
+        try {
+            DefaultJarProcessor processor = new DefaultJarProcessor();
+            if (rules != null) {
+                RulesFileParser.parse(processor, rules);
+            } else {
+                RulesFileParser.parse(processor, rulesFile);
+            }
+            // TODO: refactor with Main.java
+            JarTransformer transformer = new JarTransformer(toJar, processor);
+            ClassPath fromClassPath = new ClassPath(new File(System.getProperty("user.dir")), Collections.singleton(fromJar));
+            transformer.transform(fromClassPath);
+        } catch (IOException e) {
+            throw new MojoExecutionException(e.getMessage(), e);
+        }
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg0/Main.java b/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg0/Main.java
new file mode 100644
index 0000000..97608f6
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg0/Main.java
@@ -0,0 +1,21 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.jarjar.testdata.pkg0;
+
+import org.anarres.jarjar.testdata.pkg1.Cls1;
+
+/**
+ *
+ * @author shevek
+ */
+public class Main {
+
+    public static void main(String[] args) {
+        Cls1.m_s();
+        Cls1 c = new Cls1();
+        c.m_d();
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg1/Cls1.java b/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg1/Cls1.java
new file mode 100644
index 0000000..51daf8f
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg1/Cls1.java
@@ -0,0 +1,24 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.jarjar.testdata.pkg1;
+
+import org.anarres.jarjar.testdata.pkg2.Cls2;
+
+/**
+ *
+ * @author shevek
+ */
+public class Cls1 {
+
+    public void m_d() {
+        Cls2 c = new Cls2();
+        c.m_d();
+    }
+
+    public static void m_s() {
+        Cls2.m_s();
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg2/Cls2.java b/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg2/Cls2.java
new file mode 100644
index 0000000..1125b56
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg2/Cls2.java
@@ -0,0 +1,24 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.jarjar.testdata.pkg2;
+
+import org.anarres.jarjar.testdata.pkg3.Cls3;
+
+/**
+ *
+ * @author shevek
+ */
+public class Cls2 {
+
+    public void m_d() {
+        Cls3 c = new Cls3();
+        c.m_d();
+    }
+
+    public static void m_s() {
+        Cls3.m_s();
+    }
+}
diff --git a/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg3/Cls3.java b/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg3/Cls3.java
new file mode 100644
index 0000000..637e0a1
--- /dev/null
+++ b/third_party/java/jarjar/jarjar-testdata/src/main/java/org/anarres/jarjar/testdata/pkg3/Cls3.java
@@ -0,0 +1,19 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.anarres.jarjar.testdata.pkg3;
+
+/**
+ *
+ * @author shevek
+ */
+public class Cls3 {
+
+    public void m_d() {
+    }
+
+    public static void m_s() {
+    }
+}
diff --git a/third_party/java/jarjar/settings.gradle b/third_party/java/jarjar/settings.gradle
new file mode 100644
index 0000000..e5e182a
--- /dev/null
+++ b/third_party/java/jarjar/settings.gradle
@@ -0,0 +1,7 @@
+rootProject.name='jarjar'
+include 'jarjar-testdata',
+	'jarjar-core',
+	'jarjar-ant',
+	'jarjar-maven',
+	'jarjar-gradle',
+	'jarjar-command'
diff --git a/third_party/java/jarjar/src/main/ghpages/index.html b/third_party/java/jarjar/src/main/ghpages/index.html
new file mode 100644
index 0000000..32292c3
--- /dev/null
+++ b/third_party/java/jarjar/src/main/ghpages/index.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+<a href="docs/javadoc/">Javadoc</a>
+<a href="docs/cobertura/">Coverage</a>
+</body>
+</html>