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