001/* 002 * Copyright (C) 2009 The Android Open Source Project 003 * Copyright (C) 2017 Keepsafe Software 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package com.android.dexdeps; 019 020import java.io.PrintStream; 021 022/** 023 * Generate fancy output. 024 */ 025public class Output { 026 private static final String IN0 = ""; 027 private static final String IN1 = " "; 028 private static final String IN2 = " "; 029 private static final String IN3 = " "; 030 private static final String IN4 = " "; 031 032 private static final PrintStream out = System.out; 033 034 private static void generateHeader0(String fileName, String format) { 035 if (format.equals("brief")) { 036 if (fileName != null) { 037 out.println("File: " + fileName); 038 } 039 } else if (format.equals("xml")) { 040 if (fileName != null) { 041 out.println(IN0 + "<external file=\"" + fileName + "\">"); 042 } else { 043 out.println(IN0 + "<external>"); 044 } 045 } else { 046 /* should've been trapped in arg handler */ 047 throw new RuntimeException("unknown output format"); 048 } 049 } 050 051 public static void generateFirstHeader(String fileName, String format) { 052 generateHeader0(fileName, format); 053 } 054 055 public static void generateHeader(String fileName, String format) { 056 out.println(); 057 generateHeader0(fileName, format); 058 } 059 060 public static void generateFooter(String format) { 061 if (format.equals("brief")) { 062 // Nothing to do. 063 } else if (format.equals("xml")) { 064 out.println("</external>"); 065 } else { 066 /* should've been trapped in arg handler */ 067 throw new RuntimeException("unknown output format"); 068 } 069 } 070 071 public static void generate(DexData dexData, String format, 072 boolean justClasses) { 073 if (format.equals("brief")) { 074 printBrief(dexData, justClasses); 075 } else if (format.equals("xml")) { 076 printXml(dexData, justClasses); 077 } else { 078 /* should've been trapped in arg handler */ 079 throw new RuntimeException("unknown output format"); 080 } 081 } 082 083 /** 084 * Prints the data in a simple human-readable format. 085 */ 086 static void printBrief(DexData dexData, boolean justClasses) { 087 ClassRef[] externClassRefs = dexData.getExternalReferences(); 088 089 printClassRefs(externClassRefs, justClasses); 090 091 if (!justClasses) { 092 printFieldRefs(externClassRefs); 093 printMethodRefs(externClassRefs); 094 } 095 } 096 097 /** 098 * Prints the list of classes in a simple human-readable format. 099 */ 100 static void printClassRefs(ClassRef[] classes, boolean justClasses) { 101 if (!justClasses) { 102 out.println("Classes:"); 103 } 104 105 for (int i = 0; i < classes.length; i++) { 106 ClassRef ref = classes[i]; 107 108 out.println(descriptorToDot(ref.getName())); 109 } 110 } 111 112 /** 113 * Prints the list of fields in a simple human-readable format. 114 */ 115 static void printFieldRefs(ClassRef[] classes) { 116 out.println("\nFields:"); 117 for (int i = 0; i < classes.length; i++) { 118 FieldRef[] fields = classes[i].getFieldArray(); 119 120 for (int j = 0; j < fields.length; j++) { 121 FieldRef ref = fields[j]; 122 123 out.println(descriptorToDot(ref.getDeclClassName()) + 124 "." + ref.getName() + " : " + ref.getTypeName()); 125 } 126 } 127 } 128 129 /** 130 * Prints the list of methods in a simple human-readable format. 131 */ 132 static void printMethodRefs(ClassRef[] classes) { 133 out.println("\nMethods:"); 134 for (int i = 0; i < classes.length; i++) { 135 MethodRef[] methods = classes[i].getMethodArray(); 136 137 for (int j = 0; j < methods.length; j++) { 138 MethodRef ref = methods[j]; 139 140 out.println(descriptorToDot(ref.getDeclClassName()) + 141 "." + ref.getName() + " : " + ref.getDescriptor()); 142 } 143 } 144 } 145 146 /** 147 * Prints the output in XML format. 148 * 149 * We shouldn't need to XML-escape the field/method info. 150 */ 151 static void printXml(DexData dexData, boolean justClasses) { 152 ClassRef[] externClassRefs = dexData.getExternalReferences(); 153 154 /* 155 * Iterate through externClassRefs. For each class, dump all of 156 * the matching fields and methods. 157 */ 158 String prevPackage = null; 159 for (int i = 0; i < externClassRefs.length; i++) { 160 ClassRef cref = externClassRefs[i]; 161 String declClassName = cref.getName(); 162 String className = classNameOnly(declClassName); 163 String packageName = packageNameOnly(declClassName); 164 165 /* 166 * If we're in a different package, emit the appropriate tags. 167 */ 168 if (!packageName.equals(prevPackage)) { 169 if (prevPackage != null) { 170 out.println(IN1 + "</package>"); 171 } 172 173 out.println(IN1 + 174 "<package name=\"" + packageName + "\">"); 175 176 prevPackage = packageName; 177 } 178 179 out.println(IN2 + "<class name=\"" + className + "\">"); 180 if (!justClasses) { 181 printXmlFields(cref); 182 printXmlMethods(cref); 183 } 184 out.println(IN2 + "</class>"); 185 } 186 187 if (prevPackage != null) 188 out.println(IN1 + "</package>"); 189 } 190 191 /** 192 * Prints the externally-visible fields in XML format. 193 */ 194 private static void printXmlFields(ClassRef cref) { 195 FieldRef[] fields = cref.getFieldArray(); 196 for (int i = 0; i < fields.length; i++) { 197 FieldRef fref = fields[i]; 198 199 out.println(IN3 + "<field name=\"" + fref.getName() + 200 "\" type=\"" + descriptorToDot(fref.getTypeName()) + "\"/>"); 201 } 202 } 203 204 /** 205 * Prints the externally-visible methods in XML format. 206 */ 207 private static void printXmlMethods(ClassRef cref) { 208 MethodRef[] methods = cref.getMethodArray(); 209 for (int i = 0; i < methods.length; i++) { 210 MethodRef mref = methods[i]; 211 String declClassName = mref.getDeclClassName(); 212 boolean constructor; 213 214 constructor = mref.getName().equals("<init>"); 215 if (constructor) { 216 // use class name instead of method name 217 out.println(IN3 + "<constructor name=\"" + 218 classNameOnly(declClassName) + "\">"); 219 } else { 220 out.println(IN3 + "<method name=\"" + mref.getName() + 221 "\" return=\"" + descriptorToDot(mref.getReturnTypeName()) + 222 "\">"); 223 } 224 String[] args = mref.getArgumentTypeNames(); 225 for (int j = 0; j < args.length; j++) { 226 out.println(IN4 + "<parameter type=\"" + 227 descriptorToDot(args[j]) + "\"/>"); 228 } 229 if (constructor) { 230 out.println(IN3 + "</constructor>"); 231 } else { 232 out.println(IN3 + "</method>"); 233 } 234 } 235 } 236 237 238 /* 239 * ======================================================================= 240 * Utility functions 241 * ======================================================================= 242 */ 243 244 /* 245 * MODIFICATIONS: 246 * primitiveTypeLabel, descriptorToDot both made public. 247 */ 248 249 /** 250 * Converts a single-character primitive type into its human-readable 251 * equivalent. 252 */ 253 public static String primitiveTypeLabel(char typeChar) { 254 /* primitive type; substitute human-readable name in */ 255 switch (typeChar) { 256 case 'B': return "byte"; 257 case 'C': return "char"; 258 case 'D': return "double"; 259 case 'F': return "float"; 260 case 'I': return "int"; 261 case 'J': return "long"; 262 case 'S': return "short"; 263 case 'V': return "void"; 264 case 'Z': return "boolean"; 265 default: 266 /* huh? */ 267 System.err.println("Unexpected class char " + typeChar); 268 assert false; 269 return "UNKNOWN"; 270 } 271 } 272 273 /** 274 * Converts a type descriptor to human-readable "dotted" form. For 275 * example, "Ljava/lang/String;" becomes "java.lang.String", and 276 * "[I" becomes "int[]. 277 */ 278 public static String descriptorToDot(String descr) { 279 int targetLen = descr.length(); 280 int offset = 0; 281 int arrayDepth = 0; 282 283 /* strip leading [s; will be added to end */ 284 while (targetLen > 1 && descr.charAt(offset) == '[') { 285 offset++; 286 targetLen--; 287 } 288 arrayDepth = offset; 289 290 if (targetLen == 1) { 291 descr = primitiveTypeLabel(descr.charAt(offset)); 292 offset = 0; 293 targetLen = descr.length(); 294 } else { 295 /* account for leading 'L' and trailing ';' */ 296 if (targetLen >= 2 && descr.charAt(offset) == 'L' && 297 descr.charAt(offset+targetLen-1) == ';') 298 { 299 targetLen -= 2; /* two fewer chars to copy */ 300 offset++; /* skip the 'L' */ 301 } 302 } 303 304 char[] buf = new char[targetLen + arrayDepth * 2]; 305 306 /* copy class name over */ 307 int i; 308 for (i = 0; i < targetLen; i++) { 309 char ch = descr.charAt(offset + i); 310 buf[i] = (ch == '/') ? '.' : ch; 311 } 312 313 /* add the appopriate number of brackets for arrays */ 314 while (arrayDepth-- > 0) { 315 buf[i++] = '['; 316 buf[i++] = ']'; 317 } 318 assert i == buf.length; 319 320 return new String(buf); 321 } 322 323 /** 324 * Extracts the class name from a type descriptor. 325 */ 326 public static String classNameOnly(String typeName) { 327 String dotted = descriptorToDot(typeName); 328 329 int start = dotted.lastIndexOf("."); 330 if (start < 0) { 331 return dotted; 332 } else { 333 return dotted.substring(start+1); 334 } 335 } 336 337 /** 338 * Extracts the package name from a type descriptor, and returns it in 339 * dotted form. 340 */ 341 public static String packageNameOnly(String typeName) { 342 String dotted = descriptorToDot(typeName); 343 344 int end = dotted.lastIndexOf("."); 345 if (end < 0) { 346 /* lives in default package */ 347 return ""; 348 } else { 349 return dotted.substring(0, end); 350 } 351 } 352}