/* * Copyright (C) 2009 The Android Open Source Project * * 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.android.mkstubs; import org.objectweb.asm.ClassReader; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.Map; /** * Main entry point of the MkStubs app. * <p/> * 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) */ static class Params { private String mInputJarPath; private String mOutputJarPath; private Filter mFilter; public Params(String inputJarPath, String outputJarPath) { mInputJarPath = inputJarPath; mOutputJarPath = outputJarPath; mFilter = new Filter(); } /** Returns the name of the input jar, where to read classes from. */ public String getInputJarPath() { return mInputJarPath; } /** Returns the name of the output jar, where to write classes to. */ public String getOutputJarPath() { return mOutputJarPath; } /** Returns the current instance of the filter, the include/exclude patterns. */ public Filter getFilter() { return mFilter; } } /** * 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); m.process(p); } catch (IOException e) { e.printStackTrace(); } } /** * Grabs command-line arguments. * The expected arguments are: * <ul> * <li> The filename of the input Jar. * <li> The filename of the output Jar. * <li> One or more include/exclude patterns or files containing these patterns. * See {@link #addString(Params, String)} for syntax. * </ul> * @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(args[0], args[1]); for (int i = 2; i < args.length; i++) { addString(p, args[i]); } return p; } /** * Adds one pattern string to the current filter. * The syntax must be: * <ul> * <li> +full_include or +prefix_include* * <li> -full_exclude or -prefix_exclude* * <li> @filename * </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 */ private void addString(Params p, String s) throws IOException { if (s == null) { return; } s = s.trim(); 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("*")) { p.getFilter().getExcludePrefix().add(s.substring(0, s.length() - 1)); } else { p.getFilter().getExcludeFull().add(s); } } else if (mode == '+') { s = s.replace('.', '/'); // transform FQCN into ASM internal name if (s.endsWith("*")) { p.getFilter().getIncludePrefix().add(s.substring(0, s.length() - 1)); } else { p.getFilter().getIncludeFull().add(s); } } } /** * 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) throws IOException { BufferedReader br = null; try { br = new BufferedReader(new FileReader(osFilePath)); String line; while ((line = br.readLine()) != null) { addString(p, line); } } finally { br.close(); } } /** * Prints some help to stdout. */ private void usage() { System.out.println("Usage: mkstub input.jar output.jar [excluded-class @excluded-classes-file ...]"); 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" + "Inclusion is not supported at method/field level.\n\n"); System.out.println("Exclude 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); } /** * Performs the main workflow of this app: * <ul> * <li> Read the input Jar to get all its classes. * <li> Filter out all classes that should not be included or that should be excluded. * <li> Goes thru the classes, filters methods/fields and generate their source * in a directory called "<outpath_jar_path>_sources" * <li> Does the same filtering on the classes but this time generates the real stubbed * output jar. * </ul> */ private void process(Params p) throws IOException { 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()); System.out.println(String.format("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()); // dump the stubbed jar StubGenerator stub_gen = new StubGenerator(); File dst_jar = new File(p.getOutputJarPath()); stub_gen.generateStubbedJar(dst_jar, classes, p.getFilter()); } }