| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dexdeps; |
| |
| import java.io.PrintStream; |
| |
| /** |
| * Generate fancy output. |
| */ |
| public class Output { |
| private static final String IN0 = ""; |
| private static final String IN1 = " "; |
| private static final String IN2 = " "; |
| private static final String IN3 = " "; |
| private static final String IN4 = " "; |
| |
| private static final PrintStream out = System.out; |
| |
| private static void generateHeader0(String fileName, String format) { |
| if (format.equals("brief")) { |
| if (fileName != null) { |
| out.println("File: " + fileName); |
| } |
| } else if (format.equals("xml")) { |
| if (fileName != null) { |
| out.println(IN0 + "<external file=\"" + fileName + "\">"); |
| } else { |
| out.println(IN0 + "<external>"); |
| } |
| } else { |
| /* should've been trapped in arg handler */ |
| throw new RuntimeException("unknown output format"); |
| } |
| } |
| |
| public static void generateFirstHeader(String fileName, String format) { |
| generateHeader0(fileName, format); |
| } |
| |
| public static void generateHeader(String fileName, String format) { |
| out.println(); |
| generateHeader0(fileName, format); |
| } |
| |
| public static void generateFooter(String format) { |
| if (format.equals("brief")) { |
| // Nothing to do. |
| } else if (format.equals("xml")) { |
| out.println("</external>"); |
| } else { |
| /* should've been trapped in arg handler */ |
| throw new RuntimeException("unknown output format"); |
| } |
| } |
| |
| public static void generate(DexData dexData, String format, |
| boolean justClasses) { |
| if (format.equals("brief")) { |
| printBrief(dexData, justClasses); |
| } else if (format.equals("xml")) { |
| printXml(dexData, justClasses); |
| } else { |
| /* should've been trapped in arg handler */ |
| throw new RuntimeException("unknown output format"); |
| } |
| } |
| |
| /** |
| * Prints the data in a simple human-readable format. |
| */ |
| static void printBrief(DexData dexData, boolean justClasses) { |
| ClassRef[] externClassRefs = dexData.getExternalReferences(); |
| |
| printClassRefs(externClassRefs, justClasses); |
| |
| if (!justClasses) { |
| printFieldRefs(externClassRefs); |
| printMethodRefs(externClassRefs); |
| } |
| } |
| |
| /** |
| * Prints the list of classes in a simple human-readable format. |
| */ |
| static void printClassRefs(ClassRef[] classes, boolean justClasses) { |
| if (!justClasses) { |
| out.println("Classes:"); |
| } |
| |
| for (int i = 0; i < classes.length; i++) { |
| ClassRef ref = classes[i]; |
| |
| out.println(descriptorToDot(ref.getName())); |
| } |
| } |
| |
| /** |
| * Prints the list of fields in a simple human-readable format. |
| */ |
| static void printFieldRefs(ClassRef[] classes) { |
| out.println("\nFields:"); |
| for (int i = 0; i < classes.length; i++) { |
| FieldRef[] fields = classes[i].getFieldArray(); |
| |
| for (int j = 0; j < fields.length; j++) { |
| FieldRef ref = fields[j]; |
| |
| out.println(descriptorToDot(ref.getDeclClassName()) + |
| "." + ref.getName() + " : " + ref.getTypeName()); |
| } |
| } |
| } |
| |
| /** |
| * Prints the list of methods in a simple human-readable format. |
| */ |
| static void printMethodRefs(ClassRef[] classes) { |
| out.println("\nMethods:"); |
| for (int i = 0; i < classes.length; i++) { |
| MethodRef[] methods = classes[i].getMethodArray(); |
| |
| for (int j = 0; j < methods.length; j++) { |
| MethodRef ref = methods[j]; |
| |
| out.println(descriptorToDot(ref.getDeclClassName()) + |
| "." + ref.getName() + " : " + ref.getDescriptor()); |
| } |
| } |
| } |
| |
| /** |
| * Prints the output in XML format. |
| * |
| * We shouldn't need to XML-escape the field/method info. |
| */ |
| static void printXml(DexData dexData, boolean justClasses) { |
| ClassRef[] externClassRefs = dexData.getExternalReferences(); |
| |
| /* |
| * Iterate through externClassRefs. For each class, dump all of |
| * the matching fields and methods. |
| */ |
| String prevPackage = null; |
| for (int i = 0; i < externClassRefs.length; i++) { |
| ClassRef cref = externClassRefs[i]; |
| String declClassName = cref.getName(); |
| String className = classNameOnly(declClassName); |
| String packageName = packageNameOnly(declClassName); |
| |
| /* |
| * If we're in a different package, emit the appropriate tags. |
| */ |
| if (!packageName.equals(prevPackage)) { |
| if (prevPackage != null) { |
| out.println(IN1 + "</package>"); |
| } |
| |
| out.println(IN1 + |
| "<package name=\"" + packageName + "\">"); |
| |
| prevPackage = packageName; |
| } |
| |
| out.println(IN2 + "<class name=\"" + className + "\">"); |
| if (!justClasses) { |
| printXmlFields(cref); |
| printXmlMethods(cref); |
| } |
| out.println(IN2 + "</class>"); |
| } |
| |
| if (prevPackage != null) |
| out.println(IN1 + "</package>"); |
| } |
| |
| /** |
| * Prints the externally-visible fields in XML format. |
| */ |
| private static void printXmlFields(ClassRef cref) { |
| FieldRef[] fields = cref.getFieldArray(); |
| for (int i = 0; i < fields.length; i++) { |
| FieldRef fref = fields[i]; |
| |
| out.println(IN3 + "<field name=\"" + fref.getName() + |
| "\" type=\"" + descriptorToDot(fref.getTypeName()) + "\"/>"); |
| } |
| } |
| |
| /** |
| * Prints the externally-visible methods in XML format. |
| */ |
| private static void printXmlMethods(ClassRef cref) { |
| MethodRef[] methods = cref.getMethodArray(); |
| for (int i = 0; i < methods.length; i++) { |
| MethodRef mref = methods[i]; |
| String declClassName = mref.getDeclClassName(); |
| boolean constructor; |
| |
| constructor = mref.getName().equals("<init>"); |
| if (constructor) { |
| // use class name instead of method name |
| out.println(IN3 + "<constructor name=\"" + |
| classNameOnly(declClassName) + "\">"); |
| } else { |
| out.println(IN3 + "<method name=\"" + mref.getName() + |
| "\" return=\"" + descriptorToDot(mref.getReturnTypeName()) + |
| "\">"); |
| } |
| String[] args = mref.getArgumentTypeNames(); |
| for (int j = 0; j < args.length; j++) { |
| out.println(IN4 + "<parameter type=\"" + |
| descriptorToDot(args[j]) + "\"/>"); |
| } |
| if (constructor) { |
| out.println(IN3 + "</constructor>"); |
| } else { |
| out.println(IN3 + "</method>"); |
| } |
| } |
| } |
| |
| |
| /* |
| * ======================================================================= |
| * Utility functions |
| * ======================================================================= |
| */ |
| |
| /** |
| * Converts a single-character primitive type into its human-readable |
| * equivalent. |
| */ |
| static String primitiveTypeLabel(char typeChar) { |
| /* primitive type; substitute human-readable name in */ |
| switch (typeChar) { |
| case 'B': return "byte"; |
| case 'C': return "char"; |
| case 'D': return "double"; |
| case 'F': return "float"; |
| case 'I': return "int"; |
| case 'J': return "long"; |
| case 'S': return "short"; |
| case 'V': return "void"; |
| case 'Z': return "boolean"; |
| default: |
| /* huh? */ |
| System.err.println("Unexpected class char " + typeChar); |
| assert false; |
| return "UNKNOWN"; |
| } |
| } |
| |
| /** |
| * Converts a type descriptor to human-readable "dotted" form. For |
| * example, "Ljava/lang/String;" becomes "java.lang.String", and |
| * "[I" becomes "int[]. |
| */ |
| static String descriptorToDot(String descr) { |
| int targetLen = descr.length(); |
| int offset = 0; |
| int arrayDepth = 0; |
| |
| /* strip leading [s; will be added to end */ |
| while (targetLen > 1 && descr.charAt(offset) == '[') { |
| offset++; |
| targetLen--; |
| } |
| arrayDepth = offset; |
| |
| if (targetLen == 1) { |
| descr = primitiveTypeLabel(descr.charAt(offset)); |
| offset = 0; |
| targetLen = descr.length(); |
| } else { |
| /* account for leading 'L' and trailing ';' */ |
| if (targetLen >= 2 && descr.charAt(offset) == 'L' && |
| descr.charAt(offset+targetLen-1) == ';') |
| { |
| targetLen -= 2; /* two fewer chars to copy */ |
| offset++; /* skip the 'L' */ |
| } |
| } |
| |
| char[] buf = new char[targetLen + arrayDepth * 2]; |
| |
| /* copy class name over */ |
| int i; |
| for (i = 0; i < targetLen; i++) { |
| char ch = descr.charAt(offset + i); |
| buf[i] = (ch == '/') ? '.' : ch; |
| } |
| |
| /* add the appopriate number of brackets for arrays */ |
| while (arrayDepth-- > 0) { |
| buf[i++] = '['; |
| buf[i++] = ']'; |
| } |
| assert i == buf.length; |
| |
| return new String(buf); |
| } |
| |
| /** |
| * Extracts the class name from a type descriptor. |
| */ |
| static String classNameOnly(String typeName) { |
| String dotted = descriptorToDot(typeName); |
| |
| int start = dotted.lastIndexOf("."); |
| if (start < 0) { |
| return dotted; |
| } else { |
| return dotted.substring(start+1); |
| } |
| } |
| |
| /** |
| * Extracts the package name from a type descriptor, and returns it in |
| * dotted form. |
| */ |
| static String packageNameOnly(String typeName) { |
| String dotted = descriptorToDot(typeName); |
| |
| int end = dotted.lastIndexOf("."); |
| if (end < 0) { |
| /* lives in default package */ |
| return ""; |
| } else { |
| return dotted.substring(0, end); |
| } |
| } |
| } |