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}