82N/A/*
815N/A * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
82N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
82N/A *
82N/A * This code is free software; you can redistribute it and/or modify it
82N/A * under the terms of the GNU General Public License version 2 only, as
553N/A * published by the Free Software Foundation. Oracle designates this
82N/A * particular file as subject to the "Classpath" exception as provided
553N/A * by Oracle in the LICENSE file that accompanied this code.
82N/A *
82N/A * This code is distributed in the hope that it will be useful, but WITHOUT
82N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
82N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
82N/A * version 2 for more details (a copy is included in the LICENSE file that
82N/A * accompanied this code).
82N/A *
82N/A * You should have received a copy of the GNU General Public License version
82N/A * 2 along with this work; if not, write to the Free Software Foundation,
82N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
82N/A *
553N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
553N/A * or visit www.oracle.com if you need additional information or have any
553N/A * questions.
82N/A */
82N/Apackage com.sun.tools.javac.util;
82N/A
220N/Aimport java.util.Arrays;
82N/Aimport java.util.Collection;
220N/Aimport java.util.EnumSet;
220N/Aimport java.util.HashMap;
82N/Aimport java.util.Locale;
220N/Aimport java.util.Map;
220N/Aimport java.util.Set;
82N/Aimport javax.tools.JavaFileObject;
82N/A
82N/Aimport com.sun.tools.javac.api.DiagnosticFormatter;
220N/Aimport com.sun.tools.javac.api.DiagnosticFormatter.Configuration.DiagnosticPart;
220N/Aimport com.sun.tools.javac.api.DiagnosticFormatter.Configuration.MultilineLimit;
220N/Aimport com.sun.tools.javac.api.DiagnosticFormatter.PositionKind;
82N/Aimport com.sun.tools.javac.api.Formattable;
611N/Aimport com.sun.tools.javac.code.Lint.LintCategory;
237N/Aimport com.sun.tools.javac.code.Printer;
237N/Aimport com.sun.tools.javac.code.Symbol;
237N/Aimport com.sun.tools.javac.code.Type;
237N/Aimport com.sun.tools.javac.code.Type.CapturedType;
220N/A
414N/Aimport com.sun.tools.javac.file.BaseFileObject;
136N/Aimport static com.sun.tools.javac.util.JCDiagnostic.DiagnosticType.*;
82N/A
82N/A/**
82N/A * This abstract class provides a basic implementation of the functionalities that should be provided
82N/A * by any formatter used by javac. Among the main features provided by AbstractDiagnosticFormatter are:
82N/A *
82N/A * <ul>
82N/A * <li> Provides a standard implementation of the visitor-like methods defined in the interface DiagnisticFormatter.
82N/A * Those implementations are specifically targeting JCDiagnostic objects.
82N/A * <li> Provides basic support for i18n and a method for executing all locale-dependent conversions
82N/A * <li> Provides the formatting logic for rendering the arguments of a JCDiagnostic object.
82N/A * <ul>
82N/A *
580N/A * <p><b>This is NOT part of any supported API.
332N/A * If you write code that depends on this, you do so at your own risk.
332N/A * This code and its internal interfaces are subject to change or
332N/A * deletion without notice.</b>
82N/A */
82N/Apublic abstract class AbstractDiagnosticFormatter implements DiagnosticFormatter<JCDiagnostic> {
82N/A
82N/A /**
220N/A * JavacMessages object used by this formatter for i18n.
82N/A */
135N/A protected JavacMessages messages;
237N/A
237N/A /**
237N/A * Configuration object used by this formatter
237N/A */
220N/A private SimpleConfiguration config;
237N/A
237N/A /**
237N/A * Current depth level of the disgnostic being formatted
237N/A * (!= 0 for subdiagnostics)
237N/A */
220N/A protected int depth = 0;
82N/A
82N/A /**
287N/A * All captured types that have been encountered during diagnostic formatting.
287N/A * This info is used by the FormatterPrinter in order to print friendly unique
287N/A * ids for captured types
237N/A */
287N/A private List<Type> allCaptured = List.nil();
237N/A
237N/A /**
220N/A * Initialize an AbstractDiagnosticFormatter by setting its JavacMessages object.
82N/A * @param messages
82N/A */
220N/A protected AbstractDiagnosticFormatter(JavacMessages messages, SimpleConfiguration config) {
136N/A this.messages = messages;
220N/A this.config = config;
82N/A }
82N/A
82N/A public String formatKind(JCDiagnostic d, Locale l) {
82N/A switch (d.getType()) {
82N/A case FRAGMENT: return "";
82N/A case NOTE: return localize(l, "compiler.note.note");
82N/A case WARNING: return localize(l, "compiler.warn.warning");
82N/A case ERROR: return localize(l, "compiler.err.error");
82N/A default:
82N/A throw new AssertionError("Unknown diagnostic type: " + d.getType());
82N/A }
82N/A }
82N/A
237N/A @Override
237N/A public String format(JCDiagnostic d, Locale locale) {
287N/A allCaptured = List.nil();
237N/A return formatDiagnostic(d, locale);
237N/A }
237N/A
303N/A protected abstract String formatDiagnostic(JCDiagnostic d, Locale locale);
237N/A
82N/A public String formatPosition(JCDiagnostic d, PositionKind pk,Locale l) {
815N/A Assert.check(d.getPosition() != Position.NOPOS);
82N/A return String.valueOf(getPosition(d, pk));
82N/A }
220N/A //where
220N/A private long getPosition(JCDiagnostic d, PositionKind pk) {
82N/A switch (pk) {
82N/A case START: return d.getIntStartPosition();
82N/A case END: return d.getIntEndPosition();
82N/A case LINE: return d.getLineNumber();
82N/A case COLUMN: return d.getColumnNumber();
82N/A case OFFSET: return d.getIntPosition();
82N/A default:
82N/A throw new AssertionError("Unknown diagnostic position: " + pk);
82N/A }
82N/A }
82N/A
99N/A public String formatSource(JCDiagnostic d, boolean fullname, Locale l) {
414N/A JavaFileObject fo = d.getSource();
414N/A if (fo == null)
414N/A throw new IllegalArgumentException(); // d should have source set
414N/A if (fullname)
414N/A return fo.getName();
414N/A else if (fo instanceof BaseFileObject)
414N/A return ((BaseFileObject) fo).getShortName();
414N/A else
414N/A return BaseFileObject.getSimpleName(fo);
82N/A }
82N/A
82N/A /**
82N/A * Format the arguments of a given diagnostic.
82N/A *
82N/A * @param d diagnostic whose arguments are to be formatted
82N/A * @param l locale object to be used for i18n
82N/A * @return a Collection whose elements are the formatted arguments of the diagnostic
82N/A */
82N/A protected Collection<String> formatArguments(JCDiagnostic d, Locale l) {
82N/A ListBuffer<String> buf = new ListBuffer<String>();
82N/A for (Object o : d.getArgs()) {
82N/A buf.append(formatArgument(d, o, l));
82N/A }
82N/A return buf.toList();
82N/A }
82N/A
82N/A /**
82N/A * Format a single argument of a given diagnostic.
82N/A *
82N/A * @param d diagnostic whose argument is to be formatted
82N/A * @param arg argument to be formatted
82N/A * @param l locale object to be used for i18n
82N/A * @return string representation of the diagnostic argument
82N/A */
82N/A protected String formatArgument(JCDiagnostic d, Object arg, Locale l) {
220N/A if (arg instanceof JCDiagnostic) {
220N/A String s = null;
220N/A depth++;
220N/A try {
220N/A s = formatMessage((JCDiagnostic)arg, l);
220N/A }
220N/A finally {
220N/A depth--;
220N/A }
220N/A return s;
220N/A }
82N/A else if (arg instanceof Iterable<?>) {
82N/A return formatIterable(d, (Iterable<?>)arg, l);
82N/A }
237N/A else if (arg instanceof Type) {
237N/A return printer.visit((Type)arg, l);
237N/A }
237N/A else if (arg instanceof Symbol) {
237N/A return printer.visit((Symbol)arg, l);
237N/A }
237N/A else if (arg instanceof JavaFileObject) {
414N/A return ((JavaFileObject)arg).getName();
237N/A }
237N/A else if (arg instanceof Formattable) {
135N/A return ((Formattable)arg).toString(l, messages);
237N/A }
237N/A else {
82N/A return String.valueOf(arg);
237N/A }
82N/A }
82N/A
82N/A /**
82N/A * Format an iterable argument of a given diagnostic.
82N/A *
82N/A * @param d diagnostic whose argument is to be formatted
82N/A * @param it iterable argument to be formatted
82N/A * @param l locale object to be used for i18n
82N/A * @return string representation of the diagnostic iterable argument
82N/A */
82N/A protected String formatIterable(JCDiagnostic d, Iterable<?> it, Locale l) {
82N/A StringBuilder sbuf = new StringBuilder();
82N/A String sep = "";
82N/A for (Object o : it) {
82N/A sbuf.append(sep);
82N/A sbuf.append(formatArgument(d, o, l));
82N/A sep = ",";
82N/A }
82N/A return sbuf.toString();
82N/A }
82N/A
167N/A /**
220N/A * Format all the subdiagnostics attached to a given diagnostic.
167N/A *
167N/A * @param d diagnostic whose subdiagnostics are to be formatted
167N/A * @param l locale object to be used for i18n
220N/A * @return list of all string representations of the subdiagnostics
220N/A */
220N/A protected List<String> formatSubdiagnostics(JCDiagnostic d, Locale l) {
220N/A List<String> subdiagnostics = List.nil();
220N/A int maxDepth = config.getMultilineLimit(MultilineLimit.DEPTH);
220N/A if (maxDepth == -1 || depth < maxDepth) {
220N/A depth++;
220N/A try {
220N/A int maxCount = config.getMultilineLimit(MultilineLimit.LENGTH);
220N/A int count = 0;
220N/A for (JCDiagnostic d2 : d.getSubdiagnostics()) {
220N/A if (maxCount == -1 || count < maxCount) {
220N/A subdiagnostics = subdiagnostics.append(formatSubdiagnostic(d, d2, l));
220N/A count++;
220N/A }
220N/A else
220N/A break;
220N/A }
220N/A }
220N/A finally {
220N/A depth--;
220N/A }
220N/A }
220N/A return subdiagnostics;
220N/A }
220N/A
220N/A /**
220N/A * Format a subdiagnostics attached to a given diagnostic.
220N/A *
220N/A * @param parent multiline diagnostic whose subdiagnostics is to be formatted
220N/A * @param sub subdiagnostic to be formatted
220N/A * @param l locale object to be used for i18n
167N/A * @return string representation of the subdiagnostics
167N/A */
220N/A protected String formatSubdiagnostic(JCDiagnostic parent, JCDiagnostic sub, Locale l) {
220N/A return formatMessage(sub, l);
167N/A }
167N/A
136N/A /** Format the faulty source code line and point to the error.
136N/A * @param d The diagnostic for which the error line should be printed
136N/A */
220N/A protected String formatSourceLine(JCDiagnostic d, int nSpaces) {
136N/A StringBuilder buf = new StringBuilder();
136N/A DiagnosticSource source = d.getDiagnosticSource();
136N/A int pos = d.getIntPosition();
220N/A if (d.getIntPosition() == Position.NOPOS)
220N/A throw new AssertionError();
220N/A String line = (source == null ? null : source.getLine(pos));
220N/A if (line == null)
220N/A return "";
220N/A buf.append(indent(line, nSpaces));
220N/A int col = source.getColumnNumber(pos, false);
220N/A if (config.isCaretEnabled()) {
220N/A buf.append("\n");
136N/A for (int i = 0; i < col - 1; i++) {
136N/A buf.append((line.charAt(i) == '\t') ? "\t" : " ");
136N/A }
220N/A buf.append(indent("^", nSpaces));
220N/A }
220N/A return buf.toString();
136N/A }
136N/A
611N/A protected String formatLintCategory(JCDiagnostic d, Locale l) {
611N/A LintCategory lc = d.getLintCategory();
611N/A if (lc == null)
611N/A return "";
611N/A return localize(l, "compiler.warn.lintOption", lc.option);
611N/A }
611N/A
82N/A /**
220N/A * Converts a String into a locale-dependent representation accordingly to a given locale.
82N/A *
82N/A * @param l locale object to be used for i18n
82N/A * @param key locale-independent key used for looking up in a resource file
82N/A * @param args localization arguments
82N/A * @return a locale-dependent string
82N/A */
82N/A protected String localize(Locale l, String key, Object... args) {
135N/A return messages.getLocalizedString(l, key, args);
82N/A }
136N/A
136N/A public boolean displaySource(JCDiagnostic d) {
220N/A return config.getVisible().contains(DiagnosticPart.SOURCE) &&
220N/A d.getType() != FRAGMENT &&
220N/A d.getIntPosition() != Position.NOPOS;
136N/A }
167N/A
287N/A public boolean isRaw() {
287N/A return false;
287N/A }
287N/A
167N/A /**
167N/A * Creates a string with a given amount of empty spaces. Useful for
167N/A * indenting the text of a diagnostic message.
167N/A *
167N/A * @param nSpaces the amount of spaces to be added to the result string
167N/A * @return the indentation string
167N/A */
167N/A protected String indentString(int nSpaces) {
167N/A String spaces = " ";
167N/A if (nSpaces <= spaces.length())
167N/A return spaces.substring(0, nSpaces);
167N/A else {
167N/A StringBuilder buf = new StringBuilder();
167N/A for (int i = 0 ; i < nSpaces ; i++)
167N/A buf.append(" ");
167N/A return buf.toString();
167N/A }
167N/A }
167N/A
167N/A /**
167N/A * Indent a string by prepending a given amount of empty spaces to each line
220N/A * of the string.
167N/A *
167N/A * @param s the string to be indented
167N/A * @param nSpaces the amount of spaces that should be prepended to each line
167N/A * of the string
167N/A * @return an indented string
167N/A */
167N/A protected String indent(String s, int nSpaces) {
167N/A String indent = indentString(nSpaces);
167N/A StringBuilder buf = new StringBuilder();
167N/A String nl = "";
167N/A for (String line : s.split("\n")) {
167N/A buf.append(nl);
167N/A buf.append(indent + line);
167N/A nl = "\n";
167N/A }
167N/A return buf.toString();
167N/A }
220N/A
220N/A public SimpleConfiguration getConfiguration() {
220N/A return config;
220N/A }
220N/A
220N/A static public class SimpleConfiguration implements Configuration {
220N/A
220N/A protected Map<MultilineLimit, Integer> multilineLimits;
220N/A protected EnumSet<DiagnosticPart> visibleParts;
220N/A protected boolean caretEnabled;
220N/A
220N/A public SimpleConfiguration(Set<DiagnosticPart> parts) {
220N/A multilineLimits = new HashMap<MultilineLimit, Integer>();
220N/A setVisible(parts);
220N/A setMultilineLimit(MultilineLimit.DEPTH, -1);
220N/A setMultilineLimit(MultilineLimit.LENGTH, -1);
220N/A setCaretEnabled(true);
220N/A }
220N/A
220N/A @SuppressWarnings("fallthrough")
220N/A public SimpleConfiguration(Options options, Set<DiagnosticPart> parts) {
220N/A this(parts);
220N/A String showSource = null;
220N/A if ((showSource = options.get("showSource")) != null) {
220N/A if (showSource.equals("true"))
287N/A setVisiblePart(DiagnosticPart.SOURCE, true);
220N/A else if (showSource.equals("false"))
287N/A setVisiblePart(DiagnosticPart.SOURCE, false);
220N/A }
220N/A String diagOpts = options.get("diags");
220N/A if (diagOpts != null) {//override -XDshowSource
220N/A Collection<String> args = Arrays.asList(diagOpts.split(","));
220N/A if (args.contains("short")) {
287N/A setVisiblePart(DiagnosticPart.DETAILS, false);
287N/A setVisiblePart(DiagnosticPart.SUBDIAGNOSTICS, false);
220N/A }
220N/A if (args.contains("source"))
287N/A setVisiblePart(DiagnosticPart.SOURCE, true);
220N/A if (args.contains("-source"))
287N/A setVisiblePart(DiagnosticPart.SOURCE, false);
220N/A }
220N/A String multiPolicy = null;
220N/A if ((multiPolicy = options.get("multilinePolicy")) != null) {
220N/A if (multiPolicy.equals("disabled"))
287N/A setVisiblePart(DiagnosticPart.SUBDIAGNOSTICS, false);
220N/A else if (multiPolicy.startsWith("limit:")) {
220N/A String limitString = multiPolicy.substring("limit:".length());
220N/A String[] limits = limitString.split(":");
220N/A try {
220N/A switch (limits.length) {
220N/A case 2: {
220N/A if (!limits[1].equals("*"))
220N/A setMultilineLimit(MultilineLimit.DEPTH, Integer.parseInt(limits[1]));
220N/A }
220N/A case 1: {
220N/A if (!limits[0].equals("*"))
220N/A setMultilineLimit(MultilineLimit.LENGTH, Integer.parseInt(limits[0]));
220N/A }
220N/A }
220N/A }
220N/A catch(NumberFormatException ex) {
220N/A setMultilineLimit(MultilineLimit.DEPTH, -1);
220N/A setMultilineLimit(MultilineLimit.LENGTH, -1);
220N/A }
220N/A }
220N/A }
220N/A String showCaret = null;
220N/A if (((showCaret = options.get("showCaret")) != null) &&
220N/A showCaret.equals("false"))
220N/A setCaretEnabled(false);
220N/A else
220N/A setCaretEnabled(true);
220N/A }
220N/A
220N/A public int getMultilineLimit(MultilineLimit limit) {
220N/A return multilineLimits.get(limit);
220N/A }
220N/A
220N/A public EnumSet<DiagnosticPart> getVisible() {
220N/A return EnumSet.copyOf(visibleParts);
220N/A }
220N/A
220N/A public void setMultilineLimit(MultilineLimit limit, int value) {
220N/A multilineLimits.put(limit, value < -1 ? -1 : value);
220N/A }
220N/A
220N/A
220N/A public void setVisible(Set<DiagnosticPart> diagParts) {
220N/A visibleParts = EnumSet.copyOf(diagParts);
220N/A }
220N/A
287N/A public void setVisiblePart(DiagnosticPart diagParts, boolean enabled) {
287N/A if (enabled)
287N/A visibleParts.add(diagParts);
287N/A else
287N/A visibleParts.remove(diagParts);
287N/A }
287N/A
220N/A /**
220N/A * Shows a '^' sign under the source line displayed by the formatter
220N/A * (if applicable).
220N/A *
220N/A * @param caretEnabled if true enables caret
220N/A */
220N/A public void setCaretEnabled(boolean caretEnabled) {
220N/A this.caretEnabled = caretEnabled;
220N/A }
220N/A
220N/A /**
220N/A * Tells whether the caret display is active or not.
220N/A *
220N/A * @param caretEnabled if true the caret is enabled
220N/A */
220N/A public boolean isCaretEnabled() {
220N/A return caretEnabled;
220N/A }
220N/A }
237N/A
287N/A public Printer getPrinter() {
287N/A return printer;
287N/A }
287N/A
287N/A public void setPrinter(Printer printer) {
287N/A this.printer = printer;
287N/A }
287N/A
237N/A /**
237N/A * An enhanced printer for formatting types/symbols used by
237N/A * AbstractDiagnosticFormatter. Provides alternate numbering of captured
237N/A * types (they are numbered starting from 1 on each new diagnostic, instead
237N/A * of relying on the underlying hashcode() method which generates unstable
237N/A * output). Also detects cycles in wildcard messages (e.g. if the wildcard
237N/A * type referred by a given captured type C contains C itself) which might
237N/A * lead to infinite loops.
237N/A */
287N/A protected Printer printer = new Printer() {
237N/A @Override
237N/A protected String localize(Locale locale, String key, Object... args) {
237N/A return AbstractDiagnosticFormatter.this.localize(locale, key, args);
237N/A }
237N/A @Override
287N/A protected String capturedVarId(CapturedType t, Locale locale) {
287N/A return "" + (allCaptured.indexOf(t) + 1);
237N/A }
295N/A @Override
295N/A public String visitCapturedType(CapturedType t, Locale locale) {
295N/A if (!allCaptured.contains(t)) {
295N/A allCaptured = allCaptured.append(t);
295N/A }
295N/A return super.visitCapturedType(t, locale);
295N/A }
287N/A };
82N/A}