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