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