1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.jdeprscan;
27
28import java.util.regex.Matcher;
29import java.util.regex.Pattern;
30
31/**
32 * Utility class for pretty-printing various bits of API syntax.
33 */
34public class Pretty {
35    /**
36     * Converts deprecation information into an {@code @Deprecated} annotation.
37     * The output is minimized: an empty since string is omitted, a forRemoval
38     * value of false is omitted; and if both are omitted, the trailing parentheses
39     * are also omitted.
40     *
41     * @param since the since value
42     * @param forRemoval the forRemoval value
43     * @return string containing an annotation
44     */
45    static String depr(String since, boolean forRemoval) {
46        String d = "@Deprecated";
47
48        if (since.isEmpty() && !forRemoval) {
49            return d;
50        }
51
52        StringBuilder sb = new StringBuilder(d).append('(');
53
54        if (!since.isEmpty()) {
55            sb.append("since=\"")
56              .append(since.replace("\"", "\\\""))
57              .append('"');
58        }
59
60        if (forRemoval) {
61            if (!since.isEmpty()) {
62                sb.append(", ");
63            }
64            sb.append("forRemoval=true");
65        }
66
67        sb.append(')');
68
69        return sb.toString();
70    }
71
72    /**
73     * Converts a slash-$ style name into a dot-separated name.
74     *
75     * @param n the input name
76     * @return the result name
77     */
78    static String unslashify(String n) {
79        return n.replace("/", ".")
80                .replace("$", ".");
81    }
82
83    /**
84     * Converts a type descriptor to a readable string.
85     *
86     * @param desc the input descriptor
87     * @return the result string
88     */
89    static String desc(String desc) {
90        return desc(desc, new int[] { 0 });
91    }
92
93    /**
94     * Converts one type descriptor to a readable string, starting
95     * from position {@code pos_inout[0]}, and updating it to the
96     * location following the descriptor just parsed. A type descriptor
97     * mostly corresponds to a FieldType in JVMS 4.3.2. It can be one of a
98     * BaseType (a single character denoting a primitive, plus void),
99     * an object type ("Lname;"), or an array type (one more more '[' followed
100     * by a base or object type).
101     *
102     * @param desc a string possibly containing several descriptors
103     * @param pos_inout on input, the start position; on return, the position
104     *                  following the just-parsed descriptor
105     * @return the result string
106     */
107    static String desc(String desc, int[] pos_inout) {
108        int dims = 0;
109        int pos = pos_inout[0];
110        final int len = desc.length();
111
112        while (pos < len && desc.charAt(pos) == '[') {
113            pos++;
114            dims++;
115        }
116
117        String name;
118
119        if (pos >= len) {
120            return null;
121        }
122
123        char c = desc.charAt(pos++);
124        switch (c) {
125            case 'Z':
126                name = "boolean";
127                break;
128            case 'B':
129                name = "byte";
130                break;
131            case 'S':
132                name = "short";
133                break;
134            case 'C':
135                name = "char";
136                break;
137            case 'I':
138                name = "int";
139                break;
140            case 'J':
141                name = "long";
142                break;
143            case 'F':
144                name = "float";
145                break;
146            case 'D':
147                name = "double";
148                break;
149            case 'V':
150                name = "void";
151                break;
152            case 'L':
153                int semi = desc.indexOf(';', pos);
154                if (semi == -1) {
155                    return null;
156                }
157                name = unslashify(desc.substring(pos, semi));
158                pos = semi + 1;
159                break;
160            default:
161                return null;
162        }
163
164        StringBuilder sb = new StringBuilder(name);
165        for (int i = 0; i < dims; i++) {
166            sb.append("[]");
167        }
168        pos_inout[0] = pos;
169        return sb.toString();
170    }
171
172    /**
173     * Converts a series of type descriptors into a comma-separated,
174     * readable string. This is used for the parameter types of a
175     * method descriptor.
176     *
177     * @param types the parameter types
178     * @return the readable string
179     */
180    static String parms(String types) {
181        int[] pos = new int[] { 0 };
182        StringBuilder sb = new StringBuilder();
183
184        boolean first = true;
185
186        String t;
187
188        while ((t = desc(types, pos)) != null) {
189            if (first) {
190                first = false;
191            } else {
192                sb.append(',');
193            }
194            sb.append(t);
195        }
196
197        return sb.toString();
198    }
199
200    /**
201     * Pattern for matching a method descriptor. Match results can
202     * be retrieved from named capture groups as follows: "name(params)return".
203     */
204    static final Pattern DESC_PAT = Pattern.compile("(?<name>.*)\\((?<args>.*)\\)(?<return>.*)");
205
206    /**
207     * Pretty-prints the data contained in the given DeprData object.
208     *
209     * @param dd the deprecation data object
210     * @return the formatted string
211     */
212    public static String print(DeprData dd) {
213        StringBuilder sb = new StringBuilder();
214        sb.append(depr(dd.since, dd.forRemoval))
215          .append(' ');
216
217        switch (dd.kind) {
218            case ANNOTATION_TYPE:
219                sb.append("@interface ");
220                sb.append(unslashify(dd.typeName));
221                break;
222            case CLASS:
223                sb.append("class ");
224                sb.append(unslashify(dd.typeName));
225                break;
226            case ENUM:
227                sb.append("enum ");
228                sb.append(unslashify(dd.typeName));
229                break;
230            case INTERFACE:
231                sb.append("interface ");
232                sb.append(unslashify(dd.typeName));
233                break;
234
235            case ENUM_CONSTANT:
236            case FIELD:
237                sb.append(unslashify(dd.typeName))
238                  .append('.')
239                  .append(dd.nameSig);
240                break;
241            case CONSTRUCTOR:
242                Matcher cons = DESC_PAT.matcher(dd.nameSig);
243                sb.append(unslashify(dd.typeName));
244                if (cons.matches()) {
245                    sb.append('(')
246                      .append(parms(cons.group("args")))
247                      .append(')');
248                } else {
249                    sb.append('.')
250                      .append(dd.nameSig);
251                }
252                break;
253            case METHOD:
254                Matcher meth = DESC_PAT.matcher(dd.nameSig);
255                if (meth.matches()) {
256                    sb.append(desc(meth.group("return")))
257                      .append(' ')
258                      .append(unslashify(dd.typeName))
259                      .append('.')
260                      .append(meth.group("name"))
261                      .append('(')
262                      .append(parms(meth.group("args")))
263                      .append(')');
264                } else {
265                    sb.append(unslashify(dd.typeName))
266                      .append('.')
267                      .append(dd.nameSig);
268                }
269                break;
270        }
271
272        return sb.toString();
273    }
274}
275