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 diagnostic is first preprocessed in order to keep
  55  * track of each types/symbols in it; after these 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) {
 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 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             else if (longform)
 392                 return nameSimplifier.simplify(sym).toString();
 393             else
 394                 return sym.name.toString();
 395         }
 396 
 397         @Override
 398         public String visitTypeVar(TypeVar t, Locale locale) {
 399             if (unique(t) ||
 400                     !getConfiguration().isEnabled(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES)) {
 401                 return t.toString();
 402             }
 403             else {
 404                 return localize(locale,
 405                         "compiler.misc.type.var",
 406                         t.toString(), indexOf(t, WhereClauseKind.TYPEVAR));
 407             }
 408         }
 409 
 410         @Override
 411         public String visitClassSymbol(ClassSymbol s, Locale locale) {
 412             if (s.type.isCompound()) {
 413                 return visit(s.type, locale);
 414             }
 415             String name = nameSimplifier.simplify(s);
 416             if (name.length() == 0 ||
 417                     !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
 418                 return super.visitClassSymbol(s, locale);
 419             }
 420             else {
 421                 return name;
 422             }
 423         }
 424 
 425         @Override
 426         public String visitMethodSymbol(MethodSymbol s, Locale locale) {
 427             String ownerName = visit(s.owner, locale);
 428             if (s.isStaticOrInstanceInit()) {
 429                return ownerName;
 430             } else {
 431                 String ms = (s.name == s.name.table.names.init)
 432                     ? ownerName
 433                     : s.name.toString();
 434                 if (s.type != null) {
 435                     if (s.type.hasTag(FORALL)) {
 436                         ms = "<" + visitTypes(s.type.getTypeArguments(), locale) + ">" + ms;
 437                     }
 438                     ms += "(" + printMethodArgs(
 439                             s.type.getParameterTypes(),
 440                             (s.flags() & VARARGS) != 0,
 441                             locale) + ")";
 442                 }
 443                 return ms;
 444             }
 445         }
 446     }
 447     // </editor-fold>
 448 
 449     // <editor-fold defaultstate="collapsed" desc="type scanner">
 450     /**
 451      * Preprocess a given type looking for (i) additional info (where clauses) to be
 452      * added to the main diagnostic (ii) names to be compacted.
 453      */
 454     protected void preprocessType(Type t) {
 455         typePreprocessor.visit(t);
 456     }
 457     //where
 458     protected Types.UnaryVisitor<Void> typePreprocessor =
 459             new Types.UnaryVisitor<Void>() {
 460 
 461         public Void visit(List<Type> ts) {
 462             for (Type t : ts)
 463                 visit(t);
 464             return null;
 465         }
 466 
 467         @Override
 468         public Void visitForAll(ForAll t, Void ignored) {
 469             visit(t.tvars);
 470             visit(t.qtype);
 471             return null;
 472         }
 473 
 474         @Override
 475         public Void visitMethodType(MethodType t, Void ignored) {
 476             visit(t.argtypes);
 477             visit(t.restype);
 478             return null;
 479         }
 480 
 481         @Override
 482         public Void visitErrorType(ErrorType t, Void ignored) {
 483             Type ot = t.getOriginalType();
 484             if (ot != null)
 485                 visit(ot);
 486             return null;
 487         }
 488 
 489         @Override
 490         public Void visitArrayType(ArrayType t, Void ignored) {
 491             visit(t.elemtype);
 492             return null;
 493         }
 494 
 495         @Override
 496         public Void visitWildcardType(WildcardType t, Void ignored) {
 497             visit(t.type);
 498             return null;
 499         }
 500 
 501         public Void visitType(Type t, Void ignored) {
 502             return null;
 503         }
 504 
 505         @Override
 506         public Void visitCapturedType(CapturedType t, Void ignored) {
 507             if (indexOf(t, WhereClauseKind.CAPTURED) == -1) {
 508                 String suffix = t.lower == syms.botType ? ".1" : "";
 509                 JCDiagnostic d = diags.fragment("where.captured"+ suffix, t, t.getUpperBound(), t.lower, t.wildcard);
 510                 whereClauses.get(WhereClauseKind.CAPTURED).put(t, d);
 511                 visit(t.wildcard);
 512                 visit(t.lower);
 513                 visit(t.getUpperBound());
 514             }
 515             return null;
 516         }
 517 
 518         @Override
 519         public Void visitClassType(ClassType t, Void ignored) {
 520             if (t.isCompound()) {
 521                 if (indexOf(t, WhereClauseKind.INTERSECTION) == -1) {
 522                     Type supertype = types.supertype(t);
 523                     List<Type> interfaces = types.interfaces(t);
 524                     JCDiagnostic d = diags.fragment(Fragments.WhereIntersection(t, interfaces.prepend(supertype)));
 525                     whereClauses.get(WhereClauseKind.INTERSECTION).put(t, d);
 526                     visit(supertype);
 527                     visit(interfaces);
 528                 }
 529             } else if (t.tsym.name.isEmpty()) {
 530                 //anon class
 531                 ClassType norm = (ClassType) t.tsym.type;
 532                 if (norm != null) {
 533                     if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
 534                         visit(norm.interfaces_field.head);
 535                     } else {
 536                         visit(norm.supertype_field);
 537                     }
 538                 }
 539             }
 540             nameSimplifier.addUsage(t.tsym);
 541             visit(t.getTypeArguments());
 542             Type enclosingType;
 543             try {
 544                 enclosingType = t.getEnclosingType();
 545             } catch (CompletionFailure cf) {
 546                 return null;
 547             }
 548             if (enclosingType != Type.noType)
 549                 visit(t.getEnclosingType());
 550             return null;
 551         }
 552 
 553         @Override
 554         public Void visitTypeVar(TypeVar t, Void ignored) {
 555             t = (TypeVar)t.stripMetadataIfNeeded();
 556             if (indexOf(t, WhereClauseKind.TYPEVAR) == -1) {
 557                 //access the bound type and skip error types
 558                 Type bound = t.getUpperBound();
 559                 while ((bound instanceof ErrorType))
 560                     bound = ((ErrorType)bound).getOriginalType();
 561                 //retrieve the bound list - if the type variable
 562                 //has not been attributed the bound is not set
 563                 List<Type> bounds = (bound != null) &&
 564                         (bound.hasTag(CLASS) || bound.hasTag(TYPEVAR)) ?
 565                     getBounds(bound) :
 566                     List.nil();
 567 
 568                 nameSimplifier.addUsage(t.tsym);
 569 
 570                 boolean boundErroneous = bounds.head == null ||
 571                                          bounds.head.hasTag(NONE) ||
 572                                          bounds.head.hasTag(ERROR);
 573 
 574                 if ((t.tsym.flags() & SYNTHETIC) == 0) {
 575                     //this is a true typevar
 576                     JCDiagnostic d = diags.fragment("where.typevar" +
 577                         (boundErroneous ? ".1" : ""), t, bounds,
 578                         kindName(t.tsym.location()), t.tsym.location());
 579                     whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
 580                     symbolPreprocessor.visit(t.tsym.location(), null);
 581                     visit(bounds);
 582                 } else {
 583                     Assert.check(!boundErroneous);
 584                     //this is a fresh (synthetic) tvar
 585                     JCDiagnostic d = diags.fragment(Fragments.WhereFreshTypevar(t, bounds));
 586                     whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
 587                     visit(bounds);
 588                 }
 589 
 590             }
 591             return null;
 592         }
 593         //where:
 594             private List<Type> getBounds(Type bound) {
 595                 return bound.isCompound() ? types.directSupertypes(bound) : List.of(bound);
 596             }
 597     };
 598     // </editor-fold>
 599 
 600     // <editor-fold defaultstate="collapsed" desc="symbol scanner">
 601     /**
 602      * Preprocess a given symbol looking for (i) additional info (where clauses) to be
 603      * added to the main diagnostic (ii) names to be compacted
 604      */
 605     protected void preprocessSymbol(Symbol s) {
 606         symbolPreprocessor.visit(s, null);
 607     }
 608     //where
 609     protected Types.DefaultSymbolVisitor<Void, Void> symbolPreprocessor =
 610             new Types.DefaultSymbolVisitor<Void, Void>() {
 611 
 612         @Override
 613         public Void visitClassSymbol(ClassSymbol s, Void ignored) {
 614             if (s.type.isCompound()) {
 615                 typePreprocessor.visit(s.type);
 616             } else {
 617                 nameSimplifier.addUsage(s);
 618             }
 619             return null;
 620         }
 621 
 622         @Override
 623         public Void visitSymbol(Symbol s, Void ignored) {
 624             return null;
 625         }
 626 
 627         @Override
 628         public Void visitMethodSymbol(MethodSymbol s, Void ignored) {
 629             visit(s.owner, null);
 630             if (s.type != null)
 631                 typePreprocessor.visit(s.type);
 632             return null;
 633         }
 634     };
 635     // </editor-fold>
 636 
 637     @Override
 638     public RichConfiguration getConfiguration() {
 639         //the following cast is always safe - see init
 640         return (RichConfiguration)configuration;
 641     }
 642 
 643     /**
 644      * Configuration object provided by the rich formatter.
 645      */
 646     public static class RichConfiguration extends ForwardingDiagnosticFormatter.ForwardingConfiguration {
 647 
 648         /** set of enabled rich formatter's features */
 649         protected java.util.EnumSet<RichFormatterFeature> features;
 650 
 651         @SuppressWarnings("fallthrough")
 652         public RichConfiguration(Options options, AbstractDiagnosticFormatter formatter) {
 653             super(formatter.getConfiguration());
 654             features = formatter.isRaw() ? EnumSet.noneOf(RichFormatterFeature.class) :
 655                 EnumSet.of(RichFormatterFeature.SIMPLE_NAMES,
 656                     RichFormatterFeature.WHERE_CLAUSES,
 657                     RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
 658             String diagOpts = options.get("diags.formatterOptions");
 659             if (diagOpts != null) {
 660                 for (String args: diagOpts.split(",")) {
 661                     if (args.equals("-where")) {
 662                         features.remove(RichFormatterFeature.WHERE_CLAUSES);
 663                     }
 664                     else if (args.equals("where")) {
 665                         features.add(RichFormatterFeature.WHERE_CLAUSES);
 666                     }
 667                     if (args.equals("-simpleNames")) {
 668                         features.remove(RichFormatterFeature.SIMPLE_NAMES);
 669                     }
 670                     else if (args.equals("simpleNames")) {
 671                         features.add(RichFormatterFeature.SIMPLE_NAMES);
 672                     }
 673                     if (args.equals("-disambiguateTvars")) {
 674                         features.remove(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
 675                     }
 676                     else if (args.equals("disambiguateTvars")) {
 677                         features.add(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
 678                     }
 679                 }
 680             }
 681         }
 682 
 683         /**
 684          * Returns a list of all the features supported by the rich formatter.
 685          * @return list of supported features
 686          */
 687         public RichFormatterFeature[] getAvailableFeatures() {
 688             return RichFormatterFeature.values();
 689         }
 690 
 691         /**
 692          * Enable a specific feature on this rich formatter.
 693          * @param feature feature to be enabled
 694          */
 695         public void enable(RichFormatterFeature feature) {
 696             features.add(feature);
 697         }
 698 
 699         /**
 700          * Disable a specific feature on this rich formatter.
 701          * @param feature feature to be disabled
 702          */
 703         public void disable(RichFormatterFeature feature) {
 704             features.remove(feature);
 705         }
 706 
 707         /**
 708          * Is a given feature enabled on this formatter?
 709          * @param feature feature to be tested
 710          */
 711         public boolean isEnabled(RichFormatterFeature feature) {
 712             return features.contains(feature);
 713         }
 714 
 715         /**
 716          * The advanced formatting features provided by the rich formatter
 717          */
 718         public enum RichFormatterFeature {
 719             /** a list of additional info regarding a given type/symbol */
 720             WHERE_CLAUSES,
 721             /** full class names simplification (where possible) */
 722             SIMPLE_NAMES,
 723             /** type-variable names disambiguation */
 724             UNIQUE_TYPEVAR_NAMES
 725         }
 726     }
 727 }