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