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.dexdeps; 18 19 import java.io.PrintStream; 20 21 /** 22 * Generate fancy output. 23 */ 24 public class Output { 25 private static final String IN0 = ""; 26 private static final String IN1 = " "; 27 private static final String IN2 = " "; 28 private static final String IN3 = " "; 29 private static final String IN4 = " "; 30 31 private static final PrintStream out = System.out; 32 generateHeader0(String fileName, String format)33 private static void generateHeader0(String fileName, String format) { 34 if (format.equals("brief")) { 35 if (fileName != null) { 36 out.println("File: " + fileName); 37 } 38 } else if (format.equals("xml")) { 39 if (fileName != null) { 40 out.println(IN0 + "<external file=\"" + fileName + "\">"); 41 } else { 42 out.println(IN0 + "<external>"); 43 } 44 } else { 45 /* should've been trapped in arg handler */ 46 throw new RuntimeException("unknown output format"); 47 } 48 } 49 generateFirstHeader(String fileName, String format)50 public static void generateFirstHeader(String fileName, String format) { 51 generateHeader0(fileName, format); 52 } 53 generateHeader(String fileName, String format)54 public static void generateHeader(String fileName, String format) { 55 out.println(); 56 generateHeader0(fileName, format); 57 } 58 generateFooter(String format)59 public static void generateFooter(String format) { 60 if (format.equals("brief")) { 61 // Nothing to do. 62 } else if (format.equals("xml")) { 63 out.println("</external>"); 64 } else { 65 /* should've been trapped in arg handler */ 66 throw new RuntimeException("unknown output format"); 67 } 68 } 69 generate(DexData dexData, String format, boolean justClasses)70 public static void generate(DexData dexData, String format, 71 boolean justClasses) { 72 if (format.equals("brief")) { 73 printBrief(dexData, justClasses); 74 } else if (format.equals("xml")) { 75 printXml(dexData, justClasses); 76 } else { 77 /* should've been trapped in arg handler */ 78 throw new RuntimeException("unknown output format"); 79 } 80 } 81 82 /** 83 * Prints the data in a simple human-readable format. 84 */ printBrief(DexData dexData, boolean justClasses)85 static void printBrief(DexData dexData, boolean justClasses) { 86 ClassRef[] externClassRefs = dexData.getExternalReferences(); 87 88 printClassRefs(externClassRefs, justClasses); 89 90 if (!justClasses) { 91 printFieldRefs(externClassRefs); 92 printMethodRefs(externClassRefs); 93 } 94 } 95 96 /** 97 * Prints the list of classes in a simple human-readable format. 98 */ printClassRefs(ClassRef[] classes, boolean justClasses)99 static void printClassRefs(ClassRef[] classes, boolean justClasses) { 100 if (!justClasses) { 101 out.println("Classes:"); 102 } 103 104 for (int i = 0; i < classes.length; i++) { 105 ClassRef ref = classes[i]; 106 107 out.println(descriptorToDot(ref.getName())); 108 } 109 } 110 111 /** 112 * Prints the list of fields in a simple human-readable format. 113 */ printFieldRefs(ClassRef[] classes)114 static void printFieldRefs(ClassRef[] classes) { 115 out.println("\nFields:"); 116 for (int i = 0; i < classes.length; i++) { 117 FieldRef[] fields = classes[i].getFieldArray(); 118 119 for (int j = 0; j < fields.length; j++) { 120 FieldRef ref = fields[j]; 121 122 out.println(descriptorToDot(ref.getDeclClassName()) + 123 "." + ref.getName() + " : " + ref.getTypeName()); 124 } 125 } 126 } 127 128 /** 129 * Prints the list of methods in a simple human-readable format. 130 */ printMethodRefs(ClassRef[] classes)131 static void printMethodRefs(ClassRef[] classes) { 132 out.println("\nMethods:"); 133 for (int i = 0; i < classes.length; i++) { 134 MethodRef[] methods = classes[i].getMethodArray(); 135 136 for (int j = 0; j < methods.length; j++) { 137 MethodRef ref = methods[j]; 138 139 out.println(descriptorToDot(ref.getDeclClassName()) + 140 "." + ref.getName() + " : " + ref.getDescriptor()); 141 } 142 } 143 } 144 145 /** 146 * Prints the output in XML format. 147 * 148 * We shouldn't need to XML-escape the field/method info. 149 */ printXml(DexData dexData, boolean justClasses)150 static void printXml(DexData dexData, boolean justClasses) { 151 ClassRef[] externClassRefs = dexData.getExternalReferences(); 152 153 /* 154 * Iterate through externClassRefs. For each class, dump all of 155 * the matching fields and methods. 156 */ 157 String prevPackage = null; 158 for (int i = 0; i < externClassRefs.length; i++) { 159 ClassRef cref = externClassRefs[i]; 160 String declClassName = cref.getName(); 161 String className = classNameOnly(declClassName); 162 String packageName = packageNameOnly(declClassName); 163 164 /* 165 * If we're in a different package, emit the appropriate tags. 166 */ 167 if (!packageName.equals(prevPackage)) { 168 if (prevPackage != null) { 169 out.println(IN1 + "</package>"); 170 } 171 172 out.println(IN1 + 173 "<package name=\"" + packageName + "\">"); 174 175 prevPackage = packageName; 176 } 177 178 out.println(IN2 + "<class name=\"" + className + "\">"); 179 if (!justClasses) { 180 printXmlFields(cref); 181 printXmlMethods(cref); 182 } 183 out.println(IN2 + "</class>"); 184 } 185 186 if (prevPackage != null) 187 out.println(IN1 + "</package>"); 188 } 189 190 /** 191 * Prints the externally-visible fields in XML format. 192 */ printXmlFields(ClassRef cref)193 private static void printXmlFields(ClassRef cref) { 194 FieldRef[] fields = cref.getFieldArray(); 195 for (int i = 0; i < fields.length; i++) { 196 FieldRef fref = fields[i]; 197 198 out.println(IN3 + "<field name=\"" + fref.getName() + 199 "\" type=\"" + descriptorToDot(fref.getTypeName()) + "\"/>"); 200 } 201 } 202 203 /** 204 * Prints the externally-visible methods in XML format. 205 */ printXmlMethods(ClassRef cref)206 private static void printXmlMethods(ClassRef cref) { 207 MethodRef[] methods = cref.getMethodArray(); 208 for (int i = 0; i < methods.length; i++) { 209 MethodRef mref = methods[i]; 210 String declClassName = mref.getDeclClassName(); 211 boolean constructor; 212 213 constructor = mref.getName().equals("<init>"); 214 if (constructor) { 215 // use class name instead of method name 216 out.println(IN3 + "<constructor name=\"" + 217 classNameOnly(declClassName) + "\">"); 218 } else { 219 out.println(IN3 + "<method name=\"" + mref.getName() + 220 "\" return=\"" + descriptorToDot(mref.getReturnTypeName()) + 221 "\">"); 222 } 223 String[] args = mref.getArgumentTypeNames(); 224 for (int j = 0; j < args.length; j++) { 225 out.println(IN4 + "<parameter type=\"" + 226 descriptorToDot(args[j]) + "\"/>"); 227 } 228 if (constructor) { 229 out.println(IN3 + "</constructor>"); 230 } else { 231 out.println(IN3 + "</method>"); 232 } 233 } 234 } 235 236 237 /* 238 * ======================================================================= 239 * Utility functions 240 * ======================================================================= 241 */ 242 243 /** 244 * Converts a single-character primitive type into its human-readable 245 * equivalent. 246 */ primitiveTypeLabel(char typeChar)247 static String primitiveTypeLabel(char typeChar) { 248 /* primitive type; substitute human-readable name in */ 249 switch (typeChar) { 250 case 'B': return "byte"; 251 case 'C': return "char"; 252 case 'D': return "double"; 253 case 'F': return "float"; 254 case 'I': return "int"; 255 case 'J': return "long"; 256 case 'S': return "short"; 257 case 'V': return "void"; 258 case 'Z': return "boolean"; 259 default: 260 /* huh? */ 261 System.err.println("Unexpected class char " + typeChar); 262 assert false; 263 return "UNKNOWN"; 264 } 265 } 266 267 /** 268 * Converts a type descriptor to human-readable "dotted" form. For 269 * example, "Ljava/lang/String;" becomes "java.lang.String", and 270 * "[I" becomes "int[]. 271 */ descriptorToDot(String descr)272 static String descriptorToDot(String descr) { 273 int targetLen = descr.length(); 274 int offset = 0; 275 int arrayDepth = 0; 276 277 /* strip leading [s; will be added to end */ 278 while (targetLen > 1 && descr.charAt(offset) == '[') { 279 offset++; 280 targetLen--; 281 } 282 arrayDepth = offset; 283 284 if (targetLen == 1) { 285 descr = primitiveTypeLabel(descr.charAt(offset)); 286 offset = 0; 287 targetLen = descr.length(); 288 } else { 289 /* account for leading 'L' and trailing ';' */ 290 if (targetLen >= 2 && descr.charAt(offset) == 'L' && 291 descr.charAt(offset+targetLen-1) == ';') 292 { 293 targetLen -= 2; /* two fewer chars to copy */ 294 offset++; /* skip the 'L' */ 295 } 296 } 297 298 char[] buf = new char[targetLen + arrayDepth * 2]; 299 300 /* copy class name over */ 301 int i; 302 for (i = 0; i < targetLen; i++) { 303 char ch = descr.charAt(offset + i); 304 buf[i] = (ch == '/') ? '.' : ch; 305 } 306 307 /* add the appopriate number of brackets for arrays */ 308 while (arrayDepth-- > 0) { 309 buf[i++] = '['; 310 buf[i++] = ']'; 311 } 312 assert i == buf.length; 313 314 return new String(buf); 315 } 316 317 /** 318 * Extracts the class name from a type descriptor. 319 */ classNameOnly(String typeName)320 static String classNameOnly(String typeName) { 321 String dotted = descriptorToDot(typeName); 322 323 int start = dotted.lastIndexOf("."); 324 if (start < 0) { 325 return dotted; 326 } else { 327 return dotted.substring(start+1); 328 } 329 } 330 331 /** 332 * Extracts the package name from a type descriptor, and returns it in 333 * dotted form. 334 */ packageNameOnly(String typeName)335 static String packageNameOnly(String typeName) { 336 String dotted = descriptorToDot(typeName); 337 338 int end = dotted.lastIndexOf("."); 339 if (end < 0) { 340 /* lives in default package */ 341 return ""; 342 } else { 343 return dotted.substring(0, end); 344 } 345 } 346 } 347