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; 26 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; 33 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; 42 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.*; 47 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> { 70 71 final Symtab syms; 72 final Types types; 73 final JCDiagnostic.Factory diags; 74 final JavacMessages messages; 75 76 /* name simplifier used by this formatter */ 77 protected ClassNameSimplifier nameSimplifier; 78 79 /* type/symbol printer used by this formatter */ 80 private RichPrinter printer; 81 82 /* map for keeping track of a where clause associated to a given type */ 83 Map<WhereClauseKind, Map<Type, JCDiagnostic>> whereClauses; 84 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 } 92 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 } 105 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 } 127 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 } 136 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 } 144 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 } 164 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 } 187 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 } 212 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 } 226 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 { 244 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"); 251 252 /** resource key for this where clause kind */ 253 private final String key; 254 255 WhereClauseKind(String key) { 256 this.key = key; 257 } 258 259 String key() { 260 return key; 261 } 262 } 263 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 { 273 274 /* table for keeping track of all short name usages */ 275 Map<Name, List<Symbol>> nameClashes = new HashMap<Name, List<Symbol>>(); 276 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 } 289 290 public String simplify(Symbol s) { 291 String name = s.getQualifiedName().toString(); 292 if (!s.type.isCompound()) { 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> 319 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 { 328 329 @Override 330 public String localize(Locale locale, String key, Object... args) { 331 return formatter.localize(locale, key, args); 332 } 333 334 @Override 335 public String capturedVarId(CapturedType t, Locale locale) { 336 return indexOf(t, WhereClauseKind.CAPTURED) + ""; 337 } 338 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 } 346 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 } 357 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 } 369 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 } 382 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 } 395 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 } 407 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> 431 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>() { 443 444 public Void visit(List<Type> ts) { 445 for (Type t : ts) 446 visit(t); 447 return null; 448 } 449 450 @Override 451 public Void visitForAll(ForAll t, Void ignored) { 452 visit(t.tvars); 453 visit(t.qtype); 454 return null; 455 } 456 457 @Override 458 public Void visitMethodType(MethodType t, Void ignored) { 459 visit(t.argtypes); 460 visit(t.restype); 461 return null; 462 } 463 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 } 471 472 @Override 473 public Void visitArrayType(ArrayType t, Void ignored) { 474 visit(t.elemtype); 475 return null; 476 } 477 478 @Override 479 public Void visitWildcardType(WildcardType t, Void ignored) { 480 visit(t.type); 481 return null; 482 } 483 484 public Void visitType(Type t, Void ignored) { 485 return null; 486 } 487 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 } 500 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 } 519 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(); 533 534 nameSimplifier.addUsage(t.tsym); 535 536 boolean boundErroneous = bounds.head == null || 537 bounds.head.hasTag(NONE) || 538 bounds.head.hasTag(ERROR); 539 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 } 555 556 } 557 return null; 558 } 559 }; 560 // </editor-fold> 561 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>() { 573 574 @Override 575 public Void visitClassSymbol(ClassSymbol s, Void ignored) { 576 nameSimplifier.addUsage(s); 577 return null; 578 } 579 580 @Override 581 public Void visitSymbol(Symbol s, Void ignored) { 582 return null; 583 } 584 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> 594 595 @Override 596 public RichConfiguration getConfiguration() { 597 //the following cast is always safe - see init 598 return (RichConfiguration)configuration; 599 } 600 601 /** 602 * Configuration object provided by the rich formatter. 603 */ 604 public static class RichConfiguration extends ForwardingDiagnosticFormatter.ForwardingConfiguration { 605 606 /** set of enabled rich formatter's features */ 607 protected java.util.EnumSet<RichFormatterFeature> features; 608 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 } 640 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 } 648 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 } 656 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 } 664 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 } 672 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 }