Log.java revision 2837:1e3266d870d6
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        Assert.checkNonNull(name);
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        Assert.checkNonNull(pw);
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        noticeWriter = warnWriter = errWriter = Assert.checkNonNull(pw);
387    }
388
389    /**
390     * Replace the specified diagnostic handler with the
391     * handler that was current at the time this handler was created.
392     * The given handler must be the currently installed handler;
393     * it must be specified explicitly for clarity and consistency checking.
394     */
395    public void popDiagnosticHandler(DiagnosticHandler h) {
396        Assert.check(diagnosticHandler == h);
397        diagnosticHandler = h.prev;
398    }
399
400    /** Flush the logs
401     */
402    public void flush() {
403        errWriter.flush();
404        warnWriter.flush();
405        noticeWriter.flush();
406    }
407
408    public void flush(WriterKind kind) {
409        getWriter(kind).flush();
410    }
411
412    /** Returns true if an error needs to be reported for a given
413     * source name and pos.
414     */
415    protected boolean shouldReport(JavaFileObject file, int pos) {
416        if (file == null)
417            return true;
418
419        Pair<JavaFileObject,Integer> coords = new Pair<>(file, pos);
420        boolean shouldReport = !recorded.contains(coords);
421        if (shouldReport)
422            recorded.add(coords);
423        return shouldReport;
424    }
425
426    /** Prompt user after an error.
427     */
428    public void prompt() {
429        if (promptOnError) {
430            System.err.println(localize("resume.abort"));
431            try {
432                while (true) {
433                    switch (System.in.read()) {
434                    case 'a': case 'A':
435                        System.exit(-1);
436                        return;
437                    case 'r': case 'R':
438                        return;
439                    case 'x': case 'X':
440                        throw new AssertionError("user abort");
441                    default:
442                    }
443                }
444            } catch (IOException e) {}
445        }
446    }
447
448    /** Print the faulty source code line and point to the error.
449     *  @param pos   Buffer index of the error position, must be on current line
450     */
451    private void printErrLine(int pos, PrintWriter writer) {
452        String line = (source == null ? null : source.getLine(pos));
453        if (line == null)
454            return;
455        int col = source.getColumnNumber(pos, false);
456
457        printRawLines(writer, line);
458        for (int i = 0; i < col - 1; i++) {
459            writer.print((line.charAt(i) == '\t') ? "\t" : " ");
460        }
461        writer.println("^");
462        writer.flush();
463    }
464
465    public void printNewline() {
466        noticeWriter.println();
467    }
468
469    public void printNewline(WriterKind wk) {
470        getWriter(wk).println();
471    }
472
473    public void printLines(String key, Object... args) {
474        printRawLines(noticeWriter, localize(key, args));
475    }
476
477    public void printLines(PrefixKind pk, String key, Object... args) {
478        printRawLines(noticeWriter, localize(pk, key, args));
479    }
480
481    public void printLines(WriterKind wk, String key, Object... args) {
482        printRawLines(getWriter(wk), localize(key, args));
483    }
484
485    public void printLines(WriterKind wk, PrefixKind pk, String key, Object... args) {
486        printRawLines(getWriter(wk), localize(pk, key, args));
487    }
488
489    /** Print the text of a message, translating newlines appropriately
490     *  for the platform.
491     */
492    public void printRawLines(String msg) {
493        printRawLines(noticeWriter, msg);
494    }
495
496    /** Print the text of a message, translating newlines appropriately
497     *  for the platform.
498     */
499    public void printRawLines(WriterKind kind, String msg) {
500        printRawLines(getWriter(kind), msg);
501    }
502
503    /** Print the text of a message, translating newlines appropriately
504     *  for the platform.
505     */
506    public static void printRawLines(PrintWriter writer, String msg) {
507        int nl;
508        while ((nl = msg.indexOf('\n')) != -1) {
509            writer.println(msg.substring(0, nl));
510            msg = msg.substring(nl+1);
511        }
512        if (msg.length() != 0) writer.println(msg);
513    }
514
515    /**
516     * Print the localized text of a "verbose" message to the
517     * noticeWriter stream.
518     */
519    public void printVerbose(String key, Object... args) {
520        printRawLines(noticeWriter, localize("verbose." + key, args));
521    }
522
523    protected void directError(String key, Object... args) {
524        printRawLines(errWriter, localize(key, args));
525        errWriter.flush();
526    }
527
528    /** Report a warning that cannot be suppressed.
529     *  @param pos    The source position at which to report the warning.
530     *  @param key    The key for the localized warning message.
531     *  @param args   Fields of the warning message.
532     */
533    public void strictWarning(DiagnosticPosition pos, String key, Object ... args) {
534        writeDiagnostic(diags.warning(null, source, pos, key, args));
535        nwarnings++;
536    }
537
538    /**
539     * Primary method to report a diagnostic.
540     * @param diagnostic
541     */
542    public void report(JCDiagnostic diagnostic) {
543        diagnosticHandler.report(diagnostic);
544     }
545
546    /**
547     * Common diagnostic handling.
548     * The diagnostic is counted, and depending on the options and how many diagnostics have been
549     * reported so far, the diagnostic may be handed off to writeDiagnostic.
550     */
551    private class DefaultDiagnosticHandler extends DiagnosticHandler {
552        public void report(JCDiagnostic diagnostic) {
553            if (expectDiagKeys != null)
554                expectDiagKeys.remove(diagnostic.getCode());
555
556            switch (diagnostic.getType()) {
557            case FRAGMENT:
558                throw new IllegalArgumentException();
559
560            case NOTE:
561                // Print out notes only when we are permitted to report warnings
562                // Notes are only generated at the end of a compilation, so should be small
563                // in number.
564                if ((emitWarnings || diagnostic.isMandatory()) && !suppressNotes) {
565                    writeDiagnostic(diagnostic);
566                }
567                break;
568
569            case WARNING:
570                if (emitWarnings || diagnostic.isMandatory()) {
571                    if (nwarnings < MaxWarnings) {
572                        writeDiagnostic(diagnostic);
573                        nwarnings++;
574                    }
575                }
576                break;
577
578            case ERROR:
579                if (nerrors < MaxErrors &&
580                    (diagnostic.isFlagSet(DiagnosticFlag.MULTIPLE) ||
581                     shouldReport(diagnostic.getSource(), diagnostic.getIntPosition()))) {
582                    writeDiagnostic(diagnostic);
583                    nerrors++;
584                }
585                break;
586            }
587            if (diagnostic.isFlagSet(JCDiagnostic.DiagnosticFlag.COMPRESSED)) {
588                compressedOutput = true;
589            }
590        }
591    }
592
593    /**
594     * Write out a diagnostic.
595     */
596    protected void writeDiagnostic(JCDiagnostic diag) {
597        if (diagListener != null) {
598            diagListener.report(diag);
599            return;
600        }
601
602        PrintWriter writer = getWriterForDiagnosticType(diag.getType());
603
604        printRawLines(writer, diagFormatter.format(diag, messages.getCurrentLocale()));
605
606        if (promptOnError) {
607            switch (diag.getType()) {
608            case ERROR:
609            case WARNING:
610                prompt();
611            }
612        }
613
614        if (dumpOnError)
615            new RuntimeException().printStackTrace(writer);
616
617        writer.flush();
618    }
619
620    @Deprecated
621    protected PrintWriter getWriterForDiagnosticType(DiagnosticType dt) {
622        switch (dt) {
623        case FRAGMENT:
624            throw new IllegalArgumentException();
625
626        case NOTE:
627            return noticeWriter;
628
629        case WARNING:
630            return warnWriter;
631
632        case ERROR:
633            return errWriter;
634
635        default:
636            throw new Error();
637        }
638    }
639
640    /** Find a localized string in the resource bundle.
641     *  Because this method is static, it ignores the locale.
642     *  Use localize(key, args) when possible.
643     *  @param key    The key for the localized string.
644     *  @param args   Fields to substitute into the string.
645     */
646    public static String getLocalizedString(String key, Object ... args) {
647        return JavacMessages.getDefaultLocalizedString(PrefixKind.COMPILER_MISC.key(key), args);
648    }
649
650    /** Find a localized string in the resource bundle.
651     *  @param key    The key for the localized string.
652     *  @param args   Fields to substitute into the string.
653     */
654    public String localize(String key, Object... args) {
655        return localize(PrefixKind.COMPILER_MISC, key, args);
656    }
657
658    /** Find a localized string in the resource bundle.
659     *  @param key    The key for the localized string.
660     *  @param args   Fields to substitute into the string.
661     */
662    public String localize(PrefixKind pk, String key, Object... args) {
663        if (useRawMessages)
664            return pk.key(key);
665        else
666            return messages.getLocalizedString(pk.key(key), args);
667    }
668    // where
669        // backdoor hook for testing, should transition to use -XDrawDiagnostics
670        private static boolean useRawMessages = false;
671
672/***************************************************************************
673 * raw error messages without internationalization; used for experimentation
674 * and quick prototyping
675 ***************************************************************************/
676
677    /** print an error or warning message:
678     */
679    private void printRawError(int pos, String msg) {
680        if (source == null || pos == Position.NOPOS) {
681            printRawLines(errWriter, "error: " + msg);
682        } else {
683            int line = source.getLineNumber(pos);
684            JavaFileObject file = source.getFile();
685            if (file != null)
686                printRawLines(errWriter,
687                           file.getName() + ":" +
688                           line + ": " + msg);
689            printErrLine(pos, errWriter);
690        }
691        errWriter.flush();
692    }
693
694    /** report an error:
695     */
696    public void rawError(int pos, String msg) {
697        if (nerrors < MaxErrors && shouldReport(currentSourceFile(), pos)) {
698            printRawError(pos, msg);
699            prompt();
700            nerrors++;
701        }
702        errWriter.flush();
703    }
704
705    /** report a warning:
706     */
707    public void rawWarning(int pos, String msg) {
708        if (nwarnings < MaxWarnings && emitWarnings) {
709            printRawError(pos, "warning: " + msg);
710        }
711        prompt();
712        nwarnings++;
713        errWriter.flush();
714    }
715
716    public static String format(String fmt, Object... args) {
717        return String.format((java.util.Locale)null, fmt, args);
718    }
719
720}
721