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