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 }