Sun, 17 Feb 2013 16:44:55 -0500
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 }