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