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