BasicDiagnosticFormatter.java revision 3547:e18190929198
1/*
2 * Copyright (c) 2005, 2016, 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.util.Collection;
29import java.util.EnumMap;
30import java.util.EnumSet;
31import java.util.HashMap;
32import java.util.Locale;
33import java.util.Map;
34import java.util.regex.Matcher;
35import javax.tools.JavaFileObject;
36
37import com.sun.tools.javac.util.AbstractDiagnosticFormatter.SimpleConfiguration;
38import com.sun.tools.javac.util.BasicDiagnosticFormatter.BasicConfiguration;
39
40import static com.sun.tools.javac.api.DiagnosticFormatter.PositionKind.*;
41import static com.sun.tools.javac.util.BasicDiagnosticFormatter.BasicConfiguration.*;
42import static com.sun.tools.javac.util.LayoutCharacters.*;
43
44/**
45 * A basic formatter for diagnostic messages.
46 * The basic formatter will format a diagnostic according to one of three format patterns, depending on whether
47 * or not the source name and position are set. The formatter supports a printf-like string for patterns
48 * with the following special characters:
49 * <ul>
50 * <li>%b: the base of the source name
51 * <li>%f: the source name (full absolute path)
52 * <li>%l: the line number of the diagnostic, derived from the character offset
53 * <li>%c: the column number of the diagnostic, derived from the character offset
54 * <li>%o: the character offset of the diagnostic if set
55 * <li>%p: the prefix for the diagnostic, derived from the diagnostic type
56 * <li>%t: the prefix as it normally appears in standard diagnostics. In this case, no prefix is
57 *        shown if the type is ERROR and if a source name is set
58 * <li>%m: the text or the diagnostic, including any appropriate arguments
59 * <li>%_: space delimiter, useful for formatting purposes
60 * </ul>
61 *
62 * <p><b>This is NOT part of any supported API.
63 * If you write code that depends on this, you do so at your own risk.
64 * This code and its internal interfaces are subject to change or
65 * deletion without notice.</b>
66 */
67public class BasicDiagnosticFormatter extends AbstractDiagnosticFormatter {
68
69    /**
70     * Create a basic formatter based on the supplied options.
71     *
72     * @param options list of command-line options
73     * @param msgs JavacMessages object used for i18n
74     */
75    public BasicDiagnosticFormatter(Options options, JavacMessages msgs) {
76        super(msgs, new BasicConfiguration(options));
77    }
78
79    /**
80     * Create a standard basic formatter
81     *
82     * @param msgs JavacMessages object used for i18n
83     */
84    public BasicDiagnosticFormatter(JavacMessages msgs) {
85        super(msgs, new BasicConfiguration());
86    }
87
88    public String formatDiagnostic(JCDiagnostic d, Locale l) {
89        if (l == null)
90            l = messages.getCurrentLocale();
91        String format = selectFormat(d);
92        StringBuilder buf = new StringBuilder();
93        for (int i = 0; i < format.length(); i++) {
94            char c = format.charAt(i);
95            boolean meta = false;
96            if (c == '%' && i < format.length() - 1) {
97                meta = true;
98                c = format.charAt(++i);
99            }
100            buf.append(meta ? formatMeta(c, d, l) : String.valueOf(c));
101        }
102        if (depth == 0)
103            return addSourceLineIfNeeded(d, buf.toString());
104        else
105            return buf.toString();
106    }
107
108    public String formatMessage(JCDiagnostic d, Locale l) {
109        int currentIndentation = 0;
110        StringBuilder buf = new StringBuilder();
111        Collection<String> args = formatArguments(d, l);
112        String msg = localize(l, d.getCode(), args.toArray());
113        String[] lines = msg.split("\n");
114        if (lines.length == 0) // will happen when msg only contains one or more separators: "\n", "\n\n", etc.
115            lines = new String[] { "" };
116        if (getConfiguration().getVisible().contains(DiagnosticPart.SUMMARY)) {
117            currentIndentation += getConfiguration().getIndentation(DiagnosticPart.SUMMARY);
118            buf.append(indent(lines[0], currentIndentation)); //summary
119        }
120        if (lines.length > 1 && getConfiguration().getVisible().contains(DiagnosticPart.DETAILS)) {
121            currentIndentation += getConfiguration().getIndentation(DiagnosticPart.DETAILS);
122            for (int i = 1;i < lines.length; i++) {
123                buf.append("\n" + indent(lines[i], currentIndentation));
124            }
125        }
126        if (d.isMultiline() && getConfiguration().getVisible().contains(DiagnosticPart.SUBDIAGNOSTICS)) {
127            currentIndentation += getConfiguration().getIndentation(DiagnosticPart.SUBDIAGNOSTICS);
128                for (String sub : formatSubdiagnostics(d, l)) {
129                    buf.append("\n" + indent(sub, currentIndentation));
130            }
131        }
132        return buf.toString();
133    }
134
135    protected String addSourceLineIfNeeded(JCDiagnostic d, String msg) {
136        if (!displaySource(d))
137            return msg;
138        else {
139            BasicConfiguration conf = getConfiguration();
140            int indentSource = conf.getIndentation(DiagnosticPart.SOURCE);
141            String sourceLine = "\n" + formatSourceLine(d, indentSource);
142            boolean singleLine = !msg.contains("\n");
143            if (singleLine || getConfiguration().getSourcePosition() == SourcePosition.BOTTOM)
144                return msg + sourceLine;
145            else
146                return msg.replaceFirst("\n", Matcher.quoteReplacement(sourceLine) + "\n");
147        }
148    }
149
150    protected String formatMeta(char c, JCDiagnostic d, Locale l) {
151        switch (c) {
152            case 'b':
153                return formatSource(d, false, l);
154            case 'e':
155                return formatPosition(d, END, l);
156            case 'f':
157                return formatSource(d, true, l);
158            case 'l':
159                return formatPosition(d, LINE, l);
160            case 'c':
161                return formatPosition(d, COLUMN, l);
162            case 'o':
163                return formatPosition(d, OFFSET, l);
164            case 'p':
165                return formatKind(d, l);
166            case 's':
167                return formatPosition(d, START, l);
168            case 't': {
169                boolean usePrefix;
170                switch (d.getType()) {
171                case FRAGMENT:
172                    usePrefix = false;
173                    break;
174                case ERROR:
175                    usePrefix = (d.getIntPosition() == Position.NOPOS);
176                    break;
177                default:
178                    usePrefix = true;
179                }
180                if (usePrefix)
181                    return formatKind(d, l);
182                else
183                    return "";
184            }
185            case 'm':
186                return formatMessage(d, l);
187            case 'L':
188                return formatLintCategory(d, l);
189            case '_':
190                return " ";
191            case '%':
192                return "%";
193            default:
194                return String.valueOf(c);
195        }
196    }
197
198    private String selectFormat(JCDiagnostic d) {
199        DiagnosticSource source = d.getDiagnosticSource();
200        String format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT);
201        if (source != null && source != DiagnosticSource.NO_SOURCE) {
202            if (d.getIntPosition() != Position.NOPOS) {
203                format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_POS_FORMAT);
204            } else if (source.getFile() != null &&
205                       source.getFile().getKind() == JavaFileObject.Kind.CLASS) {
206                format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT);
207            }
208        }
209        return format;
210    }
211
212    @Override
213    public BasicConfiguration getConfiguration() {
214        //the following cast is always safe - see init
215        return (BasicConfiguration)super.getConfiguration();
216    }
217
218    static public class BasicConfiguration extends SimpleConfiguration {
219
220        protected Map<DiagnosticPart, Integer> indentationLevels;
221        protected Map<BasicFormatKind, String> availableFormats;
222        protected SourcePosition sourcePosition;
223
224        @SuppressWarnings("fallthrough")
225        public BasicConfiguration(Options options) {
226            super(options, EnumSet.of(DiagnosticPart.SUMMARY,
227                            DiagnosticPart.DETAILS,
228                            DiagnosticPart.SUBDIAGNOSTICS,
229                            DiagnosticPart.SOURCE));
230            initFormat();
231            initIndentation();
232            if (options.isSet("diags.legacy"))
233                initOldFormat();
234            String fmt = options.get("diags.layout");
235            if (fmt != null) {
236                if (fmt.equals("OLD"))
237                    initOldFormat();
238                else
239                    initFormats(fmt);
240            }
241            String srcPos = null;
242            if ((((srcPos = options.get("diags.sourcePosition")) != null)) &&
243                    srcPos.equals("bottom"))
244                    setSourcePosition(SourcePosition.BOTTOM);
245            else
246                setSourcePosition(SourcePosition.AFTER_SUMMARY);
247            String indent = options.get("diags.indent");
248            if (indent != null) {
249                String[] levels = indent.split("\\|");
250                try {
251                    switch (levels.length) {
252                        case 5:
253                            setIndentation(DiagnosticPart.JLS,
254                                    Integer.parseInt(levels[4]));
255                        case 4:
256                            setIndentation(DiagnosticPart.SUBDIAGNOSTICS,
257                                    Integer.parseInt(levels[3]));
258                        case 3:
259                            setIndentation(DiagnosticPart.SOURCE,
260                                    Integer.parseInt(levels[2]));
261                        case 2:
262                            setIndentation(DiagnosticPart.DETAILS,
263                                    Integer.parseInt(levels[1]));
264                        default:
265                            setIndentation(DiagnosticPart.SUMMARY,
266                                    Integer.parseInt(levels[0]));
267                    }
268                }
269                catch (NumberFormatException ex) {
270                    initIndentation();
271                }
272            }
273        }
274
275        public BasicConfiguration() {
276            super(EnumSet.of(DiagnosticPart.SUMMARY,
277                  DiagnosticPart.DETAILS,
278                  DiagnosticPart.SUBDIAGNOSTICS,
279                  DiagnosticPart.SOURCE));
280            initFormat();
281            initIndentation();
282        }
283
284        private void initFormat() {
285            initFormats("%f:%l:%_%p%L%m", "%p%L%m", "%f:%_%p%L%m");
286        }
287
288        private void initOldFormat() {
289            initFormats("%f:%l:%_%t%L%m", "%p%L%m", "%f:%_%t%L%m");
290        }
291
292        private void initFormats(String pos, String nopos, String clazz) {
293            availableFormats = new EnumMap<>(BasicFormatKind.class);
294            setFormat(BasicFormatKind.DEFAULT_POS_FORMAT,    pos);
295            setFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT, nopos);
296            setFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT,  clazz);
297        }
298
299        @SuppressWarnings("fallthrough")
300        private void initFormats(String fmt) {
301            String[] formats = fmt.split("\\|");
302            switch (formats.length) {
303                case 3:
304                    setFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT, formats[2]);
305                case 2:
306                    setFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT, formats[1]);
307                default:
308                    setFormat(BasicFormatKind.DEFAULT_POS_FORMAT, formats[0]);
309            }
310        }
311
312        private void initIndentation() {
313            indentationLevels = new HashMap<>();
314            setIndentation(DiagnosticPart.SUMMARY, 0);
315            setIndentation(DiagnosticPart.DETAILS, DetailsInc);
316            setIndentation(DiagnosticPart.SUBDIAGNOSTICS, DiagInc);
317            setIndentation(DiagnosticPart.SOURCE, 0);
318        }
319
320        /**
321         * Get the amount of spaces for a given indentation kind
322         * @param diagPart the diagnostic part for which the indentation is
323         * to be retrieved
324         * @return the amount of spaces used for the specified indentation kind
325         */
326        public int getIndentation(DiagnosticPart diagPart) {
327            return indentationLevels.get(diagPart);
328        }
329
330        /**
331         * Set the indentation level for various element of a given diagnostic -
332         * this might lead to more readable diagnostics
333         *
334         * @param diagPart
335         * @param nSpaces amount of spaces for the specified diagnostic part
336         */
337        public void setIndentation(DiagnosticPart diagPart, int nSpaces) {
338            indentationLevels.put(diagPart, nSpaces);
339        }
340
341        /**
342         * Set the source line positioning used by this formatter
343         *
344         * @param sourcePos a positioning value for source line
345         */
346        public void setSourcePosition(SourcePosition sourcePos) {
347            sourcePosition = sourcePos;
348        }
349
350        /**
351         * Get the source line positioning used by this formatter
352         *
353         * @return the positioning value used by this formatter
354         */
355        public SourcePosition getSourcePosition() {
356            return sourcePosition;
357        }
358        //where
359        /**
360         * A source positioning value controls the position (within a given
361         * diagnostic message) in which the source line the diagnostic refers to
362         * should be displayed (if applicable)
363         */
364        public enum SourcePosition {
365            /**
366             * Source line is displayed after the diagnostic message
367             */
368            BOTTOM,
369            /**
370             * Source line is displayed after the first line of the diagnostic
371             * message
372             */
373            AFTER_SUMMARY
374        }
375
376        /**
377         * Set a metachar string for a specific format
378         *
379         * @param kind the format kind to be set
380         * @param s the metachar string specifying the format
381         */
382        public void setFormat(BasicFormatKind kind, String s) {
383            availableFormats.put(kind, s);
384        }
385
386        /**
387         * Get a metachar string for a specific format
388         *
389         * @param kind the format kind for which to get the metachar string
390         */
391        public String getFormat(BasicFormatKind kind) {
392            return availableFormats.get(kind);
393        }
394        //where
395        /**
396         * This enum contains all the kinds of formatting patterns supported
397         * by a basic diagnostic formatter.
398         */
399        public enum BasicFormatKind {
400            /**
401            * A format string to be used for diagnostics with a given position.
402            */
403            DEFAULT_POS_FORMAT,
404            /**
405            * A format string to be used for diagnostics without a given position.
406            */
407            DEFAULT_NO_POS_FORMAT,
408            /**
409            * A format string to be used for diagnostics regarding classfiles
410            */
411            DEFAULT_CLASS_FORMAT
412        }
413    }
414}
415