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