RichDiagnosticFormatter.java revision 3211:1fd828240c4d
1/*
2 * Copyright (c) 2009, 2015, 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
26package com.sun.tools.javac.util;
27
28import java.nio.file.Path;
29import java.util.EnumMap;
30import java.util.EnumSet;
31import java.util.HashMap;
32import java.util.LinkedHashMap;
33import java.util.Locale;
34import java.util.Map;
35
36import com.sun.tools.javac.code.Printer;
37import com.sun.tools.javac.code.Symbol;
38import com.sun.tools.javac.code.Symbol.*;
39import com.sun.tools.javac.code.Symtab;
40import com.sun.tools.javac.code.Type;
41import com.sun.tools.javac.code.Type.*;
42import com.sun.tools.javac.code.Types;
43
44import static com.sun.tools.javac.code.Flags.*;
45import static com.sun.tools.javac.code.TypeTag.*;
46import static com.sun.tools.javac.code.Kinds.*;
47import static com.sun.tools.javac.code.Kinds.Kind.*;
48import static com.sun.tools.javac.util.LayoutCharacters.*;
49import static com.sun.tools.javac.util.RichDiagnosticFormatter.RichConfiguration.*;
50
51/**
52 * A rich diagnostic formatter is a formatter that provides better integration
53 * with javac's type system. A diagostic is first preprocessed in order to keep
54 * track of each types/symbols in it; after these informations are collected,
55 * the diagnostic is rendered using a standard formatter, whose type/symbol printer
56 * has been replaced by a more refined version provided by this rich formatter.
57 * The rich formatter currently enables three different features: (i) simple class
58 * names - that is class names are displayed used a non qualified name (thus
59 * omitting package info) whenever possible - (ii) where clause list - a list of
60 * additional subdiagnostics that provide specific info about type-variables,
61 * captured types, intersection types that occur in the diagnostic that is to be
62 * formatted and (iii) type-variable disambiguation - when the diagnostic refers
63 * to two different type-variables with the same name, their representation is
64 * disambiguated by appending an index to the type variable name.
65 *
66 * <p><b>This is NOT part of any supported API.
67 * If you write code that depends on this, you do so at your own risk.
68 * This code and its internal interfaces are subject to change or
69 * deletion without notice.</b>
70 */
71public class RichDiagnosticFormatter extends
72        ForwardingDiagnosticFormatter<JCDiagnostic, AbstractDiagnosticFormatter> {
73
74    final Symtab syms;
75    final Types types;
76    final JCDiagnostic.Factory diags;
77    final JavacMessages messages;
78
79    /* name simplifier used by this formatter */
80    protected ClassNameSimplifier nameSimplifier;
81
82    /* type/symbol printer used by this formatter */
83    private RichPrinter printer;
84
85    /* map for keeping track of a where clause associated to a given type */
86    Map<WhereClauseKind, Map<Type, JCDiagnostic>> whereClauses;
87
88    /** Get the DiagnosticFormatter instance for this context. */
89    public static RichDiagnosticFormatter instance(Context context) {
90        RichDiagnosticFormatter instance = context.get(RichDiagnosticFormatter.class);
91        if (instance == null)
92            instance = new RichDiagnosticFormatter(context);
93        return instance;
94    }
95
96    protected RichDiagnosticFormatter(Context context) {
97        super((AbstractDiagnosticFormatter)Log.instance(context).getDiagnosticFormatter());
98        setRichPrinter(new RichPrinter());
99        this.syms = Symtab.instance(context);
100        this.diags = JCDiagnostic.Factory.instance(context);
101        this.types = Types.instance(context);
102        this.messages = JavacMessages.instance(context);
103        whereClauses = new EnumMap<>(WhereClauseKind.class);
104        configuration = new RichConfiguration(Options.instance(context), formatter);
105        for (WhereClauseKind kind : WhereClauseKind.values())
106            whereClauses.put(kind, new LinkedHashMap<Type, JCDiagnostic>());
107    }
108
109    @Override
110    public String format(JCDiagnostic diag, Locale l) {
111        StringBuilder sb = new StringBuilder();
112        nameSimplifier = new ClassNameSimplifier();
113        for (WhereClauseKind kind : WhereClauseKind.values())
114            whereClauses.get(kind).clear();
115        preprocessDiagnostic(diag);
116        sb.append(formatter.format(diag, l));
117        if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
118            List<JCDiagnostic> clauses = getWhereClauses();
119            String indent = formatter.isRaw() ? "" :
120                formatter.indentString(DetailsInc);
121            for (JCDiagnostic d : clauses) {
122                String whereClause = formatter.format(d, l);
123                if (whereClause.length() > 0) {
124                    sb.append('\n' + indent + whereClause);
125                }
126            }
127        }
128        return sb.toString();
129    }
130
131    @Override
132    public String formatMessage(JCDiagnostic diag, Locale l) {
133        nameSimplifier = new ClassNameSimplifier();
134        preprocessDiagnostic(diag);
135        return super.formatMessage(diag, l);
136    }
137
138    /**
139     * Sets the type/symbol printer used by this formatter.
140     * @param printer the rich printer to be set
141     */
142    protected void setRichPrinter(RichPrinter printer) {
143        this.printer = printer;
144        formatter.setPrinter(printer);
145    }
146
147    /**
148     * Returns the type/symbol printer used by this formatter.
149     * @return type/symbol rich printer
150     */
151    protected RichPrinter getRichPrinter() {
152        return printer;
153    }
154
155    /**
156     * Preprocess a given diagnostic by looking both into its arguments and into
157     * its subdiagnostics (if any). This preprocessing is responsible for
158     * generating info corresponding to features like where clauses, name
159     * simplification, etc.
160     *
161     * @param diag the diagnostic to be preprocessed
162     */
163    protected void preprocessDiagnostic(JCDiagnostic diag) {
164        for (Object o : diag.getArgs()) {
165            if (o != null) {
166                preprocessArgument(o);
167            }
168        }
169        if (diag.isMultiline()) {
170            for (JCDiagnostic d : diag.getSubdiagnostics())
171                preprocessDiagnostic(d);
172        }
173    }
174
175    /**
176     * Preprocess a diagnostic argument. A type/symbol argument is
177     * preprocessed by specialized type/symbol preprocessors.
178     *
179     * @param arg the argument to be translated
180     */
181    protected void preprocessArgument(Object arg) {
182        if (arg instanceof Type) {
183            preprocessType((Type)arg);
184        }
185        else if (arg instanceof Symbol) {
186            preprocessSymbol((Symbol)arg);
187        }
188        else if (arg instanceof JCDiagnostic) {
189            preprocessDiagnostic((JCDiagnostic)arg);
190        }
191        else if (arg instanceof Iterable<?> && !(arg instanceof Path)) {
192            for (Object o : (Iterable<?>)arg) {
193                preprocessArgument(o);
194            }
195        }
196    }
197
198    /**
199     * Build a list of multiline diagnostics containing detailed info about
200     * type-variables, captured types, and intersection types
201     *
202     * @return where clause list
203     */
204    protected List<JCDiagnostic> getWhereClauses() {
205        List<JCDiagnostic> clauses = List.nil();
206        for (WhereClauseKind kind : WhereClauseKind.values()) {
207            List<JCDiagnostic> lines = List.nil();
208            for (Map.Entry<Type, JCDiagnostic> entry : whereClauses.get(kind).entrySet()) {
209                lines = lines.prepend(entry.getValue());
210            }
211            if (!lines.isEmpty()) {
212                String key = kind.key();
213                if (lines.size() > 1)
214                    key += ".1";
215                JCDiagnostic d = diags.fragment(key, whereClauses.get(kind).keySet());
216                d = new JCDiagnostic.MultilineDiagnostic(d, lines.reverse());
217                clauses = clauses.prepend(d);
218            }
219        }
220        return clauses.reverse();
221    }
222
223    private int indexOf(Type type, WhereClauseKind kind) {
224        int index = 1;
225        for (Type t : whereClauses.get(kind).keySet()) {
226            if (t.tsym == type.tsym) {
227                return index;
228            }
229            if (kind != WhereClauseKind.TYPEVAR ||
230                    t.toString().equals(type.toString())) {
231                index++;
232            }
233        }
234        return -1;
235    }
236
237    private boolean unique(TypeVar typevar) {
238        typevar = (TypeVar) typevar.stripMetadata();
239
240        int found = 0;
241        for (Type t : whereClauses.get(WhereClauseKind.TYPEVAR).keySet()) {
242            if (t.stripMetadata().toString().equals(typevar.toString())) {
243                found++;
244            }
245        }
246        if (found < 1)
247            throw new AssertionError("Missing type variable in where clause: " + typevar);
248        return found == 1;
249    }
250    //where
251    /**
252     * This enum defines all posssible kinds of where clauses that can be
253     * attached by a rich diagnostic formatter to a given diagnostic
254     */
255    enum WhereClauseKind {
256
257        /** where clause regarding a type variable */
258        TYPEVAR("where.description.typevar"),
259        /** where clause regarding a captured type */
260        CAPTURED("where.description.captured"),
261        /** where clause regarding an intersection type */
262        INTERSECTION("where.description.intersection");
263
264        /** resource key for this where clause kind */
265        private final String key;
266
267        WhereClauseKind(String key) {
268            this.key = key;
269        }
270
271        String key() {
272            return key;
273        }
274    }
275
276    // <editor-fold defaultstate="collapsed" desc="name simplifier">
277    /**
278     * A name simplifier keeps track of class names usages in order to determine
279     * whether a class name can be compacted or not. Short names are not used
280     * if a conflict is detected, e.g. when two classes with the same simple
281     * name belong to different packages - in this case the formatter reverts
282     * to fullnames as compact names might lead to a confusing diagnostic.
283     */
284    protected class ClassNameSimplifier {
285
286        /* table for keeping track of all short name usages */
287        Map<Name, List<Symbol>> nameClashes = new HashMap<>();
288
289        /**
290         * Add a name usage to the simplifier's internal cache
291         */
292        protected void addUsage(Symbol sym) {
293            Name n = sym.getSimpleName();
294            List<Symbol> conflicts = nameClashes.get(n);
295            if (conflicts == null) {
296                conflicts = List.nil();
297            }
298            if (!conflicts.contains(sym))
299                nameClashes.put(n, conflicts.append(sym));
300        }
301
302        public String simplify(Symbol s) {
303            String name = s.getQualifiedName().toString();
304            if (!s.type.isCompound() && !s.type.isPrimitive()) {
305                List<Symbol> conflicts = nameClashes.get(s.getSimpleName());
306                if (conflicts == null ||
307                    (conflicts.size() == 1 &&
308                    conflicts.contains(s))) {
309                    List<Name> l = List.nil();
310                    Symbol s2 = s;
311                    while (s2.type.hasTag(CLASS) &&
312                            s2.type.getEnclosingType().hasTag(CLASS) &&
313                            s2.owner.kind == TYP) {
314                        l = l.prepend(s2.getSimpleName());
315                        s2 = s2.owner;
316                    }
317                    l = l.prepend(s2.getSimpleName());
318                    StringBuilder buf = new StringBuilder();
319                    String sep = "";
320                    for (Name n2 : l) {
321                        buf.append(sep);
322                        buf.append(n2);
323                        sep = ".";
324                    }
325                    name = buf.toString();
326                }
327            }
328            return name;
329        }
330    }
331    // </editor-fold>
332
333    // <editor-fold defaultstate="collapsed" desc="rich printer">
334    /**
335     * Enhanced type/symbol printer that provides support for features like simple names
336     * and type variable disambiguation. This enriched printer exploits the info
337     * discovered during type/symbol preprocessing. This printer is set on the delegate
338     * formatter so that rich type/symbol info can be properly rendered.
339     */
340    protected class RichPrinter extends Printer {
341
342        @Override
343        public String localize(Locale locale, String key, Object... args) {
344            return formatter.localize(locale, key, args);
345        }
346
347        @Override
348        public String capturedVarId(CapturedType t, Locale locale) {
349            return indexOf(t, WhereClauseKind.CAPTURED) + "";
350        }
351
352        @Override
353        public String visitType(Type t, Locale locale) {
354            String s = super.visitType(t, locale);
355            if (t == syms.botType)
356                s = localize(locale, "compiler.misc.type.null");
357            return s;
358        }
359
360        @Override
361        public String visitCapturedType(CapturedType t, Locale locale) {
362            if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
363                return localize(locale,
364                    "compiler.misc.captured.type",
365                    indexOf(t, WhereClauseKind.CAPTURED));
366            }
367            else
368                return super.visitCapturedType(t, locale);
369        }
370
371        @Override
372        public String visitClassType(ClassType t, Locale locale) {
373            if (t.isCompound() &&
374                    getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
375                return localize(locale,
376                        "compiler.misc.intersection.type",
377                        indexOf(t, WhereClauseKind.INTERSECTION));
378            }
379            else
380                return super.visitClassType(t, locale);
381        }
382
383        @Override
384        protected String className(ClassType t, boolean longform, Locale locale) {
385            Symbol sym = t.tsym;
386            if (sym.name.length() == 0 ||
387                    !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
388                return super.className(t, longform, locale);
389            }
390            else if (longform)
391                return nameSimplifier.simplify(sym).toString();
392            else
393                return sym.name.toString();
394        }
395
396        @Override
397        public String visitTypeVar(TypeVar t, Locale locale) {
398            if (unique(t) ||
399                    !getConfiguration().isEnabled(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES)) {
400                return t.toString();
401            }
402            else {
403                return localize(locale,
404                        "compiler.misc.type.var",
405                        t.toString(), indexOf(t, WhereClauseKind.TYPEVAR));
406            }
407        }
408
409        @Override
410        public String visitClassSymbol(ClassSymbol s, Locale locale) {
411            if (s.type.isCompound()) {
412                return visit(s.type, locale);
413            }
414            String name = nameSimplifier.simplify(s);
415            if (name.length() == 0 ||
416                    !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
417                return super.visitClassSymbol(s, locale);
418            }
419            else {
420                return name;
421            }
422        }
423
424        @Override
425        public String visitMethodSymbol(MethodSymbol s, Locale locale) {
426            String ownerName = visit(s.owner, locale);
427            if (s.isStaticOrInstanceInit()) {
428               return ownerName;
429            } else {
430                String ms = (s.name == s.name.table.names.init)
431                    ? ownerName
432                    : s.name.toString();
433                if (s.type != null) {
434                    if (s.type.hasTag(FORALL)) {
435                        ms = "<" + visitTypes(s.type.getTypeArguments(), locale) + ">" + ms;
436                    }
437                    ms += "(" + printMethodArgs(
438                            s.type.getParameterTypes(),
439                            (s.flags() & VARARGS) != 0,
440                            locale) + ")";
441                }
442                return ms;
443            }
444        }
445    }
446    // </editor-fold>
447
448    // <editor-fold defaultstate="collapsed" desc="type scanner">
449    /**
450     * Preprocess a given type looking for (i) additional info (where clauses) to be
451     * added to the main diagnostic (ii) names to be compacted.
452     */
453    protected void preprocessType(Type t) {
454        typePreprocessor.visit(t);
455    }
456    //where
457    protected Types.UnaryVisitor<Void> typePreprocessor =
458            new Types.UnaryVisitor<Void>() {
459
460        public Void visit(List<Type> ts) {
461            for (Type t : ts)
462                visit(t);
463            return null;
464        }
465
466        @Override
467        public Void visitForAll(ForAll t, Void ignored) {
468            visit(t.tvars);
469            visit(t.qtype);
470            return null;
471        }
472
473        @Override
474        public Void visitMethodType(MethodType t, Void ignored) {
475            visit(t.argtypes);
476            visit(t.restype);
477            return null;
478        }
479
480        @Override
481        public Void visitErrorType(ErrorType t, Void ignored) {
482            Type ot = t.getOriginalType();
483            if (ot != null)
484                visit(ot);
485            return null;
486        }
487
488        @Override
489        public Void visitArrayType(ArrayType t, Void ignored) {
490            visit(t.elemtype);
491            return null;
492        }
493
494        @Override
495        public Void visitWildcardType(WildcardType t, Void ignored) {
496            visit(t.type);
497            return null;
498        }
499
500        public Void visitType(Type t, Void ignored) {
501            return null;
502        }
503
504        @Override
505        public Void visitCapturedType(CapturedType t, Void ignored) {
506            if (indexOf(t, WhereClauseKind.CAPTURED) == -1) {
507                String suffix = t.lower == syms.botType ? ".1" : "";
508                JCDiagnostic d = diags.fragment("where.captured"+ suffix, t, t.bound, t.lower, t.wildcard);
509                whereClauses.get(WhereClauseKind.CAPTURED).put(t, d);
510                visit(t.wildcard);
511                visit(t.lower);
512                visit(t.bound);
513            }
514            return null;
515        }
516
517        @Override
518        public Void visitClassType(ClassType t, Void ignored) {
519            if (t.isCompound()) {
520                if (indexOf(t, WhereClauseKind.INTERSECTION) == -1) {
521                    Type supertype = types.supertype(t);
522                    List<Type> interfaces = types.interfaces(t);
523                    JCDiagnostic d = diags.fragment("where.intersection", t, interfaces.prepend(supertype));
524                    whereClauses.get(WhereClauseKind.INTERSECTION).put(t, d);
525                    visit(supertype);
526                    visit(interfaces);
527                }
528            } else if (t.tsym.name.isEmpty()) {
529                //anon class
530                ClassType norm = (ClassType) t.tsym.type;
531                if (norm != null) {
532                    if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
533                        visit(norm.interfaces_field.head);
534                    } else {
535                        visit(norm.supertype_field);
536                    }
537                }
538            }
539            nameSimplifier.addUsage(t.tsym);
540            visit(t.getTypeArguments());
541            if (t.getEnclosingType() != Type.noType)
542                visit(t.getEnclosingType());
543            return null;
544        }
545
546        @Override
547        public Void visitTypeVar(TypeVar t, Void ignored) {
548            t = (TypeVar)t.stripMetadataIfNeeded();
549            if (indexOf(t, WhereClauseKind.TYPEVAR) == -1) {
550                //access the bound type and skip error types
551                Type bound = t.bound;
552                while ((bound instanceof ErrorType))
553                    bound = ((ErrorType)bound).getOriginalType();
554                //retrieve the bound list - if the type variable
555                //has not been attributed the bound is not set
556                List<Type> bounds = (bound != null) &&
557                        (bound.hasTag(CLASS) || bound.hasTag(TYPEVAR)) ?
558                    types.getBounds(t) :
559                    List.<Type>nil();
560
561                nameSimplifier.addUsage(t.tsym);
562
563                boolean boundErroneous = bounds.head == null ||
564                                         bounds.head.hasTag(NONE) ||
565                                         bounds.head.hasTag(ERROR);
566
567                if ((t.tsym.flags() & SYNTHETIC) == 0) {
568                    //this is a true typevar
569                    JCDiagnostic d = diags.fragment("where.typevar" +
570                        (boundErroneous ? ".1" : ""), t, bounds,
571                        kindName(t.tsym.location()), t.tsym.location());
572                    whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
573                    symbolPreprocessor.visit(t.tsym.location(), null);
574                    visit(bounds);
575                } else {
576                    Assert.check(!boundErroneous);
577                    //this is a fresh (synthetic) tvar
578                    JCDiagnostic d = diags.fragment("where.fresh.typevar", t, bounds);
579                    whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
580                    visit(bounds);
581                }
582
583            }
584            return null;
585        }
586    };
587    // </editor-fold>
588
589    // <editor-fold defaultstate="collapsed" desc="symbol scanner">
590    /**
591     * Preprocess a given symbol looking for (i) additional info (where clauses) to be
592     * added to the main diagnostic (ii) names to be compacted
593     */
594    protected void preprocessSymbol(Symbol s) {
595        symbolPreprocessor.visit(s, null);
596    }
597    //where
598    protected Types.DefaultSymbolVisitor<Void, Void> symbolPreprocessor =
599            new Types.DefaultSymbolVisitor<Void, Void>() {
600
601        @Override
602        public Void visitClassSymbol(ClassSymbol s, Void ignored) {
603            if (s.type.isCompound()) {
604                typePreprocessor.visit(s.type);
605            } else {
606                nameSimplifier.addUsage(s);
607            }
608            return null;
609        }
610
611        @Override
612        public Void visitSymbol(Symbol s, Void ignored) {
613            return null;
614        }
615
616        @Override
617        public Void visitMethodSymbol(MethodSymbol s, Void ignored) {
618            visit(s.owner, null);
619            if (s.type != null)
620                typePreprocessor.visit(s.type);
621            return null;
622        }
623    };
624    // </editor-fold>
625
626    @Override
627    public RichConfiguration getConfiguration() {
628        //the following cast is always safe - see init
629        return (RichConfiguration)configuration;
630    }
631
632    /**
633     * Configuration object provided by the rich formatter.
634     */
635    public static class RichConfiguration extends ForwardingDiagnosticFormatter.ForwardingConfiguration {
636
637        /** set of enabled rich formatter's features */
638        protected java.util.EnumSet<RichFormatterFeature> features;
639
640        @SuppressWarnings("fallthrough")
641        public RichConfiguration(Options options, AbstractDiagnosticFormatter formatter) {
642            super(formatter.getConfiguration());
643            features = formatter.isRaw() ? EnumSet.noneOf(RichFormatterFeature.class) :
644                EnumSet.of(RichFormatterFeature.SIMPLE_NAMES,
645                    RichFormatterFeature.WHERE_CLAUSES,
646                    RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
647            String diagOpts = options.get("diags");
648            if (diagOpts != null) {
649                for (String args: diagOpts.split(",")) {
650                    if (args.equals("-where")) {
651                        features.remove(RichFormatterFeature.WHERE_CLAUSES);
652                    }
653                    else if (args.equals("where")) {
654                        features.add(RichFormatterFeature.WHERE_CLAUSES);
655                    }
656                    if (args.equals("-simpleNames")) {
657                        features.remove(RichFormatterFeature.SIMPLE_NAMES);
658                    }
659                    else if (args.equals("simpleNames")) {
660                        features.add(RichFormatterFeature.SIMPLE_NAMES);
661                    }
662                    if (args.equals("-disambiguateTvars")) {
663                        features.remove(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
664                    }
665                    else if (args.equals("disambiguateTvars")) {
666                        features.add(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
667                    }
668                }
669            }
670        }
671
672        /**
673         * Returns a list of all the features supported by the rich formatter.
674         * @return list of supported features
675         */
676        public RichFormatterFeature[] getAvailableFeatures() {
677            return RichFormatterFeature.values();
678        }
679
680        /**
681         * Enable a specific feature on this rich formatter.
682         * @param feature feature to be enabled
683         */
684        public void enable(RichFormatterFeature feature) {
685            features.add(feature);
686        }
687
688        /**
689         * Disable a specific feature on this rich formatter.
690         * @param feature feature to be disabled
691         */
692        public void disable(RichFormatterFeature feature) {
693            features.remove(feature);
694        }
695
696        /**
697         * Is a given feature enabled on this formatter?
698         * @param feature feature to be tested
699         */
700        public boolean isEnabled(RichFormatterFeature feature) {
701            return features.contains(feature);
702        }
703
704        /**
705         * The advanced formatting features provided by the rich formatter
706         */
707        public enum RichFormatterFeature {
708            /** a list of additional info regarding a given type/symbol */
709            WHERE_CLAUSES,
710            /** full class names simplification (where possible) */
711            SIMPLE_NAMES,
712            /** type-variable names disambiguation */
713            UNIQUE_TYPEVAR_NAMES
714        }
715    }
716}
717