RichDiagnosticFormatter.java revision 4202:2bd34895dda2
1/* 2 * Copyright (c) 2009, 2016, 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 26package com.sun.tools.javac.util; 27 28import java.nio.file.Path; 29import java.util.EnumMap; 30import java.util.EnumSet; 31import java.util.HashMap; 32import java.util.LinkedHashMap; 33import java.util.Locale; 34import java.util.Map; 35 36import com.sun.tools.javac.code.Printer; 37import com.sun.tools.javac.code.Symbol; 38import com.sun.tools.javac.code.Symbol.*; 39import com.sun.tools.javac.code.Symtab; 40import com.sun.tools.javac.code.Type; 41import com.sun.tools.javac.code.Type.*; 42import com.sun.tools.javac.code.Types; 43import com.sun.tools.javac.resources.CompilerProperties.Fragments; 44 45import static com.sun.tools.javac.code.Flags.*; 46import static com.sun.tools.javac.code.TypeTag.*; 47import static com.sun.tools.javac.code.Kinds.*; 48import static com.sun.tools.javac.code.Kinds.Kind.*; 49import static com.sun.tools.javac.util.LayoutCharacters.*; 50import 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 */ 72public 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 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.bound, t.lower, t.wildcard); 510 whereClauses.get(WhereClauseKind.CAPTURED).put(t, d); 511 visit(t.wildcard); 512 visit(t.lower); 513 visit(t.bound); 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 if (t.getEnclosingType() != Type.noType) 543 visit(t.getEnclosingType()); 544 return null; 545 } 546 547 @Override 548 public Void visitTypeVar(TypeVar t, Void ignored) { 549 t = (TypeVar)t.stripMetadataIfNeeded(); 550 if (indexOf(t, WhereClauseKind.TYPEVAR) == -1) { 551 //access the bound type and skip error types 552 Type bound = t.bound; 553 while ((bound instanceof ErrorType)) 554 bound = ((ErrorType)bound).getOriginalType(); 555 //retrieve the bound list - if the type variable 556 //has not been attributed the bound is not set 557 List<Type> bounds = (bound != null) && 558 (bound.hasTag(CLASS) || bound.hasTag(TYPEVAR)) ? 559 types.getBounds(t) : 560 List.nil(); 561 562 nameSimplifier.addUsage(t.tsym); 563 564 boolean boundErroneous = bounds.head == null || 565 bounds.head.hasTag(NONE) || 566 bounds.head.hasTag(ERROR); 567 568 if ((t.tsym.flags() & SYNTHETIC) == 0) { 569 //this is a true typevar 570 JCDiagnostic d = diags.fragment("where.typevar" + 571 (boundErroneous ? ".1" : ""), t, bounds, 572 kindName(t.tsym.location()), t.tsym.location()); 573 whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d); 574 symbolPreprocessor.visit(t.tsym.location(), null); 575 visit(bounds); 576 } else { 577 Assert.check(!boundErroneous); 578 //this is a fresh (synthetic) tvar 579 JCDiagnostic d = diags.fragment(Fragments.WhereFreshTypevar(t, bounds)); 580 whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d); 581 visit(bounds); 582 } 583 584 } 585 return null; 586 } 587 }; 588 // </editor-fold> 589 590 // <editor-fold defaultstate="collapsed" desc="symbol scanner"> 591 /** 592 * Preprocess a given symbol looking for (i) additional info (where clauses) to be 593 * added to the main diagnostic (ii) names to be compacted 594 */ 595 protected void preprocessSymbol(Symbol s) { 596 symbolPreprocessor.visit(s, null); 597 } 598 //where 599 protected Types.DefaultSymbolVisitor<Void, Void> symbolPreprocessor = 600 new Types.DefaultSymbolVisitor<Void, Void>() { 601 602 @Override 603 public Void visitClassSymbol(ClassSymbol s, Void ignored) { 604 if (s.type.isCompound()) { 605 typePreprocessor.visit(s.type); 606 } else { 607 nameSimplifier.addUsage(s); 608 } 609 return null; 610 } 611 612 @Override 613 public Void visitSymbol(Symbol s, Void ignored) { 614 return null; 615 } 616 617 @Override 618 public Void visitMethodSymbol(MethodSymbol s, Void ignored) { 619 visit(s.owner, null); 620 if (s.type != null) 621 typePreprocessor.visit(s.type); 622 return null; 623 } 624 }; 625 // </editor-fold> 626 627 @Override 628 public RichConfiguration getConfiguration() { 629 //the following cast is always safe - see init 630 return (RichConfiguration)configuration; 631 } 632 633 /** 634 * Configuration object provided by the rich formatter. 635 */ 636 public static class RichConfiguration extends ForwardingDiagnosticFormatter.ForwardingConfiguration { 637 638 /** set of enabled rich formatter's features */ 639 protected java.util.EnumSet<RichFormatterFeature> features; 640 641 @SuppressWarnings("fallthrough") 642 public RichConfiguration(Options options, AbstractDiagnosticFormatter formatter) { 643 super(formatter.getConfiguration()); 644 features = formatter.isRaw() ? EnumSet.noneOf(RichFormatterFeature.class) : 645 EnumSet.of(RichFormatterFeature.SIMPLE_NAMES, 646 RichFormatterFeature.WHERE_CLAUSES, 647 RichFormatterFeature.UNIQUE_TYPEVAR_NAMES); 648 String diagOpts = options.get("diags.formatterOptions"); 649 if (diagOpts != null) { 650 for (String args: diagOpts.split(",")) { 651 if (args.equals("-where")) { 652 features.remove(RichFormatterFeature.WHERE_CLAUSES); 653 } 654 else if (args.equals("where")) { 655 features.add(RichFormatterFeature.WHERE_CLAUSES); 656 } 657 if (args.equals("-simpleNames")) { 658 features.remove(RichFormatterFeature.SIMPLE_NAMES); 659 } 660 else if (args.equals("simpleNames")) { 661 features.add(RichFormatterFeature.SIMPLE_NAMES); 662 } 663 if (args.equals("-disambiguateTvars")) { 664 features.remove(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES); 665 } 666 else if (args.equals("disambiguateTvars")) { 667 features.add(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES); 668 } 669 } 670 } 671 } 672 673 /** 674 * Returns a list of all the features supported by the rich formatter. 675 * @return list of supported features 676 */ 677 public RichFormatterFeature[] getAvailableFeatures() { 678 return RichFormatterFeature.values(); 679 } 680 681 /** 682 * Enable a specific feature on this rich formatter. 683 * @param feature feature to be enabled 684 */ 685 public void enable(RichFormatterFeature feature) { 686 features.add(feature); 687 } 688 689 /** 690 * Disable a specific feature on this rich formatter. 691 * @param feature feature to be disabled 692 */ 693 public void disable(RichFormatterFeature feature) { 694 features.remove(feature); 695 } 696 697 /** 698 * Is a given feature enabled on this formatter? 699 * @param feature feature to be tested 700 */ 701 public boolean isEnabled(RichFormatterFeature feature) { 702 return features.contains(feature); 703 } 704 705 /** 706 * The advanced formatting features provided by the rich formatter 707 */ 708 public enum RichFormatterFeature { 709 /** a list of additional info regarding a given type/symbol */ 710 WHERE_CLAUSES, 711 /** full class names simplification (where possible) */ 712 SIMPLE_NAMES, 713 /** type-variable names disambiguation */ 714 UNIQUE_TYPEVAR_NAMES 715 } 716 } 717} 718