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