Tue, 15 Jul 2008 09:50:36 -0700
6724071: refactor Log into a front end and back end
Reviewed-by: darcy
1 /*
2 * Copyright 1999-2008 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
26 package com.sun.tools.javac.util;
28 import java.io.*;
29 import java.util.HashSet;
30 import java.util.Map;
31 import java.util.Set;
32 import javax.tools.DiagnosticListener;
33 import javax.tools.JavaFileObject;
35 import com.sun.tools.javac.file.JavacFileManager;
36 import com.sun.tools.javac.tree.JCTree;
37 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
38 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
40 /** A class for error logs. Reports errors and warnings, and
41 * keeps track of error numbers and positions.
42 *
43 * <p><b>This is NOT part of any API supported by Sun Microsystems. If
44 * you write code that depends on this, you do so at your own risk.
45 * This code and its internal interfaces are subject to change or
46 * deletion without notice.</b>
47 */
48 public class Log extends AbstractLog {
49 /** The context key for the log. */
50 public static final Context.Key<Log> logKey
51 = new Context.Key<Log>();
53 /** The context key for the output PrintWriter. */
54 public static final Context.Key<PrintWriter> outKey =
55 new Context.Key<PrintWriter>();
57 //@Deprecated
58 public final PrintWriter errWriter;
60 //@Deprecated
61 public final PrintWriter warnWriter;
63 //@Deprecated
64 public final PrintWriter noticeWriter;
66 /** The maximum number of errors/warnings that are reported.
67 */
68 public final int MaxErrors;
69 public final int MaxWarnings;
71 /** Whether or not to display the line of source containing a diagnostic.
72 */
73 private final boolean showSourceLine;
75 /** Switch: prompt user on each error.
76 */
77 public boolean promptOnError;
79 /** Switch: emit warning messages.
80 */
81 public boolean emitWarnings;
83 /** Print stack trace on errors?
84 */
85 public boolean dumpOnError;
87 /** Print multiple errors for same source locations.
88 */
89 public boolean multipleErrors;
91 /**
92 * Diagnostic listener, if provided through programmatic
93 * interface to javac (JSR 199).
94 */
95 protected DiagnosticListener<? super JavaFileObject> diagListener;
97 /**
98 * Formatter for diagnostics
99 */
100 private DiagnosticFormatter diagFormatter;
102 /** Construct a log with given I/O redirections.
103 */
104 @Deprecated
105 protected Log(Context context, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter) {
106 super(JCDiagnostic.Factory.instance(context));
107 context.put(logKey, this);
108 this.errWriter = errWriter;
109 this.warnWriter = warnWriter;
110 this.noticeWriter = noticeWriter;
112 Options options = Options.instance(context);
113 this.dumpOnError = options.get("-doe") != null;
114 this.promptOnError = options.get("-prompt") != null;
115 this.emitWarnings = options.get("-Xlint:none") == null;
116 this.MaxErrors = getIntOption(options, "-Xmaxerrs", 100);
117 this.MaxWarnings = getIntOption(options, "-Xmaxwarns", 100);
118 this.showSourceLine = options.get("rawDiagnostics") == null;
120 this.diagFormatter = DiagnosticFormatter.instance(context);
121 @SuppressWarnings("unchecked") // FIXME
122 DiagnosticListener<? super JavaFileObject> diagListener =
123 context.get(DiagnosticListener.class);
124 this.diagListener = diagListener;
125 }
126 // where
127 private int getIntOption(Options options, String optionName, int defaultValue) {
128 String s = options.get(optionName);
129 try {
130 if (s != null) return Integer.parseInt(s);
131 } catch (NumberFormatException e) {
132 // silently ignore ill-formed numbers
133 }
134 return defaultValue;
135 }
137 /** The default writer for diagnostics
138 */
139 static final PrintWriter defaultWriter(Context context) {
140 PrintWriter result = context.get(outKey);
141 if (result == null)
142 context.put(outKey, result = new PrintWriter(System.err));
143 return result;
144 }
146 /** Construct a log with default settings.
147 */
148 protected Log(Context context) {
149 this(context, defaultWriter(context));
150 }
152 /** Construct a log with all output redirected.
153 */
154 protected Log(Context context, PrintWriter defaultWriter) {
155 this(context, defaultWriter, defaultWriter, defaultWriter);
156 }
158 /** Get the Log instance for this context. */
159 public static Log instance(Context context) {
160 Log instance = context.get(logKey);
161 if (instance == null)
162 instance = new Log(context);
163 return instance;
164 }
166 /** The number of errors encountered so far.
167 */
168 public int nerrors = 0;
170 /** The number of warnings encountered so far.
171 */
172 public int nwarnings = 0;
174 /** A set of all errors generated so far. This is used to avoid printing an
175 * error message more than once. For each error, a pair consisting of the
176 * source file name and source code position of the error is added to the set.
177 */
178 private Set<Pair<JavaFileObject, Integer>> recorded = new HashSet<Pair<JavaFileObject,Integer>>();
180 public boolean hasDiagnosticListener() {
181 return diagListener != null;
182 }
184 public void setEndPosTable(JavaFileObject name, Map<JCTree, Integer> table) {
185 name.getClass(); // null check
186 getSource(name).setEndPosTable(table);
187 }
189 /** Return current source name.
190 */
191 public JavaFileObject currentSource() {
192 return source == null ? null : source.getFile();
193 }
195 /** Flush the logs
196 */
197 public void flush() {
198 errWriter.flush();
199 warnWriter.flush();
200 noticeWriter.flush();
201 }
203 /** Returns true if an error needs to be reported for a given
204 * source name and pos.
205 */
206 protected boolean shouldReport(JavaFileObject file, int pos) {
207 if (multipleErrors || file == null)
208 return true;
210 Pair<JavaFileObject,Integer> coords = new Pair<JavaFileObject,Integer>(file, pos);
211 boolean shouldReport = !recorded.contains(coords);
212 if (shouldReport)
213 recorded.add(coords);
214 return shouldReport;
215 }
217 /** Prompt user after an error.
218 */
219 public void prompt() {
220 if (promptOnError) {
221 System.err.println(getLocalizedString("resume.abort"));
222 char ch;
223 try {
224 while (true) {
225 switch (System.in.read()) {
226 case 'a': case 'A':
227 System.exit(-1);
228 return;
229 case 'r': case 'R':
230 return;
231 case 'x': case 'X':
232 throw new AssertionError("user abort");
233 default:
234 }
235 }
236 } catch (IOException e) {}
237 }
238 }
240 /** Print the faulty source code line and point to the error.
241 * @param pos Buffer index of the error position, must be on current line
242 */
243 private void printErrLine(int pos, PrintWriter writer) {
244 String line = (source == null ? null : source.getLine(pos));
245 if (line == null)
246 return;
247 int col = source.getColumnNumber(pos);
249 printLines(writer, line);
250 for (int i = 0; i < col - 1; i++) {
251 writer.print((line.charAt(i) == '\t') ? "\t" : " ");
252 }
253 writer.println("^");
254 writer.flush();
255 }
257 /** Print the text of a message, translating newlines appropriately
258 * for the platform.
259 */
260 public static void printLines(PrintWriter writer, String msg) {
261 int nl;
262 while ((nl = msg.indexOf('\n')) != -1) {
263 writer.println(msg.substring(0, nl));
264 msg = msg.substring(nl+1);
265 }
266 if (msg.length() != 0) writer.println(msg);
267 }
269 protected void directError(String key, Object... args) {
270 printLines(errWriter, getLocalizedString(key, args));
271 errWriter.flush();
272 }
274 /** Report a warning that cannot be suppressed.
275 * @param pos The source position at which to report the warning.
276 * @param key The key for the localized warning message.
277 * @param args Fields of the warning message.
278 */
279 public void strictWarning(DiagnosticPosition pos, String key, Object ... args) {
280 writeDiagnostic(diags.warning(source, pos, key, args));
281 nwarnings++;
282 }
284 /**
285 * Common diagnostic handling.
286 * The diagnostic is counted, and depending on the options and how many diagnostics have been
287 * reported so far, the diagnostic may be handed off to writeDiagnostic.
288 */
289 public void report(JCDiagnostic diagnostic) {
290 switch (diagnostic.getType()) {
291 case FRAGMENT:
292 throw new IllegalArgumentException();
294 case NOTE:
295 // Print out notes only when we are permitted to report warnings
296 // Notes are only generated at the end of a compilation, so should be small
297 // in number.
298 if (emitWarnings || diagnostic.isMandatory()) {
299 writeDiagnostic(diagnostic);
300 }
301 break;
303 case WARNING:
304 if (emitWarnings || diagnostic.isMandatory()) {
305 if (nwarnings < MaxWarnings) {
306 writeDiagnostic(diagnostic);
307 nwarnings++;
308 }
309 }
310 break;
312 case ERROR:
313 if (nerrors < MaxErrors
314 && shouldReport(diagnostic.getSource(), diagnostic.getIntPosition())) {
315 writeDiagnostic(diagnostic);
316 nerrors++;
317 }
318 break;
319 }
320 }
322 /**
323 * Write out a diagnostic.
324 */
325 protected void writeDiagnostic(JCDiagnostic diag) {
326 if (diagListener != null) {
327 try {
328 diagListener.report(diag);
329 return;
330 }
331 catch (Throwable t) {
332 throw new ClientCodeException(t);
333 }
334 }
336 PrintWriter writer = getWriterForDiagnosticType(diag.getType());
338 printLines(writer, diagFormatter.format(diag));
339 if (showSourceLine) {
340 int pos = diag.getIntPosition();
341 if (pos != Position.NOPOS) {
342 JavaFileObject prev = useSource(diag.getSource());
343 printErrLine(pos, writer);
344 useSource(prev);
345 }
346 }
348 if (promptOnError) {
349 switch (diag.getType()) {
350 case ERROR:
351 case WARNING:
352 prompt();
353 }
354 }
356 if (dumpOnError)
357 new RuntimeException().printStackTrace(writer);
359 writer.flush();
360 }
362 @Deprecated
363 protected PrintWriter getWriterForDiagnosticType(DiagnosticType dt) {
364 switch (dt) {
365 case FRAGMENT:
366 throw new IllegalArgumentException();
368 case NOTE:
369 return noticeWriter;
371 case WARNING:
372 return warnWriter;
374 case ERROR:
375 return errWriter;
377 default:
378 throw new Error();
379 }
380 }
382 /** Find a localized string in the resource bundle.
383 * @param key The key for the localized string.
384 * @param args Fields to substitute into the string.
385 */
386 public static String getLocalizedString(String key, Object ... args) {
387 return Messages.getDefaultLocalizedString("compiler.misc." + key, args);
388 }
390 /***************************************************************************
391 * raw error messages without internationalization; used for experimentation
392 * and quick prototyping
393 ***************************************************************************/
395 /** print an error or warning message:
396 */
397 private void printRawError(int pos, String msg) {
398 if (source == null || pos == Position.NOPOS) {
399 printLines(errWriter, "error: " + msg);
400 } else {
401 int line = source.getLineNumber(pos);
402 JavaFileObject file = currentSource();
403 if (file != null)
404 printLines(errWriter,
405 JavacFileManager.getJavacFileName(file) + ":" +
406 line + ": " + msg);
407 printErrLine(pos, errWriter);
408 }
409 errWriter.flush();
410 }
412 /** report an error:
413 */
414 public void rawError(int pos, String msg) {
415 if (nerrors < MaxErrors && shouldReport(currentSource(), pos)) {
416 printRawError(pos, msg);
417 prompt();
418 nerrors++;
419 }
420 errWriter.flush();
421 }
423 /** report a warning:
424 */
425 public void rawWarning(int pos, String msg) {
426 if (nwarnings < MaxWarnings && emitWarnings) {
427 printRawError(pos, "warning: " + msg);
428 }
429 prompt();
430 nwarnings++;
431 errWriter.flush();
432 }
434 public static String format(String fmt, Object... args) {
435 return String.format((java.util.Locale)null, fmt, args);
436 }
438 }