RichDiagnosticFormatter.java revision 2734:b96d74fa60aa
1135446Strhodes/*
2193149Sdougb * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
3135446Strhodes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4135446Strhodes *
5174187Sdougb * This code is free software; you can redistribute it and/or modify it
6135446Strhodes * under the terms of the GNU General Public License version 2 only, as
7135446Strhodes * published by the Free Software Foundation.  Oracle designates this
8135446Strhodes * particular file as subject to the "Classpath" exception as provided
9135446Strhodes * by Oracle in the LICENSE file that accompanied this code.
10135446Strhodes *
11135446Strhodes * This code is distributed in the hope that it will be useful, but WITHOUT
12135446Strhodes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13135446Strhodes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14135446Strhodes * version 2 for more details (a copy is included in the LICENSE file that
15135446Strhodes * accompanied this code).
16135446Strhodes *
17135446Strhodes * You should have received a copy of the GNU General Public License version
18193149Sdougb * 2 along with this work; if not, write to the Free Software Foundation,
19135446Strhodes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20135446Strhodes *
21135446Strhodes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22135446Strhodes * or visit www.oracle.com if you need additional information or have any
23135446Strhodes * questions.
24135446Strhodes */
25135446Strhodespackage com.sun.tools.javac.util;
26135446Strhodes
27193149Sdougbimport java.nio.file.Path;
28170222Sdougbimport java.util.EnumMap;
29135446Strhodesimport java.util.EnumSet;
30135446Strhodesimport java.util.HashMap;
31135446Strhodesimport java.util.LinkedHashMap;
32135446Strhodesimport java.util.Locale;
33135446Strhodesimport java.util.Map;
34170222Sdougb
35135446Strhodesimport com.sun.tools.javac.code.Printer;
36135446Strhodesimport com.sun.tools.javac.code.Symbol;
37170222Sdougbimport com.sun.tools.javac.code.Symbol.*;
38135446Strhodesimport com.sun.tools.javac.code.Symtab;
39135446Strhodesimport com.sun.tools.javac.code.Type;
40135446Strhodesimport com.sun.tools.javac.code.Type.*;
41170222Sdougbimport com.sun.tools.javac.code.Types;
42135446Strhodes
43135446Strhodesimport static com.sun.tools.javac.code.Flags.*;
44170222Sdougbimport static com.sun.tools.javac.code.TypeTag.*;
45135446Strhodesimport static com.sun.tools.javac.code.Kinds.*;
46135446Strhodesimport static com.sun.tools.javac.code.Kinds.Kind.*;
47170222Sdougbimport static com.sun.tools.javac.util.LayoutCharacters.*;
48135446Strhodesimport static com.sun.tools.javac.util.RichDiagnosticFormatter.RichConfiguration.*;
49135446Strhodes
50170222Sdougb/**
51135446Strhodes * A rich diagnostic formatter is a formatter that provides better integration
52135446Strhodes * with javac's type system. A diagostic is first preprocessed in order to keep
53135446Strhodes * track of each types/symbols in it; after these informations are collected,
54135446Strhodes * the diagnostic is rendered using a standard formatter, whose type/symbol printer
55135446Strhodes * has been replaced by a more refined version provided by this rich formatter.
56135446Strhodes * The rich formatter currently enables three different features: (i) simple class
57135446Strhodes * names - that is class names are displayed used a non qualified name (thus
58135446Strhodes * omitting package info) whenever possible - (ii) where clause list - a list of
59135446Strhodes * additional subdiagnostics that provide specific info about type-variables,
60135446Strhodes * captured types, intersection types that occur in the diagnostic that is to be
61135446Strhodes * formatted and (iii) type-variable disambiguation - when the diagnostic refers
62135446Strhodes * to two different type-variables with the same name, their representation is
63135446Strhodes * disambiguated by appending an index to the type variable name.
64135446Strhodes *
65135446Strhodes * <p><b>This is NOT part of any supported API.
66135446Strhodes * If you write code that depends on this, you do so at your own risk.
67135446Strhodes * This code and its internal interfaces are subject to change or
68135446Strhodes * deletion without notice.</b>
69135446Strhodes */
70135446Strhodespublic class RichDiagnosticFormatter extends
71135446Strhodes        ForwardingDiagnosticFormatter<JCDiagnostic, AbstractDiagnosticFormatter> {
72135446Strhodes
73135446Strhodes    final Symtab syms;
74135446Strhodes    final Types types;
75135446Strhodes    final JCDiagnostic.Factory diags;
76135446Strhodes    final JavacMessages messages;
77135446Strhodes
78170222Sdougb    /* name simplifier used by this formatter */
79170222Sdougb    protected ClassNameSimplifier nameSimplifier;
80135446Strhodes
81135446Strhodes    /* type/symbol printer used by this formatter */
82135446Strhodes    private RichPrinter printer;
83135446Strhodes
84135446Strhodes    /* map for keeping track of a where clause associated to a given type */
85135446Strhodes    Map<WhereClauseKind, Map<Type, JCDiagnostic>> whereClauses;
86135446Strhodes
87135446Strhodes    /** Get the DiagnosticFormatter instance for this context. */
88135446Strhodes    public static RichDiagnosticFormatter instance(Context context) {
89135446Strhodes        RichDiagnosticFormatter instance = context.get(RichDiagnosticFormatter.class);
90135446Strhodes        if (instance == null)
91135446Strhodes            instance = new RichDiagnosticFormatter(context);
92135446Strhodes        return instance;
93135446Strhodes    }
94135446Strhodes
95135446Strhodes    protected RichDiagnosticFormatter(Context context) {
96135446Strhodes        super((AbstractDiagnosticFormatter)Log.instance(context).getDiagnosticFormatter());
97135446Strhodes        setRichPrinter(new RichPrinter());
98135446Strhodes        this.syms = Symtab.instance(context);
99135446Strhodes        this.diags = JCDiagnostic.Factory.instance(context);
100135446Strhodes        this.types = Types.instance(context);
101135446Strhodes        this.messages = JavacMessages.instance(context);
102135446Strhodes        whereClauses = new EnumMap<>(WhereClauseKind.class);
103135446Strhodes        configuration = new RichConfiguration(Options.instance(context), formatter);
104135446Strhodes        for (WhereClauseKind kind : WhereClauseKind.values())
105135446Strhodes            whereClauses.put(kind, new LinkedHashMap<Type, JCDiagnostic>());
106135446Strhodes    }
107135446Strhodes
108135446Strhodes    @Override
109135446Strhodes    public String format(JCDiagnostic diag, Locale l) {
110135446Strhodes        StringBuilder sb = new StringBuilder();
111135446Strhodes        nameSimplifier = new ClassNameSimplifier();
112135446Strhodes        for (WhereClauseKind kind : WhereClauseKind.values())
113135446Strhodes            whereClauses.get(kind).clear();
114193149Sdougb        preprocessDiagnostic(diag);
115135446Strhodes        sb.append(formatter.format(diag, l));
116135446Strhodes        if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
117135446Strhodes            List<JCDiagnostic> clauses = getWhereClauses();
118135446Strhodes            String indent = formatter.isRaw() ? "" :
119135446Strhodes                formatter.indentString(DetailsInc);
120135446Strhodes            for (JCDiagnostic d : clauses) {
121135446Strhodes                String whereClause = formatter.format(d, l);
122135446Strhodes                if (whereClause.length() > 0) {
123135446Strhodes                    sb.append('\n' + indent + whereClause);
124135446Strhodes                }
125135446Strhodes            }
126135446Strhodes        }
127135446Strhodes        return sb.toString();
128135446Strhodes    }
129135446Strhodes
130135446Strhodes    @Override
131135446Strhodes    public String formatMessage(JCDiagnostic diag, Locale l) {
132135446Strhodes        nameSimplifier = new ClassNameSimplifier();
133135446Strhodes        preprocessDiagnostic(diag);
134135446Strhodes        return super.formatMessage(diag, l);
135135446Strhodes    }
136135446Strhodes
137135446Strhodes    /**
138135446Strhodes     * Sets the type/symbol printer used by this formatter.
139135446Strhodes     * @param printer the rich printer to be set
140135446Strhodes     */
141135446Strhodes    protected void setRichPrinter(RichPrinter printer) {
142135446Strhodes        this.printer = printer;
143135446Strhodes        formatter.setPrinter(printer);
144135446Strhodes    }
145135446Strhodes
146135446Strhodes    /**
147170222Sdougb     * Returns the type/symbol printer used by this formatter.
148193149Sdougb     * @return type/symbol rich printer
149193149Sdougb     */
150193149Sdougb    protected RichPrinter getRichPrinter() {
151193149Sdougb        return printer;
152193149Sdougb    }
153193149Sdougb
154193149Sdougb    /**
155193149Sdougb     * Preprocess a given diagnostic by looking both into its arguments and into
156193149Sdougb     * its subdiagnostics (if any). This preprocessing is responsible for
157193149Sdougb     * generating info corresponding to features like where clauses, name
158193149Sdougb     * simplification, etc.
159193149Sdougb     *
160193149Sdougb     * @param diag the diagnostic to be preprocessed
161193149Sdougb     */
162193149Sdougb    protected void preprocessDiagnostic(JCDiagnostic diag) {
163193149Sdougb        for (Object o : diag.getArgs()) {
164193149Sdougb            if (o != null) {
165193149Sdougb                preprocessArgument(o);
166193149Sdougb            }
167193149Sdougb        }
168193149Sdougb        if (diag.isMultiline()) {
169193149Sdougb            for (JCDiagnostic d : diag.getSubdiagnostics())
170135446Strhodes                preprocessDiagnostic(d);
171135446Strhodes        }
172135446Strhodes    }
173135446Strhodes
174135446Strhodes    /**
175135446Strhodes     * Preprocess a diagnostic argument. A type/symbol argument is
176135446Strhodes     * preprocessed by specialized type/symbol preprocessors.
177193149Sdougb     *
178135446Strhodes     * @param arg the argument to be translated
179135446Strhodes     */
180135446Strhodes    protected void preprocessArgument(Object arg) {
181170222Sdougb        if (arg instanceof Type) {
182135446Strhodes            preprocessType((Type)arg);
183135446Strhodes        }
184170222Sdougb        else if (arg instanceof Symbol) {
185135446Strhodes            preprocessSymbol((Symbol)arg);
186135446Strhodes        }
187135446Strhodes        else if (arg instanceof JCDiagnostic) {
188135446Strhodes            preprocessDiagnostic((JCDiagnostic)arg);
189135446Strhodes        }
190135446Strhodes        else if (arg instanceof Iterable<?> && !(arg instanceof Path)) {
191135446Strhodes            for (Object o : (Iterable<?>)arg) {
192135446Strhodes                preprocessArgument(o);
193135446Strhodes            }
194135446Strhodes        }
195135446Strhodes    }
196135446Strhodes
197135446Strhodes    /**
198135446Strhodes     * Build a list of multiline diagnostics containing detailed info about
199135446Strhodes     * type-variables, captured types, and intersection types
200135446Strhodes     *
201135446Strhodes     * @return where clause list
202135446Strhodes     */
203135446Strhodes    protected List<JCDiagnostic> getWhereClauses() {
204170222Sdougb        List<JCDiagnostic> clauses = List.nil();
205170222Sdougb        for (WhereClauseKind kind : WhereClauseKind.values()) {
206135446Strhodes            List<JCDiagnostic> lines = List.nil();
207135446Strhodes            for (Map.Entry<Type, JCDiagnostic> entry : whereClauses.get(kind).entrySet()) {
208135446Strhodes                lines = lines.prepend(entry.getValue());
209135446Strhodes            }
210135446Strhodes            if (!lines.isEmpty()) {
211135446Strhodes                String key = kind.key();
212135446Strhodes                if (lines.size() > 1)
213135446Strhodes                    key += ".1";
214135446Strhodes                JCDiagnostic d = diags.fragment(key, whereClauses.get(kind).keySet());
215193149Sdougb                d = new JCDiagnostic.MultilineDiagnostic(d, lines.reverse());
216170222Sdougb                clauses = clauses.prepend(d);
217135446Strhodes            }
218170222Sdougb        }
219170222Sdougb        return clauses.reverse();
220135446Strhodes    }
221135446Strhodes
222135446Strhodes    private int indexOf(Type type, WhereClauseKind kind) {
223135446Strhodes        int index = 1;
224135446Strhodes        for (Type t : whereClauses.get(kind).keySet()) {
225135446Strhodes            if (t.tsym == type.tsym) {
226170222Sdougb                return index;
227135446Strhodes            }
228170222Sdougb            if (kind != WhereClauseKind.TYPEVAR ||
229135446Strhodes                    t.toString().equals(type.toString())) {
230135446Strhodes                index++;
231135446Strhodes            }
232135446Strhodes        }
233193149Sdougb        return -1;
234193149Sdougb    }
235193149Sdougb
236193149Sdougb    private boolean unique(TypeVar typevar) {
237193149Sdougb        int found = 0;
238193149Sdougb        for (Type t : whereClauses.get(WhereClauseKind.TYPEVAR).keySet()) {
239193149Sdougb            if (t.toString().equals(typevar.toString())) {
240193149Sdougb                found++;
241193149Sdougb            }
242135446Strhodes        }
243135446Strhodes        if (found < 1)
244135446Strhodes            throw new AssertionError("Missing type variable in where clause " + typevar);
245135446Strhodes        return found == 1;
246135446Strhodes    }
247135446Strhodes    //where
248135446Strhodes    /**
249135446Strhodes     * This enum defines all posssible kinds of where clauses that can be
250135446Strhodes     * attached by a rich diagnostic formatter to a given diagnostic
251135446Strhodes     */
252135446Strhodes    enum WhereClauseKind {
253135446Strhodes
254170222Sdougb        /** where clause regarding a type variable */
255135446Strhodes        TYPEVAR("where.description.typevar"),
256135446Strhodes        /** where clause regarding a captured type */
257135446Strhodes        CAPTURED("where.description.captured"),
258170222Sdougb        /** where clause regarding an intersection type */
259135446Strhodes        INTERSECTION("where.description.intersection");
260135446Strhodes
261135446Strhodes        /** resource key for this where clause kind */
262135446Strhodes        private final String key;
263170222Sdougb
264135446Strhodes        WhereClauseKind(String key) {
265135446Strhodes            this.key = key;
266135446Strhodes        }
267170222Sdougb
268135446Strhodes        String key() {
269170222Sdougb            return key;
270135446Strhodes        }
271170222Sdougb    }
272135446Strhodes
273135446Strhodes    // <editor-fold defaultstate="collapsed" desc="name simplifier">
274135446Strhodes    /**
275170222Sdougb     * A name simplifier keeps track of class names usages in order to determine
276135446Strhodes     * whether a class name can be compacted or not. Short names are not used
277135446Strhodes     * if a conflict is detected, e.g. when two classes with the same simple
278135446Strhodes     * name belong to different packages - in this case the formatter reverts
279135446Strhodes     * to fullnames as compact names might lead to a confusing diagnostic.
280135446Strhodes     */
281170222Sdougb    protected class ClassNameSimplifier {
282170222Sdougb
283170222Sdougb        /* table for keeping track of all short name usages */
284135446Strhodes        Map<Name, List<Symbol>> nameClashes = new HashMap<>();
285170222Sdougb
286135446Strhodes        /**
287135446Strhodes         * Add a name usage to the simplifier's internal cache
288135446Strhodes         */
289135446Strhodes        protected void addUsage(Symbol sym) {
290135446Strhodes            Name n = sym.getSimpleName();
291170222Sdougb            List<Symbol> conflicts = nameClashes.get(n);
292135446Strhodes            if (conflicts == null) {
293135446Strhodes                conflicts = List.nil();
294135446Strhodes            }
295135446Strhodes            if (!conflicts.contains(sym))
296170222Sdougb                nameClashes.put(n, conflicts.append(sym));
297135446Strhodes        }
298170222Sdougb
299135446Strhodes        public String simplify(Symbol s) {
300135446Strhodes            String name = s.getQualifiedName().toString();
301135446Strhodes            if (!s.type.isCompound() && !s.type.isPrimitive()) {
302170222Sdougb                List<Symbol> conflicts = nameClashes.get(s.getSimpleName());
303135446Strhodes                if (conflicts == null ||
304135446Strhodes                    (conflicts.size() == 1 &&
305135446Strhodes                    conflicts.contains(s))) {
306135446Strhodes                    List<Name> l = List.nil();
307170222Sdougb                    Symbol s2 = s;
308135446Strhodes                    while (s2.type.hasTag(CLASS) &&
309135446Strhodes                            s2.type.getEnclosingType().hasTag(CLASS) &&
310135446Strhodes                            s2.owner.kind == TYP) {
311135446Strhodes                        l = l.prepend(s2.getSimpleName());
312170222Sdougb                        s2 = s2.owner;
313135446Strhodes                    }
314135446Strhodes                    l = l.prepend(s2.getSimpleName());
315135446Strhodes                    StringBuilder buf = new StringBuilder();
316170222Sdougb                    String sep = "";
317135446Strhodes                    for (Name n2 : l) {
318170222Sdougb                        buf.append(sep);
319170222Sdougb                        buf.append(n2);
320135446Strhodes                        sep = ".";
321135446Strhodes                    }
322135446Strhodes                    name = buf.toString();
323135446Strhodes                }
324170222Sdougb            }
325135446Strhodes            return name;
326135446Strhodes        }
327135446Strhodes    }
328135446Strhodes    // </editor-fold>
329135446Strhodes
330135446Strhodes    // <editor-fold defaultstate="collapsed" desc="rich printer">
331135446Strhodes    /**
332135446Strhodes     * Enhanced type/symbol printer that provides support for features like simple names
333135446Strhodes     * and type variable disambiguation. This enriched printer exploits the info
334135446Strhodes     * discovered during type/symbol preprocessing. This printer is set on the delegate
335170222Sdougb     * formatter so that rich type/symbol info can be properly rendered.
336135446Strhodes     */
337135446Strhodes    protected class RichPrinter extends Printer {
338135446Strhodes
339135446Strhodes        @Override
340170222Sdougb        public String localize(Locale locale, String key, Object... args) {
341135446Strhodes            return formatter.localize(locale, key, args);
342135446Strhodes        }
343170222Sdougb
344170222Sdougb        @Override
345135446Strhodes        public String capturedVarId(CapturedType t, Locale locale) {
346135446Strhodes            return indexOf(t, WhereClauseKind.CAPTURED) + "";
347135446Strhodes        }
348135446Strhodes
349170222Sdougb        @Override
350135446Strhodes        public String visitType(Type t, Locale locale) {
351135446Strhodes            String s = super.visitType(t, locale);
352135446Strhodes            if (t == syms.botType)
353135446Strhodes                s = localize(locale, "compiler.misc.type.null");
354170222Sdougb            return s;
355135446Strhodes        }
356135446Strhodes
357170222Sdougb        @Override
358170222Sdougb        public String visitCapturedType(CapturedType t, Locale locale) {
359135446Strhodes            if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
360135446Strhodes                return localize(locale,
361135446Strhodes                    "compiler.misc.captured.type",
362135446Strhodes                    indexOf(t, WhereClauseKind.CAPTURED));
363170222Sdougb            }
364135446Strhodes            else
365135446Strhodes                return super.visitCapturedType(t, locale);
366135446Strhodes        }
367135446Strhodes
368170222Sdougb        @Override
369135446Strhodes        public String visitClassType(ClassType t, Locale locale) {
370135446Strhodes            if (t.isCompound() &&
371170222Sdougb                    getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
372170222Sdougb                return localize(locale,
373135446Strhodes                        "compiler.misc.intersection.type",
374135446Strhodes                        indexOf(t, WhereClauseKind.INTERSECTION));
375135446Strhodes            }
376135446Strhodes            else
377170222Sdougb                return super.visitClassType(t, locale);
378135446Strhodes        }
379135446Strhodes
380135446Strhodes        @Override
381135446Strhodes        protected String className(ClassType t, boolean longform, Locale locale) {
382170222Sdougb            Symbol sym = t.tsym;
383135446Strhodes            if (sym.name.length() == 0 ||
384135446Strhodes                    !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
385170222Sdougb                return super.className(t, longform, locale);
386170222Sdougb            }
387135446Strhodes            else if (longform)
388135446Strhodes                return nameSimplifier.simplify(sym).toString();
389193149Sdougb            else
390193149Sdougb                return sym.name.toString();
391193149Sdougb        }
392193149Sdougb
393193149Sdougb        @Override
394193149Sdougb        public String visitTypeVar(TypeVar t, Locale locale) {
395193149Sdougb            if (unique(t) ||
396193149Sdougb                    !getConfiguration().isEnabled(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES)) {
397193149Sdougb                return t.toString();
398193149Sdougb            }
399193149Sdougb            else {
400193149Sdougb                return localize(locale,
401193149Sdougb                        "compiler.misc.type.var",
402193149Sdougb                        t.toString(), indexOf(t, WhereClauseKind.TYPEVAR));
403135446Strhodes            }
404135446Strhodes        }
405170222Sdougb
406135446Strhodes        @Override
407135446Strhodes        public String visitClassSymbol(ClassSymbol s, Locale locale) {
408135446Strhodes            if (s.type.isCompound()) {
409135446Strhodes                return visit(s.type, locale);
410135446Strhodes            }
411135446Strhodes            String name = nameSimplifier.simplify(s);
412170222Sdougb            if (name.length() == 0 ||
413135446Strhodes                    !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
414135446Strhodes                return super.visitClassSymbol(s, locale);
415135446Strhodes            }
416170222Sdougb            else {
417135446Strhodes                return name;
418135446Strhodes            }
419135446Strhodes        }
420135446Strhodes
421170222Sdougb        @Override
422135446Strhodes        public String visitMethodSymbol(MethodSymbol s, Locale locale) {
423135446Strhodes            String ownerName = visit(s.owner, locale);
424135446Strhodes            if (s.isStaticOrInstanceInit()) {
425135446Strhodes               return ownerName;
426170222Sdougb            } else {
427135446Strhodes                String ms = (s.name == s.name.table.names.init)
428135446Strhodes                    ? ownerName
429135446Strhodes                    : s.name.toString();
430170222Sdougb                if (s.type != null) {
431135446Strhodes                    if (s.type.hasTag(FORALL)) {
432135446Strhodes                        ms = "<" + visitTypes(s.type.getTypeArguments(), locale) + ">" + ms;
433135446Strhodes                    }
434135446Strhodes                    ms += "(" + printMethodArgs(
435135446Strhodes                            s.type.getParameterTypes(),
436170222Sdougb                            (s.flags() & VARARGS) != 0,
437135446Strhodes                            locale) + ")";
438135446Strhodes                }
439135446Strhodes                return ms;
440135446Strhodes            }
441170222Sdougb        }
442135446Strhodes    }
443170222Sdougb    // </editor-fold>
444135446Strhodes
445170222Sdougb    // <editor-fold defaultstate="collapsed" desc="type scanner">
446135446Strhodes    /**
447170222Sdougb     * Preprocess a given type looking for (i) additional info (where clauses) to be
448135446Strhodes     * added to the main diagnostic (ii) names to be compacted.
449135446Strhodes     */
450135446Strhodes    protected void preprocessType(Type t) {
451170222Sdougb        typePreprocessor.visit(t);
452135446Strhodes    }
453135446Strhodes    //where
454135446Strhodes    protected Types.UnaryVisitor<Void> typePreprocessor =
455135446Strhodes            new Types.UnaryVisitor<Void>() {
456135446Strhodes
457170222Sdougb        public Void visit(List<Type> ts) {
458170222Sdougb            for (Type t : ts)
459135446Strhodes                visit(t);
460170222Sdougb            return null;
461135446Strhodes        }
462135446Strhodes
463135446Strhodes        @Override
464135446Strhodes        public Void visitForAll(ForAll t, Void ignored) {
465135446Strhodes            visit(t.tvars);
466170222Sdougb            visit(t.qtype);
467135446Strhodes            return null;
468135446Strhodes        }
469135446Strhodes
470135446Strhodes        @Override
471170222Sdougb        public Void visitMethodType(MethodType t, Void ignored) {
472135446Strhodes            visit(t.argtypes);
473170222Sdougb            visit(t.restype);
474135446Strhodes            return null;
475135446Strhodes        }
476135446Strhodes
477170222Sdougb        @Override
478135446Strhodes        public Void visitErrorType(ErrorType t, Void ignored) {
479135446Strhodes            Type ot = t.getOriginalType();
480135446Strhodes            if (ot != null)
481170222Sdougb                visit(ot);
482170222Sdougb            return null;
483135446Strhodes        }
484170222Sdougb
485135446Strhodes        @Override
486135446Strhodes        public Void visitArrayType(ArrayType t, Void ignored) {
487135446Strhodes            visit(t.elemtype);
488135446Strhodes            return null;
489135446Strhodes        }
490170222Sdougb
491170222Sdougb        @Override
492170222Sdougb        public Void visitWildcardType(WildcardType t, Void ignored) {
493170222Sdougb            visit(t.type);
494135446Strhodes            return null;
495135446Strhodes        }
496135446Strhodes
497170222Sdougb        public Void visitType(Type t, Void ignored) {
498135446Strhodes            return null;
499170222Sdougb        }
500135446Strhodes
501135446Strhodes        @Override
502135446Strhodes        public Void visitCapturedType(CapturedType t, Void ignored) {
503170222Sdougb            if (indexOf(t, WhereClauseKind.CAPTURED) == -1) {
504135446Strhodes                String suffix = t.lower == syms.botType ? ".1" : "";
505135446Strhodes                JCDiagnostic d = diags.fragment("where.captured"+ suffix, t, t.bound, t.lower, t.wildcard);
506135446Strhodes                whereClauses.get(WhereClauseKind.CAPTURED).put(t, d);
507170222Sdougb                visit(t.wildcard);
508135446Strhodes                visit(t.lower);
509170222Sdougb                visit(t.bound);
510135446Strhodes            }
511135446Strhodes            return null;
512135446Strhodes        }
513170222Sdougb
514170222Sdougb        @Override
515135446Strhodes        public Void visitClassType(ClassType t, Void ignored) {
516170222Sdougb            if (t.isCompound()) {
517135446Strhodes                if (indexOf(t, WhereClauseKind.INTERSECTION) == -1) {
518135446Strhodes                    Type supertype = types.supertype(t);
519135446Strhodes                    List<Type> interfaces = types.interfaces(t);
520135446Strhodes                    JCDiagnostic d = diags.fragment("where.intersection", t, interfaces.prepend(supertype));
521135446Strhodes                    whereClauses.get(WhereClauseKind.INTERSECTION).put(t, d);
522170222Sdougb                    visit(supertype);
523170222Sdougb                    visit(interfaces);
524170222Sdougb                }
525170222Sdougb            } else if (t.tsym.name.isEmpty()) {
526170222Sdougb                //anon class
527135446Strhodes                ClassType norm = (ClassType) t.tsym.type;
528135446Strhodes                if (norm != null) {
529135446Strhodes                    if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
530135446Strhodes                        visit(norm.interfaces_field.head);
531170222Sdougb                    } else {
532135446Strhodes                        visit(norm.supertype_field);
533170222Sdougb                    }
534135446Strhodes                }
535135446Strhodes            }
536135446Strhodes            nameSimplifier.addUsage(t.tsym);
537170222Sdougb            visit(t.getTypeArguments());
538170222Sdougb            if (t.getEnclosingType() != Type.noType)
539135446Strhodes                visit(t.getEnclosingType());
540170222Sdougb            return null;
541135446Strhodes        }
542135446Strhodes
543135446Strhodes        @Override
544135446Strhodes        public Void visitTypeVar(TypeVar t, Void ignored) {
545135446Strhodes            if (indexOf(t, WhereClauseKind.TYPEVAR) == -1) {
546135446Strhodes                //access the bound type and skip error types
547135446Strhodes                Type bound = t.bound;
548135446Strhodes                while ((bound instanceof ErrorType))
549135446Strhodes                    bound = ((ErrorType)bound).getOriginalType();
550170222Sdougb                //retrieve the bound list - if the type variable
551135446Strhodes                //has not been attributed the bound is not set
552135446Strhodes                List<Type> bounds = (bound != null) &&
553135446Strhodes                        (bound.hasTag(CLASS) || bound.hasTag(TYPEVAR)) ?
554135446Strhodes                    types.getBounds(t) :
555170222Sdougb                    List.<Type>nil();
556135446Strhodes
557170222Sdougb                nameSimplifier.addUsage(t.tsym);
558135446Strhodes
559135446Strhodes                boolean boundErroneous = bounds.head == null ||
560135446Strhodes                                         bounds.head.hasTag(NONE) ||
561170222Sdougb                                         bounds.head.hasTag(ERROR);
562135446Strhodes
563135446Strhodes                if ((t.tsym.flags() & SYNTHETIC) == 0) {
564135446Strhodes                    //this is a true typevar
565135446Strhodes                    JCDiagnostic d = diags.fragment("where.typevar" +
566135446Strhodes                        (boundErroneous ? ".1" : ""), t, bounds,
567170222Sdougb                        kindName(t.tsym.location()), t.tsym.location());
568135446Strhodes                    whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
569135446Strhodes                    symbolPreprocessor.visit(t.tsym.location(), null);
570135446Strhodes                    visit(bounds);
571135446Strhodes                } else {
572170222Sdougb                    Assert.check(!boundErroneous);
573135446Strhodes                    //this is a fresh (synthetic) tvar
574170222Sdougb                    JCDiagnostic d = diags.fragment("where.fresh.typevar", t, bounds);
575135446Strhodes                    whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
576135446Strhodes                    visit(bounds);
577135446Strhodes                }
578170222Sdougb
579135446Strhodes            }
580135446Strhodes            return null;
581135446Strhodes        }
582170222Sdougb    };
583170222Sdougb    // </editor-fold>
584135446Strhodes
585170222Sdougb    // <editor-fold defaultstate="collapsed" desc="symbol scanner">
586135446Strhodes    /**
587135446Strhodes     * Preprocess a given symbol looking for (i) additional info (where clauses) to be
588135446Strhodes     * added to the main diagnostic (ii) names to be compacted
589135446Strhodes     */
590135446Strhodes    protected void preprocessSymbol(Symbol s) {
591135446Strhodes        symbolPreprocessor.visit(s, null);
592170222Sdougb    }
593135446Strhodes    //where
594135446Strhodes    protected Types.DefaultSymbolVisitor<Void, Void> symbolPreprocessor =
595135446Strhodes            new Types.DefaultSymbolVisitor<Void, Void>() {
596135446Strhodes
597170222Sdougb        @Override
598135446Strhodes        public Void visitClassSymbol(ClassSymbol s, Void ignored) {
599170222Sdougb            if (s.type.isCompound()) {
600135446Strhodes                typePreprocessor.visit(s.type);
601170222Sdougb            } else {
602135446Strhodes                nameSimplifier.addUsage(s);
603135446Strhodes            }
604135446Strhodes            return null;
605170222Sdougb        }
606135446Strhodes
607135446Strhodes        @Override
608135446Strhodes        public Void visitSymbol(Symbol s, Void ignored) {
609135446Strhodes            return null;
610135446Strhodes        }
611170222Sdougb
612135446Strhodes        @Override
613135446Strhodes        public Void visitMethodSymbol(MethodSymbol s, Void ignored) {
614135446Strhodes            visit(s.owner, null);
615135446Strhodes            if (s.type != null)
616135446Strhodes                typePreprocessor.visit(s.type);
617135446Strhodes            return null;
618135446Strhodes        }
619135446Strhodes    };
620135446Strhodes    // </editor-fold>
621170222Sdougb
622135446Strhodes    @Override
623170222Sdougb    public RichConfiguration getConfiguration() {
624135446Strhodes        //the following cast is always safe - see init
625170222Sdougb        return (RichConfiguration)configuration;
626135446Strhodes    }
627135446Strhodes
628135446Strhodes    /**
629135446Strhodes     * Configuration object provided by the rich formatter.
630135446Strhodes     */
631170222Sdougb    public static class RichConfiguration extends ForwardingDiagnosticFormatter.ForwardingConfiguration {
632135446Strhodes
633170222Sdougb        /** set of enabled rich formatter's features */
634135446Strhodes        protected java.util.EnumSet<RichFormatterFeature> features;
635135446Strhodes
636135446Strhodes        @SuppressWarnings("fallthrough")
637135446Strhodes        public RichConfiguration(Options options, AbstractDiagnosticFormatter formatter) {
638135446Strhodes            super(formatter.getConfiguration());
639135446Strhodes            features = formatter.isRaw() ? EnumSet.noneOf(RichFormatterFeature.class) :
640135446Strhodes                EnumSet.of(RichFormatterFeature.SIMPLE_NAMES,
641135446Strhodes                    RichFormatterFeature.WHERE_CLAUSES,
642135446Strhodes                    RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
643135446Strhodes            String diagOpts = options.get("diags");
644135446Strhodes            if (diagOpts != null) {
645135446Strhodes                for (String args: diagOpts.split(",")) {
646170222Sdougb                    if (args.equals("-where")) {
647135446Strhodes                        features.remove(RichFormatterFeature.WHERE_CLAUSES);
648135446Strhodes                    }
649135446Strhodes                    else if (args.equals("where")) {
650170222Sdougb                        features.add(RichFormatterFeature.WHERE_CLAUSES);
651135446Strhodes                    }
652135446Strhodes                    if (args.equals("-simpleNames")) {
653170222Sdougb                        features.remove(RichFormatterFeature.SIMPLE_NAMES);
654135446Strhodes                    }
655135446Strhodes                    else if (args.equals("simpleNames")) {
656135446Strhodes                        features.add(RichFormatterFeature.SIMPLE_NAMES);
657135446Strhodes                    }
658135446Strhodes                    if (args.equals("-disambiguateTvars")) {
659135446Strhodes                        features.remove(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
660170222Sdougb                    }
661135446Strhodes                    else if (args.equals("disambiguateTvars")) {
662170222Sdougb                        features.add(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
663135446Strhodes                    }
664170222Sdougb                }
665135446Strhodes            }
666135446Strhodes        }
667135446Strhodes
668170222Sdougb        /**
669135446Strhodes         * Returns a list of all the features supported by the rich formatter.
670135446Strhodes         * @return list of supported features
671135446Strhodes         */
672170222Sdougb        public RichFormatterFeature[] getAvailableFeatures() {
673170222Sdougb            return RichFormatterFeature.values();
674193149Sdougb        }
675135446Strhodes
676170222Sdougb        /**
677135446Strhodes         * Enable a specific feature on this rich formatter.
678135446Strhodes         * @param feature feature to be enabled
679135446Strhodes         */
680135446Strhodes        public void enable(RichFormatterFeature feature) {
681135446Strhodes            features.add(feature);
682135446Strhodes        }
683135446Strhodes
684135446Strhodes        /**
685170222Sdougb         * Disable a specific feature on this rich formatter.
686135446Strhodes         * @param feature feature to be disabled
687135446Strhodes         */
688135446Strhodes        public void disable(RichFormatterFeature feature) {
689135446Strhodes            features.remove(feature);
690170222Sdougb        }
691135446Strhodes
692170222Sdougb        /**
693170222Sdougb         * Is a given feature enabled on this formatter?
694135446Strhodes         * @param feature feature to be tested
695135446Strhodes         */
696135446Strhodes        public boolean isEnabled(RichFormatterFeature feature) {
697170222Sdougb            return features.contains(feature);
698135446Strhodes        }
699135446Strhodes
700135446Strhodes        /**
701170222Sdougb         * The advanced formatting features provided by the rich formatter
702135446Strhodes         */
703135446Strhodes        public enum RichFormatterFeature {
704170222Sdougb            /** a list of additional info regarding a given type/symbol */
705135446Strhodes            WHERE_CLAUSES,
706135446Strhodes            /** full class names simplification (where possible) */
707135446Strhodes            SIMPLE_NAMES,
708135446Strhodes            /** type-variable names disambiguation */
709170222Sdougb            UNIQUE_TYPEVAR_NAMES
710135446Strhodes        }
711135446Strhodes    }
712135446Strhodes}
713135446Strhodes