src/share/classes/com/sun/tools/javac/util/RichDiagnosticFormatter.java

Sun, 17 Feb 2013 16:44:55 -0500

author
dholmes
date
Sun, 17 Feb 2013 16:44:55 -0500
changeset 1571
af8417e590f4
parent 1570
f91144b7da75
child 1656
5da12e8a59ba
permissions
-rw-r--r--

Merge

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

mercurial