1 /*
   2  * Copyright (c) 2009, 2019, 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 diagostic is first preprocessed in order to keep
  55  * track of each types/symbols in it; after these informations are 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) {
 184             preprocessType((Type)arg);
 185         }
 186         else if (arg instanceof Symbol) {
 187             preprocessSymbol((Symbol)arg);
 188         }
 189         else if (arg instanceof JCDiagnostic) {
 190             preprocessDiagnostic((JCDiagnostic)arg);
 191         }
 192         else if (arg instanceof Iterable<?> && !(arg instanceof Path)) {
 193             for (Object o : (Iterable<?>)arg) {
 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 posssible 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 sym.isProjectedNullable() ? s + '?' : 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))
 563                     bound = ((ErrorType)bound).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 }