AbstractDiagnosticFormatter.java revision 3547:e18190929198
1229997Sken/* 2229997Sken * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. 3290776Smav * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4229997Sken * 5229997Sken * This code is free software; you can redistribute it and/or modify it 6229997Sken * under the terms of the GNU General Public License version 2 only, as 7229997Sken * published by the Free Software Foundation. Oracle designates this 8229997Sken * particular file as subject to the "Classpath" exception as provided 9229997Sken * by Oracle in the LICENSE file that accompanied this code. 10229997Sken * 11229997Sken * This code is distributed in the hope that it will be useful, but WITHOUT 12229997Sken * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13229997Sken * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14229997Sken * version 2 for more details (a copy is included in the LICENSE file that 15229997Sken * accompanied this code). 16229997Sken * 17229997Sken * You should have received a copy of the GNU General Public License version 18229997Sken * 2 along with this work; if not, write to the Free Software Foundation, 19229997Sken * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20229997Sken * 21229997Sken * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22229997Sken * or visit www.oracle.com if you need additional information or have any 23229997Sken * questions. 24229997Sken */ 25229997Sken 26229997Skenpackage com.sun.tools.javac.util; 27229997Sken 28229997Skenimport java.nio.file.Path; 29229997Skenimport java.util.Arrays; 30229997Skenimport java.util.Collection; 31229997Skenimport java.util.EnumSet; 32229997Skenimport java.util.HashMap; 33229997Skenimport java.util.Locale; 34229997Skenimport java.util.Map; 35229997Skenimport java.util.Set; 36229997Sken 37229997Skenimport javax.tools.JavaFileObject; 38229997Sken 39229997Skenimport com.sun.tools.javac.api.DiagnosticFormatter; 40229997Skenimport com.sun.tools.javac.api.DiagnosticFormatter.Configuration.DiagnosticPart; 41229997Skenimport com.sun.tools.javac.api.DiagnosticFormatter.Configuration.MultilineLimit; 42229997Skenimport com.sun.tools.javac.api.DiagnosticFormatter.PositionKind; 43229997Skenimport com.sun.tools.javac.api.Formattable; 44229997Skenimport com.sun.tools.javac.code.Lint.LintCategory; 45229997Skenimport com.sun.tools.javac.code.Printer; 46229997Skenimport com.sun.tools.javac.code.Symbol; 47229997Skenimport com.sun.tools.javac.code.Type; 48229997Skenimport com.sun.tools.javac.code.Type.CapturedType; 49229997Skenimport com.sun.tools.javac.file.PathFileObject; 50229997Skenimport com.sun.tools.javac.jvm.Profile; 51229997Skenimport com.sun.tools.javac.tree.JCTree.*; 52229997Skenimport com.sun.tools.javac.tree.Pretty; 53229997Sken 54229997Skenimport static com.sun.tools.javac.util.JCDiagnostic.DiagnosticType.*; 55229997Sken 56314749Smav/** 57229997Sken * This abstract class provides a basic implementation of the functionalities that should be provided 58229997Sken * by any formatter used by javac. Among the main features provided by AbstractDiagnosticFormatter are: 59229997Sken * 60229997Sken * <ul> 61229997Sken * <li> Provides a standard implementation of the visitor-like methods defined in the interface DiagnisticFormatter. 62229997Sken * Those implementations are specifically targeting JCDiagnostic objects. 63229997Sken * <li> Provides basic support for i18n and a method for executing all locale-dependent conversions 64229997Sken * <li> Provides the formatting logic for rendering the arguments of a JCDiagnostic object. 65229997Sken * </ul> 66229997Sken * 67229997Sken * <p><b>This is NOT part of any supported API. 68229997Sken * If you write code that depends on this, you do so at your own risk. 69229997Sken * This code and its internal interfaces are subject to change or 70229997Sken * deletion without notice.</b> 71229997Sken */ 72229997Skenpublic abstract class AbstractDiagnosticFormatter implements DiagnosticFormatter<JCDiagnostic> { 73229997Sken 74229997Sken /** 75229997Sken * JavacMessages object used by this formatter for i18n. 76229997Sken */ 77229997Sken protected JavacMessages messages; 78290776Smav 79290776Smav /** 80290776Smav * Configuration object used by this formatter 81290776Smav */ 82290776Smav private SimpleConfiguration config; 83229997Sken 84290776Smav /** 85290776Smav * Current depth level of the disgnostic being formatted 86229997Sken * (!= 0 for subdiagnostics) 87229997Sken */ 88229997Sken protected int depth = 0; 89229997Sken 90229997Sken /** 91229997Sken * All captured types that have been encountered during diagnostic formatting. 92229997Sken * This info is used by the FormatterPrinter in order to print friendly unique 93229997Sken * ids for captured types 94229997Sken */ 95229997Sken private List<Type> allCaptured = List.nil(); 96229997Sken 97229997Sken /** 98229997Sken * Initialize an AbstractDiagnosticFormatter by setting its JavacMessages object. 99229997Sken * @param messages 100229997Sken */ 101229997Sken protected AbstractDiagnosticFormatter(JavacMessages messages, SimpleConfiguration config) { 102229997Sken this.messages = messages; 103314749Smav this.config = config; 104314749Smav } 105314749Smav 106314749Smav public String formatKind(JCDiagnostic d, Locale l) { 107314749Smav switch (d.getType()) { 108315887Smav case FRAGMENT: return ""; 109315889Smav case NOTE: return localize(l, "compiler.note.note"); 110315889Smav case WARNING: return localize(l, "compiler.warn.warning"); 111229997Sken case ERROR: return localize(l, "compiler.err.error"); 112229997Sken default: 113229997Sken throw new AssertionError("Unknown diagnostic type: " + d.getType()); 114229997Sken } 115229997Sken } 116229997Sken 117229997Sken @Override 118229997Sken public String format(JCDiagnostic d, Locale locale) { 119288723Smav allCaptured = List.nil(); 120229997Sken return formatDiagnostic(d, locale); 121265641Smav } 122229997Sken 123229997Sken protected abstract String formatDiagnostic(JCDiagnostic d, Locale locale); 124229997Sken 125229997Sken public String formatPosition(JCDiagnostic d, PositionKind pk,Locale l) { 126229997Sken Assert.check(d.getPosition() != Position.NOPOS); 127229997Sken return String.valueOf(getPosition(d, pk)); 128265641Smav } 129265641Smav //where 130229997Sken private long getPosition(JCDiagnostic d, PositionKind pk) { 131229997Sken switch (pk) { 132229997Sken case START: return d.getIntStartPosition(); 133229997Sken case END: return d.getIntEndPosition(); 134229997Sken case LINE: return d.getLineNumber(); 135229997Sken case COLUMN: return d.getColumnNumber(); 136229997Sken case OFFSET: return d.getIntPosition(); 137229997Sken default: 138229997Sken throw new AssertionError("Unknown diagnostic position: " + pk); 139229997Sken } 140229997Sken } 141229997Sken 142229997Sken public String formatSource(JCDiagnostic d, boolean fullname, Locale l) { 143229997Sken JavaFileObject fo = d.getSource(); 144229997Sken if (fo == null) 145229997Sken throw new IllegalArgumentException(); // d should have source set 146229997Sken if (fullname) 147229997Sken return fo.getName(); 148229997Sken else if (fo instanceof PathFileObject) 149229997Sken return ((PathFileObject) fo).getShortName(); 150229997Sken else 151229997Sken return PathFileObject.getSimpleName(fo); 152229997Sken } 153314751Smav 154229997Sken /** 155314751Smav * Format the arguments of a given diagnostic. 156229997Sken * 157229997Sken * @param d diagnostic whose arguments are to be formatted 158229997Sken * @param l locale object to be used for i18n 159229997Sken * @return a Collection whose elements are the formatted arguments of the diagnostic 160229997Sken */ 161229997Sken protected Collection<String> formatArguments(JCDiagnostic d, Locale l) { 162229997Sken ListBuffer<String> buf = new ListBuffer<>(); 163229997Sken for (Object o : d.getArgs()) { 164229997Sken buf.append(formatArgument(d, o, l)); 165229997Sken } 166275878Smav return buf.toList(); 167229997Sken } 168229997Sken 169229997Sken /** 170229997Sken * Format a single argument of a given diagnostic. 171312585Smav * 172312585Smav * @param d diagnostic whose argument is to be formatted 173312585Smav * @param arg argument to be formatted 174313369Smav * @param l locale object to be used for i18n 175313369Smav * @return string representation of the diagnostic argument 176268677Smav */ 177229997Sken protected String formatArgument(JCDiagnostic d, Object arg, Locale l) { 178229997Sken if (arg instanceof JCDiagnostic) { 179229997Sken String s = null; 180229997Sken depth++; 181229997Sken try { 182229997Sken s = formatMessage((JCDiagnostic)arg, l); 183229997Sken } 184229997Sken finally { 185229997Sken depth--; 186229997Sken } 187229997Sken return s; 188229997Sken } 189284798Smav else if (arg instanceof JCExpression) { 190284798Smav return expr2String((JCExpression)arg); 191229997Sken } 192229997Sken else if (arg instanceof Iterable<?> && !(arg instanceof Path)) { 193275880Smav return formatIterable(d, (Iterable<?>)arg, l); 194275880Smav } 195229997Sken else if (arg instanceof Type) { 196314739Smav return printer.visit((Type)arg, l); 197314739Smav } 198314739Smav else if (arg instanceof Symbol) { 199314739Smav return printer.visit((Symbol)arg, l); 200229997Sken } 201229997Sken else if (arg instanceof JavaFileObject) { 202229997Sken return ((JavaFileObject)arg).getName(); 203268677Smav } 204273316Smav else if (arg instanceof Profile) { 205273316Smav return ((Profile)arg).name; 206229997Sken } 207229997Sken else if (arg instanceof Formattable) { 208268677Smav return ((Formattable)arg).toString(l, messages); 209268677Smav } 210273318Smav else { 211268677Smav return String.valueOf(arg); 212268677Smav } 213268677Smav } 214249009Strasz //where 215268677Smav private String expr2String(JCExpression tree) { 216249009Strasz switch(tree.getTag()) { 217313369Smav case PARENS: 218229997Sken return expr2String(((JCParens)tree).expr); 219229997Sken case LAMBDA: 220313369Smav case REFERENCE: 221313369Smav case CONDEXPR: 222313369Smav return Pretty.toSimpleString(tree); 223229997Sken default: 224229997Sken Assert.error("unexpected tree kind " + tree.getKind()); 225313369Smav return null; 226268677Smav } 227229997Sken } 228229997Sken 229229997Sken /** 230229997Sken * Format an iterable argument of a given diagnostic. 231268677Smav * 232268677Smav * @param d diagnostic whose argument is to be formatted 233268677Smav * @param it iterable argument to be formatted 234229997Sken * @param l locale object to be used for i18n 235313369Smav * @return string representation of the diagnostic iterable argument 236268677Smav */ 237268677Smav protected String formatIterable(JCDiagnostic d, Iterable<?> it, Locale l) { 238268677Smav StringBuilder sbuf = new StringBuilder(); 239229997Sken String sep = ""; 240229997Sken for (Object o : it) { 241229997Sken sbuf.append(sep); 242229997Sken sbuf.append(formatArgument(d, o, l)); 243229997Sken sep = ","; 244229997Sken } 245229997Sken return sbuf.toString(); 246229997Sken } 247229997Sken 248229997Sken /** 249229997Sken * Format all the subdiagnostics attached to a given diagnostic. 250229997Sken * 251273317Smav * @param d diagnostic whose subdiagnostics are to be formatted 252229997Sken * @param l locale object to be used for i18n 253229997Sken * @return list of all string representations of the subdiagnostics 254229997Sken */ 255229997Sken protected List<String> formatSubdiagnostics(JCDiagnostic d, Locale l) { 256229997Sken List<String> subdiagnostics = List.nil(); 257273317Smav int maxDepth = config.getMultilineLimit(MultilineLimit.DEPTH); 258273317Smav if (maxDepth == -1 || depth < maxDepth) { 259273317Smav depth++; 260273317Smav try { 261273317Smav int maxCount = config.getMultilineLimit(MultilineLimit.LENGTH); 262273317Smav int count = 0; 263273317Smav for (JCDiagnostic d2 : d.getSubdiagnostics()) { 264229997Sken if (maxCount == -1 || count < maxCount) { 265229997Sken subdiagnostics = subdiagnostics.append(formatSubdiagnostic(d, d2, l)); 266229997Sken count++; 267229997Sken } 268229997Sken else 269229997Sken break; 270229997Sken } 271268677Smav } 272229997Sken finally { 273229997Sken depth--; 274229997Sken } 275229997Sken } 276229997Sken return subdiagnostics; 277229997Sken } 278229997Sken 279230033Sken /** 280229997Sken * Format a subdiagnostics attached to a given diagnostic. 281229997Sken * 282230033Sken * @param parent multiline diagnostic whose subdiagnostics is to be formatted 283229997Sken * @param sub subdiagnostic to be formatted 284229997Sken * @param l locale object to be used for i18n 285229997Sken * @return string representation of the subdiagnostics 286273317Smav */ 287273317Smav protected String formatSubdiagnostic(JCDiagnostic parent, JCDiagnostic sub, Locale l) { 288273317Smav return formatMessage(sub, l); 289273317Smav } 290273317Smav 291273317Smav /** Format the faulty source code line and point to the error. 292273317Smav * @param d The diagnostic for which the error line should be printed 293273317Smav */ 294229997Sken protected String formatSourceLine(JCDiagnostic d, int nSpaces) { 295229997Sken StringBuilder buf = new StringBuilder(); 296229997Sken DiagnosticSource source = d.getDiagnosticSource(); 297229997Sken int pos = d.getIntPosition(); 298229997Sken if (d.getIntPosition() == Position.NOPOS) 299273317Smav throw new AssertionError(); 300273317Smav String line = (source == null ? null : source.getLine(pos)); 301229997Sken if (line == null) 302273317Smav return ""; 303229997Sken buf.append(indent(line, nSpaces)); 304229997Sken int col = source.getColumnNumber(pos, false); 305229997Sken if (config.isCaretEnabled()) { 306273317Smav buf.append("\n"); 307288713Smav for (int i = 0; i < col - 1; i++) { 308273317Smav buf.append((line.charAt(i) == '\t') ? "\t" : " "); 309290776Smav } 310265641Smav buf.append(indent("^", nSpaces)); 311273317Smav } 312265641Smav return buf.toString(); 313273317Smav } 314273317Smav 315273317Smav protected String formatLintCategory(JCDiagnostic d, Locale l) { 316229997Sken LintCategory lc = d.getLintCategory(); 317273317Smav if (lc == null) 318268677Smav return ""; 319229997Sken return localize(l, "compiler.warn.lintOption", lc.option); 320229997Sken } 321229997Sken 322229997Sken /** 323229997Sken * Converts a String into a locale-dependent representation accordingly to a given locale. 324268677Smav * 325268694Smav * @param l locale object to be used for i18n 326268694Smav * @param key locale-independent key used for looking up in a resource file 327229997Sken * @param args localization arguments 328268677Smav * @return a locale-dependent string 329229997Sken */ 330229997Sken protected String localize(Locale l, String key, Object... args) { 331314751Smav return messages.getLocalizedString(l, key, args); 332273317Smav } 333229997Sken 334229997Sken public boolean displaySource(JCDiagnostic d) { 335229997Sken return config.getVisible().contains(DiagnosticPart.SOURCE) && 336229997Sken d.getType() != FRAGMENT && 337229997Sken d.getIntPosition() != Position.NOPOS; 338273317Smav } 339273319Smav 340273319Smav public boolean isRaw() { 341268677Smav return false; 342268677Smav } 343273317Smav 344268677Smav /** 345268677Smav * Creates a string with a given amount of empty spaces. Useful for 346273317Smav * indenting the text of a diagnostic message. 347275880Smav * 348275880Smav * @param nSpaces the amount of spaces to be added to the result string 349229997Sken * @return the indentation string 350229997Sken */ 351229997Sken protected String indentString(int nSpaces) { 352229997Sken String spaces = " "; 353268677Smav if (nSpaces <= spaces.length()) 354268677Smav return spaces.substring(0, nSpaces); 355288732Smav else { 356314751Smav StringBuilder buf = new StringBuilder(); 357275493Smav for (int i = 0 ; i < nSpaces ; i++) 358229997Sken buf.append(" "); 359268677Smav return buf.toString(); 360229997Sken } 361273317Smav } 362273317Smav 363229997Sken /** 364229997Sken * Indent a string by prepending a given amount of empty spaces to each line 365229997Sken * of the string. 366273317Smav * 367229997Sken * @param s the string to be indented 368229997Sken * @param nSpaces the amount of spaces that should be prepended to each line 369229997Sken * of the string 370245228Sken * @return an indented string 371245228Sken */ 372245228Sken protected String indent(String s, int nSpaces) { 373245228Sken String indent = indentString(nSpaces); 374245228Sken StringBuilder buf = new StringBuilder(); 375245228Sken String nl = ""; 376245228Sken for (String line : s.split("\n")) { 377245228Sken buf.append(nl); 378245228Sken buf.append(indent + line); 379273317Smav nl = "\n"; 380273317Smav } 381273317Smav return buf.toString(); 382273317Smav } 383268677Smav 384260387Sscottl public SimpleConfiguration getConfiguration() { 385245228Sken return config; 386229997Sken } 387229997Sken 388229997Sken static public class SimpleConfiguration implements Configuration { 389229997Sken 390229997Sken protected Map<MultilineLimit, Integer> multilineLimits; 391229997Sken protected EnumSet<DiagnosticPart> visibleParts; 392229997Sken protected boolean caretEnabled; 393229997Sken 394229997Sken public SimpleConfiguration(Set<DiagnosticPart> parts) { 395229997Sken multilineLimits = new HashMap<>(); 396229997Sken setVisible(parts); 397273317Smav setMultilineLimit(MultilineLimit.DEPTH, -1); 398229997Sken setMultilineLimit(MultilineLimit.LENGTH, -1); 399229997Sken setCaretEnabled(true); 400229997Sken } 401236426Smjacob 402229997Sken @SuppressWarnings("fallthrough") 403229997Sken public SimpleConfiguration(Options options, Set<DiagnosticPart> parts) { 404229997Sken this(parts); 405229997Sken String showSource = null; 406273317Smav if ((showSource = options.get("diags.showSource")) != null) { 407229997Sken if (showSource.equals("true")) 408229997Sken setVisiblePart(DiagnosticPart.SOURCE, true); 409229997Sken else if (showSource.equals("false")) 410229997Sken setVisiblePart(DiagnosticPart.SOURCE, false); 411229997Sken } 412229997Sken String diagOpts = options.get("diags.formatterOptions"); 413268692Smav if (diagOpts != null) {//override -XDshowSource 414268692Smav Collection<String> args = Arrays.asList(diagOpts.split(",")); 415229997Sken if (args.contains("short")) { 416268692Smav setVisiblePart(DiagnosticPart.DETAILS, false); 417268692Smav setVisiblePart(DiagnosticPart.SUBDIAGNOSTICS, false); 418229997Sken } 419229997Sken if (args.contains("source")) 420268692Smav setVisiblePart(DiagnosticPart.SOURCE, true); 421229997Sken if (args.contains("-source")) 422229997Sken setVisiblePart(DiagnosticPart.SOURCE, false); 423229997Sken } 424268677Smav String multiPolicy = null; 425229997Sken if ((multiPolicy = options.get("diags.multilinePolicy")) != null) { 426229997Sken if (multiPolicy.equals("disabled")) 427229997Sken setVisiblePart(DiagnosticPart.SUBDIAGNOSTICS, false); 428229997Sken else if (multiPolicy.startsWith("limit:")) { 429229997Sken String limitString = multiPolicy.substring("limit:".length()); 430229997Sken String[] limits = limitString.split(":"); 431229997Sken try { 432229997Sken switch (limits.length) { 433229997Sken case 2: { 434229997Sken if (!limits[1].equals("*")) 435229997Sken setMultilineLimit(MultilineLimit.DEPTH, Integer.parseInt(limits[1])); 436229997Sken } 437229997Sken case 1: { 438229997Sken if (!limits[0].equals("*")) 439229997Sken setMultilineLimit(MultilineLimit.LENGTH, Integer.parseInt(limits[0])); 440229997Sken } 441229997Sken } 442229997Sken } 443229997Sken catch(NumberFormatException ex) { 444229997Sken setMultilineLimit(MultilineLimit.DEPTH, -1); 445229997Sken setMultilineLimit(MultilineLimit.LENGTH, -1); 446229997Sken } 447315889Smav } 448229997Sken } 449229997Sken String showCaret = null; 450229997Sken if (((showCaret = options.get("diags.showCaret")) != null) && 451229997Sken showCaret.equals("false")) 452229997Sken setCaretEnabled(false); 453229997Sken else 454315887Smav setCaretEnabled(true); 455315889Smav } 456315889Smav 457229997Sken public int getMultilineLimit(MultilineLimit limit) { 458229997Sken return multilineLimits.get(limit); 459229997Sken } 460315939Smav 461315939Smav public EnumSet<DiagnosticPart> getVisible() { 462315939Smav return EnumSet.copyOf(visibleParts); 463315939Smav } 464315939Smav 465315939Smav public void setMultilineLimit(MultilineLimit limit, int value) { 466315939Smav multilineLimits.put(limit, value < -1 ? -1 : value); 467315939Smav } 468315939Smav 469315939Smav 470315889Smav public void setVisible(Set<DiagnosticPart> diagParts) { 471315889Smav visibleParts = EnumSet.copyOf(diagParts); 472315889Smav } 473315889Smav 474315889Smav public void setVisiblePart(DiagnosticPart diagParts, boolean enabled) { 475315889Smav if (enabled) 476315889Smav visibleParts.add(diagParts); 477229997Sken else 478229997Sken visibleParts.remove(diagParts); 479315889Smav } 480229997Sken 481229997Sken /** 482229997Sken * Shows a '^' sign under the source line displayed by the formatter 483229997Sken * (if applicable). 484229997Sken * 485229997Sken * @param caretEnabled if true enables caret 486229997Sken */ 487275878Smav public void setCaretEnabled(boolean caretEnabled) { 488288723Smav this.caretEnabled = caretEnabled; 489229997Sken } 490229997Sken 491236426Smjacob /** 492229997Sken * Tells whether the caret display is active or not. 493229997Sken * 494229997Sken * @return true if the caret is enabled 495229997Sken */ 496275878Smav public boolean isCaretEnabled() { 497275878Smav return caretEnabled; 498275878Smav } 499275878Smav } 500275878Smav 501275878Smav public Printer getPrinter() { 502288723Smav return printer; 503288723Smav } 504288723Smav 505288723Smav public void setPrinter(Printer printer) { 506288723Smav this.printer = printer; 507288723Smav } 508288723Smav 509288723Smav /** 510312585Smav * An enhanced printer for formatting types/symbols used by 511284793Smav * AbstractDiagnosticFormatter. Provides alternate numbering of captured 512275878Smav * types (they are numbered starting from 1 on each new diagnostic, instead 513315889Smav * of relying on the underlying hashcode() method which generates unstable 514275878Smav * output). Also detects cycles in wildcard messages (e.g. if the wildcard 515229997Sken * type referred by a given captured type C contains C itself) which might 516229997Sken * lead to infinite loops. 517229997Sken */ 518260387Sscottl protected Printer printer = new Printer() { 519229997Sken 520229997Sken @Override 521229997Sken protected String localize(Locale locale, String key, Object... args) { 522288723Smav return AbstractDiagnosticFormatter.this.localize(locale, key, args); 523275878Smav } 524229997Sken @Override 525229997Sken protected String capturedVarId(CapturedType t, Locale locale) { 526229997Sken return "" + (allCaptured.indexOf(t) + 1); 527229997Sken } 528229997Sken @Override 529229997Sken public String visitCapturedType(CapturedType t, Locale locale) { 530229997Sken if (!allCaptured.contains(t)) { 531229997Sken allCaptured = allCaptured.append(t); 532229997Sken } 533229997Sken return super.visitCapturedType(t, locale); 534229997Sken } 535229997Sken }; 536229997Sken} 537229997Sken