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