View Javadoc
1   /*
2    * Copyright (c) 2005, 2012, 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       */
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 (getConfiguration().getVisible().contains(DiagnosticPart.SUMMARY)) {
115             currentIndentation += getConfiguration().getIndentation(DiagnosticPart.SUMMARY);
116             buf.append(indent(lines[0], currentIndentation)); //summary
117         }
118         if (lines.length > 1 && getConfiguration().getVisible().contains(DiagnosticPart.DETAILS)) {
119             currentIndentation += getConfiguration().getIndentation(DiagnosticPart.DETAILS);
120             for (int i = 1;i < lines.length; i++) {
121                 buf.append("\n" + indent(lines[i], currentIndentation));
122             }
123         }
124         if (d.isMultiline() && getConfiguration().getVisible().contains(DiagnosticPart.SUBDIAGNOSTICS)) {
125             currentIndentation += getConfiguration().getIndentation(DiagnosticPart.SUBDIAGNOSTICS);
126                 for (String sub : formatSubdiagnostics(d, l)) {
127                     buf.append("\n" + indent(sub, currentIndentation));
128             }
129         }
130         return buf.toString();
131     }
132 
133     protected String addSourceLineIfNeeded(JCDiagnostic d, String msg) {
134         if (!displaySource(d))
135             return msg;
136         else {
137             BasicConfiguration conf = getConfiguration();
138             int indentSource = conf.getIndentation(DiagnosticPart.SOURCE);
139             String sourceLine = "\n" + formatSourceLine(d, indentSource);
140             boolean singleLine = msg.indexOf("\n") == -1;
141             if (singleLine || getConfiguration().getSourcePosition() == SourcePosition.BOTTOM)
142                 return msg + sourceLine;
143             else
144                 return msg.replaceFirst("\n", Matcher.quoteReplacement(sourceLine) + "\n");
145         }
146     }
147 
148     protected String formatMeta(char c, JCDiagnostic d, Locale l) {
149         switch (c) {
150             case 'b':
151                 return formatSource(d, false, l);
152             case 'e':
153                 return formatPosition(d, END, l);
154             case 'f':
155                 return formatSource(d, true, l);
156             case 'l':
157                 return formatPosition(d, LINE, l);
158             case 'c':
159                 return formatPosition(d, COLUMN, l);
160             case 'o':
161                 return formatPosition(d, OFFSET, l);
162             case 'p':
163                 return formatKind(d, l);
164             case 's':
165                 return formatPosition(d, START, l);
166             case 't': {
167                 boolean usePrefix;
168                 switch (d.getType()) {
169                 case FRAGMENT:
170                     usePrefix = false;
171                     break;
172                 case ERROR:
173                     usePrefix = (d.getIntPosition() == Position.NOPOS);
174                     break;
175                 default:
176                     usePrefix = true;
177                 }
178                 if (usePrefix)
179                     return formatKind(d, l);
180                 else
181                     return "";
182             }
183             case 'm':
184                 return formatMessage(d, l);
185             case 'L':
186                 return formatLintCategory(d, l);
187             case '_':
188                 return " ";
189             case '%':
190                 return "%";
191             default:
192                 return String.valueOf(c);
193         }
194     }
195 
196     private String selectFormat(JCDiagnostic d) {
197         DiagnosticSource source = d.getDiagnosticSource();
198         String format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT);
199         if (source != null && source != DiagnosticSource.NO_SOURCE) {
200             if (d.getIntPosition() != Position.NOPOS) {
201                 format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_POS_FORMAT);
202             } else if (source.getFile() != null &&
203                        source.getFile().getKind() == JavaFileObject.Kind.CLASS) {
204                 format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT);
205             }
206         }
207         return format;
208     }
209 
210     @Override
211     public BasicConfiguration getConfiguration() {
212         //the following cast is always safe - see init
213         return (BasicConfiguration)super.getConfiguration();
214     }
215 
216     static public class BasicConfiguration extends SimpleConfiguration {
217 
218         protected Map<DiagnosticPart, Integer> indentationLevels;
219         protected Map<BasicFormatKind, String> availableFormats;
220         protected SourcePosition sourcePosition;
221 
222         @SuppressWarnings("fallthrough")
223         public BasicConfiguration(Options options) {
224             super(options, EnumSet.of(DiagnosticPart.SUMMARY,
225                             DiagnosticPart.DETAILS,
226                             DiagnosticPart.SUBDIAGNOSTICS,
227                             DiagnosticPart.SOURCE));
228             initFormat();
229             initIndentation();
230             if (options.isSet("oldDiags"))
231                 initOldFormat();
232             String fmt = options.get("diagsFormat");
233             if (fmt != null) {
234                 if (fmt.equals("OLD"))
235                     initOldFormat();
236                 else
237                     initFormats(fmt);
238             }
239             String srcPos = null;
240             if ((((srcPos = options.get("sourcePosition")) != null)) &&
241                     srcPos.equals("bottom"))
242                     setSourcePosition(SourcePosition.BOTTOM);
243             else
244                 setSourcePosition(SourcePosition.AFTER_SUMMARY);
245             String indent = options.get("diagsIndentation");
246             if (indent != null) {
247                 String[] levels = indent.split("\\|");
248                 try {
249                     switch (levels.length) {
250                         case 5:
251                             setIndentation(DiagnosticPart.JLS,
252                                     Integer.parseInt(levels[4]));
253                         case 4:
254                             setIndentation(DiagnosticPart.SUBDIAGNOSTICS,
255                                     Integer.parseInt(levels[3]));
256                         case 3:
257                             setIndentation(DiagnosticPart.SOURCE,
258                                     Integer.parseInt(levels[2]));
259                         case 2:
260                             setIndentation(DiagnosticPart.DETAILS,
261                                     Integer.parseInt(levels[1]));
262                         default:
263                             setIndentation(DiagnosticPart.SUMMARY,
264                                     Integer.parseInt(levels[0]));
265                     }
266                 }
267                 catch (NumberFormatException ex) {
268                     initIndentation();
269                 }
270             }
271         }
272 
273         public BasicConfiguration() {
274             super(EnumSet.of(DiagnosticPart.SUMMARY,
275                   DiagnosticPart.DETAILS,
276                   DiagnosticPart.SUBDIAGNOSTICS,
277                   DiagnosticPart.SOURCE));
278             initFormat();
279             initIndentation();
280         }
281 
282         private void initFormat() {
283             initFormats("%f:%l:%_%p%L%m", "%p%L%m", "%f:%_%p%L%m");
284         }
285 
286         private void initOldFormat() {
287             initFormats("%f:%l:%_%t%L%m", "%p%L%m", "%f:%_%t%L%m");
288         }
289 
290         private void initFormats(String pos, String nopos, String clazz) {
291             availableFormats = new EnumMap<BasicFormatKind, String>(BasicFormatKind.class);
292             setFormat(BasicFormatKind.DEFAULT_POS_FORMAT,    pos);
293             setFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT, nopos);
294             setFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT,  clazz);
295         }
296 
297         @SuppressWarnings("fallthrough")
298         private void initFormats(String fmt) {
299             String[] formats = fmt.split("\\|");
300             switch (formats.length) {
301                 case 3:
302                     setFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT, formats[2]);
303                 case 2:
304                     setFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT, formats[1]);
305                 default:
306                     setFormat(BasicFormatKind.DEFAULT_POS_FORMAT, formats[0]);
307             }
308         }
309 
310         private void initIndentation() {
311             indentationLevels = new HashMap<DiagnosticPart, Integer>();
312             setIndentation(DiagnosticPart.SUMMARY, 0);
313             setIndentation(DiagnosticPart.DETAILS, DetailsInc);
314             setIndentation(DiagnosticPart.SUBDIAGNOSTICS, DiagInc);
315             setIndentation(DiagnosticPart.SOURCE, 0);
316         }
317 
318         /**
319          * Get the amount of spaces for a given indentation kind
320          * @param diagPart the diagnostic part for which the indentation is
321          * to be retrieved
322          * @return the amount of spaces used for the specified indentation kind
323          */
324         public int getIndentation(DiagnosticPart diagPart) {
325             return indentationLevels.get(diagPart);
326         }
327 
328         /**
329          * Set the indentation level for various element of a given diagnostic -
330          * this might lead to more readable diagnostics
331          *
332          * @param diagPart
333          * @param nSpaces amount of spaces for the specified diagnostic part
334          */
335         public void setIndentation(DiagnosticPart diagPart, int nSpaces) {
336             indentationLevels.put(diagPart, nSpaces);
337         }
338 
339         /**
340          * Set the source line positioning used by this formatter
341          *
342          * @param sourcePos a positioning value for source line
343          */
344         public void setSourcePosition(SourcePosition sourcePos) {
345             sourcePosition = sourcePos;
346         }
347 
348         /**
349          * Get the source line positioning used by this formatter
350          *
351          * @return the positioning value used by this formatter
352          */
353         public SourcePosition getSourcePosition() {
354             return sourcePosition;
355         }
356         //where
357         /**
358          * A source positioning value controls the position (within a given
359          * diagnostic message) in which the source line the diagnostic refers to
360          * should be displayed (if applicable)
361          */
362         public enum SourcePosition {
363             /**
364              * Source line is displayed after the diagnostic message
365              */
366             BOTTOM,
367             /**
368              * Source line is displayed after the first line of the diagnostic
369              * message
370              */
371             AFTER_SUMMARY;
372         }
373 
374         /**
375          * Set a metachar string for a specific format
376          *
377          * @param kind the format kind to be set
378          * @param s the metachar string specifying the format
379          */
380         public void setFormat(BasicFormatKind kind, String s) {
381             availableFormats.put(kind, s);
382         }
383 
384         /**
385          * Get a metachar string for a specific format
386          *
387          * @param kind the format kind for which to get the metachar string
388          */
389         public String getFormat(BasicFormatKind kind) {
390             return availableFormats.get(kind);
391         }
392         //where
393         /**
394          * This enum contains all the kinds of formatting patterns supported
395          * by a basic diagnostic formatter.
396          */
397         public enum BasicFormatKind {
398             /**
399             * A format string to be used for diagnostics with a given position.
400             */
401             DEFAULT_POS_FORMAT,
402             /**
403             * A format string to be used for diagnostics without a given position.
404             */
405             DEFAULT_NO_POS_FORMAT,
406             /**
407             * A format string to be used for diagnostics regarding classfiles
408             */
409             DEFAULT_CLASS_FORMAT;
410         }
411     }
412 }