1 /*
   2  * Copyright (c) 2009, 2014, 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.javac.code;
  27 
  28 import java.util.Locale;
  29 
  30 import com.sun.tools.javac.api.Messages;
  31 import com.sun.tools.javac.code.Type.ArrayType;
  32 import com.sun.tools.javac.code.Symbol.*;
  33 import com.sun.tools.javac.code.Type.*;
  34 import com.sun.tools.javac.util.List;
  35 import com.sun.tools.javac.util.ListBuffer;
  36 
  37 import static com.sun.tools.javac.code.BoundKind.*;
  38 import static com.sun.tools.javac.code.Flags.*;
  39 import static com.sun.tools.javac.code.Kinds.Kind.*;
  40 import static com.sun.tools.javac.code.TypeTag.CLASS;
  41 import static com.sun.tools.javac.code.TypeTag.FORALL;
  42 
  43 /**
  44  * A combined type/symbol visitor for generating non-trivial localized string
  45  * representation of types and symbols.
  46  *
  47  * <p><b>This is NOT part of any supported API.
  48  * If you write code that depends on this, you do so at your own risk.
  49  * This code and its internal interfaces are subject to change or
  50  * deletion without notice.</b>
  51  */
  52 public abstract class Printer implements Type.Visitor<String, Locale>, Symbol.Visitor<String, Locale> {
  53 
  54     List<Type> seenCaptured = List.nil();
  55     static final int PRIME = 997;  // largest prime less than 1000
  56 
  57     protected Printer() { }
  58 
  59     /**
  60      * This method should be overriden in order to provide proper i18n support.
  61      *
  62      * @param locale the locale in which the string is to be rendered
  63      * @param key the key corresponding to the message to be displayed
  64      * @param args a list of optional arguments
  65      * @return localized string representation
  66      */
  67     protected abstract String localize(Locale locale, String key, Object... args);
  68 
  69     /**
  70      * Maps a captured type into an unique identifier.
  71      *
  72      * @param t the captured type for which an id is to be retrieved
  73      * @param locale locale settings
  74      * @return unique id representing this captured type
  75      */
  76     protected abstract String capturedVarId(CapturedType t, Locale locale);
  77 
  78     /**
  79      * Create a printer with default i18n support provided by Messages. By default,
  80      * captured types ids are generated using hashcode.
  81      *
  82      * @param messages Messages class to be used for i18n
  83      * @return printer visitor instance
  84      */
  85     public static Printer createStandardPrinter(final Messages messages) {
  86         return new Printer() {
  87             @Override
  88             protected String localize(Locale locale, String key, Object... args) {
  89                 return messages.getLocalizedString(locale, key, args);
  90             }
  91 
  92             @Override
  93             protected String capturedVarId(CapturedType t, Locale locale) {
  94                 return (t.hashCode() & 0xFFFFFFFFL) % PRIME + "";
  95         }};
  96     }
  97 
  98     /**
  99      * Get a localized string representation for all the types in the input list.
 100      *
 101      * @param ts types to be displayed
 102      * @param locale the locale in which the string is to be rendered
 103      * @return localized string representation
 104      */
 105     public String visitTypes(List<Type> ts, Locale locale) {
 106         ListBuffer<String> sbuf = new ListBuffer<>();
 107         for (Type t : ts) {
 108             sbuf.append(visit(t, locale));
 109         }
 110         return sbuf.toList().toString();
 111     }
 112 
 113     /**
 114      * * Get a localized string representation for all the symbols in the input list.
 115      *
 116      * @param ts symbols to be displayed
 117      * @param locale the locale in which the string is to be rendered
 118      * @return localized string representation
 119      */
 120     public String visitSymbols(List<Symbol> ts, Locale locale) {
 121         ListBuffer<String> sbuf = new ListBuffer<>();
 122         for (Symbol t : ts) {
 123             sbuf.append(visit(t, locale));
 124         }
 125         return sbuf.toList().toString();
 126     }
 127 
 128     /**
 129      * Get a localized string representation for a given type.
 130      *
 131      * @param t type to be displayed
 132      * @param locale the locale in which the string is to be rendered
 133      * @return localized string representation
 134      */
 135     public String visit(Type t, Locale locale) {
 136         return t.accept(this, locale);
 137     }
 138 
 139     /**
 140      * Get a localized string representation for a given symbol.
 141      *
 142      * @param s symbol to be displayed
 143      * @param locale the locale in which the string is to be rendered
 144      * @return localized string representation
 145      */
 146     public String visit(Symbol s, Locale locale) {
 147         return s.accept(this, locale);
 148     }
 149 
 150     @Override
 151     public String visitCapturedType(CapturedType t, Locale locale) {
 152         if (seenCaptured.contains(t))
 153             return printAnnotations(t) +
 154                 localize(locale, "compiler.misc.type.captureof.1",
 155                 capturedVarId(t, locale));
 156         else {
 157             try {
 158                 seenCaptured = seenCaptured.prepend(t);
 159                 return printAnnotations(t) +
 160                     localize(locale, "compiler.misc.type.captureof",
 161                     capturedVarId(t, locale),
 162                     visit(t.wildcard, locale));
 163             }
 164             finally {
 165                 seenCaptured = seenCaptured.tail;
 166             }
 167         }
 168     }
 169 
 170     @Override
 171     public String visitForAll(ForAll t, Locale locale) {
 172         return printAnnotations(t) + "<" + visitTypes(t.tvars, locale) +
 173             ">" + visit(t.qtype, locale);
 174     }
 175 
 176     @Override
 177     public String visitUndetVar(UndetVar t, Locale locale) {
 178         if (t.getInst() != null) {
 179             return printAnnotations(t) + visit(t.getInst(), locale);
 180         } else {
 181             return printAnnotations(t) + visit(t.qtype, locale) + "?";
 182         }
 183     }
 184 
 185     @Override
 186     public String visitArrayType(ArrayType t, Locale locale) {
 187         StringBuilder res = new StringBuilder();
 188         printBaseElementType(t, res, locale);
 189         printBrackets(t, res, locale);
 190         return res.toString();
 191     }
 192 
 193     private String printAnnotations(Type t) {
 194         return printAnnotations(t, false);
 195     }
 196 
 197     private String printAnnotations(Type t, boolean prefix) {
 198         StringBuilder sb = new StringBuilder();
 199         List<Attribute.TypeCompound> annos = t.getAnnotationMirrors();
 200         if (!annos.isEmpty()) {
 201             if (prefix) sb.append(' ');
 202             sb.append(annos);
 203             sb.append(' ');
 204         }
 205         return sb.toString();
 206     }
 207 
 208     private void printBaseElementType(Type t, StringBuilder sb, Locale locale) {
 209         Type arrel = t;
 210         while (arrel.hasTag(TypeTag.ARRAY)) {
 211             arrel = ((ArrayType) arrel).elemtype;
 212         }
 213         sb.append(visit(arrel, locale));
 214     }
 215 
 216     private void printBrackets(Type t, StringBuilder sb, Locale locale) {
 217         Type arrel = t;
 218         while (arrel.hasTag(TypeTag.ARRAY)) {
 219             sb.append(printAnnotations(arrel, true));
 220             sb.append("[]");
 221             arrel = ((ArrayType) arrel).elemtype;
 222         }
 223     }
 224 
 225     @Override
 226     public String visitClassType(ClassType t, Locale locale) {
 227         StringBuilder buf = new StringBuilder();
 228         if (t.getEnclosingType().hasTag(CLASS) && t.tsym.owner.kind == TYP) {
 229             buf.append(visit(t.getEnclosingType(), locale));
 230             buf.append('.');
 231             buf.append(printAnnotations(t));
 232             buf.append(className(t, false, locale));
 233         } else {
 234             buf.append(printAnnotations(t));
 235             buf.append(className(t, true, locale));
 236         }
 237         if (t.getTypeArguments().nonEmpty()) {
 238             buf.append('<');
 239             buf.append(visitTypes(t.getTypeArguments(), locale));
 240             buf.append('>');
 241         }
 242         return buf.toString();
 243     }
 244 
 245     @Override
 246     public String visitMethodType(MethodType t, Locale locale) {
 247         return "(" + printMethodArgs(t.argtypes, false, locale) + ")" +
 248             visit(t.restype, locale);
 249     }
 250 
 251     @Override
 252     public String visitPackageType(PackageType t, Locale locale) {
 253         return t.tsym.getQualifiedName().toString();
 254     }
 255 
 256     @Override
 257     public String visitWildcardType(WildcardType t, Locale locale) {
 258         StringBuilder s = new StringBuilder();
 259         s.append(t.kind);
 260         if (t.kind != UNBOUND) {
 261             s.append(printAnnotations(t));
 262             s.append(visit(t.type, locale));
 263         }
 264         return s.toString();
 265     }
 266 
 267     @Override
 268     public String visitErrorType(ErrorType t, Locale locale) {
 269         return visitType(t, locale);
 270     }
 271 
 272     @Override
 273     public String visitTypeVar(TypeVar t, Locale locale) {
 274         return visitType(t, locale);
 275     }
 276 
 277     @Override
 278     public String visitModuleType(ModuleType t, Locale locale) {
 279         return visitType(t, locale);
 280     }
 281 
 282     public String visitType(Type t, Locale locale) {
 283         String s = (t.tsym == null || t.tsym.name == null)
 284                 ? localize(locale, "compiler.misc.type.none")
 285                 : t.tsym.name.toString();
 286         return s;
 287     }
 288 
 289     /**
 290      * Converts a class name into a (possibly localized) string. Anonymous
 291      * inner classes get converted into a localized string.
 292      *
 293      * @param t the type of the class whose name is to be rendered
 294      * @param longform if set, the class' fullname is displayed - if unset the
 295      * short name is chosen (w/o package)
 296      * @param locale the locale in which the string is to be rendered
 297      * @return localized string representation
 298      */
 299     protected String className(ClassType t, boolean longform, Locale locale) {
 300         Symbol sym = t.tsym;
 301         if (sym.name.length() == 0 && (sym.flags() & COMPOUND) != 0) {
 302             StringBuilder s = new StringBuilder(visit(t.supertype_field, locale));
 303             for (List<Type> is = t.interfaces_field; is.nonEmpty(); is = is.tail) {
 304                 s.append('&');
 305                 s.append(visit(is.head, locale));
 306             }
 307             return s.toString();
 308         } else if (sym.name.length() == 0) {
 309             String s;
 310             ClassType norm = (ClassType) t.tsym.type;
 311             if (norm == null) {
 312                 s = localize(locale, "compiler.misc.anonymous.class", (Object) null);
 313             } else if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
 314                 s = localize(locale, "compiler.misc.anonymous.class",
 315                         visit(norm.interfaces_field.head, locale));
 316             } else {
 317                 s = localize(locale, "compiler.misc.anonymous.class",
 318                         visit(norm.supertype_field, locale));
 319             }
 320             return s;
 321         }
 322         String s;
 323         if (longform) {
 324              s =  sym.getQualifiedName().toString();
 325         } else {
 326             s =  sym.name.toString();
 327         }
 328 
 329         return sym.isProjectedNullable() ? s + '?' : s;
 330     }
 331 
 332     /**
 333      * Converts a set of method argument types into their corresponding
 334      * localized string representation.
 335      *
 336      * @param args arguments to be rendered
 337      * @param varArgs if true, the last method argument is regarded as a vararg
 338      * @param locale the locale in which the string is to be rendered
 339      * @return localized string representation
 340      */
 341     protected String printMethodArgs(List<Type> args, boolean varArgs, Locale locale) {
 342         if (!varArgs) {
 343             return visitTypes(args, locale);
 344         } else {
 345             StringBuilder buf = new StringBuilder();
 346             while (args.tail.nonEmpty()) {
 347                 buf.append(visit(args.head, locale));
 348                 args = args.tail;
 349                 buf.append(',');
 350             }
 351             if (args.head.hasTag(TypeTag.ARRAY)) {
 352                 buf.append(visit(((ArrayType) args.head).elemtype, locale));
 353                 if (args.head.getAnnotationMirrors().nonEmpty()) {
 354                     buf.append(' ');
 355                     buf.append(args.head.getAnnotationMirrors());
 356                     buf.append(' ');
 357                 }
 358                 buf.append("...");
 359             } else {
 360                 buf.append(visit(args.head, locale));
 361             }
 362             return buf.toString();
 363         }
 364     }
 365 
 366     @Override
 367     public String visitClassSymbol(ClassSymbol sym, Locale locale) {
 368         return sym.name.isEmpty()
 369                 ? localize(locale, "compiler.misc.anonymous.class", sym.flatname)
 370                 : sym.fullname.toString();
 371     }
 372 
 373     @Override
 374     public String visitMethodSymbol(MethodSymbol s, Locale locale) {
 375         if (s.isStaticOrInstanceInit()) {
 376             return s.owner.name.toString();
 377         } else {
 378             String ms = (s.name == s.name.table.names.init)
 379                     ? s.owner.name.toString()
 380                     : s.name.toString();
 381             if (s.type != null) {
 382                 if (s.type.hasTag(FORALL)) {
 383                     ms = "<" + visitTypes(s.type.getTypeArguments(), locale) + ">" + ms;
 384                 }
 385                 ms += "(" + printMethodArgs(
 386                         s.type.getParameterTypes(),
 387                         (s.flags() & VARARGS) != 0,
 388                         locale) + ")";
 389             }
 390             return ms;
 391         }
 392     }
 393 
 394     @Override
 395     public String visitOperatorSymbol(OperatorSymbol s, Locale locale) {
 396         return visitMethodSymbol(s, locale);
 397     }
 398 
 399     @Override
 400     public String visitPackageSymbol(PackageSymbol s, Locale locale) {
 401         return s.isUnnamed()
 402                 ? localize(locale, "compiler.misc.unnamed.package")
 403                 : s.fullname.toString();
 404     }
 405 
 406     @Override
 407     public String visitTypeSymbol(TypeSymbol s, Locale locale) {
 408         return visitSymbol(s, locale);
 409     }
 410 
 411     @Override
 412     public String visitVarSymbol(VarSymbol s, Locale locale) {
 413         return visitSymbol(s, locale);
 414     }
 415 
 416     @Override
 417     public String visitSymbol(Symbol s, Locale locale) {
 418         return s.name.toString();
 419     }
 420 }