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: * - %b: the base of the source name
aoqi@0: *
- %f: the source name (full absolute path)
aoqi@0: *
- %l: the line number of the diagnostic, derived from the character offset
aoqi@0: *
- %c: the column number of the diagnostic, derived from the character offset
aoqi@0: *
- %o: the character offset of the diagnostic if set
aoqi@0: *
- %p: the prefix for the diagnostic, derived from the diagnostic type
aoqi@0: *
- %t: the prefix as it normally appears in standard diagnostics. In this case, no prefix is
aoqi@0: * shown if the type is ERROR and if a source name is set
aoqi@0: *
- %m: the text or the diagnostic, including any appropriate arguments
aoqi@0: *
- %_: space delimiter, useful for formatting purposes
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: }