summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Jarjar.iml23
-rw-r--r--src/main/com/tonicsystems/jarjar/AbstractDepHandler.java56
-rw-r--r--src/main/com/tonicsystems/jarjar/DepFind.java80
-rw-r--r--src/main/com/tonicsystems/jarjar/DepFindVisitor.java71
-rw-r--r--src/main/com/tonicsystems/jarjar/DepHandler.java29
-rw-r--r--src/main/com/tonicsystems/jarjar/EmptyClassVisitor.java50
-rw-r--r--src/main/com/tonicsystems/jarjar/ExcludeProcessor.java40
-rw-r--r--src/main/com/tonicsystems/jarjar/JarJarMojo.java53
-rw-r--r--src/main/com/tonicsystems/jarjar/JarJarTask.java61
-rw-r--r--src/main/com/tonicsystems/jarjar/Keep.java21
-rw-r--r--src/main/com/tonicsystems/jarjar/KeepProcessor.java112
-rw-r--r--src/main/com/tonicsystems/jarjar/Main.java97
-rw-r--r--src/main/com/tonicsystems/jarjar/MainProcessor.java106
-rw-r--r--src/main/com/tonicsystems/jarjar/MainUtil.java86
-rw-r--r--src/main/com/tonicsystems/jarjar/ManifestProcessor.java38
-rw-r--r--src/main/com/tonicsystems/jarjar/PackageRemapper.java128
-rw-r--r--src/main/com/tonicsystems/jarjar/PathClass.java40
-rw-r--r--src/main/com/tonicsystems/jarjar/PatternElement.java44
-rw-r--r--src/main/com/tonicsystems/jarjar/ResourceProcessor.java37
-rw-r--r--src/main/com/tonicsystems/jarjar/Rule.java30
-rw-r--r--src/main/com/tonicsystems/jarjar/RulesFileParser.java81
-rw-r--r--src/main/com/tonicsystems/jarjar/StringDumper.java96
-rw-r--r--src/main/com/tonicsystems/jarjar/StringReader.java100
-rw-r--r--src/main/com/tonicsystems/jarjar/TextDepHandler.java34
-rw-r--r--src/main/com/tonicsystems/jarjar/Wildcard.java143
-rw-r--r--src/main/com/tonicsystems/jarjar/Zap.java21
-rw-r--r--src/main/com/tonicsystems/jarjar/ZapProcessor.java47
-rw-r--r--src/main/com/tonicsystems/jarjar/util/AntJarProcessor.java107
-rw-r--r--src/main/com/tonicsystems/jarjar/util/ClassHeaderReader.java186
-rw-r--r--src/main/com/tonicsystems/jarjar/util/ClassPathEntry.java26
-rw-r--r--src/main/com/tonicsystems/jarjar/util/ClassPathIterator.java234
-rw-r--r--src/main/com/tonicsystems/jarjar/util/EntryStruct.java27
-rw-r--r--src/main/com/tonicsystems/jarjar/util/GetNameClassWriter.java43
-rw-r--r--src/main/com/tonicsystems/jarjar/util/IoUtil.java131
-rw-r--r--src/main/com/tonicsystems/jarjar/util/JarProcessor.java35
-rw-r--r--src/main/com/tonicsystems/jarjar/util/JarProcessorChain.java48
-rw-r--r--src/main/com/tonicsystems/jarjar/util/JarTransformer.java47
-rw-r--r--src/main/com/tonicsystems/jarjar/util/JarTransformerChain.java36
-rw-r--r--src/main/com/tonicsystems/jarjar/util/RemappingClassTransformer.java34
-rw-r--r--src/main/com/tonicsystems/jarjar/util/RuntimeIOException.java28
-rw-r--r--src/main/com/tonicsystems/jarjar/util/StandaloneJarProcessor.java71
-rw-r--r--src/test/Generics.classbin0 -> 575 bytes
-rw-r--r--src/test/com/tonicsystems/jarjar/GenericsTest.java44
-rw-r--r--src/test/com/tonicsystems/jarjar/PackageRemapperTest.java59
-rw-r--r--src/test/com/tonicsystems/jarjar/RulesFileParserTest.java36
-rw-r--r--src/test/com/tonicsystems/jarjar/WildcardTest.java48
-rw-r--r--src/test/com/tonicsystems/jarjar/example/Example.java5
-rw-r--r--src/test/enumtest.jarbin0 -> 1324 bytes
48 files changed, 2969 insertions, 0 deletions
diff --git a/src/Jarjar.iml b/src/Jarjar.iml
new file mode 100644
index 0000000..70f1faf
--- /dev/null
+++ b/src/Jarjar.iml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/main" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="lib" level="project" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MAVEN_REPOSITORY$/org/apache/ant/ant/1.7.1/ant-1.7.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ </component>
+</module>
+
diff --git a/src/main/com/tonicsystems/jarjar/AbstractDepHandler.java b/src/main/com/tonicsystems/jarjar/AbstractDepHandler.java
new file mode 100644
index 0000000..5a54737
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/AbstractDepHandler.java
@@ -0,0 +1,56 @@
+/**
+ * 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.util.*;
+import java.io.*;
+import java.util.*;
+
+abstract public class AbstractDepHandler implements DepHandler
+{
+ protected final int level;
+ private final Set<List<Object>> seenIt = new HashSet<List<Object>>();
+
+ protected AbstractDepHandler(int level) {
+ this.level = level;
+ }
+
+ public void handle(PathClass from, PathClass to) throws IOException {
+ List<Object> pair;
+ if (level == LEVEL_JAR) {
+ pair = createPair(from.getClassPath(), to.getClassPath());
+ } else {
+ pair = createPair(from.getClassName(), to.getClassName());
+ }
+ if (!seenIt.contains(pair)) {
+ seenIt.add(pair);
+ handle(pair.get(0).toString(), pair.get(1).toString());
+ }
+ }
+
+ abstract protected void handle(String from, String to) throws IOException;
+
+ public void handleStart() throws IOException { }
+ public void handleEnd() throws IOException { }
+
+ private static List<Object> createPair(Object o1, Object o2) {
+ List<Object> list = new ArrayList<Object>(2);
+ list.add(o1);
+ list.add(o2);
+ return list;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/DepFind.java b/src/main/com/tonicsystems/jarjar/DepFind.java
new file mode 100644
index 0000000..40f68ff
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/DepFind.java
@@ -0,0 +1,80 @@
+/**
+ * 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.util.*;
+import java.io.*;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+
+public class DepFind
+{
+ private File curDir = new File(System.getProperty("user.dir"));
+
+ public void setCurrentDirectory(File curDir) {
+ this.curDir = curDir;
+ }
+
+ public void run(String from, String to, DepHandler handler) throws IOException {
+ try {
+ ClassHeaderReader header = new ClassHeaderReader();
+ Map<String, String> classes = new HashMap<String, String>();
+ ClassPathIterator cp = new ClassPathIterator(curDir, to, null);
+ try {
+ while (cp.hasNext()) {
+ ClassPathEntry entry = cp.next();
+ InputStream in = entry.openStream();
+ try {
+ header.read(in);
+ classes.put(header.getClassName(), entry.getSource());
+ } catch (Exception e) {
+ System.err.println("Error reading " + entry.getName() + ": " + e.getMessage());
+ } finally {
+ in.close();
+ }
+ }
+ } finally {
+ cp.close();
+ }
+
+ handler.handleStart();
+ cp = new ClassPathIterator(curDir, from, null);
+ try {
+ while (cp.hasNext()) {
+ ClassPathEntry entry = cp.next();
+ InputStream in = entry.openStream();
+ try {
+ new ClassReader(in).accept(
+ new DepFindVisitor(classes, entry.getSource(), handler),
+ ClassReader.SKIP_DEBUG);
+ } catch (Exception e) {
+ System.err.println("Error reading " + entry.getName() + ": " + e.getMessage());
+ } finally {
+ in.close();
+ }
+ }
+ } finally {
+ cp.close();
+ }
+ handler.handleEnd();
+ } catch (RuntimeIOException e) {
+ throw (IOException)e.getCause();
+ }
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/DepFindVisitor.java b/src/main/com/tonicsystems/jarjar/DepFindVisitor.java
new file mode 100644
index 0000000..9d6611e
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/DepFindVisitor.java
@@ -0,0 +1,71 @@
+/**
+ * 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.util.*;
+import java.io.*;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.objectweb.asm.*;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.*;
+
+class DepFindVisitor extends RemappingClassAdapter
+{
+ public DepFindVisitor(Map<String, String> classes, String source, DepHandler handler) throws IOException {
+ super(null, new DepFindRemapper(classes, source, handler));
+ }
+
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ ((DepFindRemapper)remapper).setClassName(name);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ private static class DepFindRemapper extends Remapper
+ {
+ private final Map<String, String> classes;
+ private final String source;
+ private final DepHandler handler;
+ private PathClass curPathClass;
+
+ public DepFindRemapper(Map<String, String> classes, String source, DepHandler handler) throws IOException {
+ this.classes = classes;
+ this.source = source;
+ this.handler = handler;
+ }
+
+ public void setClassName(String name) {
+ curPathClass = new PathClass(source, name);
+ }
+
+ public String map(String key) {
+ try {
+ if (classes.containsKey(key)) {
+ String otherSource = classes.get(key);
+ if (!source.equals(otherSource)) {
+ // TODO: some escape mechanism?
+ handler.handle(curPathClass, new PathClass(otherSource, key));
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/DepHandler.java b/src/main/com/tonicsystems/jarjar/DepHandler.java
new file mode 100644
index 0000000..33107d4
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/DepHandler.java
@@ -0,0 +1,29 @@
+/**
+ * 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 java.io.IOException;
+
+public interface DepHandler
+{
+ public static final int LEVEL_CLASS = 0;
+ public static final int LEVEL_JAR = 1;
+
+ void handleStart() throws IOException;
+ void handle(PathClass from, PathClass to) throws IOException;
+ void handleEnd() throws IOException;
+}
diff --git a/src/main/com/tonicsystems/jarjar/EmptyClassVisitor.java b/src/main/com/tonicsystems/jarjar/EmptyClassVisitor.java
new file mode 100644
index 0000000..348e03d
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/EmptyClassVisitor.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2003-2010 the original author or authors.
+ *
+ * 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.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * An ASM3 EmptyVisitor replacement
+ * @author <a href="mailto:blackdrag@gmx.org">Jochen "blackdrag" Theodorou</a>
+ */
+public class EmptyClassVisitor extends ClassVisitor {
+
+ public EmptyClassVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ return new MethodVisitor(Opcodes.ASM4) {};
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return new AnnotationVisitor(Opcodes.ASM4) {};
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+ return new FieldVisitor(Opcodes.ASM4) {};
+ }
+
+}
diff --git a/src/main/com/tonicsystems/jarjar/ExcludeProcessor.java b/src/main/com/tonicsystems/jarjar/ExcludeProcessor.java
new file mode 100644
index 0000000..191803f
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/ExcludeProcessor.java
@@ -0,0 +1,40 @@
+/**
+ * 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.util.*;
+import java.io.IOException;
+import java.util.*;
+
+class ExcludeProcessor implements JarProcessor
+{
+ private final Set<String> excludes;
+ private final boolean verbose;
+
+ public ExcludeProcessor(Set<String> excludes, boolean verbose) {
+ this.excludes = excludes;
+ this.verbose = verbose;
+ }
+
+ public boolean process(EntryStruct struct) throws IOException {
+ boolean toKeep = !excludes.contains(struct.name);
+ if (verbose && !toKeep)
+ System.err.println("Excluding " + struct.name);
+ return toKeep;
+ }
+}
+
diff --git a/src/main/com/tonicsystems/jarjar/JarJarMojo.java b/src/main/com/tonicsystems/jarjar/JarJarMojo.java
new file mode 100644
index 0000000..9d37b77
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/JarJarMojo.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;
+
+import com.tonicsystems.jarjar.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+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;
+ private boolean verbose;
+
+ public void execute() throws MojoExecutionException {
+ if (!((rulesFile == null || !rulesFile.exists()) ^ (rules == null)))
+ throw new MojoExecutionException("Exactly one of rules or rulesFile is required");
+
+ try {
+ List<PatternElement> patterns;
+ if (rules != null) {
+ patterns = RulesFileParser.parse(rules);
+ } else {
+ patterns = RulesFileParser.parse(rulesFile);
+ }
+ // TODO: refactor with Main.java
+ MainProcessor proc = new MainProcessor(patterns, verbose, true);
+ StandaloneJarProcessor.run(fromJar, toJar, proc);
+ proc.strip(toJar);
+ } catch (IOException e) {
+ throw new MojoExecutionException(e.getMessage(), e);
+ }
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/JarJarTask.java b/src/main/com/tonicsystems/jarjar/JarJarTask.java
new file mode 100644
index 0000000..2c183c9
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/JarJarTask.java
@@ -0,0 +1,61 @@
+/**
+ * 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.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import org.apache.tools.ant.BuildException;
+
+public class JarJarTask extends AntJarProcessor
+{
+ private List<PatternElement> patterns = new ArrayList<PatternElement>();
+
+ public void addConfiguredRule(Rule rule) {
+ if (rule.getPattern() == null || rule.getResult() == null)
+ throw new IllegalArgumentException("The <rule> element requires both \"pattern\" and \"result\" attributes.");
+ patterns.add(rule);
+ }
+
+ public void addConfiguredZap(Zap zap) {
+ if (zap.getPattern() == null)
+ throw new IllegalArgumentException("The <zap> element requires a \"pattern\" attribute.");
+ patterns.add(zap);
+ }
+
+ public void addConfiguredKeep(Keep keep) {
+ if (keep.getPattern() == null)
+ throw new IllegalArgumentException("The <keep> element requires a \"pattern\" attribute.");
+ patterns.add(keep);
+ }
+
+ public void execute() throws BuildException {
+ MainProcessor proc = new MainProcessor(patterns, verbose, false);
+ execute(proc);
+ try {
+ proc.strip(getDestFile());
+ } catch (IOException e) {
+ throw new BuildException(e);
+ }
+ }
+
+ protected void cleanHelper() {
+ super.cleanHelper();
+ patterns.clear();
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/Keep.java b/src/main/com/tonicsystems/jarjar/Keep.java
new file mode 100644
index 0000000..14b719e
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/Keep.java
@@ -0,0 +1,21 @@
+/**
+ * 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;
+
+public class Keep extends PatternElement
+{
+}
diff --git a/src/main/com/tonicsystems/jarjar/KeepProcessor.java b/src/main/com/tonicsystems/jarjar/KeepProcessor.java
new file mode 100644
index 0000000..0176b84
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/KeepProcessor.java
@@ -0,0 +1,112 @@
+/**
+ * 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.util.*;
+import java.io.*;
+import java.util.*;
+import org.objectweb.asm.*;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.*;
+
+// TODO: this can probably be refactored into JarClassVisitor, etc.
+class KeepProcessor extends Remapper implements JarProcessor
+{
+ private final ClassVisitor cv = new RemappingClassAdapter(new EmptyClassVisitor(), this);
+ private final List<Wildcard> wildcards;
+ private final List<String> roots = new ArrayList<String>();
+ private final Map<String, Set<String>> depend = new HashMap<String, Set<String>>();
+
+ public KeepProcessor(List<Keep> patterns) {
+ wildcards = PatternElement.createWildcards(patterns);
+ }
+
+ public boolean isEnabled() {
+ return !wildcards.isEmpty();
+ }
+
+ public Set<String> getExcludes() {
+ Set<String> closure = new HashSet<String>();
+ closureHelper(closure, roots);
+ Set<String> removable = new HashSet<String>(depend.keySet());
+ removable.removeAll(closure);
+ return removable;
+ }
+
+ private void closureHelper(Set<String> closure, Collection<String> process) {
+ if (process == null)
+ return;
+ for (String name : process) {
+ if (closure.add(name))
+ closureHelper(closure, depend.get(name));
+ }
+ }
+
+ private Set<String> curSet;
+ private byte[] buf = new byte[0x2000];
+
+ public boolean process(EntryStruct struct) throws IOException {
+ try {
+ if (struct.name.endsWith(".class")) {
+ String name = struct.name.substring(0, struct.name.length() - 6);
+ for (Wildcard wildcard : wildcards)
+ if (wildcard.matches(name))
+ roots.add(name);
+ depend.put(name, curSet = new HashSet<String>());
+ new ClassReader(new ByteArrayInputStream(struct.data)).accept(cv,
+ ClassReader.EXPAND_FRAMES);
+ curSet.remove(name);
+ }
+ } catch (Exception e) {
+ System.err.println("Error reading " + struct.name + ": " + e.getMessage());
+ }
+ return true;
+ }
+
+ public String map(String key) {
+ if (key.startsWith("java/") || key.startsWith("javax/"))
+ return null;
+ curSet.add(key);
+ return null;
+ }
+
+ public Object mapValue(Object value) {
+ if (value instanceof String) {
+ String s = (String)value;
+ if (PackageRemapper.isArrayForName(s)) {
+ mapDesc(s.replace('.', '/'));
+ } else if (isForName(s)) {
+ map(s.replace('.', '/'));
+ }
+ return value;
+ } else {
+ return super.mapValue(value);
+ }
+ }
+
+ // TODO: use this for package remapping too?
+ private static boolean isForName(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;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/Main.java b/src/main/com/tonicsystems/jarjar/Main.java
new file mode 100644
index 0000000..117bdbb
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/Main.java
@@ -0,0 +1,97 @@
+/**
+ * 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.util.*;
+import java.io.*;
+import java.util.*;
+
+public class Main {
+
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+ private static final String HELP;
+
+ static {
+ try {
+ HELP = readIntoString(Main.class.getResourceAsStream("help.txt"));
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ private static String readIntoString(InputStream in) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ BufferedReader r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ String line = null;
+ while ((line = r.readLine()) != null)
+ sb.append(line).append(LINE_SEPARATOR);
+ return sb.toString();
+ }
+
+ private boolean verbose;
+ private List patterns;
+ private int level = DepHandler.LEVEL_CLASS;
+
+ public static void main(String[] args) throws Exception {
+ MainUtil.runMain(new Main(), args, "help");
+ }
+
+ public void help() {
+ System.err.print(HELP);
+ }
+
+ public void strings(String cp) throws IOException {
+ if (cp == null) {
+ throw new IllegalArgumentException("cp is required");
+ }
+ new StringDumper().run(cp, new PrintWriter(System.out));
+ }
+
+ // TODO: make level an enum
+ public void find(String level, String cp1, String cp2) throws IOException {
+ if (level == null || cp1 == null) {
+ throw new IllegalArgumentException("level and cp1 are required");
+ }
+ if (cp2 == null) {
+ cp2 = cp1;
+ }
+ int levelFlag;
+ if ("class".equals(level)) {
+ levelFlag = DepHandler.LEVEL_CLASS;
+ } else if ("jar".equals(level)) {
+ levelFlag = DepHandler.LEVEL_JAR;
+ } else {
+ throw new IllegalArgumentException("unknown level " + level);
+ }
+ PrintWriter w = new PrintWriter(System.out);
+ DepHandler handler = new TextDepHandler(w, levelFlag);
+ new DepFind().run(cp1, cp2, handler);
+ w.flush();
+ }
+
+ public void process(File rulesFile, File inJar, File outJar) throws IOException {
+ if (rulesFile == null || inJar == null || outJar == null) {
+ throw new IllegalArgumentException("rulesFile, inJar, and outJar are required");
+ }
+ List<PatternElement> rules = RulesFileParser.parse(rulesFile);
+ boolean verbose = Boolean.getBoolean("verbose");
+ boolean skipManifest = Boolean.getBoolean("skipManifest");
+ MainProcessor proc = new MainProcessor(rules, verbose, skipManifest);
+ StandaloneJarProcessor.run(inJar, outJar, proc);
+ proc.strip(outJar);
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/MainProcessor.java b/src/main/com/tonicsystems/jarjar/MainProcessor.java
new file mode 100644
index 0000000..de15924
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/MainProcessor.java
@@ -0,0 +1,106 @@
+/**
+ * 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.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+class MainProcessor implements JarProcessor
+{
+ private final boolean verbose;
+ private final JarProcessorChain chain;
+ private final KeepProcessor kp;
+ private final Map<String, String> renames = new HashMap<String, String>();
+
+ public MainProcessor(List<PatternElement> patterns, boolean verbose, boolean skipManifest) {
+ this.verbose = verbose;
+ List<Zap> zapList = new ArrayList<Zap>();
+ List<Rule> ruleList = new ArrayList<Rule>();
+ List<Keep> keepList = new ArrayList<Keep>();
+ for (PatternElement pattern : patterns) {
+ if (pattern instanceof Zap) {
+ zapList.add((Zap) pattern);
+ } else if (pattern instanceof Rule) {
+ ruleList.add((Rule) pattern);
+ } else if (pattern instanceof Keep) {
+ keepList.add((Keep) pattern);
+ }
+ }
+
+ PackageRemapper pr = new PackageRemapper(ruleList, verbose);
+ kp = keepList.isEmpty() ? null : new KeepProcessor(keepList);
+
+ List<JarProcessor> processors = new ArrayList<JarProcessor>();
+ if (skipManifest)
+ processors.add(ManifestProcessor.getInstance());
+ if (kp != null)
+ processors.add(kp);
+ processors.add(new ZapProcessor(zapList));
+ processors.add(new JarTransformerChain(new RemappingClassTransformer[]{ new RemappingClassTransformer(pr) }));
+ processors.add(new ResourceProcessor(pr));
+ chain = new JarProcessorChain(processors.toArray(new JarProcessor[processors.size()]));
+ }
+
+ public void strip(File file) throws IOException {
+ if (kp == null)
+ return;
+ Set<String> excludes = getExcludes();
+ if (!excludes.isEmpty())
+ StandaloneJarProcessor.run(file, file, new ExcludeProcessor(excludes, verbose));
+ }
+
+ /**
+ * Returns the <code>.class</code> files to delete. As well the root-parameter as the rename ones
+ * are taken in consideration, so that the concerned files are not listed in the result.
+ *
+ * @return the paths of the files in the jar-archive, including the <code>.class</code> suffix
+ */
+ private Set<String> getExcludes() {
+ Set<String> result = new HashSet<String>();
+ for (String exclude : kp.getExcludes()) {
+ String name = exclude + ".class";
+ String renamed = renames.get(name);
+ result.add((renamed != null) ? renamed : name);
+ }
+ return result;
+ }
+
+ /**
+ *
+ * @param struct
+ * @return <code>true</code> if the entry is to include in the output jar
+ * @throws IOException
+ */
+ public boolean process(EntryStruct struct) throws IOException {
+ String name = struct.name;
+ boolean keepIt = chain.process(struct);
+ if (keepIt) {
+ if (!name.equals(struct.name)) {
+ if (kp != null)
+ renames.put(name, struct.name);
+ if (verbose)
+ System.err.println("Renamed " + name + " -> " + struct.name);
+ }
+ } else {
+ if (verbose)
+ System.err.println("Removed " + name);
+ }
+ return keepIt;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/MainUtil.java b/src/main/com/tonicsystems/jarjar/MainUtil.java
new file mode 100644
index 0000000..068ece6
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/MainUtil.java
@@ -0,0 +1,86 @@
+/**
+ * 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 java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+class MainUtil
+{
+ public static void runMain(Object main, String[] args, String defCommand) throws Exception {
+ if (args.length > 0) {
+ String command = args[0];
+ Method[] methods = main.getClass().getMethods();
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ if (method.getName().equals(command)) {
+ String[] remaining = new String[args.length - 1];
+ System.arraycopy(args, 1, remaining, 0, remaining.length);
+ try {
+ method.invoke(main, bindParameters(method, remaining));
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof IllegalArgumentException) {
+ System.err.println("Syntax error: " + cause.getMessage());
+ } else if (cause instanceof Exception) {
+ throw (Exception) cause;
+ } else {
+ throw e;
+ }
+ }
+ return;
+ }
+ }
+ }
+ if (defCommand != null)
+ runMain(main, new String[]{ defCommand }, null);
+ }
+
+ private static Object[] bindParameters(Method method, String[] args) {
+ List<Object> parameters = new ArrayList<Object>();
+ Class[] parameterTypes = method.getParameterTypes();
+ for (int i = 0, len = parameterTypes.length; i < len; i++) {
+ Class type = parameterTypes[i];
+ int remaining = Math.max(0, args.length - i);
+ if (type.equals(String[].class)) {
+ String[] rest = new String[remaining];
+ System.arraycopy(args, 1, rest, 0, remaining);
+ parameters.add(rest);
+ } else if (remaining > 0) {
+ parameters.add(convertParameter(args[i], parameterTypes[i]));
+ } else {
+ parameters.add(null);
+ }
+ }
+ return parameters.toArray();
+ }
+
+ private static Object convertParameter(String arg, Class type) {
+ if (type.equals(String.class)) {
+ return arg;
+ } else if (type.equals(Integer.class)) {
+ return Integer.valueOf(arg, 10);
+ } else if (type.equals(File.class)) {
+ return new File(arg);
+ } else {
+ throw new UnsupportedOperationException("Unknown type " + type);
+ }
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/ManifestProcessor.java b/src/main/com/tonicsystems/jarjar/ManifestProcessor.java
new file mode 100644
index 0000000..91f1f76
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/ManifestProcessor.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 com.tonicsystems.jarjar.util.*;
+import java.io.IOException;
+import java.util.*;
+
+class ManifestProcessor implements JarProcessor
+{
+ private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
+ private static final ManifestProcessor INSTANCE = new ManifestProcessor();
+
+ public static ManifestProcessor getInstance() {
+ return INSTANCE;
+ }
+
+ private ManifestProcessor() {}
+
+ public boolean process(EntryStruct struct) throws IOException {
+ return !struct.name.equalsIgnoreCase(MANIFEST_PATH);
+ }
+}
+
diff --git a/src/main/com/tonicsystems/jarjar/PackageRemapper.java b/src/main/com/tonicsystems/jarjar/PackageRemapper.java
new file mode 100644
index 0000000..4d102be
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/PackageRemapper.java
@@ -0,0 +1,128 @@
+/**
+ * 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.objectweb.asm.*;
+import org.objectweb.asm.commons.*;
+import java.util.*;
+import java.util.regex.Pattern;
+
+class PackageRemapper extends Remapper
+{
+ private static final String RESOURCE_SUFFIX = "RESOURCE";
+
+ private static final Pattern ARRAY_FOR_NAME_PATTERN
+ = Pattern.compile("\\[L[\\p{javaJavaIdentifierPart}\\.]+?;");
+
+ private final List<Wildcard> wildcards;
+ 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>();
+ private final boolean verbose;
+
+ public PackageRemapper(List<Rule> ruleList, boolean verbose) {
+ this.verbose = verbose;
+ wildcards = PatternElement.createWildcards(ruleList);
+ }
+
+ // also used by KeepProcessor
+ static boolean isArrayForName(String value) {
+ return ARRAY_FOR_NAME_PATTERN.matcher(value).matches();
+ }
+
+ 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;
+ }
+
+ public Object mapValue(Object value) {
+ if (value instanceof String) {
+ String s = valueCache.get(value);
+ if (s == null) {
+ s = (String)value;
+ if (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 (verbose && !s.equals(value))
+ System.err.println("Changed \"" + value + "\" -> \"" + s + "\"");
+ return s;
+ } else {
+ return super.mapValue(value);
+ }
+ }
+
+ private String replaceHelper(String value) {
+ for (Wildcard wildcard : wildcards) {
+ String test = wildcard.replace(value);
+ if (test != null)
+ return test;
+ }
+ return value;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/PathClass.java b/src/main/com/tonicsystems/jarjar/PathClass.java
new file mode 100644
index 0000000..fbbb7ed
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/PathClass.java
@@ -0,0 +1,40 @@
+/**
+ * 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;
+
+public class PathClass
+{
+ private String classPath;
+ private String className;
+
+ public PathClass(String classPath, String className) {
+ this.classPath = classPath;
+ this.className = className;
+ }
+
+ public String getClassPath() {
+ return classPath;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String toString() {
+ return classPath + "!" + className;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/PatternElement.java b/src/main/com/tonicsystems/jarjar/PatternElement.java
new file mode 100644
index 0000000..6ccd9ea
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/PatternElement.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;
+
+import java.util.*;
+
+abstract public class PatternElement
+{
+ private String pattern;
+
+ public void setPattern(String pattern) {
+ this.pattern = pattern;
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+
+ static List<Wildcard> createWildcards(List<? extends PatternElement> patterns) {
+ List<Wildcard> wildcards = new ArrayList<Wildcard>();
+ for (PatternElement pattern : patterns) {
+ String result = (pattern instanceof Rule) ? ((Rule)pattern).getResult() : "";
+ String expr = pattern.getPattern();
+ if (expr.indexOf('/') >= 0)
+ throw new IllegalArgumentException("Patterns cannot contain slashes");
+ wildcards.add(new Wildcard(expr.replace('.', '/'), result));
+ }
+ return wildcards;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/ResourceProcessor.java b/src/main/com/tonicsystems/jarjar/ResourceProcessor.java
new file mode 100644
index 0000000..c07f664
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/ResourceProcessor.java
@@ -0,0 +1,37 @@
+/**
+ * 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.util.*;
+import java.io.IOException;
+import java.util.*;
+
+class ResourceProcessor implements JarProcessor
+{
+ private PackageRemapper pr;
+
+ public ResourceProcessor(PackageRemapper pr) {
+ this.pr = pr;
+ }
+
+ public boolean process(EntryStruct struct) throws IOException {
+ if (!struct.name.endsWith(".class"))
+ struct.name = pr.mapPath(struct.name);
+ return true;
+ }
+}
+
diff --git a/src/main/com/tonicsystems/jarjar/Rule.java b/src/main/com/tonicsystems/jarjar/Rule.java
new file mode 100644
index 0000000..2e76ca8
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/Rule.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;
+
+public class Rule extends PatternElement
+{
+ private String result;
+
+ public void setResult(String result) {
+ this.result = result;
+ }
+
+ public String getResult() {
+ return result;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/RulesFileParser.java b/src/main/com/tonicsystems/jarjar/RulesFileParser.java
new file mode 100644
index 0000000..f54f3b9
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/RulesFileParser.java
@@ -0,0 +1,81 @@
+/**
+ * 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 java.io.*;
+import java.util.*;
+
+class RulesFileParser
+{
+ private RulesFileParser() {
+ }
+
+ public static List<PatternElement> parse(File file) throws IOException {
+ return parse(new FileReader(file));
+ }
+
+ public static List<PatternElement> parse(String value) throws IOException {
+ return parse(new java.io.StringReader(value));
+ }
+
+ private static String stripComment(String in) {
+ int p = in.indexOf("#");
+ return p < 0 ? in : in.substring(0, p);
+ }
+
+ private static List<PatternElement> parse(Reader r) throws IOException {
+ try {
+ List<PatternElement> patterns = new ArrayList<PatternElement>();
+ BufferedReader br = new BufferedReader(r);
+ int c = 1;
+ String line;
+ while ((line = br.readLine()) != null) {
+ line = stripComment(line);
+ if (line.isEmpty())
+ continue;
+ String[] parts = line.split("\\s+");
+ if (parts.length < 2)
+ error(c, parts);
+ String type = parts[0];
+ PatternElement element = null;
+ if (type.equals("rule")) {
+ if (parts.length < 3)
+ error(c, parts);
+ Rule rule = new Rule();
+ rule.setResult(parts[2]);
+ element = rule;
+ } else if (type.equals("zap")) {
+ element = new Zap();
+ } else if (type.equals("keep")) {
+ element = new Keep();
+ } else {
+ error(c, parts);
+ }
+ element.setPattern(parts[1]);
+ patterns.add(element);
+ c++;
+ }
+ return patterns;
+ } finally {
+ r.close();
+ }
+ }
+
+ private static void error(int line, String[] parts) {
+ throw new IllegalArgumentException("Error on line " + line + ": " + Arrays.asList(parts));
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/StringDumper.java b/src/main/com/tonicsystems/jarjar/StringDumper.java
new file mode 100644
index 0000000..5086314
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/StringDumper.java
@@ -0,0 +1,96 @@
+/**
+ * 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.util.*;
+import java.io.*;
+import org.objectweb.asm.*;
+
+class StringDumper
+{
+ public StringDumper() {
+ }
+
+ public void run(String classPath, PrintWriter pw) throws IOException {
+ StringReader stringReader = new DumpStringReader(pw);
+ ClassPathIterator cp = new ClassPathIterator(classPath);
+ try {
+ while (cp.hasNext()) {
+ ClassPathEntry entry = cp.next();
+ InputStream in = entry.openStream();
+ try {
+ new ClassReader(in).accept(stringReader, 0);
+ } catch (Exception e) {
+ System.err.println("Error reading " + entry.getName() + ": " + e.getMessage());
+ } finally {
+ in.close();
+ }
+ pw.flush();
+ }
+ } catch (RuntimeIOException e) {
+ throw (IOException)e.getCause();
+ } finally {
+ cp.close();
+ }
+ }
+
+ private static class DumpStringReader extends StringReader
+ {
+ private final PrintWriter pw;
+ private String className;
+
+ public DumpStringReader(PrintWriter pw) {
+ this.pw = pw;
+ }
+
+ public void visitString(String className, String value, int line) {
+ if (value.length() > 0) {
+ if (!className.equals(this.className)) {
+ this.className = className;
+ pw.println(className.replace('/', '.'));
+ }
+ pw.print("\t");
+ if (line >= 0)
+ pw.print(line + ": ");
+ pw.print(escapeStringLiteral(value));
+ pw.println();
+ }
+ }
+ };
+
+ private static String escapeStringLiteral(String value) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("\"");
+ char[] chars = value.toCharArray();
+ for (int i = 0, size = chars.length; i < size; i++) {
+ char ch = chars[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/src/main/com/tonicsystems/jarjar/StringReader.java b/src/main/com/tonicsystems/jarjar/StringReader.java
new file mode 100644
index 0000000..ba7bb29
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/StringReader.java
@@ -0,0 +1,100 @@
+/**
+ * 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.objectweb.asm.*;
+
+abstract class StringReader extends ClassVisitor
+{
+ private int line = -1;
+ private String className;
+
+ public StringReader() {
+ super(Opcodes.ASM4);
+ }
+
+ abstract public void visitString(String className, String value, 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;
+ }
+
+ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+ handleObject(value);
+ return new FieldVisitor(Opcodes.ASM4){
+ @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.ASM4) {
+ @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) {
+ MethodVisitor mv = new MethodVisitor(Opcodes.ASM4){
+ @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);
+ }
+ };
+ return mv;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/TextDepHandler.java b/src/main/com/tonicsystems/jarjar/TextDepHandler.java
new file mode 100644
index 0000000..3551395
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/TextDepHandler.java
@@ -0,0 +1,34 @@
+/**
+ * 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 java.io.*;
+import java.util.*;
+
+public class TextDepHandler extends AbstractDepHandler
+{
+ private PrintWriter w;
+
+ public TextDepHandler(PrintWriter w, int level) {
+ super(level);
+ this.w = w;
+ }
+
+ protected void handle(String from, String to) throws IOException {
+ w.println(from + " -> " + to);
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/Wildcard.java b/src/main/com/tonicsystems/jarjar/Wildcard.java
new file mode 100644
index 0000000..723c6f5
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/Wildcard.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;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+class Wildcard
+{
+ private static Pattern dstar = Pattern.compile("\\*\\*");
+ private static Pattern star = Pattern.compile("\\*");
+ private static Pattern estar = Pattern.compile("\\+\\??\\)\\Z");
+
+ private final Pattern pattern;
+ private final int count;
+ private final ArrayList<Object> parts = new ArrayList<Object>(16); // kept for debugging
+ private final String[] strings;
+ private final int[] refs;
+
+ public Wildcard(String pattern, String result) {
+ if (pattern.equals("**"))
+ throw new IllegalArgumentException("'**' is not a valid pattern");
+ if (!checkIdentifierChars(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(dstar, regex, "(.+?)");
+ regex = replaceAllLiteral(star, regex, "([^/]+)");
+ regex = replaceAllLiteral(estar, regex, "*)");
+ this.pattern = Pattern.compile("\\A" + regex + "\\Z");
+ this.count = this.pattern.matcher("foo").groupCount();
+
+ // TODO: check for illegal characters
+ char[] chars = result.toCharArray();
+ int max = 0;
+ for (int i = 0, mark = 0, state = 0, len = chars.length; i < len + 1; i++) {
+ char ch = (i == len) ? '@' : chars[i];
+ if (state == 0) {
+ if (ch == '@') {
+ parts.add(new String(chars, mark, i - mark));
+ mark = i + 1;
+ state = 1;
+ }
+ } else {
+ 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(new String(chars, mark, i - mark));
+ if (n > max)
+ max = n;
+ parts.add(new Integer(n));
+ mark = i--;
+ state = 0;
+ }
+ }
+ }
+ int size = parts.size();
+ strings = new String[size];
+ refs = new int[size];
+ Arrays.fill(refs, -1);
+ for (int i = 0; i < size; i++) {
+ Object v = parts.get(i);
+ if (v instanceof String) {
+ strings[i] = ((String)v).replace('.', '/');
+ } else {
+ refs[i] = ((Integer)v).intValue();
+ }
+ }
+ if (count < max)
+ throw new IllegalArgumentException("Result includes impossible placeholder \"@" + max + "\": " + result);
+ // System.err.println(this);
+ }
+
+ public boolean matches(String value) {
+ return getMatcher(value) != null;
+ }
+
+ public String replace(String value) {
+ Matcher matcher = getMatcher(value);
+ if (matcher != null) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < strings.length; i++)
+ sb.append((refs[i] >= 0) ? matcher.group(refs[i]) : strings[i]);
+ return sb.toString();
+ }
+ return null;
+ }
+
+ private Matcher getMatcher(String value) {
+ Matcher matcher = pattern.matcher(value);
+ if (matcher.matches() && checkIdentifierChars(value, "/"))
+ return matcher;
+ return null;
+ }
+
+ private static boolean checkIdentifierChars(String expr, String extra) {
+ // 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 (expr.endsWith("package-info")) {
+ expr = expr.substring(0, expr.length() - "package-info".length());
+ }
+ for (int i = 0, len = expr.length(); i < len; i++) {
+ char c = expr.charAt(i);
+ if (extra.indexOf(c) >= 0)
+ continue;
+ if (!Character.isJavaIdentifierPart(c))
+ return false;
+ }
+ return true;
+ }
+
+ private static String replaceAllLiteral(Pattern pattern, String value, String replace) {
+ replace = replace.replaceAll("([$\\\\])", "\\\\$0");
+ return pattern.matcher(value).replaceAll(replace);
+ }
+
+ public String toString() {
+ return "Wildcard{pattern=" + pattern + ",parts=" + parts + "}";
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/Zap.java b/src/main/com/tonicsystems/jarjar/Zap.java
new file mode 100644
index 0000000..ed5c828
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/Zap.java
@@ -0,0 +1,21 @@
+/**
+ * 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;
+
+public class Zap extends PatternElement
+{
+}
diff --git a/src/main/com/tonicsystems/jarjar/ZapProcessor.java b/src/main/com/tonicsystems/jarjar/ZapProcessor.java
new file mode 100644
index 0000000..5b6b680
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/ZapProcessor.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.util.*;
+import java.io.IOException;
+import java.util.*;
+
+class ZapProcessor implements JarProcessor
+{
+ private List<Wildcard> wildcards;
+
+ public ZapProcessor(List<Zap> zapList) {
+ wildcards = PatternElement.createWildcards(zapList);
+ }
+
+ public boolean process(EntryStruct struct) throws IOException {
+ String name = struct.name;
+ if (name.endsWith(".class"))
+ return !zap(name.substring(0, name.length() - 6));
+ return true;
+ }
+
+ private boolean zap(String desc) {
+ // TODO: optimize
+ for (Wildcard wildcard : wildcards) {
+ if (wildcard.matches(desc))
+ return true;
+ }
+ return false;
+ }
+}
+
diff --git a/src/main/com/tonicsystems/jarjar/util/AntJarProcessor.java b/src/main/com/tonicsystems/jarjar/util/AntJarProcessor.java
new file mode 100644
index 0000000..ac30418
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/AntJarProcessor.java
@@ -0,0 +1,107 @@
+/**
+ * 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.*;
+import java.util.*;
+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;
+
+abstract public class AntJarProcessor extends Jar
+{
+ private EntryStruct struct = new EntryStruct();
+ private JarProcessor proc;
+ private byte[] buf = new byte[0x2000];
+
+ private 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;
+ }
+
+ public abstract void execute() throws BuildException;
+
+ public void execute(JarProcessor proc) throws BuildException {
+ this.proc = proc;
+ super.execute();
+ }
+
+ public void setFilesonly(boolean f) {
+ super.setFilesonly(f);
+ filesOnly = f;
+ }
+
+ protected void zipDir(File dir, ZipOutputStream zOut, String vPath, int mode)
+ throws IOException {
+ }
+
+ protected void zipFile(InputStream is, ZipOutputStream zOut, String vPath,
+ long lastModified, File fromArchive, int mode) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ IoUtil.pipe(is, baos, buf);
+ struct.data = baos.toByteArray();
+ struct.name = vPath;
+ struct.time = lastModified;
+ if (proc.process(struct)) {
+ 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);
+ }
+ }
+ }
+
+ public void reset() {
+ super.reset();
+ cleanHelper();
+ }
+
+ protected void cleanUp() {
+ super.cleanUp();
+ cleanHelper();
+ }
+
+ protected void cleanHelper() {
+ verbose = false;
+ filesOnly = false;
+ dirs.clear();
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/ClassHeaderReader.java b/src/main/com/tonicsystems/jarjar/util/ClassHeaderReader.java
new file mode 100644
index 0000000..6e448be
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/ClassHeaderReader.java
@@ -0,0 +1,186 @@
+/**
+ * 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.*;
+import java.lang.reflect.Array;
+import java.util.*;
+
+public 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;
+ count = buf.length;
+ }
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/ClassPathEntry.java b/src/main/com/tonicsystems/jarjar/util/ClassPathEntry.java
new file mode 100644
index 0000000..d8de708
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/ClassPathEntry.java
@@ -0,0 +1,26 @@
+/**
+ * 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.InputStream;
+import java.io.IOException;
+
+public interface ClassPathEntry {
+ String getSource() throws IOException;
+ String getName();
+ InputStream openStream() throws IOException;
+} \ No newline at end of file
diff --git a/src/main/com/tonicsystems/jarjar/util/ClassPathIterator.java b/src/main/com/tonicsystems/jarjar/util/ClassPathIterator.java
new file mode 100644
index 0000000..f4f54f0
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/ClassPathIterator.java
@@ -0,0 +1,234 @@
+/**
+ * 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.util.*;
+import java.util.zip.*;
+import java.io.*;
+import java.util.jar.*;
+
+public class ClassPathIterator implements Iterator<ClassPathEntry>
+{
+ private static final FileFilter CLASS_FILTER = new FileFilter() {
+ public boolean accept(File file) {
+ return file.isDirectory() || isClass(file.getName());
+ }
+ };
+
+ private static final FileFilter JAR_FILTER = new FileFilter() {
+ public boolean accept(File file) {
+ return hasExtension(file.getName(), ".jar");
+ }
+ };
+
+ private final Iterator<File> files;
+ private Iterator<ClassPathEntry> entries = Collections.<ClassPathEntry>emptyList().iterator();
+ private ClassPathEntry next;
+ private List<ZipFile> zips = new ArrayList<ZipFile>();
+
+ public ClassPathIterator(String classPath) throws IOException {
+ this(new File(System.getProperty("user.dir")), classPath, null);
+ }
+
+ public ClassPathIterator(File parent, String classPath, String delim) throws IOException {
+ if (delim == null) {
+ delim = System.getProperty("path.separator");
+ }
+ StringTokenizer st = new StringTokenizer(classPath, delim);
+ List<File> fileList = new ArrayList<File>();
+ while (st.hasMoreTokens()) {
+ String part = (String)st.nextElement();
+ boolean wildcard = false;
+ if (part.endsWith("/*")) {
+ part = part.substring(0, part.length() - 1);
+ if (part.indexOf('*') >= 0)
+ throw new IllegalArgumentException("Multiple wildcards are not allowed: " + part);
+ wildcard = true;
+ } else if (part.indexOf('*') >= 0) {
+ throw new IllegalArgumentException("Incorrect wildcard usage: " + part);
+ }
+
+ File file = new File(part);
+ if (!file.isAbsolute())
+ file = new File(parent, part);
+ if (!file.exists())
+ throw new IllegalArgumentException("File " + file + " does not exist");
+
+ if (wildcard) {
+ if (!file.isDirectory())
+ throw new IllegalArgumentException("File " + file + " + is not a directory");
+ fileList.addAll(findFiles(file, JAR_FILTER, false, new ArrayList<File>()));
+ } else {
+ fileList.add(file);
+ }
+ }
+ this.files = fileList.iterator();
+ advance();
+ }
+
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ /** Closes all zip files opened by this iterator. */
+ public void close() throws IOException {
+ next = null;
+ for (ZipFile zip : zips) {
+ zip.close();
+ }
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public ClassPathEntry next() {
+ if (!hasNext())
+ throw new NoSuchElementException();
+ ClassPathEntry result = next;
+ try {
+ advance();
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ return result;
+ }
+
+ private void advance() throws IOException {
+ if (!entries.hasNext()) {
+ if (!files.hasNext()) {
+ next = null;
+ return;
+ }
+ File file = files.next();
+ if (hasExtension(file.getName(), ".jar")) {
+ ZipFile zip = new JarFile(file);
+ zips.add(zip);
+ entries = new ZipIterator(zip);
+ } else if (hasExtension(file.getName(), ".zip")) {
+ ZipFile zip = new ZipFile(file);
+ zips.add(zip);
+ entries = new ZipIterator(zip);
+ } else if (file.isDirectory()) {
+ entries = new FileIterator(file);
+ } else {
+ throw new IllegalArgumentException("Do not know how to handle " + file);
+ }
+ }
+
+ boolean foundClass = false;
+ while (!foundClass && entries.hasNext()) {
+ next = entries.next();
+ foundClass = isClass(next.getName());
+ }
+ if (!foundClass) {
+ advance();
+ }
+ }
+
+ private static class ZipIterator implements Iterator<ClassPathEntry> {
+ private final ZipFile zip;
+ private final Enumeration<? extends ZipEntry> entries;
+
+ ZipIterator(ZipFile zip) {
+ this.zip = zip;
+ this.entries = zip.entries();
+ }
+
+ public boolean hasNext() {
+ return entries.hasMoreElements();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public ClassPathEntry next() {
+ final ZipEntry entry = entries.nextElement();
+ return new ClassPathEntry() {
+ public String getSource() {
+ return zip.getName();
+ }
+
+ public String getName() {
+ return entry.getName();
+ }
+
+ public InputStream openStream() throws IOException {
+ return zip.getInputStream(entry);
+ }
+ };
+ }
+ }
+
+ private static class FileIterator implements Iterator<ClassPathEntry> {
+ private final File dir;
+ private final Iterator<File> entries;
+
+ FileIterator(File dir) {
+ this.dir = dir;
+ this.entries = findFiles(dir, CLASS_FILTER, true, new ArrayList<File>()).iterator();
+ }
+
+ public boolean hasNext() {
+ return entries.hasNext();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public ClassPathEntry next() {
+ final File file = entries.next();
+ return new ClassPathEntry() {
+ public String getSource() throws IOException {
+ return dir.getCanonicalPath();
+ }
+
+ public String getName() {
+ return file.getName();
+ }
+
+ public InputStream openStream() throws IOException {
+ return new BufferedInputStream(new FileInputStream(file));
+ }
+ };
+ }
+ }
+
+ private static List<File> findFiles(File dir, FileFilter filter, boolean recurse, List<File> collect) {
+ for (File file : dir.listFiles(filter)) {
+ if (recurse && file.isDirectory()) {
+ findFiles(file, filter, recurse, collect);
+ } else {
+ collect.add(file);
+ }
+ }
+ return collect;
+ }
+
+ private static boolean isClass(String name) {
+ return hasExtension(name, ".class");
+ }
+
+ private static boolean hasExtension(String name, String ext) {
+ if (name.length() < ext.length())
+ return false;
+ String actual = name.substring(name.length() - ext.length());
+ return actual.equals(ext) || actual.equals(ext.toUpperCase());
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/EntryStruct.java b/src/main/com/tonicsystems/jarjar/util/EntryStruct.java
new file mode 100644
index 0000000..93f7622
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/EntryStruct.java
@@ -0,0 +1,27 @@
+/**
+ * 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.InputStream;
+import java.io.File;
+
+public class EntryStruct
+{
+ public byte[] data;
+ public String name;
+ public long time;
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/GetNameClassWriter.java b/src/main/com/tonicsystems/jarjar/util/GetNameClassWriter.java
new file mode 100644
index 0000000..bbb8590
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/GetNameClassWriter.java
@@ -0,0 +1,43 @@
+/**
+ * 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 org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+public class GetNameClassWriter extends ClassVisitor
+{
+ private String className;
+
+ public GetNameClassWriter(int flags) {
+ super(Opcodes.ASM4,new ClassWriter(flags));
+ }
+
+ 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);
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public byte[] toByteArray() {
+ return ((ClassWriter) cv).toByteArray();
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/IoUtil.java b/src/main/com/tonicsystems/jarjar/util/IoUtil.java
new file mode 100644
index 0000000..aef7ade
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/IoUtil.java
@@ -0,0 +1,131 @@
+/**
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+class IoUtil {
+ private IoUtil() {}
+
+ public static void pipe(InputStream is, OutputStream out, byte[] buf) throws IOException {
+ for (;;) {
+ int amt = is.read(buf);
+ if (amt < 0)
+ break;
+ out.write(buf, 0, amt);
+ }
+ }
+
+ public static void copy(File from, File to, byte[] buf) throws IOException {
+ InputStream in = new FileInputStream(from);
+ try {
+ OutputStream out = new FileOutputStream(to);
+ try {
+ pipe(in, out, buf);
+ } finally {
+ out.close();
+ }
+ } finally {
+ in.close();
+ }
+ }
+
+ /**
+ * Create a copy of an zip file without its empty directories.
+ * @param inputFile
+ * @param outputFile
+ * @throws IOException
+ */
+ public static void copyZipWithoutEmptyDirectories(final File inputFile, final File outputFile) throws IOException
+ {
+ final byte[] buf = new byte[0x2000];
+
+ final ZipFile inputZip = new ZipFile(inputFile);
+ final ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(outputFile));
+ try
+ {
+ // read a the entries of the input zip file and sort them
+ final Enumeration<? extends ZipEntry> e = inputZip.entries();
+ final ArrayList<ZipEntry> sortedList = new ArrayList<ZipEntry>();
+ while (e.hasMoreElements()) {
+ final ZipEntry entry = e.nextElement();
+ sortedList.add(entry);
+ }
+
+ Collections.sort(sortedList, new Comparator<ZipEntry>()
+ {
+ public int compare(ZipEntry o1, ZipEntry o2)
+ {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+
+ // treat them again and write them in output, wenn they not are empty directories
+ for (int i = sortedList.size()-1; i>=0; i--)
+ {
+ final ZipEntry inputEntry = sortedList.get(i);
+ final String name = inputEntry.getName();
+ final boolean isEmptyDirectory;
+ if (inputEntry.isDirectory())
+ {
+ if (i == sortedList.size()-1)
+ {
+ // no item afterwards; it was an empty directory
+ isEmptyDirectory = true;
+ }
+ else
+ {
+ final String nextName = sortedList.get(i+1).getName();
+ isEmptyDirectory = !nextName.startsWith(name);
+ }
+ }
+ else
+ {
+ isEmptyDirectory = false;
+ }
+
+
+ // write the entry
+ if (isEmptyDirectory)
+ {
+ sortedList.remove(inputEntry);
+ }
+ else
+ {
+ final ZipEntry outputEntry = new ZipEntry(inputEntry);
+ outputStream.putNextEntry(outputEntry);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final InputStream is = inputZip.getInputStream(inputEntry);
+ IoUtil.pipe(is, baos, buf);
+ is.close();
+ outputStream.write(baos.toByteArray());
+ }
+ }
+ } finally {
+ outputStream.close();
+ }
+
+ }
+
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/JarProcessor.java b/src/main/com/tonicsystems/jarjar/util/JarProcessor.java
new file mode 100644
index 0000000..1560696
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/JarProcessor.java
@@ -0,0 +1,35 @@
+/**
+ * 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;
+
+public interface JarProcessor
+{
+ /**
+ * Process the entry (p.ex. rename the file)
+ * <p>
+ * Returns <code>true</code> if the processor has has changed 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
+ * @return <code>true</code> if he process chain can continue after this process
+ * @throws IOException
+ */
+ boolean process(EntryStruct struct) throws IOException;
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/JarProcessorChain.java b/src/main/com/tonicsystems/jarjar/util/JarProcessorChain.java
new file mode 100644
index 0000000..06ac85d
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/JarProcessorChain.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.util;
+
+import java.io.IOException;
+
+public class JarProcessorChain implements JarProcessor
+{
+ private final JarProcessor[] chain;
+
+ public JarProcessorChain(JarProcessor[] chain)
+ {
+ this.chain = chain.clone();
+ }
+
+ /**
+ * @param struct
+ * @return <code>true</code> if the entry has run the complete chain
+ * @throws IOException
+ */
+ public boolean process(EntryStruct struct) throws IOException
+ {
+
+ for (JarProcessor aChain : chain)
+ {
+ if (!aChain.process(struct))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
diff --git a/src/main/com/tonicsystems/jarjar/util/JarTransformer.java b/src/main/com/tonicsystems/jarjar/util/JarTransformer.java
new file mode 100644
index 0000000..95f1216
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/JarTransformer.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.util;
+
+import java.io.*;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+
+abstract public class JarTransformer implements JarProcessor
+{
+ public boolean process(EntryStruct struct) throws IOException {
+ if (struct.name.endsWith(".class")) {
+ ClassReader reader;
+ try {
+ reader = new ClassReader(struct.data);
+ } catch (Exception e) {
+ return true; // TODO?
+ }
+ GetNameClassWriter w = new GetNameClassWriter(ClassWriter.COMPUTE_MAXS);
+ reader.accept(transform(w), ClassReader.EXPAND_FRAMES);
+ struct.data = w.toByteArray();
+ struct.name = pathFromName(w.getClassName());
+ }
+ return true;
+ }
+
+ abstract protected ClassVisitor transform(ClassVisitor v);
+
+ private static String pathFromName(String className) {
+ return className.replace('.', '/') + ".class";
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/JarTransformerChain.java b/src/main/com/tonicsystems/jarjar/util/JarTransformerChain.java
new file mode 100644
index 0000000..be16d9b
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/JarTransformerChain.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.util;
+
+import org.objectweb.asm.ClassVisitor;
+
+public class JarTransformerChain extends JarTransformer
+{
+ private final RemappingClassTransformer[] chain;
+
+ public JarTransformerChain(RemappingClassTransformer[] chain) {
+ this.chain = chain.clone();
+ for (int i = chain.length - 1; i > 0; i--) {
+ chain[i - 1].setTarget(chain[i]);
+ }
+ }
+
+ protected ClassVisitor transform(ClassVisitor v) {
+ chain[chain.length - 1].setTarget(v);
+ return chain[0];
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/RemappingClassTransformer.java b/src/main/com/tonicsystems/jarjar/util/RemappingClassTransformer.java
new file mode 100644
index 0000000..fd1b9d7
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/RemappingClassTransformer.java
@@ -0,0 +1,34 @@
+/**
+ * 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 org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.commons.Remapper;
+import org.objectweb.asm.commons.RemappingClassAdapter;
+
+import com.tonicsystems.jarjar.EmptyClassVisitor;
+
+public class RemappingClassTransformer extends RemappingClassAdapter
+{
+ public RemappingClassTransformer(Remapper pr) {
+ super(new EmptyClassVisitor(), pr);
+ }
+
+ public void setTarget(ClassVisitor target) {
+ cv = target;
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/RuntimeIOException.java b/src/main/com/tonicsystems/jarjar/util/RuntimeIOException.java
new file mode 100644
index 0000000..9b5d4a8
--- /dev/null
+++ b/src/main/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;
+
+public class RuntimeIOException extends RuntimeException
+{
+ private static final long serialVersionUID = 0L;
+
+ public RuntimeIOException(IOException e) {
+ super(e);
+ }
+}
diff --git a/src/main/com/tonicsystems/jarjar/util/StandaloneJarProcessor.java b/src/main/com/tonicsystems/jarjar/util/StandaloneJarProcessor.java
new file mode 100644
index 0000000..70b169b
--- /dev/null
+++ b/src/main/com/tonicsystems/jarjar/util/StandaloneJarProcessor.java
@@ -0,0 +1,71 @@
+/**
+ * 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.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.Enumeration;
+import java.io.*;
+import java.util.*;
+
+public class StandaloneJarProcessor
+{
+ public static void run(File from, File to, JarProcessor proc) throws IOException {
+ byte[] buf = new byte[0x2000];
+
+ JarFile in = new JarFile(from);
+ final File tmpTo = File.createTempFile("jarjar", ".jar");
+ JarOutputStream out = new JarOutputStream(new FileOutputStream(tmpTo));
+ Set<String> entries = new HashSet<String>();
+ try {
+ EntryStruct struct = new EntryStruct();
+ Enumeration<JarEntry> e = in.entries();
+ while (e.hasMoreElements()) {
+ JarEntry entry = e.nextElement();
+ struct.name = entry.getName();
+ struct.time = entry.getTime();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ IoUtil.pipe(in.getInputStream(entry), baos, buf);
+ struct.data = baos.toByteArray();
+ if (proc.process(struct)) {
+ if (entries.add(struct.name)) {
+ entry = new JarEntry(struct.name);
+ entry.setTime(struct.time);
+ entry.setCompressedSize(-1);
+ out.putNextEntry(entry);
+ out.write(struct.data);
+ } else if (struct.name.endsWith("/")) {
+ // TODO(chrisn): log
+ } else {
+ throw new IllegalArgumentException("Duplicate jar entries: " + struct.name);
+ }
+ }
+ }
+
+ }
+ finally {
+ in.close();
+ out.close();
+ }
+
+ // delete the empty directories
+ IoUtil.copyZipWithoutEmptyDirectories(tmpTo, to);
+ tmpTo.delete();
+
+ }
+}
diff --git a/src/test/Generics.class b/src/test/Generics.class
new file mode 100644
index 0000000..827519a
--- /dev/null
+++ b/src/test/Generics.class
Binary files differ
diff --git a/src/test/com/tonicsystems/jarjar/GenericsTest.java b/src/test/com/tonicsystems/jarjar/GenericsTest.java
new file mode 100644
index 0000000..1996a24
--- /dev/null
+++ b/src/test/com/tonicsystems/jarjar/GenericsTest.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;
+
+import com.tonicsystems.jarjar.util.*;
+import junit.framework.*;
+import java.util.*;
+import org.objectweb.asm.ClassReader;
+
+public class GenericsTest
+extends TestCase
+{
+ public void testTransform() throws Exception {
+ Rule rule = new Rule();
+ rule.setPattern("java.lang.String");
+ rule.setResult("com.tonicsystems.String");
+ RemappingClassTransformer t = new RemappingClassTransformer(new PackageRemapper(Arrays.asList(rule), false));
+ t.setTarget(new EmptyClassVisitor());
+ ClassReader reader = new ClassReader(getClass().getResourceAsStream("/Generics.class"));
+ reader.accept(t, 0);
+ }
+
+ public GenericsTest(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ return new TestSuite(GenericsTest.class);
+ }
+}
diff --git a/src/test/com/tonicsystems/jarjar/PackageRemapperTest.java b/src/test/com/tonicsystems/jarjar/PackageRemapperTest.java
new file mode 100644
index 0000000..aea9d46
--- /dev/null
+++ b/src/test/com/tonicsystems/jarjar/PackageRemapperTest.java
@@ -0,0 +1,59 @@
+/**
+ * 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 junit.framework.*;
+
+import java.util.Collections;
+
+public class PackageRemapperTest
+extends TestCase
+{
+ protected PackageRemapper remapper;
+
+ protected void setUp() {
+ Rule rule = new Rule();
+ rule.setPattern("org.**");
+ rule.setResult("foo.@1");
+ remapper = new PackageRemapper(Collections.singletonList(rule), false);
+ }
+
+ 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/src/test/com/tonicsystems/jarjar/RulesFileParserTest.java b/src/test/com/tonicsystems/jarjar/RulesFileParserTest.java
new file mode 100644
index 0000000..4a4c371
--- /dev/null
+++ b/src/test/com/tonicsystems/jarjar/RulesFileParserTest.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;
+
+import junit.framework.*;
+import java.io.*;
+
+public class RulesFileParserTest
+extends TestCase
+{
+ public void testSimple() throws Exception {
+ // TODO
+ }
+
+ public RulesFileParserTest(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ return new TestSuite(RulesFileParserTest.class);
+ }
+}
diff --git a/src/test/com/tonicsystems/jarjar/WildcardTest.java b/src/test/com/tonicsystems/jarjar/WildcardTest.java
new file mode 100644
index 0000000..7cdcdf7
--- /dev/null
+++ b/src/test/com/tonicsystems/jarjar/WildcardTest.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;
+
+import junit.framework.*;
+
+public class WildcardTest
+extends TestCase
+{
+ 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 void wildcard(String pattern, String result, String value, String expect) {
+ Wildcard wc = new Wildcard(pattern, result);
+ // System.err.println(wc);
+ assertEquals(expect, wc.replace(value));
+ }
+
+ public WildcardTest(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ return new TestSuite(WildcardTest.class);
+ }
+}
diff --git a/src/test/com/tonicsystems/jarjar/example/Example.java b/src/test/com/tonicsystems/jarjar/example/Example.java
new file mode 100644
index 0000000..2129962
--- /dev/null
+++ b/src/test/com/tonicsystems/jarjar/example/Example.java
@@ -0,0 +1,5 @@
+package com.tonicsystems.jarjar.example;
+
+public class Example
+{
+}
diff --git a/src/test/enumtest.jar b/src/test/enumtest.jar
new file mode 100644
index 0000000..df34d16
--- /dev/null
+++ b/src/test/enumtest.jar
Binary files differ