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