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 
 26 package com.sun.tools.jdeprscan;
 27 
 28 import java.util.regex.Matcher;
 29 import java.util.regex.Pattern;
 30 
 31 /**
 32  * Utility class for pretty-printing various bits of API syntax.
 33  */
 34 public 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 'Q':
153             case 'L':
154                 int semi = desc.indexOf(';', pos);
155                 if (semi == -1) {
156                     return null;
157                 }
158                 name = unslashify(desc.substring(pos, semi));
159                 pos = semi + 1;
160                 break;
161             default:
162                 return null;
163         }
164 
165         StringBuilder sb = new StringBuilder(name);
166         for (int i = 0; i < dims; i++) {
167             sb.append("[]");
168         }
169         pos_inout[0] = pos;
170         return sb.toString();
171     }
172 
173     /**
174      * Converts a series of type descriptors into a comma-separated,
175      * readable string. This is used for the parameter types of a
176      * method descriptor.
177      *
178      * @param types the parameter types
179      * @return the readable string
180      */
181     static String parms(String types) {
182         int[] pos = new int[] { 0 };
183         StringBuilder sb = new StringBuilder();
184 
185         boolean first = true;
186 
187         String t;
188 
189         while ((t = desc(types, pos)) != null) {
190             if (first) {
191                 first = false;
192             } else {
193                 sb.append(',');
194             }
195             sb.append(t);
196         }
197 
198         return sb.toString();
199     }
200 
201     /**
202      * Pattern for matching a method descriptor. Match results can
203      * be retrieved from named capture groups as follows: "name(params)return".
204      */
205     static final Pattern DESC_PAT = Pattern.compile("(?<name>.*)\\((?<args>.*)\\)(?<return>.*)");
206 
207     /**
208      * Pretty-prints the data contained in the given DeprData object.
209      *
210      * @param dd the deprecation data object
211      * @return the formatted string
212      */
213     public static String print(DeprData dd) {
214         StringBuilder sb = new StringBuilder();
215         sb.append(depr(dd.since, dd.forRemoval))
216           .append(' ');
217 
218         switch (dd.kind) {
219             case ANNOTATION_TYPE:
220                 sb.append("@interface ");
221                 sb.append(unslashify(dd.typeName));
222                 break;
223             case CLASS:
224                 sb.append("class ");
225                 sb.append(unslashify(dd.typeName));
226                 break;
227             case ENUM:
228                 sb.append("enum ");
229                 sb.append(unslashify(dd.typeName));
230                 break;
231             case INTERFACE:
232                 sb.append("interface ");
233                 sb.append(unslashify(dd.typeName));
234                 break;
235 
236             case ENUM_CONSTANT:
237             case FIELD:
238                 sb.append(unslashify(dd.typeName))
239                   .append('.')
240                   .append(dd.nameSig);
241                 break;
242             case CONSTRUCTOR:
243                 Matcher cons = DESC_PAT.matcher(dd.nameSig);
244                 sb.append(unslashify(dd.typeName));
245                 if (cons.matches()) {
246                     sb.append('(')
247                       .append(parms(cons.group("args")))
248                       .append(')');
249                 } else {
250                     sb.append('.')
251                       .append(dd.nameSig);
252                 }
253                 break;
254             case METHOD:
255                 Matcher meth = DESC_PAT.matcher(dd.nameSig);
256                 if (meth.matches()) {
257                     sb.append(desc(meth.group("return")))
258                       .append(' ')
259                       .append(unslashify(dd.typeName))
260                       .append('.')
261                       .append(meth.group("name"))
262                       .append('(')
263                       .append(parms(meth.group("args")))
264                       .append(')');
265                 } else {
266                     sb.append(unslashify(dd.typeName))
267                       .append('.')
268                       .append(dd.nameSig);
269                 }
270                 break;
271         }
272 
273         return sb.toString();
274     }
275 }