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 17 package com.android.mkstubs; 18 19 import org.objectweb.asm.ClassReader; 20 import org.objectweb.asm.Opcodes; 21 22 import java.io.BufferedReader; 23 import java.io.File; 24 import java.io.FileReader; 25 import java.io.IOException; 26 import java.util.Map; 27 28 29 /** 30 * Main entry point of the MkStubs app. 31 * <p/> 32 * For workflow details, see {@link #process(Params)}. 33 */ 34 public class Main { 35 36 public static final int ASM_VERSION = Opcodes.ASM5; 37 38 /** 39 * A struct-like class to hold the various input values (e.g. command-line args) 40 */ 41 static class Params { 42 private String mInputJarPath; 43 private String mOutputJarPath; 44 private Filter mFilter; 45 private boolean mVerbose; 46 private boolean mDumpSource; 47 Params()48 public Params() { 49 mFilter = new Filter(); 50 } 51 52 /** Sets the name of the input jar, where to read classes from. Must not be null. */ setInputJarPath(String inputJarPath)53 public void setInputJarPath(String inputJarPath) { 54 mInputJarPath = inputJarPath; 55 } 56 57 /** Sets the name of the output jar, where to write classes to. Must not be null. */ setOutputJarPath(String outputJarPath)58 public void setOutputJarPath(String outputJarPath) { 59 mOutputJarPath = outputJarPath; 60 } 61 62 /** Returns the name of the input jar, where to read classes from. */ getInputJarPath()63 public String getInputJarPath() { 64 return mInputJarPath; 65 } 66 67 /** Returns the name of the output jar, where to write classes to. */ getOutputJarPath()68 public String getOutputJarPath() { 69 return mOutputJarPath; 70 } 71 72 /** Returns the current instance of the filter, the include/exclude patterns. */ getFilter()73 public Filter getFilter() { 74 return mFilter; 75 } 76 77 /** Sets verbose mode on. Default is off. */ setVerbose()78 public void setVerbose() { 79 mVerbose = true; 80 } 81 82 /** Returns true if verbose mode is on. */ isVerbose()83 public boolean isVerbose() { 84 return mVerbose; 85 } 86 87 /** Sets dump source mode on. Default is off. */ setDumpSource()88 public void setDumpSource() { 89 mDumpSource = true; 90 } 91 92 /** Returns true if source should be dumped. */ isDumpSource()93 public boolean isDumpSource() { 94 return mDumpSource; 95 } 96 } 97 98 /** Logger that writes on stdout depending a conditional verbose mode. */ 99 static class Logger { 100 private final boolean mVerbose; 101 Logger(boolean verbose)102 public Logger(boolean verbose) { 103 mVerbose = verbose; 104 } 105 106 /** Writes to stdout only in verbose mode. */ debug(String msg, Object...params)107 public void debug(String msg, Object...params) { 108 if (mVerbose) { 109 System.out.println(String.format(msg, params)); 110 } 111 } 112 113 /** Writes to stdout all the time. */ info(String msg, Object...params)114 public void info(String msg, Object...params) { 115 System.out.println(String.format(msg, params)); 116 } 117 } 118 119 /** 120 * Main entry point. Processes arguments then performs the "real" work. 121 */ main(String[] args)122 public static void main(String[] args) { 123 Main m = new Main(); 124 try { 125 Params p = m.processArgs(args); 126 m.process(p); 127 } catch (IOException e) { 128 e.printStackTrace(); 129 } 130 } 131 132 /** 133 * Grabs command-line arguments. 134 * The expected arguments are: 135 * <ul> 136 * <li> The filename of the input Jar. 137 * <li> The filename of the output Jar. 138 * <li> One or more include/exclude patterns or files containing these patterns. 139 * See {@link #addString(Params, String)} for syntax. 140 * </ul> 141 * @throws IOException on failure to read a pattern file. 142 */ processArgs(String[] args)143 private Params processArgs(String[] args) throws IOException { 144 Params p = new Params(); 145 146 for (String arg : args) { 147 if (arg.startsWith("--")) { 148 if (arg.startsWith("--v")) { 149 p.setVerbose(); 150 } else if (arg.startsWith("--s")) { 151 p.setDumpSource(); 152 } else if (arg.startsWith("--h")) { 153 usage(null); 154 } else { 155 usage("Unknown argument: " + arg); 156 } 157 } else if (p.getInputJarPath() == null) { 158 p.setInputJarPath(arg); 159 } else if (p.getOutputJarPath() == null) { 160 p.setOutputJarPath(arg); 161 } else { 162 addString(p, arg); 163 } 164 } 165 166 if (p.getInputJarPath() == null && p.getOutputJarPath() == null) { 167 usage("Missing input or output JAR."); 168 } 169 170 return p; 171 } 172 173 /** 174 * Adds one pattern string to the current filter. 175 * The syntax must be: 176 * <ul> 177 * <li> +full_include or +prefix_include* 178 * <li> -full_exclude or -prefix_exclude* 179 * <li> @filename 180 * </ul> 181 * The input string is trimmed so any space around the first letter (-/+/@) or 182 * at the end is removed. Empty strings are ignored. 183 * 184 * @param p The params which filters to edit. 185 * @param s The string to examine. 186 * @throws IOException 187 */ addString(Params p, String s)188 private void addString(Params p, String s) throws IOException { 189 if (s == null) { 190 return; 191 } 192 193 s = s.trim(); 194 195 if (s.length() < 2) { 196 return; 197 } 198 199 char mode = s.charAt(0); 200 s = s.substring(1).trim(); 201 202 if (mode == '@') { 203 addStringsFromFile(p, s); 204 205 } else if (mode == '-') { 206 s = s.replace('.', '/'); // transform FQCN into ASM internal name 207 if (s.endsWith("*")) { 208 p.getFilter().getExcludePrefix().add(s.substring(0, s.length() - 1)); 209 } else { 210 p.getFilter().getExcludeFull().add(s); 211 } 212 213 } else if (mode == '+') { 214 s = s.replace('.', '/'); // transform FQCN into ASM internal name 215 if (s.endsWith("*")) { 216 p.getFilter().getIncludePrefix().add(s.substring(0, s.length() - 1)); 217 } else { 218 p.getFilter().getIncludeFull().add(s); 219 } 220 } 221 } 222 223 /** 224 * Adds all the filter strings from the given file. 225 * 226 * @param p The params which filter to edit. 227 * @param osFilePath The OS path to the file containing the patterns. 228 * @throws IOException 229 * 230 * @see #addString(Params, String) 231 */ addStringsFromFile(Params p, String osFilePath)232 private void addStringsFromFile(Params p, String osFilePath) 233 throws IOException { 234 BufferedReader br = null; 235 try { 236 br = new BufferedReader(new FileReader(osFilePath)); 237 String line; 238 while ((line = br.readLine()) != null) { 239 addString(p, line); 240 } 241 } finally { 242 if (br != null) { 243 br.close(); 244 } 245 } 246 } 247 248 /** 249 * Prints some help to stdout. 250 * @param error The error that generated the usage, if any. Can be null. 251 */ usage(String error)252 private void usage(String error) { 253 if (error != null) { 254 System.out.println("ERROR: " + error); 255 } 256 257 System.out.println("Usage: mkstub [--h|--s|--v] input.jar output.jar [excluded-class @excluded-classes-file ...]"); 258 259 System.out.println("Options:\n" + 260 " --h | --help : print this usage.\n" + 261 " --v | --verbose : verbose mode.\n" + 262 " --s | --source : dump source equivalent to modified byte code.\n\n"); 263 264 System.out.println("Include syntax:\n" + 265 "+com.package.* : whole package, with glob\n" + 266 "+com.package.Class[$Inner] or ...Class*: whole classes with optional glob\n" + 267 "Inclusion is not supported at method/field level.\n\n"); 268 269 System.out.println("Exclude syntax:\n" + 270 "-com.package.* : whole package, with glob\n" + 271 "-com.package.Class[$Inner] or ...Class*: whole classes with optional glob\n" + 272 "-com.package.Class#method: whole method or field\n" + 273 "-com.package.Class#method(IILjava/lang/String;)V: specific method with signature.\n\n"); 274 275 System.exit(1); 276 } 277 278 /** 279 * Performs the main workflow of this app: 280 * <ul> 281 * <li> Read the input Jar to get all its classes. 282 * <li> Filter out all classes that should not be included or that should be excluded. 283 * <li> Goes thru the classes, filters methods/fields and generate their source 284 * in a directory called "<outpath_jar_path>_sources" 285 * <li> Does the same filtering on the classes but this time generates the real stubbed 286 * output jar. 287 * </ul> 288 */ process(Params p)289 private void process(Params p) throws IOException { 290 AsmAnalyzer aa = new AsmAnalyzer(); 291 Map<String, ClassReader> classes = aa.parseInputJar(p.getInputJarPath()); 292 293 Logger log = new Logger(p.isVerbose()); 294 log.info("Classes loaded: %d", classes.size()); 295 296 aa.filter(classes, p.getFilter(), log); 297 log.info("Classes filtered: %d", classes.size()); 298 299 // dump as Java source files, mostly for debugging 300 if (p.isDumpSource()) { 301 SourceGenerator src_gen = new SourceGenerator(log); 302 File dst_src_dir = new File(p.getOutputJarPath() + "_sources"); 303 dst_src_dir.mkdir(); 304 src_gen.generateSource(dst_src_dir, classes, p.getFilter()); 305 } 306 307 // dump the stubbed jar 308 StubGenerator stub_gen = new StubGenerator(log); 309 File dst_jar = new File(p.getOutputJarPath()); 310 stub_gen.generateStubbedJar(dst_jar, classes, p.getFilter()); 311 } 312 } 313