1 /*
   2  * Copyright (c) 2015, 2018, 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 package jdk.jshell;
  26 
  27 import static com.sun.tools.javac.code.Flags.COMPOUND;
  28 import static com.sun.tools.javac.code.Kinds.Kind.PCK;
  29 import com.sun.tools.javac.code.Printer;
  30 import com.sun.tools.javac.code.Symbol;
  31 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  32 import com.sun.tools.javac.code.Symbol.PackageSymbol;
  33 import com.sun.tools.javac.code.Symbol.TypeSymbol;
  34 import com.sun.tools.javac.code.Type;
  35 import com.sun.tools.javac.code.Type.ClassType;
  36 import com.sun.tools.javac.code.Type.IntersectionClassType;
  37 import com.sun.tools.javac.util.JavacMessages;
  38 import java.util.Locale;
  39 import java.util.function.BinaryOperator;
  40 import java.util.function.Function;
  41 import java.util.stream.Collectors;
  42 
  43 
  44 /**
  45  * Print types in source form.
  46  */
  47 class TypePrinter extends Printer {
  48 
  49     private static final String OBJECT = "Object";
  50 
  51     private final JavacMessages messages;
  52     private final BinaryOperator<String> fullClassNameAndPackageToClass;
  53     private final Function<TypeSymbol, String> anonymousToName;
  54     private final boolean printIntersectionTypes;
  55     private final AnonymousTypeKind anonymousTypesKind;
  56 
  57     /**Create a TypePrinter.
  58      *
  59      * @param messages javac's messages
  60      * @param fullClassNameAndPackageToClass convertor to convert full class names to
  61      *                                       simple class names.
  62      * @param printIntersectionTypes whether intersection types should be printed
  63      * @param anonymousTypesKind how the anonymous types should be printed
  64      */
  65     TypePrinter(JavacMessages messages,
  66                 BinaryOperator<String> fullClassNameAndPackageToClass,
  67                 boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
  68         this(messages, fullClassNameAndPackageToClass, cs -> cs.flatName().toString(),
  69              printIntersectionTypes, anonymousTypesKind);
  70     }
  71 
  72     /**Create a TypePrinter.
  73      *
  74      * @param messages javac's messages
  75      * @param fullClassNameAndPackageToClass convertor to convert full class names to
  76      *                                       simple class names.
  77      * @param anonymousToName convertor from anonymous classes to name that should be printed
  78      *                        if anonymousTypesKind == AnonymousTypeKind.DECLARE
  79      * @param printIntersectionTypes whether intersection types should be printed
  80      * @param anonymousTypesKind how the anonymous types should be printed
  81      */
  82     TypePrinter(JavacMessages messages,
  83                 BinaryOperator<String> fullClassNameAndPackageToClass,
  84                 Function<TypeSymbol, String> anonymousToName,
  85                 boolean printIntersectionTypes, AnonymousTypeKind anonymousTypesKind) {
  86         this.messages = messages;
  87         this.fullClassNameAndPackageToClass = fullClassNameAndPackageToClass;
  88         this.anonymousToName = anonymousToName;
  89         this.printIntersectionTypes = printIntersectionTypes;
  90         this.anonymousTypesKind = anonymousTypesKind;
  91     }
  92 
  93     String toString(Type t) {
  94         return visit(t, Locale.getDefault());
  95     }
  96 
  97     @Override
  98     protected String localize(Locale locale, String key, Object... args) {
  99         return messages.getLocalizedString(locale, key, args);
 100     }
 101 
 102     @Override
 103     protected String capturedVarId(Type.CapturedType t, Locale locale) {
 104         throw new InternalError("should never call this");
 105     }
 106 
 107     @Override
 108     public String visitCapturedType(Type.CapturedType t, Locale locale) {
 109         return visit(t.wildcard, locale);
 110     }
 111 
 112     @Override
 113     public String visitType(Type t, Locale locale) {
 114         String s = (t.tsym == null || t.tsym.name == null)
 115                 ? OBJECT // none
 116                 : t.tsym.name.toString();
 117         return s;
 118     }
 119 
 120     /**
 121      * Converts a class name into a (possibly localized) string. Anonymous inner
 122      * classes get converted into a localized string.
 123      *
 124      * @param t the type of the class whose name is to be rendered
 125      * @param longform if set, the class' fullname is displayed - if unset the
 126      * short name is chosen (w/o package)
 127      * @param locale the locale in which the string is to be rendered
 128      * @return localized string representation
 129      */
 130     @Override
 131     protected String className(ClassType t, boolean longform, Locale locale) {
 132         TypeSymbol sym = t.tsym;
 133         if (sym.name.length() == 0 && (sym.flags() & COMPOUND) != 0) {
 134             if (printIntersectionTypes) {
 135                 return ((IntersectionClassType) t).getExplicitComponents()
 136                                                   .stream()
 137                                                   .map(i -> visit(i, locale))
 138                                                   .collect(Collectors.joining("&"));
 139             } else {
 140                 return OBJECT;
 141             }
 142         } else if (sym.name.length() == 0) {
 143             if (anonymousTypesKind == AnonymousTypeKind.DECLARE) {
 144                 return anonymousToName.apply(sym);
 145             }
 146             // Anonymous
 147             String s;
 148             boolean isClass;
 149             ClassType norm = (ClassType) t.tsym.type;
 150             if (norm == null) {
 151                 s = OBJECT;
 152                 isClass = true;
 153             } else if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
 154                 s = visit(norm.interfaces_field.head, locale);
 155                 isClass = false;
 156             } else {
 157                 s = visit(norm.supertype_field, locale);
 158                 isClass = true;
 159             }
 160             if (anonymousTypesKind == AnonymousTypeKind.DISPLAY) {
 161                 s = isClass ? "<anonymous class extending " + s + ">"
 162                             : "<anonymous class implementing " + s + ">";
 163             }
 164             return s;
 165         }
 166         String s;
 167         if (longform) {
 168             String pkg = "";
 169             for (Symbol psym = sym; psym != null; psym = psym.owner) {
 170                 if (psym.kind == PCK) {
 171                     pkg = psym.getQualifiedName().toString();
 172                     break;
 173                 }
 174             }
 175             s = fullClassNameAndPackageToClass.apply(
 176                     sym.getQualifiedName().toString(),
 177                     pkg
 178             );
 179         } else {
 180             s = sym.name.toString();
 181         }
 182         return sym.isProjectedNullable() ? s + '?' : s;
 183     }
 184 
 185     @Override
 186     public String visitClassSymbol(ClassSymbol sym, Locale locale) {
 187         return sym.name.isEmpty()
 188                 ? sym.flatname.toString() // Anonymous
 189                 : sym.fullname.toString();
 190     }
 191 
 192     @Override
 193     public String visitPackageSymbol(PackageSymbol s, Locale locale) {
 194         return s.isUnnamed()
 195                 ? "" // Unnamed package
 196                 : s.fullname.toString();
 197     }
 198 
 199     /** Specifies how the anonymous classes should be handled. */
 200     public enum AnonymousTypeKind {
 201         /* The anonymous class is printed as the name of its supertype. */
 202         SUPER,
 203         /* The anonymous class is printed as converted by the anonymousToName
 204          * convertor. */
 205         DECLARE,
 206         /* The anonymous class is printed in a human readable form. */
 207         DISPLAY;
 208     }
 209 }