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