Log.java revision 2790:3e11383862ce
1/* 2 * Copyright (c) 1999, 2015, 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 26package com.sun.tools.javac.util; 27 28import java.io.*; 29import java.util.Arrays; 30import java.util.EnumSet; 31import java.util.HashSet; 32import java.util.Queue; 33import java.util.Set; 34import javax.tools.DiagnosticListener; 35import javax.tools.JavaFileObject; 36 37import com.sun.tools.javac.api.DiagnosticFormatter; 38import com.sun.tools.javac.main.Main; 39import com.sun.tools.javac.main.Option; 40import com.sun.tools.javac.tree.EndPosTable; 41import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag; 42import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; 43import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType; 44 45import static com.sun.tools.javac.main.Option.*; 46 47/** A class for error logs. Reports errors and warnings, and 48 * keeps track of error numbers and positions. 49 * 50 * <p><b>This is NOT part of any supported API. 51 * If you write code that depends on this, you do so at your own risk. 52 * This code and its internal interfaces are subject to change or 53 * deletion without notice.</b> 54 */ 55public class Log extends AbstractLog { 56 /** The context key for the log. */ 57 public static final Context.Key<Log> logKey = new Context.Key<>(); 58 59 /** The context key for the output PrintWriter. */ 60 public static final Context.Key<PrintWriter> outKey = new Context.Key<>(); 61 62 /* TODO: Should unify this with prefix handling in JCDiagnostic.Factory. */ 63 public enum PrefixKind { 64 JAVAC("javac."), 65 COMPILER_MISC("compiler.misc."); 66 PrefixKind(String v) { 67 value = v; 68 } 69 public String key(String k) { 70 return value + k; 71 } 72 final String value; 73 } 74 75 /** 76 * DiagnosticHandler's provide the initial handling for diagnostics. 77 * When a diagnostic handler is created and has been initialized, it 78 * should install itself as the current diagnostic handler. When a 79 * client has finished using a handler, the client should call 80 * {@code log.removeDiagnosticHandler();} 81 * 82 * Note that javax.tools.DiagnosticListener (if set) is called later in the 83 * diagnostic pipeline. 84 */ 85 public static abstract class DiagnosticHandler { 86 /** 87 * The previously installed diagnostic handler. 88 */ 89 protected DiagnosticHandler prev; 90 91 /** 92 * Install this diagnostic handler as the current one, 93 * recording the previous one. 94 */ 95 protected void install(Log log) { 96 prev = log.diagnosticHandler; 97 log.diagnosticHandler = this; 98 } 99 100 /** 101 * Handle a diagnostic. 102 */ 103 public abstract void report(JCDiagnostic diag); 104 } 105 106 /** 107 * A DiagnosticHandler that discards all diagnostics. 108 */ 109 public static class DiscardDiagnosticHandler extends DiagnosticHandler { 110 public DiscardDiagnosticHandler(Log log) { 111 install(log); 112 } 113 114 public void report(JCDiagnostic diag) { } 115 } 116 117 /** 118 * A DiagnosticHandler that can defer some or all diagnostics, 119 * by buffering them for later examination and/or reporting. 120 * If a diagnostic is not deferred, or is subsequently reported 121 * with reportAllDiagnostics(), it will be reported to the previously 122 * active diagnostic handler. 123 */ 124 public static class DeferredDiagnosticHandler extends DiagnosticHandler { 125 private Queue<JCDiagnostic> deferred = new ListBuffer<>(); 126 private final Filter<JCDiagnostic> filter; 127 128 public DeferredDiagnosticHandler(Log log) { 129 this(log, null); 130 } 131 132 public DeferredDiagnosticHandler(Log log, Filter<JCDiagnostic> filter) { 133 this.filter = filter; 134 install(log); 135 } 136 137 public void report(JCDiagnostic diag) { 138 if (!diag.isFlagSet(JCDiagnostic.DiagnosticFlag.NON_DEFERRABLE) && 139 (filter == null || filter.accepts(diag))) { 140 deferred.add(diag); 141 } else { 142 prev.report(diag); 143 } 144 } 145 146 public Queue<JCDiagnostic> getDiagnostics() { 147 return deferred; 148 } 149 150 /** Report all deferred diagnostics. */ 151 public void reportDeferredDiagnostics() { 152 reportDeferredDiagnostics(EnumSet.allOf(JCDiagnostic.Kind.class)); 153 } 154 155 /** Report selected deferred diagnostics. */ 156 public void reportDeferredDiagnostics(Set<JCDiagnostic.Kind> kinds) { 157 JCDiagnostic d; 158 while ((d = deferred.poll()) != null) { 159 if (kinds.contains(d.getKind())) 160 prev.report(d); 161 } 162 deferred = null; // prevent accidental ongoing use 163 } 164 } 165 166 public enum WriterKind { NOTICE, WARNING, ERROR } 167 168 protected PrintWriter errWriter; 169 170 protected PrintWriter warnWriter; 171 172 protected PrintWriter noticeWriter; 173 174 /** The maximum number of errors/warnings that are reported. 175 */ 176 protected int MaxErrors; 177 protected int MaxWarnings; 178 179 /** Switch: prompt user on each error. 180 */ 181 public boolean promptOnError; 182 183 /** Switch: emit warning messages. 184 */ 185 public boolean emitWarnings; 186 187 /** Switch: suppress note messages. 188 */ 189 public boolean suppressNotes; 190 191 /** Print stack trace on errors? 192 */ 193 public boolean dumpOnError; 194 195 /** 196 * Diagnostic listener, if provided through programmatic 197 * interface to javac (JSR 199). 198 */ 199 protected DiagnosticListener<? super JavaFileObject> diagListener; 200 201 /** 202 * Formatter for diagnostics. 203 */ 204 private DiagnosticFormatter<JCDiagnostic> diagFormatter; 205 206 /** 207 * Keys for expected diagnostics. 208 */ 209 public Set<String> expectDiagKeys; 210 211 /** 212 * Set to true if a compressed diagnostic is reported 213 */ 214 public boolean compressedOutput; 215 216 /** 217 * JavacMessages object used for localization. 218 */ 219 private JavacMessages messages; 220 221 /** 222 * Handler for initial dispatch of diagnostics. 223 */ 224 private DiagnosticHandler diagnosticHandler; 225 226 /** Construct a log with given I/O redirections. 227 */ 228 protected Log(Context context, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter) { 229 super(JCDiagnostic.Factory.instance(context)); 230 context.put(logKey, this); 231 this.errWriter = errWriter; 232 this.warnWriter = warnWriter; 233 this.noticeWriter = noticeWriter; 234 235 @SuppressWarnings("unchecked") // FIXME 236 DiagnosticListener<? super JavaFileObject> dl = 237 context.get(DiagnosticListener.class); 238 this.diagListener = dl; 239 240 diagnosticHandler = new DefaultDiagnosticHandler(); 241 242 messages = JavacMessages.instance(context); 243 messages.add(Main.javacBundleName); 244 245 final Options options = Options.instance(context); 246 initOptions(options); 247 options.addListener(new Runnable() { 248 public void run() { 249 initOptions(options); 250 } 251 }); 252 } 253 // where 254 private void initOptions(Options options) { 255 this.dumpOnError = options.isSet(DOE); 256 this.promptOnError = options.isSet(PROMPT); 257 this.emitWarnings = options.isUnset(XLINT_CUSTOM, "none"); 258 this.suppressNotes = options.isSet("suppressNotes"); 259 this.MaxErrors = getIntOption(options, XMAXERRS, getDefaultMaxErrors()); 260 this.MaxWarnings = getIntOption(options, XMAXWARNS, getDefaultMaxWarnings()); 261 262 boolean rawDiagnostics = options.isSet("rawDiagnostics"); 263 this.diagFormatter = rawDiagnostics ? new RawDiagnosticFormatter(options) : 264 new BasicDiagnosticFormatter(options, messages); 265 266 String ek = options.get("expectKeys"); 267 if (ek != null) 268 expectDiagKeys = new HashSet<>(Arrays.asList(ek.split(", *"))); 269 } 270 271 private int getIntOption(Options options, Option option, int defaultValue) { 272 String s = options.get(option); 273 try { 274 if (s != null) { 275 int n = Integer.parseInt(s); 276 return (n <= 0 ? Integer.MAX_VALUE : n); 277 } 278 } catch (NumberFormatException e) { 279 // silently ignore ill-formed numbers 280 } 281 return defaultValue; 282 } 283 284 /** Default value for -Xmaxerrs. 285 */ 286 protected int getDefaultMaxErrors() { 287 return 100; 288 } 289 290 /** Default value for -Xmaxwarns. 291 */ 292 protected int getDefaultMaxWarnings() { 293 return 100; 294 } 295 296 /** The default writer for diagnostics 297 */ 298 static PrintWriter defaultWriter(Context context) { 299 PrintWriter result = context.get(outKey); 300 if (result == null) 301 context.put(outKey, result = new PrintWriter(System.err)); 302 return result; 303 } 304 305 /** Construct a log with default settings. 306 */ 307 protected Log(Context context) { 308 this(context, defaultWriter(context)); 309 } 310 311 /** Construct a log with all output redirected. 312 */ 313 protected Log(Context context, PrintWriter defaultWriter) { 314 this(context, defaultWriter, defaultWriter, defaultWriter); 315 } 316 317 /** Get the Log instance for this context. */ 318 public static Log instance(Context context) { 319 Log instance = context.get(logKey); 320 if (instance == null) 321 instance = new Log(context); 322 return instance; 323 } 324 325 /** The number of errors encountered so far. 326 */ 327 public int nerrors = 0; 328 329 /** The number of warnings encountered so far. 330 */ 331 public int nwarnings = 0; 332 333 /** A set of all errors generated so far. This is used to avoid printing an 334 * error message more than once. For each error, a pair consisting of the 335 * source file name and source code position of the error is added to the set. 336 */ 337 private Set<Pair<JavaFileObject, Integer>> recorded = new HashSet<>(); 338 339 public boolean hasDiagnosticListener() { 340 return diagListener != null; 341 } 342 343 public void setEndPosTable(JavaFileObject name, EndPosTable endPosTable) { 344 name.getClass(); // null check 345 getSource(name).setEndPosTable(endPosTable); 346 } 347 348 /** Return current sourcefile. 349 */ 350 public JavaFileObject currentSourceFile() { 351 return source == null ? null : source.getFile(); 352 } 353 354 /** Get the current diagnostic formatter. 355 */ 356 public DiagnosticFormatter<JCDiagnostic> getDiagnosticFormatter() { 357 return diagFormatter; 358 } 359 360 /** Set the current diagnostic formatter. 361 */ 362 public void setDiagnosticFormatter(DiagnosticFormatter<JCDiagnostic> diagFormatter) { 363 this.diagFormatter = diagFormatter; 364 } 365 366 public PrintWriter getWriter(WriterKind kind) { 367 switch (kind) { 368 case NOTICE: return noticeWriter; 369 case WARNING: return warnWriter; 370 case ERROR: return errWriter; 371 default: throw new IllegalArgumentException(); 372 } 373 } 374 375 public void setWriter(WriterKind kind, PrintWriter pw) { 376 pw.getClass(); 377 switch (kind) { 378 case NOTICE: noticeWriter = pw; break; 379 case WARNING: warnWriter = pw; break; 380 case ERROR: errWriter = pw; break; 381 default: throw new IllegalArgumentException(); 382 } 383 } 384 385 public void setWriters(PrintWriter pw) { 386 pw.getClass(); 387 noticeWriter = warnWriter = errWriter = pw; 388 } 389 390 /** 391 * Replace the specified diagnostic handler with the 392 * handler that was current at the time this handler was created. 393 * The given handler must be the currently installed handler; 394 * it must be specified explicitly for clarity and consistency checking. 395 */ 396 public void popDiagnosticHandler(DiagnosticHandler h) { 397 Assert.check(diagnosticHandler == h); 398 diagnosticHandler = h.prev; 399 } 400 401 /** Flush the logs 402 */ 403 public void flush() { 404 errWriter.flush(); 405 warnWriter.flush(); 406 noticeWriter.flush(); 407 } 408 409 public void flush(WriterKind kind) { 410 getWriter(kind).flush(); 411 } 412 413 /** Returns true if an error needs to be reported for a given 414 * source name and pos. 415 */ 416 protected boolean shouldReport(JavaFileObject file, int pos) { 417 if (file == null) 418 return true; 419 420 Pair<JavaFileObject,Integer> coords = new Pair<>(file, pos); 421 boolean shouldReport = !recorded.contains(coords); 422 if (shouldReport) 423 recorded.add(coords); 424 return shouldReport; 425 } 426 427 /** Prompt user after an error. 428 */ 429 public void prompt() { 430 if (promptOnError) { 431 System.err.println(localize("resume.abort")); 432 try { 433 while (true) { 434 switch (System.in.read()) { 435 case 'a': case 'A': 436 System.exit(-1); 437 return; 438 case 'r': case 'R': 439 return; 440 case 'x': case 'X': 441 throw new AssertionError("user abort"); 442 default: 443 } 444 } 445 } catch (IOException e) {} 446 } 447 } 448 449 /** Print the faulty source code line and point to the error. 450 * @param pos Buffer index of the error position, must be on current line 451 */ 452 private void printErrLine(int pos, PrintWriter writer) { 453 String line = (source == null ? null : source.getLine(pos)); 454 if (line == null) 455 return; 456 int col = source.getColumnNumber(pos, false); 457 458 printRawLines(writer, line); 459 for (int i = 0; i < col - 1; i++) { 460 writer.print((line.charAt(i) == '\t') ? "\t" : " "); 461 } 462 writer.println("^"); 463 writer.flush(); 464 } 465 466 public void printNewline() { 467 noticeWriter.println(); 468 } 469 470 public void printNewline(WriterKind wk) { 471 getWriter(wk).println(); 472 } 473 474 public void printLines(String key, Object... args) { 475 printRawLines(noticeWriter, localize(key, args)); 476 } 477 478 public void printLines(PrefixKind pk, String key, Object... args) { 479 printRawLines(noticeWriter, localize(pk, key, args)); 480 } 481 482 public void printLines(WriterKind wk, String key, Object... args) { 483 printRawLines(getWriter(wk), localize(key, args)); 484 } 485 486 public void printLines(WriterKind wk, PrefixKind pk, String key, Object... args) { 487 printRawLines(getWriter(wk), localize(pk, key, args)); 488 } 489 490 /** Print the text of a message, translating newlines appropriately 491 * for the platform. 492 */ 493 public void printRawLines(String msg) { 494 printRawLines(noticeWriter, msg); 495 } 496 497 /** Print the text of a message, translating newlines appropriately 498 * for the platform. 499 */ 500 public void printRawLines(WriterKind kind, String msg) { 501 printRawLines(getWriter(kind), msg); 502 } 503 504 /** Print the text of a message, translating newlines appropriately 505 * for the platform. 506 */ 507 public static void printRawLines(PrintWriter writer, String msg) { 508 int nl; 509 while ((nl = msg.indexOf('\n')) != -1) { 510 writer.println(msg.substring(0, nl)); 511 msg = msg.substring(nl+1); 512 } 513 if (msg.length() != 0) writer.println(msg); 514 } 515 516 /** 517 * Print the localized text of a "verbose" message to the 518 * noticeWriter stream. 519 */ 520 public void printVerbose(String key, Object... args) { 521 printRawLines(noticeWriter, localize("verbose." + key, args)); 522 } 523 524 protected void directError(String key, Object... args) { 525 printRawLines(errWriter, localize(key, args)); 526 errWriter.flush(); 527 } 528 529 /** Report a warning that cannot be suppressed. 530 * @param pos The source position at which to report the warning. 531 * @param key The key for the localized warning message. 532 * @param args Fields of the warning message. 533 */ 534 public void strictWarning(DiagnosticPosition pos, String key, Object ... args) { 535 writeDiagnostic(diags.warning(null, source, pos, key, args)); 536 nwarnings++; 537 } 538 539 /** 540 * Primary method to report a diagnostic. 541 * @param diagnostic 542 */ 543 public void report(JCDiagnostic diagnostic) { 544 diagnosticHandler.report(diagnostic); 545 } 546 547 /** 548 * Common diagnostic handling. 549 * The diagnostic is counted, and depending on the options and how many diagnostics have been 550 * reported so far, the diagnostic may be handed off to writeDiagnostic. 551 */ 552 private class DefaultDiagnosticHandler extends DiagnosticHandler { 553 public void report(JCDiagnostic diagnostic) { 554 if (expectDiagKeys != null) 555 expectDiagKeys.remove(diagnostic.getCode()); 556 557 switch (diagnostic.getType()) { 558 case FRAGMENT: 559 throw new IllegalArgumentException(); 560 561 case NOTE: 562 // Print out notes only when we are permitted to report warnings 563 // Notes are only generated at the end of a compilation, so should be small 564 // in number. 565 if ((emitWarnings || diagnostic.isMandatory()) && !suppressNotes) { 566 writeDiagnostic(diagnostic); 567 } 568 break; 569 570 case WARNING: 571 if (emitWarnings || diagnostic.isMandatory()) { 572 if (nwarnings < MaxWarnings) { 573 writeDiagnostic(diagnostic); 574 nwarnings++; 575 } 576 } 577 break; 578 579 case ERROR: 580 if (nerrors < MaxErrors && 581 (diagnostic.isFlagSet(DiagnosticFlag.MULTIPLE) || 582 shouldReport(diagnostic.getSource(), diagnostic.getIntPosition()))) { 583 writeDiagnostic(diagnostic); 584 nerrors++; 585 } 586 break; 587 } 588 if (diagnostic.isFlagSet(JCDiagnostic.DiagnosticFlag.COMPRESSED)) { 589 compressedOutput = true; 590 } 591 } 592 } 593 594 /** 595 * Write out a diagnostic. 596 */ 597 protected void writeDiagnostic(JCDiagnostic diag) { 598 if (diagListener != null) { 599 diagListener.report(diag); 600 return; 601 } 602 603 PrintWriter writer = getWriterForDiagnosticType(diag.getType()); 604 605 printRawLines(writer, diagFormatter.format(diag, messages.getCurrentLocale())); 606 607 if (promptOnError) { 608 switch (diag.getType()) { 609 case ERROR: 610 case WARNING: 611 prompt(); 612 } 613 } 614 615 if (dumpOnError) 616 new RuntimeException().printStackTrace(writer); 617 618 writer.flush(); 619 } 620 621 @Deprecated 622 protected PrintWriter getWriterForDiagnosticType(DiagnosticType dt) { 623 switch (dt) { 624 case FRAGMENT: 625 throw new IllegalArgumentException(); 626 627 case NOTE: 628 return noticeWriter; 629 630 case WARNING: 631 return warnWriter; 632 633 case ERROR: 634 return errWriter; 635 636 default: 637 throw new Error(); 638 } 639 } 640 641 /** Find a localized string in the resource bundle. 642 * Because this method is static, it ignores the locale. 643 * Use localize(key, args) when possible. 644 * @param key The key for the localized string. 645 * @param args Fields to substitute into the string. 646 */ 647 public static String getLocalizedString(String key, Object ... args) { 648 return JavacMessages.getDefaultLocalizedString(PrefixKind.COMPILER_MISC.key(key), args); 649 } 650 651 /** Find a localized string in the resource bundle. 652 * @param key The key for the localized string. 653 * @param args Fields to substitute into the string. 654 */ 655 public String localize(String key, Object... args) { 656 return localize(PrefixKind.COMPILER_MISC, key, args); 657 } 658 659 /** Find a localized string in the resource bundle. 660 * @param key The key for the localized string. 661 * @param args Fields to substitute into the string. 662 */ 663 public String localize(PrefixKind pk, String key, Object... args) { 664 if (useRawMessages) 665 return pk.key(key); 666 else 667 return messages.getLocalizedString(pk.key(key), args); 668 } 669 // where 670 // backdoor hook for testing, should transition to use -XDrawDiagnostics 671 private static boolean useRawMessages = false; 672 673/*************************************************************************** 674 * raw error messages without internationalization; used for experimentation 675 * and quick prototyping 676 ***************************************************************************/ 677 678 /** print an error or warning message: 679 */ 680 private void printRawError(int pos, String msg) { 681 if (source == null || pos == Position.NOPOS) { 682 printRawLines(errWriter, "error: " + msg); 683 } else { 684 int line = source.getLineNumber(pos); 685 JavaFileObject file = source.getFile(); 686 if (file != null) 687 printRawLines(errWriter, 688 file.getName() + ":" + 689 line + ": " + msg); 690 printErrLine(pos, errWriter); 691 } 692 errWriter.flush(); 693 } 694 695 /** report an error: 696 */ 697 public void rawError(int pos, String msg) { 698 if (nerrors < MaxErrors && shouldReport(currentSourceFile(), pos)) { 699 printRawError(pos, msg); 700 prompt(); 701 nerrors++; 702 } 703 errWriter.flush(); 704 } 705 706 /** report a warning: 707 */ 708 public void rawWarning(int pos, String msg) { 709 if (nwarnings < MaxWarnings && emitWarnings) { 710 printRawError(pos, "warning: " + msg); 711 } 712 prompt(); 713 nwarnings++; 714 errWriter.flush(); 715 } 716 717 public static String format(String fmt, Object... args) { 718 return String.format((java.util.Locale)null, fmt, args); 719 } 720 721} 722