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.dexdeps;
18
19import java.io.PrintStream;
20
21/**
22 * Generate fancy output.
23 */
24public 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
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
50    public static void generateFirstHeader(String fileName, String format) {
51        generateHeader0(fileName, format);
52    }
53
54    public static void generateHeader(String fileName, String format) {
55        out.println();
56        generateHeader0(fileName, format);
57    }
58
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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