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