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 
26 package com.sun.tools.javac.util;
27 
28 import java.util.Collection;
29 import java.util.EnumMap;
30 import java.util.EnumSet;
31 import java.util.HashMap;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.regex.Matcher;
35 import javax.tools.JavaFileObject;
36 
37 import com.sun.tools.javac.util.AbstractDiagnosticFormatter.SimpleConfiguration;
38 import com.sun.tools.javac.util.BasicDiagnosticFormatter.BasicConfiguration;
39 
40 import static com.sun.tools.javac.api.DiagnosticFormatter.PositionKind.*;
41 import static com.sun.tools.javac.util.BasicDiagnosticFormatter.BasicConfiguration.*;
42 import 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  */
67 public 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      */
BasicDiagnosticFormatter(Options options, JavacMessages msgs)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      */
BasicDiagnosticFormatter(JavacMessages msgs)84     public BasicDiagnosticFormatter(JavacMessages msgs) {
85         super(msgs, new BasicConfiguration());
86     }
87 
formatDiagnostic(JCDiagnostic d, Locale l)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 
formatMessage(JCDiagnostic d, Locale l)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 
addSourceLineIfNeeded(JCDiagnostic d, String msg)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 
formatMeta(char c, JCDiagnostic d, Locale l)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 
selectFormat(JCDiagnostic d)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
getConfiguration()213     public BasicConfiguration getConfiguration() {
214         //the following cast is always safe - see init
215         return (BasicConfiguration)super.getConfiguration();
216     }
217 
218     public static 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")
BasicConfiguration(Options options)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 
BasicConfiguration()275         public BasicConfiguration() {
276             super(EnumSet.of(DiagnosticPart.SUMMARY,
277                   DiagnosticPart.DETAILS,
278                   DiagnosticPart.SUBDIAGNOSTICS,
279                   DiagnosticPart.SOURCE));
280             initFormat();
281             initIndentation();
282         }
283 
initFormat()284         private void initFormat() {
285             initFormats("%f:%l:%_%p%L%m", "%p%L%m", "%f:%_%p%L%m");
286         }
287 
initOldFormat()288         private void initOldFormat() {
289             initFormats("%f:%l:%_%t%L%m", "%p%L%m", "%f:%_%t%L%m");
290         }
291 
initFormats(String pos, String nopos, String clazz)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")
initFormats(String fmt)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 
initIndentation()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          */
getIndentation(DiagnosticPart diagPart)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          */
setIndentation(DiagnosticPart diagPart, int nSpaces)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          */
setSourcePosition(SourcePosition sourcePos)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          */
getSourcePosition()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          */
setFormat(BasicFormatKind kind, String s)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          */
getFormat(BasicFormatKind kind)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