aoqi@0: /* aoqi@0: * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.tools.javac.util; aoqi@0: aoqi@0: import java.util.Collection; aoqi@0: import java.util.EnumMap; aoqi@0: import java.util.EnumSet; aoqi@0: import java.util.HashMap; aoqi@0: import java.util.Locale; aoqi@0: import java.util.Map; aoqi@0: import java.util.regex.Matcher; aoqi@0: import javax.tools.JavaFileObject; aoqi@0: aoqi@0: import com.sun.tools.javac.util.AbstractDiagnosticFormatter.SimpleConfiguration; aoqi@0: import com.sun.tools.javac.util.BasicDiagnosticFormatter.BasicConfiguration; aoqi@0: aoqi@0: import static com.sun.tools.javac.api.DiagnosticFormatter.PositionKind.*; aoqi@0: import static com.sun.tools.javac.util.BasicDiagnosticFormatter.BasicConfiguration.*; aoqi@0: import static com.sun.tools.javac.util.LayoutCharacters.*; aoqi@0: aoqi@0: /** aoqi@0: * A basic formatter for diagnostic messages. aoqi@0: * The basic formatter will format a diagnostic according to one of three format patterns, depending on whether aoqi@0: * or not the source name and position are set. The formatter supports a printf-like string for patterns aoqi@0: * with the following special characters: aoqi@0: * aoqi@0: * aoqi@0: *

This is NOT part of any supported API. aoqi@0: * If you write code that depends on this, you do so at your own risk. aoqi@0: * This code and its internal interfaces are subject to change or aoqi@0: * deletion without notice. aoqi@0: */ aoqi@0: public class BasicDiagnosticFormatter extends AbstractDiagnosticFormatter { aoqi@0: aoqi@0: /** aoqi@0: * Create a basic formatter based on the supplied options. aoqi@0: * aoqi@0: * @param options list of command-line options aoqi@0: * @param msgs JavacMessages object used for i18n aoqi@0: */ aoqi@0: public BasicDiagnosticFormatter(Options options, JavacMessages msgs) { aoqi@0: super(msgs, new BasicConfiguration(options)); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Create a standard basic formatter aoqi@0: * aoqi@0: * @param msgs JavacMessages object used for i18n aoqi@0: */ aoqi@0: public BasicDiagnosticFormatter(JavacMessages msgs) { aoqi@0: super(msgs, new BasicConfiguration()); aoqi@0: } aoqi@0: aoqi@0: public String formatDiagnostic(JCDiagnostic d, Locale l) { aoqi@0: if (l == null) aoqi@0: l = messages.getCurrentLocale(); aoqi@0: String format = selectFormat(d); aoqi@0: StringBuilder buf = new StringBuilder(); aoqi@0: for (int i = 0; i < format.length(); i++) { aoqi@0: char c = format.charAt(i); aoqi@0: boolean meta = false; aoqi@0: if (c == '%' && i < format.length() - 1) { aoqi@0: meta = true; aoqi@0: c = format.charAt(++i); aoqi@0: } aoqi@0: buf.append(meta ? formatMeta(c, d, l) : String.valueOf(c)); aoqi@0: } aoqi@0: if (depth == 0) aoqi@0: return addSourceLineIfNeeded(d, buf.toString()); aoqi@0: else aoqi@0: return buf.toString(); aoqi@0: } aoqi@0: aoqi@0: public String formatMessage(JCDiagnostic d, Locale l) { aoqi@0: int currentIndentation = 0; aoqi@0: StringBuilder buf = new StringBuilder(); aoqi@0: Collection args = formatArguments(d, l); aoqi@0: String msg = localize(l, d.getCode(), args.toArray()); aoqi@0: String[] lines = msg.split("\n"); aoqi@0: if (getConfiguration().getVisible().contains(DiagnosticPart.SUMMARY)) { aoqi@0: currentIndentation += getConfiguration().getIndentation(DiagnosticPart.SUMMARY); aoqi@0: buf.append(indent(lines[0], currentIndentation)); //summary aoqi@0: } aoqi@0: if (lines.length > 1 && getConfiguration().getVisible().contains(DiagnosticPart.DETAILS)) { aoqi@0: currentIndentation += getConfiguration().getIndentation(DiagnosticPart.DETAILS); aoqi@0: for (int i = 1;i < lines.length; i++) { aoqi@0: buf.append("\n" + indent(lines[i], currentIndentation)); aoqi@0: } aoqi@0: } aoqi@0: if (d.isMultiline() && getConfiguration().getVisible().contains(DiagnosticPart.SUBDIAGNOSTICS)) { aoqi@0: currentIndentation += getConfiguration().getIndentation(DiagnosticPart.SUBDIAGNOSTICS); aoqi@0: for (String sub : formatSubdiagnostics(d, l)) { aoqi@0: buf.append("\n" + indent(sub, currentIndentation)); aoqi@0: } aoqi@0: } aoqi@0: return buf.toString(); aoqi@0: } aoqi@0: aoqi@0: protected String addSourceLineIfNeeded(JCDiagnostic d, String msg) { aoqi@0: if (!displaySource(d)) aoqi@0: return msg; aoqi@0: else { aoqi@0: BasicConfiguration conf = getConfiguration(); aoqi@0: int indentSource = conf.getIndentation(DiagnosticPart.SOURCE); aoqi@0: String sourceLine = "\n" + formatSourceLine(d, indentSource); aoqi@0: boolean singleLine = msg.indexOf("\n") == -1; aoqi@0: if (singleLine || getConfiguration().getSourcePosition() == SourcePosition.BOTTOM) aoqi@0: return msg + sourceLine; aoqi@0: else aoqi@0: return msg.replaceFirst("\n", Matcher.quoteReplacement(sourceLine) + "\n"); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: protected String formatMeta(char c, JCDiagnostic d, Locale l) { aoqi@0: switch (c) { aoqi@0: case 'b': aoqi@0: return formatSource(d, false, l); aoqi@0: case 'e': aoqi@0: return formatPosition(d, END, l); aoqi@0: case 'f': aoqi@0: return formatSource(d, true, l); aoqi@0: case 'l': aoqi@0: return formatPosition(d, LINE, l); aoqi@0: case 'c': aoqi@0: return formatPosition(d, COLUMN, l); aoqi@0: case 'o': aoqi@0: return formatPosition(d, OFFSET, l); aoqi@0: case 'p': aoqi@0: return formatKind(d, l); aoqi@0: case 's': aoqi@0: return formatPosition(d, START, l); aoqi@0: case 't': { aoqi@0: boolean usePrefix; aoqi@0: switch (d.getType()) { aoqi@0: case FRAGMENT: aoqi@0: usePrefix = false; aoqi@0: break; aoqi@0: case ERROR: aoqi@0: usePrefix = (d.getIntPosition() == Position.NOPOS); aoqi@0: break; aoqi@0: default: aoqi@0: usePrefix = true; aoqi@0: } aoqi@0: if (usePrefix) aoqi@0: return formatKind(d, l); aoqi@0: else aoqi@0: return ""; aoqi@0: } aoqi@0: case 'm': aoqi@0: return formatMessage(d, l); aoqi@0: case 'L': aoqi@0: return formatLintCategory(d, l); aoqi@0: case '_': aoqi@0: return " "; aoqi@0: case '%': aoqi@0: return "%"; aoqi@0: default: aoqi@0: return String.valueOf(c); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private String selectFormat(JCDiagnostic d) { aoqi@0: DiagnosticSource source = d.getDiagnosticSource(); aoqi@0: String format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT); aoqi@0: if (source != null && source != DiagnosticSource.NO_SOURCE) { aoqi@0: if (d.getIntPosition() != Position.NOPOS) { aoqi@0: format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_POS_FORMAT); aoqi@0: } else if (source.getFile() != null && aoqi@0: source.getFile().getKind() == JavaFileObject.Kind.CLASS) { aoqi@0: format = getConfiguration().getFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT); aoqi@0: } aoqi@0: } aoqi@0: return format; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public BasicConfiguration getConfiguration() { aoqi@0: //the following cast is always safe - see init aoqi@0: return (BasicConfiguration)super.getConfiguration(); aoqi@0: } aoqi@0: aoqi@0: static public class BasicConfiguration extends SimpleConfiguration { aoqi@0: aoqi@0: protected Map indentationLevels; aoqi@0: protected Map availableFormats; aoqi@0: protected SourcePosition sourcePosition; aoqi@0: aoqi@0: @SuppressWarnings("fallthrough") aoqi@0: public BasicConfiguration(Options options) { aoqi@0: super(options, EnumSet.of(DiagnosticPart.SUMMARY, aoqi@0: DiagnosticPart.DETAILS, aoqi@0: DiagnosticPart.SUBDIAGNOSTICS, aoqi@0: DiagnosticPart.SOURCE)); aoqi@0: initFormat(); aoqi@0: initIndentation(); aoqi@0: if (options.isSet("oldDiags")) aoqi@0: initOldFormat(); aoqi@0: String fmt = options.get("diagsFormat"); aoqi@0: if (fmt != null) { aoqi@0: if (fmt.equals("OLD")) aoqi@0: initOldFormat(); aoqi@0: else aoqi@0: initFormats(fmt); aoqi@0: } aoqi@0: String srcPos = null; aoqi@0: if ((((srcPos = options.get("sourcePosition")) != null)) && aoqi@0: srcPos.equals("bottom")) aoqi@0: setSourcePosition(SourcePosition.BOTTOM); aoqi@0: else aoqi@0: setSourcePosition(SourcePosition.AFTER_SUMMARY); aoqi@0: String indent = options.get("diagsIndentation"); aoqi@0: if (indent != null) { aoqi@0: String[] levels = indent.split("\\|"); aoqi@0: try { aoqi@0: switch (levels.length) { aoqi@0: case 5: aoqi@0: setIndentation(DiagnosticPart.JLS, aoqi@0: Integer.parseInt(levels[4])); aoqi@0: case 4: aoqi@0: setIndentation(DiagnosticPart.SUBDIAGNOSTICS, aoqi@0: Integer.parseInt(levels[3])); aoqi@0: case 3: aoqi@0: setIndentation(DiagnosticPart.SOURCE, aoqi@0: Integer.parseInt(levels[2])); aoqi@0: case 2: aoqi@0: setIndentation(DiagnosticPart.DETAILS, aoqi@0: Integer.parseInt(levels[1])); aoqi@0: default: aoqi@0: setIndentation(DiagnosticPart.SUMMARY, aoqi@0: Integer.parseInt(levels[0])); aoqi@0: } aoqi@0: } aoqi@0: catch (NumberFormatException ex) { aoqi@0: initIndentation(); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public BasicConfiguration() { aoqi@0: super(EnumSet.of(DiagnosticPart.SUMMARY, aoqi@0: DiagnosticPart.DETAILS, aoqi@0: DiagnosticPart.SUBDIAGNOSTICS, aoqi@0: DiagnosticPart.SOURCE)); aoqi@0: initFormat(); aoqi@0: initIndentation(); aoqi@0: } aoqi@0: aoqi@0: private void initFormat() { aoqi@0: initFormats("%f:%l:%_%p%L%m", "%p%L%m", "%f:%_%p%L%m"); aoqi@0: } aoqi@0: aoqi@0: private void initOldFormat() { aoqi@0: initFormats("%f:%l:%_%t%L%m", "%p%L%m", "%f:%_%t%L%m"); aoqi@0: } aoqi@0: aoqi@0: private void initFormats(String pos, String nopos, String clazz) { aoqi@0: availableFormats = new EnumMap(BasicFormatKind.class); aoqi@0: setFormat(BasicFormatKind.DEFAULT_POS_FORMAT, pos); aoqi@0: setFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT, nopos); aoqi@0: setFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT, clazz); aoqi@0: } aoqi@0: aoqi@0: @SuppressWarnings("fallthrough") aoqi@0: private void initFormats(String fmt) { aoqi@0: String[] formats = fmt.split("\\|"); aoqi@0: switch (formats.length) { aoqi@0: case 3: aoqi@0: setFormat(BasicFormatKind.DEFAULT_CLASS_FORMAT, formats[2]); aoqi@0: case 2: aoqi@0: setFormat(BasicFormatKind.DEFAULT_NO_POS_FORMAT, formats[1]); aoqi@0: default: aoqi@0: setFormat(BasicFormatKind.DEFAULT_POS_FORMAT, formats[0]); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private void initIndentation() { aoqi@0: indentationLevels = new HashMap(); aoqi@0: setIndentation(DiagnosticPart.SUMMARY, 0); aoqi@0: setIndentation(DiagnosticPart.DETAILS, DetailsInc); aoqi@0: setIndentation(DiagnosticPart.SUBDIAGNOSTICS, DiagInc); aoqi@0: setIndentation(DiagnosticPart.SOURCE, 0); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Get the amount of spaces for a given indentation kind aoqi@0: * @param diagPart the diagnostic part for which the indentation is aoqi@0: * to be retrieved aoqi@0: * @return the amount of spaces used for the specified indentation kind aoqi@0: */ aoqi@0: public int getIndentation(DiagnosticPart diagPart) { aoqi@0: return indentationLevels.get(diagPart); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Set the indentation level for various element of a given diagnostic - aoqi@0: * this might lead to more readable diagnostics aoqi@0: * aoqi@0: * @param diagPart aoqi@0: * @param nSpaces amount of spaces for the specified diagnostic part aoqi@0: */ aoqi@0: public void setIndentation(DiagnosticPart diagPart, int nSpaces) { aoqi@0: indentationLevels.put(diagPart, nSpaces); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Set the source line positioning used by this formatter aoqi@0: * aoqi@0: * @param sourcePos a positioning value for source line aoqi@0: */ aoqi@0: public void setSourcePosition(SourcePosition sourcePos) { aoqi@0: sourcePosition = sourcePos; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Get the source line positioning used by this formatter aoqi@0: * aoqi@0: * @return the positioning value used by this formatter aoqi@0: */ aoqi@0: public SourcePosition getSourcePosition() { aoqi@0: return sourcePosition; aoqi@0: } aoqi@0: //where aoqi@0: /** aoqi@0: * A source positioning value controls the position (within a given aoqi@0: * diagnostic message) in which the source line the diagnostic refers to aoqi@0: * should be displayed (if applicable) aoqi@0: */ aoqi@0: public enum SourcePosition { aoqi@0: /** aoqi@0: * Source line is displayed after the diagnostic message aoqi@0: */ aoqi@0: BOTTOM, aoqi@0: /** aoqi@0: * Source line is displayed after the first line of the diagnostic aoqi@0: * message aoqi@0: */ aoqi@0: AFTER_SUMMARY; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Set a metachar string for a specific format aoqi@0: * aoqi@0: * @param kind the format kind to be set aoqi@0: * @param s the metachar string specifying the format aoqi@0: */ aoqi@0: public void setFormat(BasicFormatKind kind, String s) { aoqi@0: availableFormats.put(kind, s); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Get a metachar string for a specific format aoqi@0: * aoqi@0: * @param kind the format kind for which to get the metachar string aoqi@0: */ aoqi@0: public String getFormat(BasicFormatKind kind) { aoqi@0: return availableFormats.get(kind); aoqi@0: } aoqi@0: //where aoqi@0: /** aoqi@0: * This enum contains all the kinds of formatting patterns supported aoqi@0: * by a basic diagnostic formatter. aoqi@0: */ aoqi@0: public enum BasicFormatKind { aoqi@0: /** aoqi@0: * A format string to be used for diagnostics with a given position. aoqi@0: */ aoqi@0: DEFAULT_POS_FORMAT, aoqi@0: /** aoqi@0: * A format string to be used for diagnostics without a given position. aoqi@0: */ aoqi@0: DEFAULT_NO_POS_FORMAT, aoqi@0: /** aoqi@0: * A format string to be used for diagnostics regarding classfiles aoqi@0: */ aoqi@0: DEFAULT_CLASS_FORMAT; aoqi@0: } aoqi@0: } aoqi@0: }