001: /*
002: * Copyright 1999-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.tools.javac.util;
027:
028: import java.io.*;
029: import java.nio.CharBuffer;
030: import java.util.HashMap;
031: import java.util.HashSet;
032: import java.util.Map;
033: import java.util.Set;
034: import javax.tools.DiagnosticListener;
035: import javax.tools.JavaFileObject;
036: import com.sun.tools.javac.code.Source;
037: import com.sun.tools.javac.tree.JCTree;
038: import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
039: import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
040: import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
041: import static com.sun.tools.javac.util.LayoutCharacters.*;
042:
043: /** A class for error logs. Reports errors and warnings, and
044: * keeps track of error numbers and positions.
045: *
046: * <p><b>This is NOT part of any API supported by Sun Microsystems. If
047: * you write code that depends on this, you do so at your own risk.
048: * This code and its internal interfaces are subject to change or
049: * deletion without notice.</b>
050: */
051: @Version("@(#)Log.java 1.72 07/05/05")
052: public class Log {
053: /** The context key for the log. */
054: public static final Context.Key<Log> logKey = new Context.Key<Log>();
055:
056: /** The context key for the output PrintWriter. */
057: public static final Context.Key<PrintWriter> outKey = new Context.Key<PrintWriter>();
058:
059: //@Deprecated
060: public final PrintWriter errWriter;
061:
062: //@Deprecated
063: public final PrintWriter warnWriter;
064:
065: //@Deprecated
066: public final PrintWriter noticeWriter;
067:
068: /** The maximum number of errors/warnings that are reported.
069: */
070: public final int MaxErrors;
071: public final int MaxWarnings;
072:
073: /** Whether or not to display the line of source containing a diagnostic.
074: */
075: private final boolean showSourceLine;
076:
077: /** Switch: prompt user on each error.
078: */
079: public boolean promptOnError;
080:
081: /** Switch: emit warning messages.
082: */
083: public boolean emitWarnings;
084:
085: /** Enforce mandatory warnings.
086: */
087: private boolean enforceMandatoryWarnings;
088:
089: /** Print stack trace on errors?
090: */
091: public boolean dumpOnError;
092:
093: /** Print multiple errors for same source locations.
094: */
095: public boolean multipleErrors;
096:
097: /**
098: * Diagnostic listener, if provided through programmatic
099: * interface to javac (JSR 199).
100: */
101: protected DiagnosticListener<? super JavaFileObject> diagListener;
102: /**
103: * Formatter for diagnostics
104: */
105: private DiagnosticFormatter diagFormatter;
106:
107: /**
108: * Factory for diagnostics
109: */
110: private JCDiagnostic.Factory diags;
111:
112: /** Construct a log with given I/O redirections.
113: */
114: @Deprecated
115: protected Log(Context context, PrintWriter errWriter,
116: PrintWriter warnWriter, PrintWriter noticeWriter) {
117: context.put(logKey, this );
118: this .errWriter = errWriter;
119: this .warnWriter = warnWriter;
120: this .noticeWriter = noticeWriter;
121:
122: this .diags = JCDiagnostic.Factory.instance(context);
123:
124: Options options = Options.instance(context);
125: this .dumpOnError = options.get("-doe") != null;
126: this .promptOnError = options.get("-prompt") != null;
127: this .emitWarnings = options.get("-Xlint:none") == null;
128: this .MaxErrors = getIntOption(options, "-Xmaxerrs", 100);
129: this .MaxWarnings = getIntOption(options, "-Xmaxwarns", 100);
130: this .showSourceLine = options.get("rawDiagnostics") == null;
131:
132: this .diagFormatter = DiagnosticFormatter.instance(context);
133: @SuppressWarnings("unchecked")
134: // FIXME
135: DiagnosticListener<? super JavaFileObject> diagListener = context
136: .get(DiagnosticListener.class);
137: this .diagListener = diagListener;
138:
139: Source source = Source.instance(context);
140: this .enforceMandatoryWarnings = source
141: .enforceMandatoryWarnings();
142: }
143:
144: // where
145: private int getIntOption(Options options, String optionName,
146: int defaultValue) {
147: String s = options.get(optionName);
148: try {
149: if (s != null)
150: return Integer.parseInt(s);
151: } catch (NumberFormatException e) {
152: // silently ignore ill-formed numbers
153: }
154: return defaultValue;
155: }
156:
157: /** The default writer for diagnostics
158: */
159: static final PrintWriter defaultWriter(Context context) {
160: PrintWriter result = context.get(outKey);
161: if (result == null)
162: context.put(outKey, result = new PrintWriter(System.err));
163: return result;
164: }
165:
166: /** Construct a log with default settings.
167: */
168: protected Log(Context context) {
169: this (context, defaultWriter(context));
170: }
171:
172: /** Construct a log with all output redirected.
173: */
174: protected Log(Context context, PrintWriter defaultWriter) {
175: this (context, defaultWriter, defaultWriter, defaultWriter);
176: }
177:
178: /** Get the Log instance for this context. */
179: public static Log instance(Context context) {
180: Log instance = context.get(logKey);
181: if (instance == null)
182: instance = new Log(context);
183: return instance;
184: }
185:
186: /** The file that's currently translated.
187: */
188: protected JCDiagnostic.DiagnosticSource source;
189:
190: /** The number of errors encountered so far.
191: */
192: public int nerrors = 0;
193:
194: /** The number of warnings encountered so far.
195: */
196: public int nwarnings = 0;
197:
198: /** A set of all errors generated so far. This is used to avoid printing an
199: * error message more than once. For each error, a pair consisting of the
200: * source file name and source code position of the error is added to the set.
201: */
202: private Set<Pair<JavaFileObject, Integer>> recorded = new HashSet<Pair<JavaFileObject, Integer>>();
203:
204: private Map<JavaFileObject, Map<JCTree, Integer>> endPosTables;
205:
206: /** The buffer containing the file that's currently translated.
207: */
208: private char[] buf = null;
209:
210: /** The position in the buffer at which last error was reported
211: */
212: private int bp;
213:
214: /** number of the current source line; first line is 1
215: */
216: private int line;
217:
218: /** buffer index of the first character of the current source line
219: */
220: private int lineStart;
221:
222: public boolean hasDiagnosticListener() {
223: return diagListener != null;
224: }
225:
226: public void setEndPosTable(JavaFileObject name,
227: Map<JCTree, Integer> table) {
228: if (endPosTables == null)
229: endPosTables = new HashMap<JavaFileObject, Map<JCTree, Integer>>();
230: endPosTables.put(name, table);
231: }
232:
233: /** Re-assign source, returning previous setting.
234: */
235: public JavaFileObject useSource(final JavaFileObject name) {
236: JavaFileObject prev = currentSource();
237: if (name != prev) {
238: source = new JCDiagnostic.DiagnosticSource() {
239: public JavaFileObject getFile() {
240: return name;
241: }
242:
243: public CharSequence getName() {
244: return JavacFileManager
245: .getJavacBaseFileName(getFile());
246: }
247:
248: public int getLineNumber(int pos) {
249: return Log.this .getLineNumber(pos);
250: }
251:
252: public int getColumnNumber(int pos) {
253: return Log.this .getColumnNumber(pos);
254: }
255:
256: public Map<JCTree, Integer> getEndPosTable() {
257: return (endPosTables == null ? null : endPosTables
258: .get(name));
259: }
260: };
261: buf = null;
262: }
263: return prev;
264: }
265:
266: /** Re-assign source buffer for existing source name.
267: */
268: protected void setBuf(char[] newBuf) {
269: buf = newBuf;
270: bp = 0;
271: lineStart = 0;
272: line = 1;
273: }
274:
275: protected char[] getBuf() {
276: return buf;
277: }
278:
279: /** Return current source name.
280: */
281: public JavaFileObject currentSource() {
282: return source == null ? null : source.getFile();
283: }
284:
285: /** Flush the logs
286: */
287: public void flush() {
288: errWriter.flush();
289: warnWriter.flush();
290: noticeWriter.flush();
291: }
292:
293: /** Returns true if an error needs to be reported for a given
294: * source name and pos.
295: */
296: protected boolean shouldReport(JavaFileObject file, int pos) {
297: if (multipleErrors || file == null)
298: return true;
299:
300: Pair<JavaFileObject, Integer> coords = new Pair<JavaFileObject, Integer>(
301: file, pos);
302: boolean shouldReport = !recorded.contains(coords);
303: if (shouldReport)
304: recorded.add(coords);
305: return shouldReport;
306: }
307:
308: /** Prompt user after an error.
309: */
310: public void prompt() {
311: if (promptOnError) {
312: System.err.println(getLocalizedString("resume.abort"));
313: char ch;
314: try {
315: while (true) {
316: switch (System.in.read()) {
317: case 'a':
318: case 'A':
319: System.exit(-1);
320: return;
321: case 'r':
322: case 'R':
323: return;
324: case 'x':
325: case 'X':
326: throw new AssertionError("user abort");
327: default:
328: }
329: }
330: } catch (IOException e) {
331: }
332: }
333: }
334:
335: /** Print the faulty source code line and point to the error.
336: * @param pos Buffer index of the error position, must be on current line
337: */
338: private void printErrLine(int pos, PrintWriter writer) {
339: if (!findLine(pos))
340: return;
341:
342: int lineEnd = lineStart;
343: while (lineEnd < buf.length && buf[lineEnd] != CR
344: && buf[lineEnd] != LF)
345: lineEnd++;
346: if (lineEnd - lineStart == 0)
347: return;
348: printLines(writer, new String(buf, lineStart, lineEnd
349: - lineStart));
350: for (bp = lineStart; bp < pos; bp++) {
351: writer.print((buf[bp] == '\t') ? "\t" : " ");
352: }
353: writer.println("^");
354: writer.flush();
355: }
356:
357: protected static char[] getCharContent(JavaFileObject fileObject)
358: throws IOException {
359: CharSequence cs = fileObject.getCharContent(true);
360: if (cs instanceof CharBuffer) {
361: return JavacFileManager.toArray((CharBuffer) cs);
362: } else {
363: return cs.toString().toCharArray();
364: }
365: }
366:
367: /** Find the line in the buffer that contains the current position
368: * @param pos Character offset into the buffer
369: */
370: private boolean findLine(int pos) {
371: if (pos == Position.NOPOS || currentSource() == null)
372: return false;
373: try {
374: if (buf == null) {
375: buf = getCharContent(currentSource());
376: lineStart = 0;
377: line = 1;
378: } else if (lineStart > pos) { // messages don't come in order
379: lineStart = 0;
380: line = 1;
381: }
382: bp = lineStart;
383: while (bp < buf.length && bp < pos) {
384: switch (buf[bp++]) {
385: case CR:
386: if (bp < buf.length && buf[bp] == LF)
387: bp++;
388: line++;
389: lineStart = bp;
390: break;
391: case LF:
392: line++;
393: lineStart = bp;
394: break;
395: }
396: }
397: return bp <= buf.length;
398: } catch (IOException e) {
399: //e.printStackTrace();
400: // FIXME: include e.getLocalizedMessage() in error message
401: printLines(errWriter,
402: getLocalizedString("source.unavailable"));
403: errWriter.flush();
404: buf = new char[0];
405: }
406: return false;
407: }
408:
409: /** Print the text of a message, translating newlines appropriately
410: * for the platform.
411: */
412: public static void printLines(PrintWriter writer, String msg) {
413: int nl;
414: while ((nl = msg.indexOf('\n')) != -1) {
415: writer.println(msg.substring(0, nl));
416: msg = msg.substring(nl + 1);
417: }
418: if (msg.length() != 0)
419: writer.println(msg);
420: }
421:
422: /** Report an error, unless another error was already reported at same
423: * source position.
424: * @param key The key for the localized error message.
425: * @param args Fields of the error message.
426: */
427: public void error(String key, Object... args) {
428: report(diags.error(source, null, key, args));
429: }
430:
431: /** Report an error, unless another error was already reported at same
432: * source position.
433: * @param pos The source position at which to report the error.
434: * @param key The key for the localized error message.
435: * @param args Fields of the error message.
436: */
437: public void error(DiagnosticPosition pos, String key,
438: Object... args) {
439: report(diags.error(source, pos, key, args));
440: }
441:
442: /** Report an error, unless another error was already reported at same
443: * source position.
444: * @param pos The source position at which to report the error.
445: * @param key The key for the localized error message.
446: * @param args Fields of the error message.
447: */
448: public void error(int pos, String key, Object... args) {
449: report(diags.error(source, wrap(pos), key, args));
450: }
451:
452: /** Report a warning, unless suppressed by the -nowarn option or the
453: * maximum number of warnings has been reached.
454: * @param pos The source position at which to report the warning.
455: * @param key The key for the localized warning message.
456: * @param args Fields of the warning message.
457: */
458: public void warning(String key, Object... args) {
459: report(diags.warning(source, null, key, args));
460: }
461:
462: /** Report a warning, unless suppressed by the -nowarn option or the
463: * maximum number of warnings has been reached.
464: * @param pos The source position at which to report the warning.
465: * @param key The key for the localized warning message.
466: * @param args Fields of the warning message.
467: */
468: public void warning(DiagnosticPosition pos, String key,
469: Object... args) {
470: report(diags.warning(source, pos, key, args));
471: }
472:
473: /** Report a warning, unless suppressed by the -nowarn option or the
474: * maximum number of warnings has been reached.
475: * @param pos The source position at which to report the warning.
476: * @param key The key for the localized warning message.
477: * @param args Fields of the warning message.
478: */
479: public void warning(int pos, String key, Object... args) {
480: report(diags.warning(source, wrap(pos), key, args));
481: }
482:
483: /** Report a warning.
484: * @param pos The source position at which to report the warning.
485: * @param key The key for the localized warning message.
486: * @param args Fields of the warning message.
487: */
488: public void mandatoryWarning(DiagnosticPosition pos, String key,
489: Object... args) {
490: if (enforceMandatoryWarnings)
491: report(diags.mandatoryWarning(source, pos, key, args));
492: else
493: report(diags.warning(source, pos, key, args));
494: }
495:
496: /** Report a warning that cannot be suppressed.
497: * @param pos The source position at which to report the warning.
498: * @param key The key for the localized warning message.
499: * @param args Fields of the warning message.
500: */
501: public void strictWarning(DiagnosticPosition pos, String key,
502: Object... args) {
503: writeDiagnostic(diags.warning(source, pos, key, args));
504: nwarnings++;
505: }
506:
507: /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
508: * @param key The key for the localized notification message.
509: * @param args Fields of the notification message.
510: */
511: public void note(String key, Object... args) {
512: report(diags.note(source, null, key, args));
513: }
514:
515: /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
516: * @param key The key for the localized notification message.
517: * @param args Fields of the notification message.
518: */
519: public void note(DiagnosticPosition pos, String key, Object... args) {
520: report(diags.note(source, pos, key, args));
521: }
522:
523: /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
524: * @param key The key for the localized notification message.
525: * @param args Fields of the notification message.
526: */
527: public void note(int pos, String key, Object... args) {
528: report(diags.note(source, wrap(pos), key, args));
529: }
530:
531: /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
532: * @param key The key for the localized notification message.
533: * @param args Fields of the notification message.
534: */
535: public void mandatoryNote(final JavaFileObject file, String key,
536: Object... args) {
537: JCDiagnostic.DiagnosticSource wrapper = null;
538: if (file != null) {
539: wrapper = new JCDiagnostic.DiagnosticSource() {
540: public JavaFileObject getFile() {
541: return file;
542: }
543:
544: public CharSequence getName() {
545: return JavacFileManager
546: .getJavacBaseFileName(getFile());
547: }
548:
549: public int getLineNumber(int pos) {
550: return Log.this .getLineNumber(pos);
551: }
552:
553: public int getColumnNumber(int pos) {
554: return Log.this .getColumnNumber(pos);
555: }
556:
557: public Map<JCTree, Integer> getEndPosTable() {
558: return (endPosTables == null ? null : endPosTables
559: .get(file));
560: }
561: };
562: }
563: if (enforceMandatoryWarnings)
564: report(diags.mandatoryNote(wrapper, key, args));
565: else
566: report(diags.note(wrapper, null, key, args));
567: }
568:
569: private DiagnosticPosition wrap(int pos) {
570: return (pos == Position.NOPOS ? null
571: : new SimpleDiagnosticPosition(pos));
572: }
573:
574: /**
575: * Common diagnostic handling.
576: * The diagnostic is counted, and depending on the options and how many diagnostics have been
577: * reported so far, the diagnostic may be handed off to writeDiagnostic.
578: */
579: public void report(JCDiagnostic diagnostic) {
580: switch (diagnostic.getType()) {
581: case FRAGMENT:
582: throw new IllegalArgumentException();
583:
584: case NOTE:
585: // Print out notes only when we are permitted to report warnings
586: // Notes are only generated at the end of a compilation, so should be small
587: // in number.
588: if (emitWarnings || diagnostic.isMandatory()) {
589: writeDiagnostic(diagnostic);
590: }
591: break;
592:
593: case WARNING:
594: if (emitWarnings || diagnostic.isMandatory()) {
595: if (nwarnings < MaxWarnings) {
596: writeDiagnostic(diagnostic);
597: nwarnings++;
598: }
599: }
600: break;
601:
602: case ERROR:
603: if (nerrors < MaxErrors
604: && shouldReport(diagnostic.getSource(), diagnostic
605: .getIntPosition())) {
606: writeDiagnostic(diagnostic);
607: nerrors++;
608: }
609: break;
610: }
611: }
612:
613: /**
614: * Write out a diagnostic.
615: */
616: protected void writeDiagnostic(JCDiagnostic diag) {
617: if (diagListener != null) {
618: try {
619: diagListener.report(diag);
620: return;
621: } catch (Throwable t) {
622: throw new ClientCodeException(t);
623: }
624: }
625:
626: PrintWriter writer = getWriterForDiagnosticType(diag.getType());
627:
628: printLines(writer, diagFormatter.format(diag));
629: if (showSourceLine) {
630: int pos = diag.getIntPosition();
631: if (pos != Position.NOPOS) {
632: JavaFileObject prev = useSource(diag.getSource());
633: printErrLine(pos, writer);
634: useSource(prev);
635: }
636: }
637:
638: if (promptOnError) {
639: switch (diag.getType()) {
640: case ERROR:
641: case WARNING:
642: prompt();
643: }
644: }
645:
646: if (dumpOnError)
647: new RuntimeException().printStackTrace(writer);
648:
649: writer.flush();
650: }
651:
652: @Deprecated
653: protected PrintWriter getWriterForDiagnosticType(DiagnosticType dt) {
654: switch (dt) {
655: case FRAGMENT:
656: throw new IllegalArgumentException();
657:
658: case NOTE:
659: return noticeWriter;
660:
661: case WARNING:
662: return warnWriter;
663:
664: case ERROR:
665: return errWriter;
666:
667: default:
668: throw new Error();
669: }
670: }
671:
672: /** Find a localized string in the resource bundle.
673: * @param key The key for the localized string.
674: * @param args Fields to substitute into the string.
675: */
676: public static String getLocalizedString(String key, Object... args) {
677: return Messages.getDefaultLocalizedString("compiler.misc."
678: + key, args);
679: }
680:
681: /***************************************************************************
682: * raw error messages without internationalization; used for experimentation
683: * and quick prototyping
684: ***************************************************************************/
685:
686: /** print an error or warning message:
687: */
688: private void printRawError(int pos, String msg) {
689: if (!findLine(pos)) {
690: printLines(errWriter, "error: " + msg);
691: } else {
692: JavaFileObject file = currentSource();
693: if (file != null)
694: printLines(errWriter, JavacFileManager
695: .getJavacFileName(file)
696: + ":" + line + ": " + msg);
697: printErrLine(pos, errWriter);
698: }
699: errWriter.flush();
700: }
701:
702: /** report an error:
703: */
704: public void rawError(int pos, String msg) {
705: if (nerrors < MaxErrors && shouldReport(currentSource(), pos)) {
706: printRawError(pos, msg);
707: prompt();
708: nerrors++;
709: }
710: errWriter.flush();
711: }
712:
713: /** report a warning:
714: */
715: public void rawWarning(int pos, String msg) {
716: if (nwarnings < MaxWarnings && emitWarnings) {
717: printRawError(pos, "warning: " + msg);
718: }
719: prompt();
720: nwarnings++;
721: errWriter.flush();
722: }
723:
724: /** Return the one-based line number associated with a given pos
725: * for the current source file. Zero is returned if no line exists
726: * for the given position.
727: */
728: protected int getLineNumber(int pos) {
729: if (findLine(pos))
730: return line;
731: return 0;
732: }
733:
734: /** Return the one-based column number associated with a given pos
735: * for the current source file. Zero is returned if no column exists
736: * for the given position.
737: */
738: protected int getColumnNumber(int pos) {
739: if (findLine(pos)) {
740: int column = 0;
741: for (bp = lineStart; bp < pos; bp++) {
742: if (bp >= buf.length)
743: return 0;
744: if (buf[bp] == '\t')
745: column = (column / TabInc * TabInc) + TabInc;
746: else
747: column++;
748: }
749: return column + 1; // positions are one-based
750: }
751: return 0;
752: }
753:
754: public static String format(String fmt, Object... args) {
755: return String.format((java.util.Locale) null, fmt, args);
756: }
757:
758: }
|