Log.java revision 2837:1e3266d870d6
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 Assert.checkNonNull(name); 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 Assert.checkNonNull(pw); 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 noticeWriter = warnWriter = errWriter = Assert.checkNonNull(pw); 387 } 388 389 /** 390 * Replace the specified diagnostic handler with the 391 * handler that was current at the time this handler was created. 392 * The given handler must be the currently installed handler; 393 * it must be specified explicitly for clarity and consistency checking. 394 */ 395 public void popDiagnosticHandler(DiagnosticHandler h) { 396 Assert.check(diagnosticHandler == h); 397 diagnosticHandler = h.prev; 398 } 399 400 /** Flush the logs 401 */ 402 public void flush() { 403 errWriter.flush(); 404 warnWriter.flush(); 405 noticeWriter.flush(); 406 } 407 408 public void flush(WriterKind kind) { 409 getWriter(kind).flush(); 410 } 411 412 /** Returns true if an error needs to be reported for a given 413 * source name and pos. 414 */ 415 protected boolean shouldReport(JavaFileObject file, int pos) { 416 if (file == null) 417 return true; 418 419 Pair<JavaFileObject,Integer> coords = new Pair<>(file, pos); 420 boolean shouldReport = !recorded.contains(coords); 421 if (shouldReport) 422 recorded.add(coords); 423 return shouldReport; 424 } 425 426 /** Prompt user after an error. 427 */ 428 public void prompt() { 429 if (promptOnError) { 430 System.err.println(localize("resume.abort")); 431 try { 432 while (true) { 433 switch (System.in.read()) { 434 case 'a': case 'A': 435 System.exit(-1); 436 return; 437 case 'r': case 'R': 438 return; 439 case 'x': case 'X': 440 throw new AssertionError("user abort"); 441 default: 442 } 443 } 444 } catch (IOException e) {} 445 } 446 } 447 448 /** Print the faulty source code line and point to the error. 449 * @param pos Buffer index of the error position, must be on current line 450 */ 451 private void printErrLine(int pos, PrintWriter writer) { 452 String line = (source == null ? null : source.getLine(pos)); 453 if (line == null) 454 return; 455 int col = source.getColumnNumber(pos, false); 456 457 printRawLines(writer, line); 458 for (int i = 0; i < col - 1; i++) { 459 writer.print((line.charAt(i) == '\t') ? "\t" : " "); 460 } 461 writer.println("^"); 462 writer.flush(); 463 } 464 465 public void printNewline() { 466 noticeWriter.println(); 467 } 468 469 public void printNewline(WriterKind wk) { 470 getWriter(wk).println(); 471 } 472 473 public void printLines(String key, Object... args) { 474 printRawLines(noticeWriter, localize(key, args)); 475 } 476 477 public void printLines(PrefixKind pk, String key, Object... args) { 478 printRawLines(noticeWriter, localize(pk, key, args)); 479 } 480 481 public void printLines(WriterKind wk, String key, Object... args) { 482 printRawLines(getWriter(wk), localize(key, args)); 483 } 484 485 public void printLines(WriterKind wk, PrefixKind pk, String key, Object... args) { 486 printRawLines(getWriter(wk), localize(pk, key, args)); 487 } 488 489 /** Print the text of a message, translating newlines appropriately 490 * for the platform. 491 */ 492 public void printRawLines(String msg) { 493 printRawLines(noticeWriter, msg); 494 } 495 496 /** Print the text of a message, translating newlines appropriately 497 * for the platform. 498 */ 499 public void printRawLines(WriterKind kind, String msg) { 500 printRawLines(getWriter(kind), msg); 501 } 502 503 /** Print the text of a message, translating newlines appropriately 504 * for the platform. 505 */ 506 public static void printRawLines(PrintWriter writer, String msg) { 507 int nl; 508 while ((nl = msg.indexOf('\n')) != -1) { 509 writer.println(msg.substring(0, nl)); 510 msg = msg.substring(nl+1); 511 } 512 if (msg.length() != 0) writer.println(msg); 513 } 514 515 /** 516 * Print the localized text of a "verbose" message to the 517 * noticeWriter stream. 518 */ 519 public void printVerbose(String key, Object... args) { 520 printRawLines(noticeWriter, localize("verbose." + key, args)); 521 } 522 523 protected void directError(String key, Object... args) { 524 printRawLines(errWriter, localize(key, args)); 525 errWriter.flush(); 526 } 527 528 /** Report a warning that cannot be suppressed. 529 * @param pos The source position at which to report the warning. 530 * @param key The key for the localized warning message. 531 * @param args Fields of the warning message. 532 */ 533 public void strictWarning(DiagnosticPosition pos, String key, Object ... args) { 534 writeDiagnostic(diags.warning(null, source, pos, key, args)); 535 nwarnings++; 536 } 537 538 /** 539 * Primary method to report a diagnostic. 540 * @param diagnostic 541 */ 542 public void report(JCDiagnostic diagnostic) { 543 diagnosticHandler.report(diagnostic); 544 } 545 546 /** 547 * Common diagnostic handling. 548 * The diagnostic is counted, and depending on the options and how many diagnostics have been 549 * reported so far, the diagnostic may be handed off to writeDiagnostic. 550 */ 551 private class DefaultDiagnosticHandler extends DiagnosticHandler { 552 public void report(JCDiagnostic diagnostic) { 553 if (expectDiagKeys != null) 554 expectDiagKeys.remove(diagnostic.getCode()); 555 556 switch (diagnostic.getType()) { 557 case FRAGMENT: 558 throw new IllegalArgumentException(); 559 560 case NOTE: 561 // Print out notes only when we are permitted to report warnings 562 // Notes are only generated at the end of a compilation, so should be small 563 // in number. 564 if ((emitWarnings || diagnostic.isMandatory()) && !suppressNotes) { 565 writeDiagnostic(diagnostic); 566 } 567 break; 568 569 case WARNING: 570 if (emitWarnings || diagnostic.isMandatory()) { 571 if (nwarnings < MaxWarnings) { 572 writeDiagnostic(diagnostic); 573 nwarnings++; 574 } 575 } 576 break; 577 578 case ERROR: 579 if (nerrors < MaxErrors && 580 (diagnostic.isFlagSet(DiagnosticFlag.MULTIPLE) || 581 shouldReport(diagnostic.getSource(), diagnostic.getIntPosition()))) { 582 writeDiagnostic(diagnostic); 583 nerrors++; 584 } 585 break; 586 } 587 if (diagnostic.isFlagSet(JCDiagnostic.DiagnosticFlag.COMPRESSED)) { 588 compressedOutput = true; 589 } 590 } 591 } 592 593 /** 594 * Write out a diagnostic. 595 */ 596 protected void writeDiagnostic(JCDiagnostic diag) { 597 if (diagListener != null) { 598 diagListener.report(diag); 599 return; 600 } 601 602 PrintWriter writer = getWriterForDiagnosticType(diag.getType()); 603 604 printRawLines(writer, diagFormatter.format(diag, messages.getCurrentLocale())); 605 606 if (promptOnError) { 607 switch (diag.getType()) { 608 case ERROR: 609 case WARNING: 610 prompt(); 611 } 612 } 613 614 if (dumpOnError) 615 new RuntimeException().printStackTrace(writer); 616 617 writer.flush(); 618 } 619 620 @Deprecated 621 protected PrintWriter getWriterForDiagnosticType(DiagnosticType dt) { 622 switch (dt) { 623 case FRAGMENT: 624 throw new IllegalArgumentException(); 625 626 case NOTE: 627 return noticeWriter; 628 629 case WARNING: 630 return warnWriter; 631 632 case ERROR: 633 return errWriter; 634 635 default: 636 throw new Error(); 637 } 638 } 639 640 /** Find a localized string in the resource bundle. 641 * Because this method is static, it ignores the locale. 642 * Use localize(key, args) when possible. 643 * @param key The key for the localized string. 644 * @param args Fields to substitute into the string. 645 */ 646 public static String getLocalizedString(String key, Object ... args) { 647 return JavacMessages.getDefaultLocalizedString(PrefixKind.COMPILER_MISC.key(key), args); 648 } 649 650 /** Find a localized string in the resource bundle. 651 * @param key The key for the localized string. 652 * @param args Fields to substitute into the string. 653 */ 654 public String localize(String key, Object... args) { 655 return localize(PrefixKind.COMPILER_MISC, key, args); 656 } 657 658 /** Find a localized string in the resource bundle. 659 * @param key The key for the localized string. 660 * @param args Fields to substitute into the string. 661 */ 662 public String localize(PrefixKind pk, String key, Object... args) { 663 if (useRawMessages) 664 return pk.key(key); 665 else 666 return messages.getLocalizedString(pk.key(key), args); 667 } 668 // where 669 // backdoor hook for testing, should transition to use -XDrawDiagnostics 670 private static boolean useRawMessages = false; 671 672/*************************************************************************** 673 * raw error messages without internationalization; used for experimentation 674 * and quick prototyping 675 ***************************************************************************/ 676 677 /** print an error or warning message: 678 */ 679 private void printRawError(int pos, String msg) { 680 if (source == null || pos == Position.NOPOS) { 681 printRawLines(errWriter, "error: " + msg); 682 } else { 683 int line = source.getLineNumber(pos); 684 JavaFileObject file = source.getFile(); 685 if (file != null) 686 printRawLines(errWriter, 687 file.getName() + ":" + 688 line + ": " + msg); 689 printErrLine(pos, errWriter); 690 } 691 errWriter.flush(); 692 } 693 694 /** report an error: 695 */ 696 public void rawError(int pos, String msg) { 697 if (nerrors < MaxErrors && shouldReport(currentSourceFile(), pos)) { 698 printRawError(pos, msg); 699 prompt(); 700 nerrors++; 701 } 702 errWriter.flush(); 703 } 704 705 /** report a warning: 706 */ 707 public void rawWarning(int pos, String msg) { 708 if (nwarnings < MaxWarnings && emitWarnings) { 709 printRawError(pos, "warning: " + msg); 710 } 711 prompt(); 712 nwarnings++; 713 errWriter.flush(); 714 } 715 716 public static String format(String fmt, Object... args) { 717 return String.format((java.util.Locale)null, fmt, args); 718 } 719 720} 721