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