001    /*
002     * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
003     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004     *
005     * This code is free software; you can redistribute it and/or modify it
006     * under the terms of the GNU General Public License version 2 only, as
007     * published by the Free Software Foundation.  Sun designates this
008     * particular file as subject to the "Classpath" exception as provided
009     * by Sun in the LICENSE file that accompanied this code.
010     *
011     * This code is distributed in the hope that it will be useful, but WITHOUT
012     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013     * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014     * version 2 for more details (a copy is included in the LICENSE file that
015     * accompanied this code).
016     *
017     * You should have received a copy of the GNU General Public License version
018     * 2 along with this work; if not, write to the Free Software Foundation,
019     * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020     *
021     * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022     * CA 95054 USA or visit www.sun.com if you need additional information or
023     * have any questions.
024     */
025    package com.sun.tools.javac.util;
026    
027    import java.util.Collection;
028    import java.util.Locale;
029    import javax.tools.JavaFileObject;
030    
031    import com.sun.tools.javac.api.DiagnosticFormatter;
032    import com.sun.tools.javac.api.Formattable;
033    import com.sun.tools.javac.api.DiagnosticFormatter.PositionKind;
034    import com.sun.tools.javac.file.JavacFileManager;
035    import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticType.*;
036    import static com.sun.tools.javac.util.LayoutCharacters.*;
037    
038    /**
039     * This abstract class provides a basic implementation of the functionalities that should be provided
040     * by any formatter used by javac. Among the main features provided by AbstractDiagnosticFormatter are:
041     *
042     * <ul>
043     *  <li> Provides a standard implementation of the visitor-like methods defined in the interface DiagnisticFormatter.
044     *  Those implementations are specifically targeting JCDiagnostic objects.
045     *  <li> Provides basic support for i18n and a method for executing all locale-dependent conversions
046     *  <li> Provides the formatting logic for rendering the arguments of a JCDiagnostic object.
047     * <ul>
048     *
049     */
050    public abstract class AbstractDiagnosticFormatter implements DiagnosticFormatter<JCDiagnostic> {
051    
052        /**
053         * JavacMessages object used by this formatter for i18n
054         */
055        protected JavacMessages messages;
056        protected boolean showSource;
057    
058        /**
059         * Initialize an AbstractDiagnosticFormatter by setting its JavacMessages object
060         * @param messages
061         */
062        protected AbstractDiagnosticFormatter(JavacMessages messages, Options options, boolean showSource) {
063            this.messages = messages;
064            this.showSource = options.get("showSource") == null ? showSource :
065                              options.get("showSource").equals("true");
066        }
067    
068        protected AbstractDiagnosticFormatter(JavacMessages messages, boolean showSource) {
069            this.messages = messages;
070            this.showSource = showSource;
071        }
072    
073        public String formatMessage(JCDiagnostic d, Locale l) {
074            //this code should rely on the locale settings but it's not! See RFE 6443132
075            StringBuilder buf = new StringBuilder();
076            Collection<String> args = formatArguments(d, l);
077            buf.append(localize(l, d.getCode(), args.toArray()));
078            if (d.isMultiline()) {
079                buf.append(formatSubdiagnostics(d, l));
080            }
081            return buf.toString();
082        }
083    
084        public String formatKind(JCDiagnostic d, Locale l) {
085            switch (d.getType()) {
086                case FRAGMENT: return "";
087                case NOTE:     return localize(l, "compiler.note.note");
088                case WARNING:  return localize(l, "compiler.warn.warning");
089                case ERROR:    return localize(l, "compiler.err.error");
090                default:
091                    throw new AssertionError("Unknown diagnostic type: " + d.getType());
092            }
093        }
094    
095        public String formatPosition(JCDiagnostic d, PositionKind pk,Locale l) {
096            assert (d.getPosition() != Position.NOPOS);
097            return String.valueOf(getPosition(d, pk));
098        }
099        //WHERE
100        public long getPosition(JCDiagnostic d, PositionKind pk) {
101            switch (pk) {
102                case START: return d.getIntStartPosition();
103                case END: return d.getIntEndPosition();
104                case LINE: return d.getLineNumber();
105                case COLUMN: return d.getColumnNumber();
106                case OFFSET: return d.getIntPosition();
107                default:
108                    throw new AssertionError("Unknown diagnostic position: " + pk);
109            }
110        }
111    
112        public String formatSource(JCDiagnostic d, boolean fullname, Locale l) {
113            assert (d.getSource() != null);
114            return fullname ? d.getSourceName() : d.getSource().getName();
115        }
116    
117        /**
118         * Format the arguments of a given diagnostic.
119         *
120         * @param d diagnostic whose arguments are to be formatted
121         * @param l locale object to be used for i18n
122         * @return a Collection whose elements are the formatted arguments of the diagnostic
123         */
124        protected Collection<String> formatArguments(JCDiagnostic d, Locale l) {
125            ListBuffer<String> buf = new ListBuffer<String>();
126            for (Object o : d.getArgs()) {
127               buf.append(formatArgument(d, o, l));
128            }
129            return buf.toList();
130        }
131    
132        /**
133         * Format a single argument of a given diagnostic.
134         *
135         * @param d diagnostic whose argument is to be formatted
136         * @param arg argument to be formatted
137         * @param l locale object to be used for i18n
138         * @return string representation of the diagnostic argument
139         */
140        protected String formatArgument(JCDiagnostic d, Object arg, Locale l) {
141            if (arg instanceof JCDiagnostic)
142                return format((JCDiagnostic)arg, l);
143            else if (arg instanceof Iterable<?>) {
144                return formatIterable(d, (Iterable<?>)arg, l);
145            }
146            else if (arg instanceof JavaFileObject)
147                return JavacFileManager.getJavacBaseFileName((JavaFileObject)arg);
148            else if (arg instanceof Formattable)
149                return ((Formattable)arg).toString(l, messages);
150            else
151                return String.valueOf(arg);
152        }
153    
154        /**
155         * Format an iterable argument of a given diagnostic.
156         *
157         * @param d diagnostic whose argument is to be formatted
158         * @param it iterable argument to be formatted
159         * @param l locale object to be used for i18n
160         * @return string representation of the diagnostic iterable argument
161         */
162        protected String formatIterable(JCDiagnostic d, Iterable<?> it, Locale l) {
163            StringBuilder sbuf = new StringBuilder();
164            String sep = "";
165            for (Object o : it) {
166                sbuf.append(sep);
167                sbuf.append(formatArgument(d, o, l));
168                sep = ",";
169            }
170            return sbuf.toString();
171        }
172    
173        /**
174         * Format all the subdiagnostics attached to a given diagnostic
175         *
176         * @param d diagnostic whose subdiagnostics are to be formatted
177         * @param l locale object to be used for i18n
178         * @return string representation of the subdiagnostics
179         */
180        protected String formatSubdiagnostics(JCDiagnostic d, Locale l) {
181            StringBuilder buf = new StringBuilder();
182            for (JCDiagnostic d2 : d.getSubdiagnostics()) {
183                buf.append('\n');
184                String subdiagMsg = format(d2, l);
185                buf.append(indent(subdiagMsg, DiagInc));
186            }
187            return buf.toString();
188        }
189    
190        /** Format the faulty source code line and point to the error.
191         *  @param d The diagnostic for which the error line should be printed
192         */
193        protected String formatSourceLine(JCDiagnostic d) {
194            StringBuilder buf = new StringBuilder();
195            DiagnosticSource source = d.getDiagnosticSource();
196            int pos = d.getIntPosition();
197            if (d.getIntPosition() != Position.NOPOS) {
198                String line = (source == null ? null : source.getLine(pos));
199                if (line == null)
200                    return "";
201                buf.append(line+"\n");
202                int col = source.getColumnNumber(pos, false);
203                for (int i = 0; i < col - 1; i++)  {
204                    buf.append((line.charAt(i) == '\t') ? "\t" : " ");
205                }
206                buf.append("^");
207             }
208             return buf.toString();
209        }
210    
211        /**
212         * Converts a String into a locale-dependent representation accordingly to a given locale
213         *
214         * @param l locale object to be used for i18n
215         * @param key locale-independent key used for looking up in a resource file
216         * @param args localization arguments
217         * @return a locale-dependent string
218         */
219        protected String localize(Locale l, String key, Object... args) {
220            return messages.getLocalizedString(l, key, args);
221        }
222    
223        public boolean displaySource(JCDiagnostic d) {
224            return showSource && d.getType() != FRAGMENT;
225        }
226    
227        /**
228         * Creates a string with a given amount of empty spaces. Useful for
229         * indenting the text of a diagnostic message.
230         *
231         * @param nSpaces the amount of spaces to be added to the result string
232         * @return the indentation string
233         */
234        protected String indentString(int nSpaces) {
235            String spaces = "                        ";
236            if (nSpaces <= spaces.length())
237                return spaces.substring(0, nSpaces);
238            else {
239                StringBuilder buf = new StringBuilder();
240                for (int i = 0 ; i < nSpaces ; i++)
241                    buf.append(" ");
242                return buf.toString();
243            }
244        }
245    
246        /**
247         * Indent a string by prepending a given amount of empty spaces to each line
248         * of the string
249         *
250         * @param s the string to be indented
251         * @param nSpaces the amount of spaces that should be prepended to each line
252         * of the string
253         * @return an indented string
254         */
255        protected String indent(String s, int nSpaces) {
256            String indent = indentString(nSpaces);
257            StringBuilder buf = new StringBuilder();
258            String nl = "";
259            for (String line : s.split("\n")) {
260                buf.append(nl);
261                buf.append(indent + line);
262                nl = "\n";
263            }
264            return buf.toString();
265        }
266    }