content: /*
   * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package openjdk.tools.jdeprscan;
  
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * Utility class for pretty-printing various bits of API syntax.
   */
  public class Pretty {
      /**
       * Converts deprecation information into an {@code @Deprecated} annotation.
       * The output is minimized: an empty since string is omitted, a forRemoval
       * value of false is omitted; and if both are omitted, the trailing parentheses
       * are also omitted.
       *
       * @param since the since value
       * @param forRemoval the forRemoval value
       * @return string containing an annotation
       */
      static String depr(String since, boolean forRemoval) {
          String d = "@Deprecated";
  
          if (since.isEmpty() && !forRemoval) {
              return d;
          }
  
          StringBuilder sb = new StringBuilder(d).append('(');
  
          if (!since.isEmpty()) {
              sb.append("since=\"")
                .append(since.replace("\"", "\\\""))
                .append('"');
          }
  
          if (forRemoval) {
              if (!since.isEmpty()) {
                  sb.append(", ");
              }
              sb.append("forRemoval=true");
          }
  
          sb.append(')');
  
          return sb.toString();
      }
  
      /**
       * Converts a slash-$ style name into a dot-separated name.
       *
       * @param n the input name
       * @return the result name
       */
      static String unslashify(String n) {
          return n.replace("/", ".")
                  .replace("$", ".");
      }
  
      /**
       * Converts a type descriptor to a readable string.
       *
       * @param desc the input descriptor
       * @return the result string
       */
      static String desc(String desc) {
          return desc(desc, new int[] { 0 });
      }
  
      /**
       * Converts one type descriptor to a readable string, starting
       * from position {@code pos_inout[0]}, and updating it to the
       * location following the descriptor just parsed. A type descriptor
       * mostly corresponds to a FieldType in JVMS 4.3.2. It can be one of a
       * BaseType (a single character denoting a primitive, plus void),
       * an object type ("Lname;"), or an array type (one more more '[' followed
       * by a base or object type).
       *
       * @param desc a string possibly containing several descriptors
       * @param pos_inout on input, the start position; on return, the position
       *                  following the just-parsed descriptor
       * @return the result string
       */
      static String desc(String desc, int[] pos_inout) {
          int dims = 0;
          int pos = pos_inout[0];
          final int len = desc.length();
  
          while (pos < len && desc.charAt(pos) == '[') {
              pos++;
              dims++;
          }
  
          String name;
  
          if (pos >= len) {
              return null;
          }
  
          char c = desc.charAt(pos++);
          switch (c) {
              case 'Z':
                  name = "boolean";
                  break;
              case 'B':
                  name = "byte";
                  break;
              case 'S':
                  name = "short";
                  break;
              case 'C':
                  name = "char";
                  break;
              case 'I':
                  name = "int";
                  break;
              case 'J':
                  name = "long";
                  break;
              case 'F':
                  name = "float";
                  break;
              case 'D':
                  name = "double";
                  break;
              case 'V':
                  name = "void";
                  break;
              case 'L':
                  int semi = desc.indexOf(';', pos);
                  if (semi == -1) {
                      return null;
                  }
                  name = unslashify(desc.substring(pos, semi));
                  pos = semi + 1;
                  break;
              default:
                  return null;
          }
  
          StringBuilder sb = new StringBuilder(name);
          for (int i = 0; i < dims; i++) {
              sb.append("[]");
          }
          pos_inout[0] = pos;
          return sb.toString();
      }
  
      /**
       * Converts a series of type descriptors into a comma-separated,
       * readable string. This is used for the parameter types of a
       * method descriptor.
       *
       * @param types the parameter types
       * @return the readable string
       */
      static String parms(String types) {
          int[] pos = new int[] { 0 };
          StringBuilder sb = new StringBuilder();
  
          boolean first = true;
  
          String t;
  
          while ((t = desc(types, pos)) != null) {
              if (first) {
                  first = false;
              } else {
                  sb.append(',');
              }
              sb.append(t);
          }
  
          return sb.toString();
      }
  
      /**
       * Pattern for matching a method descriptor. Match results can
       * be retrieved from named capture groups as follows: "name(params)return".
       */
      static final Pattern DESC_PAT = Pattern.compile("(?<name>.*)\\((?<args>.*)\\)(?<return>.*)");
  
      /**
       * Pretty-prints the data contained in the given DeprData object.
       *
       * @param dd the deprecation data object
       * @return the formatted string
       */
      public static String print(DeprData dd) {
          StringBuilder sb = new StringBuilder();
          sb.append(depr(dd.since, dd.forRemoval))
            .append(' ');
  
          switch (dd.kind) {
              case ANNOTATION_TYPE:
                  sb.append("@interface ");
                  sb.append(unslashify(dd.typeName));
                  break;
              case CLASS:
                  sb.append("class ");
                  sb.append(unslashify(dd.typeName));
                  break;
              case ENUM:
                  sb.append("enum ");
                  sb.append(unslashify(dd.typeName));
                  break;
              case INTERFACE:
                  sb.append("interface ");
                  sb.append(unslashify(dd.typeName));
                  break;
  
              case ENUM_CONSTANT:
              case FIELD:
                  sb.append(unslashify(dd.typeName))
                    .append('.')
                    .append(dd.nameSig);
                  break;
              case CONSTRUCTOR:
                  Matcher cons = DESC_PAT.matcher(dd.nameSig);
                  sb.append(unslashify(dd.typeName));
                  if (cons.matches()) {
                      sb.append('(')
                        .append(parms(cons.group("args")))
                        .append(')');
                  } else {
                      sb.append('.')
                        .append(dd.nameSig);
                  }
                  break;
              case METHOD:
                  Matcher meth = DESC_PAT.matcher(dd.nameSig);
                  if (meth.matches()) {
                      sb.append(desc(meth.group("return")))
                        .append(' ')
                        .append(unslashify(dd.typeName))
                        .append('.')
                        .append(meth.group("name"))
                        .append('(')
                        .append(parms(meth.group("args")))
                        .append(')');
                  } else {
                      sb.append(unslashify(dd.typeName))
                        .append('.')
                        .append(dd.nameSig);
                  }
                  break;
          }
  
          return sb.toString();
      }
  }
  