Main.java revision adf4543e57086070c95b3ef439dbb6679b0bd562
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.mkstubs;
18
19import org.objectweb.asm.ClassReader;
20
21import java.io.BufferedReader;
22import java.io.File;
23import java.io.FileReader;
24import java.io.IOException;
25import java.util.Map;
26
27
28/**
29 * Main entry point of the MkStubs app.
30 * <p/>
31 * For workflow details, see {@link #process(Params)}.
32 */
33public class Main {
34
35    /**
36     * A struct-like class to hold the various input values (e.g. command-line args)
37     */
38    static class Params {
39        private String mInputJarPath;
40        private String mOutputJarPath;
41        private Filter mFilter;
42
43        public Params(String inputJarPath, String outputJarPath) {
44            mInputJarPath = inputJarPath;
45            mOutputJarPath = outputJarPath;
46            mFilter = new Filter();
47        }
48
49        /** Returns the name of the input jar, where to read classes from. */
50        public String getInputJarPath() {
51            return mInputJarPath;
52        }
53
54        /** Returns the name of the output jar, where to write classes to. */
55        public String getOutputJarPath() {
56            return mOutputJarPath;
57        }
58
59        /** Returns the current instance of the filter, the include/exclude patterns. */
60        public Filter getFilter() {
61            return mFilter;
62        }
63    }
64
65    /**
66     * Main entry point. Processes arguments then performs the "real" work.
67     */
68    public static void main(String[] args) {
69
70        Main m = new Main();
71        try {
72            Params p = m.processArgs(args);
73            m.process(p);
74        } catch (IOException e) {
75            e.printStackTrace();
76        }
77    }
78
79    /**
80     * Grabs command-line arguments.
81     * The expected arguments are:
82     * <ul>
83     * <li> The filename of the input Jar.
84     * <li> The filename of the output Jar.
85     * <li> One or more include/exclude patterns or files containing these patterns.
86     *      See {@link #addString(Params, String)} for syntax.
87     * </ul>
88     * @throws IOException on failure to read a pattern file.
89     */
90    private Params processArgs(String[] args) throws IOException {
91
92        if (args.length < 2) {
93            usage();
94        }
95
96        Params p = new Params(args[0], args[1]);
97
98        for (int i = 2; i < args.length; i++) {
99            addString(p, args[i]);
100        }
101
102        return p;
103    }
104
105    /**
106     * Adds one pattern string to the current filter.
107     * The syntax must be:
108     * <ul>
109     * <li> +full_include or +prefix_include*
110     * <li> -full_exclude or -prefix_exclude*
111     * <li> @filename
112     * </ul>
113     * The input string is trimmed so any space around the first letter (-/+/@) or
114     * at the end is removed. Empty strings are ignored.
115     *
116     * @param p The params which filters to edit.
117     * @param s The string to examine.
118     * @throws IOException
119     */
120    private void addString(Params p, String s) throws IOException {
121        if (s == null) {
122            return;
123        }
124
125        s = s.trim();
126
127        if (s.length() < 2) {
128            return;
129        }
130
131        char mode = s.charAt(0);
132        s = s.substring(1).trim();
133
134        if (mode == '@') {
135            addStringsFromFile(p, s);
136
137        } else if (mode == '-') {
138            s = s.replace('.', '/');  // transform FQCN into ASM internal name
139            if (s.endsWith("*")) {
140                p.getFilter().getExcludePrefix().add(s.substring(0, s.length() - 1));
141            } else {
142                p.getFilter().getExcludeFull().add(s);
143            }
144
145        } else if (mode == '+') {
146            s = s.replace('.', '/');  // transform FQCN into ASM internal name
147            if (s.endsWith("*")) {
148                p.getFilter().getIncludePrefix().add(s.substring(0, s.length() - 1));
149            } else {
150                p.getFilter().getIncludeFull().add(s);
151            }
152        }
153    }
154
155    /**
156     * Adds all the filter strings from the given file.
157     *
158     * @param p The params which filter to edit.
159     * @param osFilePath The OS path to the file containing the patterns.
160     * @throws IOException
161     *
162     * @see #addString(Params, String)
163     */
164    private void addStringsFromFile(Params p, String osFilePath)
165            throws IOException {
166        BufferedReader br = null;
167        try {
168            br = new BufferedReader(new FileReader(osFilePath));
169            String line;
170            while ((line = br.readLine()) != null) {
171                addString(p, line);
172            }
173        } finally {
174            br.close();
175        }
176    }
177
178    /**
179     * Prints some help to stdout.
180     */
181    private void usage() {
182        System.out.println("Usage: mkstub input.jar output.jar [excluded-class @excluded-classes-file ...]");
183
184        System.out.println("Include syntax:\n" +
185                "+com.package.* : whole package, with glob\n" +
186                "+com.package.Class[$Inner] or ...Class*: whole classes with optional glob\n" +
187                "Inclusion is not supported at method/field level.\n\n");
188
189        System.out.println("Exclude syntax:\n" +
190        		"-com.package.* : whole package, with glob\n" +
191        		"-com.package.Class[$Inner] or ...Class*: whole classes with optional glob\n" +
192        		"-com.package.Class#method: whole method or field\n" +
193                "-com.package.Class#method(IILjava/lang/String;)V: specific method with signature.\n\n");
194        System.exit(1);
195    }
196
197    /**
198     * Performs the main workflow of this app:
199     * <ul>
200     * <li> Read the input Jar to get all its classes.
201     * <li> Filter out all classes that should not be included or that should be excluded.
202     * <li> Goes thru the classes, filters methods/fields and generate their source
203     *      in a directory called "&lt;outpath_jar_path&gt;_sources"
204     * <li> Does the same filtering on the classes but this time generates the real stubbed
205     *      output jar.
206     * </ul>
207     */
208    private void process(Params p) throws IOException {
209        AsmAnalyzer aa = new AsmAnalyzer();
210        Map<String, ClassReader> classes = aa.parseInputJar(p.getInputJarPath());
211
212        System.out.println(String.format("Classes loaded: %d", classes.size()));
213
214        aa.filter(classes, p.getFilter());
215
216        System.out.println(String.format("Classes filtered: %d", classes.size()));
217
218        // dump as Java source files, mostly for debugging
219        SourceGenerator src_gen = new SourceGenerator();
220        File dst_src_dir = new File(p.getOutputJarPath() + "_sources");
221        dst_src_dir.mkdir();
222        src_gen.generateSource(dst_src_dir, classes, p.getFilter());
223
224        // dump the stubbed jar
225        StubGenerator stub_gen = new StubGenerator();
226        File dst_jar = new File(p.getOutputJarPath());
227        stub_gen.generateStubbedJar(dst_jar, classes, p.getFilter());
228    }
229}
230