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