mkstub is the tool used to stub the google_apis maps.jar.
Since we're integrating it in the build, we want to control its verbosity.
Change-Id: Ic0cfacb79a0aa260bcafb54bdc6bdb5a98b1c87a
package com.android.mkstubs;
+import com.android.mkstubs.Main.Logger;
+
import org.objectweb.asm.ClassReader;
import java.io.IOException;
classes.put(className, cr);
}
}
-
+
return classes;
}
/**
* Filters the set of classes. Removes all classes that should not be included in the
* filter or that should be excluded. This modifies the map in-place.
- *
+ *
* @param classes The in-out map of classes to examine and filter. The map is filtered
* in-place.
* @param filter A filter describing which classes to include and which ones to exclude.
+ * @param log
*/
- void filter(Map<String, ClassReader> classes, Filter filter) {
+ void filter(Map<String, ClassReader> classes, Filter filter, Logger log) {
Set<String> keys = classes.keySet();
for(Iterator<String> it = keys.iterator(); it.hasNext(); ) {
String key = it.next();
// TODO: We *could* filter out all private classes here: classes.get(key).getAccess().
-
+
// remove if we don't keep it
if (!filter.accept(key)) {
- System.out.println("- Remove class " + key);
+ log.debug("- Remove class " + key);
it.remove();
}
}
}
-
+
}
package com.android.mkstubs;
+import com.android.mkstubs.Main.Logger;
+
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassAdapter;
*/
class FilterClassAdapter extends ClassAdapter {
+ private final Logger mLog;
private final Filter mFilter;
private String mClassName;
- public FilterClassAdapter(ClassVisitor writer, Filter filter) {
+ public FilterClassAdapter(ClassVisitor writer, Filter filter, Logger log) {
super(writer);
mFilter = filter;
+ mLog = log;
}
@Override
String filterName = String.format("%s#%s", mClassName, name);
if (!mFilter.accept(filterName)) {
- System.out.println("- Remove field " + filterName);
+ mLog.debug("- Remove field " + filterName);
return null;
}
String filterName = String.format("%s#%s%s", mClassName, name, desc);
if (!mFilter.accept(filterName)) {
- System.out.println("- Remove method " + filterName);
+ mLog.debug("- Remove method " + filterName);
return null;
}
filterName = String.format("%s#%s%s", mClassName, name, signature);
if (!mFilter.accept(filterName)) {
- System.out.println("- Remove method " + filterName);
+ mLog.debug("- Remove method " + filterName);
return null;
}
}
package com.android.mkstubs;
+import com.android.mkstubs.Main.Params;
+
import org.objectweb.asm.ClassReader;
import java.io.BufferedReader;
* For workflow details, see {@link #process(Params)}.
*/
public class Main {
-
+
/**
* A struct-like class to hold the various input values (e.g. command-line args)
*/
private String mInputJarPath;
private String mOutputJarPath;
private Filter mFilter;
-
- public Params(String inputJarPath, String outputJarPath) {
+ private boolean mVerbose;
+ private boolean mDumpSource;
+
+ public Params() {
+ mFilter = new Filter();
+ }
+
+ /** Sets the name of the input jar, where to read classes from. Must not be null. */
+ public void setInputJarPath(String inputJarPath) {
mInputJarPath = inputJarPath;
+ }
+
+ /** Sets the name of the output jar, where to write classes to. Must not be null. */
+ public void setOutputJarPath(String outputJarPath) {
mOutputJarPath = outputJarPath;
- mFilter = new Filter();
}
/** Returns the name of the input jar, where to read classes from. */
public Filter getFilter() {
return mFilter;
}
+
+ /** Sets verbose mode on. Default is off. */
+ public void setVerbose() {
+ mVerbose = true;
+ }
+
+ /** Returns true if verbose mode is on. */
+ public boolean isVerbose() {
+ return mVerbose;
+ }
+
+ /** Sets dump source mode on. Default is off. */
+ public void setDumpSource() {
+ mDumpSource = true;
+ }
+
+ /** Returns true if source should be dumped. */
+ public boolean isDumpSource() {
+ return mDumpSource;
+ }
+ }
+
+ /** Logger that writes on stdout depending a conditional verbose mode. */
+ static class Logger {
+ private final boolean mVerbose;
+
+ public Logger(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ /** Writes to stdout only in verbose mode. */
+ public void debug(String msg, Object...params) {
+ if (mVerbose) {
+ System.out.println(String.format(msg, params));
+ }
+ }
+
+ /** Writes to stdout all the time. */
+ public void info(String msg, Object...params) {
+ System.out.println(String.format(msg, params));
+ }
}
-
+
/**
* Main entry point. Processes arguments then performs the "real" work.
*/
public static void main(String[] args) {
-
Main m = new Main();
try {
Params p = m.processArgs(args);
* @throws IOException on failure to read a pattern file.
*/
private Params processArgs(String[] args) throws IOException {
-
- if (args.length < 2) {
- usage();
+ Params p = new Params();
+
+ for (String arg : args) {
+ if (arg.startsWith("--")) {
+ if (arg.startsWith("--v")) {
+ p.setVerbose();
+ } else if (arg.startsWith("--s")) {
+ p.setDumpSource();
+ } else if (arg.startsWith("--h")) {
+ usage(null);
+ } else {
+ usage("Unknown argument: " + arg);
+ }
+ } else if (p.getInputJarPath() == null) {
+ p.setInputJarPath(arg);
+ } else if (p.getOutputJarPath() == null) {
+ p.setOutputJarPath(arg);
+ } else {
+ addString(p, arg);
+ }
}
- Params p = new Params(args[0], args[1]);
-
- for (int i = 2; i < args.length; i++) {
- addString(p, args[i]);
+ if (p.getInputJarPath() == null && p.getOutputJarPath() == null) {
+ usage("Missing input or output JAR.");
}
-
+
return p;
}
* </ul>
* The input string is trimmed so any space around the first letter (-/+/@) or
* at the end is removed. Empty strings are ignored.
- *
+ *
* @param p The params which filters to edit.
* @param s The string to examine.
* @throws IOException
if (s.length() < 2) {
return;
}
-
+
char mode = s.charAt(0);
s = s.substring(1).trim();
if (mode == '@') {
addStringsFromFile(p, s);
-
+
} else if (mode == '-') {
s = s.replace('.', '/'); // transform FQCN into ASM internal name
if (s.endsWith("*")) {
/**
* Adds all the filter strings from the given file.
- *
+ *
* @param p The params which filter to edit.
* @param osFilePath The OS path to the file containing the patterns.
* @throws IOException
- *
+ *
* @see #addString(Params, String)
*/
private void addStringsFromFile(Params p, String osFilePath)
/**
* Prints some help to stdout.
+ * @param error The error that generated the usage, if any. Can be null.
*/
- private void usage() {
- System.out.println("Usage: mkstub input.jar output.jar [excluded-class @excluded-classes-file ...]");
+ private void usage(String error) {
+ if (error != null) {
+ System.out.println("ERROR: " + error);
+ }
+
+ System.out.println("Usage: mkstub [--h|--s|--v] input.jar output.jar [excluded-class @excluded-classes-file ...]");
+
+ System.out.println("Options:\n" +
+ " --h | --help : print this usage.\n" +
+ " --v | --verbose : verbose mode.\n" +
+ " --s | --source : dump source equivalent to modified byte code.\n\n");
System.out.println("Include syntax:\n" +
"+com.package.* : whole package, with glob\n" +
"-com.package.Class[$Inner] or ...Class*: whole classes with optional glob\n" +
"-com.package.Class#method: whole method or field\n" +
"-com.package.Class#method(IILjava/lang/String;)V: specific method with signature.\n\n");
+
System.exit(1);
}
AsmAnalyzer aa = new AsmAnalyzer();
Map<String, ClassReader> classes = aa.parseInputJar(p.getInputJarPath());
- System.out.println(String.format("Classes loaded: %d", classes.size()));
-
- aa.filter(classes, p.getFilter());
+ Logger log = new Logger(p.isVerbose());
+ log.info("Classes loaded: %d", classes.size());
- System.out.println(String.format("Classes filtered: %d", classes.size()));
+ aa.filter(classes, p.getFilter(), log);
+ log.info("Classes filtered: %d", classes.size());
// dump as Java source files, mostly for debugging
- SourceGenerator src_gen = new SourceGenerator();
- File dst_src_dir = new File(p.getOutputJarPath() + "_sources");
- dst_src_dir.mkdir();
- src_gen.generateSource(dst_src_dir, classes, p.getFilter());
-
+ if (p.isDumpSource()) {
+ SourceGenerator src_gen = new SourceGenerator(log);
+ File dst_src_dir = new File(p.getOutputJarPath() + "_sources");
+ dst_src_dir.mkdir();
+ src_gen.generateSource(dst_src_dir, classes, p.getFilter());
+ }
+
// dump the stubbed jar
- StubGenerator stub_gen = new StubGenerator();
+ StubGenerator stub_gen = new StubGenerator(log);
File dst_jar = new File(p.getOutputJarPath());
stub_gen.generateStubbedJar(dst_jar, classes, p.getFilter());
}
package com.android.mkstubs;
+import com.android.mkstubs.Main.Logger;
import com.android.mkstubs.sourcer.ClassSourcer;
import com.android.mkstubs.sourcer.Output;
*/
class SourceGenerator {
+ private Logger mLog;
+
+ public SourceGenerator(Logger log) {
+ mLog = log;
+ }
+
/**
* Generate source for the stubbed classes, mostly for debug purposes.
- * @throws IOException
+ * @throws IOException
*/
public void generateSource(File baseDir,
Map<String, ClassReader> classes,
Filter filter) throws IOException {
-
+
for (Entry<String, ClassReader> entry : classes.entrySet()) {
ClassReader cr = entry.getValue();
-
+
String name = classNameToJavaPath(cr.getClassName());
FileWriter fw = null;
FileWriter createWriter(File baseDir, String name) throws IOException {
File f = new File(baseDir, name);
f.getParentFile().mkdirs();
-
- System.out.println("Writing " + f.getPath());
-
+
+ mLog.debug("Writing " + f.getPath());
+
return new FileWriter(f);
}
* minus all exclusions
*/
void visitClassSource(Writer fw, ClassReader cr, Filter filter) {
- System.out.println("Dump " + cr.getClassName());
-
+ mLog.debug("Dump " + cr.getClassName());
+
ClassVisitor javaWriter = new ClassSourcer(new Output(fw));
- ClassVisitor classFilter = new FilterClassAdapter(javaWriter, filter);
+ ClassVisitor classFilter = new FilterClassAdapter(javaWriter, filter, mLog);
cr.accept(classFilter, 0 /*flags*/);
}
package com.android.mkstubs;
+import com.android.mkstubs.Main.Logger;
import com.android.mkstubs.stubber.ClassStubber;
import org.objectweb.asm.ClassReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
-import java.util.TreeMap;
import java.util.Map.Entry;
+import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
*/
class StubGenerator {
+ private Logger mLog;
+
+ public StubGenerator(Logger log) {
+ mLog = log;
+ }
+
/**
* Generate source for the stubbed classes, mostly for debug purposes.
- * @throws IOException
+ * @throws IOException
*/
public void generateStubbedJar(File destJar,
Map<String, ClassReader> classes,
for (Entry<String, ClassReader> entry : classes.entrySet()) {
ClassReader cr = entry.getValue();
-
+
byte[] b = visitClassStubber(cr, filter);
String name = classNameToEntryPath(cr.getClassName());
all.put(name, b);
createJar(new FileOutputStream(destJar), all);
- System.out.println(String.format("Wrote %s", destJar.getPath()));
+ mLog.debug("Wrote %s", destJar.getPath());
}
/**
/**
* Writes the JAR file.
- *
- * @param outStream The file output stream were to write the JAR.
+ *
+ * @param outStream The file output stream were to write the JAR.
* @param all The map of all classes to output.
* @throws IOException if an I/O error has occurred
*/
jar.flush();
jar.close();
}
-
+
byte[] visitClassStubber(ClassReader cr, Filter filter) {
- System.out.println("Stub " + cr.getClassName());
+ mLog.debug("Stub " + cr.getClassName());
// Rewrite the new class from scratch, without reusing the constant pool from the
// original class reader.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor stubWriter = new ClassStubber(cw);
- ClassVisitor classFilter = new FilterClassAdapter(stubWriter, filter);
+ ClassVisitor classFilter = new FilterClassAdapter(stubWriter, filter, mLog);
cr.accept(classFilter, 0 /*flags*/);
return cw.toByteArray();
}
package com.android.mkstubs;
+import com.android.mkstubs.Main.Logger;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import java.io.StringWriter;
/**
- *
+ *
*/
public class SourceGeneratorTest {
@Before
public void setUp() throws Exception {
- mGen = new SourceGenerator();
+ mGen = new SourceGenerator(new Logger(false));
}
@After
public void tearDown() throws Exception {
}
-
+
@Test
public void testDumpClass() throws Exception {
StringWriter sw = new StringWriter();
ClassReader cr = new ClassReader("data/TestBaseClass");
-
+
mGen.visitClassSource(sw, cr, new Filter());
-
+
String s = sw.toString();
Assert.assertNotNull(s);
}