AbstractDiagnosticFormatter.java revision 3547:e18190929198
1229997Sken/*
2229997Sken * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
3290776Smav * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4229997Sken *
5229997Sken * This code is free software; you can redistribute it and/or modify it
6229997Sken * under the terms of the GNU General Public License version 2 only, as
7229997Sken * published by the Free Software Foundation.  Oracle designates this
8229997Sken * particular file as subject to the "Classpath" exception as provided
9229997Sken * by Oracle in the LICENSE file that accompanied this code.
10229997Sken *
11229997Sken * This code is distributed in the hope that it will be useful, but WITHOUT
12229997Sken * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13229997Sken * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14229997Sken * version 2 for more details (a copy is included in the LICENSE file that
15229997Sken * accompanied this code).
16229997Sken *
17229997Sken * You should have received a copy of the GNU General Public License version
18229997Sken * 2 along with this work; if not, write to the Free Software Foundation,
19229997Sken * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20229997Sken *
21229997Sken * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22229997Sken * or visit www.oracle.com if you need additional information or have any
23229997Sken * questions.
24229997Sken */
25229997Sken
26229997Skenpackage com.sun.tools.javac.util;
27229997Sken
28229997Skenimport java.nio.file.Path;
29229997Skenimport java.util.Arrays;
30229997Skenimport java.util.Collection;
31229997Skenimport java.util.EnumSet;
32229997Skenimport java.util.HashMap;
33229997Skenimport java.util.Locale;
34229997Skenimport java.util.Map;
35229997Skenimport java.util.Set;
36229997Sken
37229997Skenimport javax.tools.JavaFileObject;
38229997Sken
39229997Skenimport com.sun.tools.javac.api.DiagnosticFormatter;
40229997Skenimport com.sun.tools.javac.api.DiagnosticFormatter.Configuration.DiagnosticPart;
41229997Skenimport com.sun.tools.javac.api.DiagnosticFormatter.Configuration.MultilineLimit;
42229997Skenimport com.sun.tools.javac.api.DiagnosticFormatter.PositionKind;
43229997Skenimport com.sun.tools.javac.api.Formattable;
44229997Skenimport com.sun.tools.javac.code.Lint.LintCategory;
45229997Skenimport com.sun.tools.javac.code.Printer;
46229997Skenimport com.sun.tools.javac.code.Symbol;
47229997Skenimport com.sun.tools.javac.code.Type;
48229997Skenimport com.sun.tools.javac.code.Type.CapturedType;
49229997Skenimport com.sun.tools.javac.file.PathFileObject;
50229997Skenimport com.sun.tools.javac.jvm.Profile;
51229997Skenimport com.sun.tools.javac.tree.JCTree.*;
52229997Skenimport com.sun.tools.javac.tree.Pretty;
53229997Sken
54229997Skenimport static com.sun.tools.javac.util.JCDiagnostic.DiagnosticType.*;
55229997Sken
56314749Smav/**
57229997Sken * This abstract class provides a basic implementation of the functionalities that should be provided
58229997Sken * by any formatter used by javac. Among the main features provided by AbstractDiagnosticFormatter are:
59229997Sken *
60229997Sken * <ul>
61229997Sken *  <li> Provides a standard implementation of the visitor-like methods defined in the interface DiagnisticFormatter.
62229997Sken *  Those implementations are specifically targeting JCDiagnostic objects.
63229997Sken *  <li> Provides basic support for i18n and a method for executing all locale-dependent conversions
64229997Sken *  <li> Provides the formatting logic for rendering the arguments of a JCDiagnostic object.
65229997Sken * </ul>
66229997Sken *
67229997Sken * <p><b>This is NOT part of any supported API.
68229997Sken * If you write code that depends on this, you do so at your own risk.
69229997Sken * This code and its internal interfaces are subject to change or
70229997Sken * deletion without notice.</b>
71229997Sken */
72229997Skenpublic abstract class AbstractDiagnosticFormatter implements DiagnosticFormatter<JCDiagnostic> {
73229997Sken
74229997Sken    /**
75229997Sken     * JavacMessages object used by this formatter for i18n.
76229997Sken     */
77229997Sken    protected JavacMessages messages;
78290776Smav
79290776Smav    /**
80290776Smav     * Configuration object used by this formatter
81290776Smav     */
82290776Smav    private SimpleConfiguration config;
83229997Sken
84290776Smav    /**
85290776Smav     * Current depth level of the disgnostic being formatted
86229997Sken     * (!= 0 for subdiagnostics)
87229997Sken     */
88229997Sken    protected int depth = 0;
89229997Sken
90229997Sken    /**
91229997Sken     * All captured types that have been encountered during diagnostic formatting.
92229997Sken     * This info is used by the FormatterPrinter in order to print friendly unique
93229997Sken     * ids for captured types
94229997Sken     */
95229997Sken    private List<Type> allCaptured = List.nil();
96229997Sken
97229997Sken    /**
98229997Sken     * Initialize an AbstractDiagnosticFormatter by setting its JavacMessages object.
99229997Sken     * @param messages
100229997Sken     */
101229997Sken    protected AbstractDiagnosticFormatter(JavacMessages messages, SimpleConfiguration config) {
102229997Sken        this.messages = messages;
103314749Smav        this.config = config;
104314749Smav    }
105314749Smav
106314749Smav    public String formatKind(JCDiagnostic d, Locale l) {
107314749Smav        switch (d.getType()) {
108315887Smav            case FRAGMENT: return "";
109315889Smav            case NOTE:     return localize(l, "compiler.note.note");
110315889Smav            case WARNING:  return localize(l, "compiler.warn.warning");
111229997Sken            case ERROR:    return localize(l, "compiler.err.error");
112229997Sken            default:
113229997Sken                throw new AssertionError("Unknown diagnostic type: " + d.getType());
114229997Sken        }
115229997Sken    }
116229997Sken
117229997Sken    @Override
118229997Sken    public String format(JCDiagnostic d, Locale locale) {
119288723Smav        allCaptured = List.nil();
120229997Sken        return formatDiagnostic(d, locale);
121265641Smav    }
122229997Sken
123229997Sken    protected abstract String formatDiagnostic(JCDiagnostic d, Locale locale);
124229997Sken
125229997Sken    public String formatPosition(JCDiagnostic d, PositionKind pk,Locale l) {
126229997Sken        Assert.check(d.getPosition() != Position.NOPOS);
127229997Sken        return String.valueOf(getPosition(d, pk));
128265641Smav    }
129265641Smav    //where
130229997Sken    private long getPosition(JCDiagnostic d, PositionKind pk) {
131229997Sken        switch (pk) {
132229997Sken            case START: return d.getIntStartPosition();
133229997Sken            case END: return d.getIntEndPosition();
134229997Sken            case LINE: return d.getLineNumber();
135229997Sken            case COLUMN: return d.getColumnNumber();
136229997Sken            case OFFSET: return d.getIntPosition();
137229997Sken            default:
138229997Sken                throw new AssertionError("Unknown diagnostic position: " + pk);
139229997Sken        }
140229997Sken    }
141229997Sken
142229997Sken    public String formatSource(JCDiagnostic d, boolean fullname, Locale l) {
143229997Sken        JavaFileObject fo = d.getSource();
144229997Sken        if (fo == null)
145229997Sken            throw new IllegalArgumentException(); // d should have source set
146229997Sken        if (fullname)
147229997Sken            return fo.getName();
148229997Sken        else if (fo instanceof PathFileObject)
149229997Sken            return ((PathFileObject) fo).getShortName();
150229997Sken        else
151229997Sken            return PathFileObject.getSimpleName(fo);
152229997Sken    }
153314751Smav
154229997Sken    /**
155314751Smav     * Format the arguments of a given diagnostic.
156229997Sken     *
157229997Sken     * @param d diagnostic whose arguments are to be formatted
158229997Sken     * @param l locale object to be used for i18n
159229997Sken     * @return a Collection whose elements are the formatted arguments of the diagnostic
160229997Sken     */
161229997Sken    protected Collection<String> formatArguments(JCDiagnostic d, Locale l) {
162229997Sken        ListBuffer<String> buf = new ListBuffer<>();
163229997Sken        for (Object o : d.getArgs()) {
164229997Sken           buf.append(formatArgument(d, o, l));
165229997Sken        }
166275878Smav        return buf.toList();
167229997Sken    }
168229997Sken
169229997Sken    /**
170229997Sken     * Format a single argument of a given diagnostic.
171312585Smav     *
172312585Smav     * @param d diagnostic whose argument is to be formatted
173312585Smav     * @param arg argument to be formatted
174313369Smav     * @param l locale object to be used for i18n
175313369Smav     * @return string representation of the diagnostic argument
176268677Smav     */
177229997Sken    protected String formatArgument(JCDiagnostic d, Object arg, Locale l) {
178229997Sken        if (arg instanceof JCDiagnostic) {
179229997Sken            String s = null;
180229997Sken            depth++;
181229997Sken            try {
182229997Sken                s = formatMessage((JCDiagnostic)arg, l);
183229997Sken            }
184229997Sken            finally {
185229997Sken                depth--;
186229997Sken            }
187229997Sken            return s;
188229997Sken        }
189284798Smav        else if (arg instanceof JCExpression) {
190284798Smav            return expr2String((JCExpression)arg);
191229997Sken        }
192229997Sken        else if (arg instanceof Iterable<?> && !(arg instanceof Path)) {
193275880Smav            return formatIterable(d, (Iterable<?>)arg, l);
194275880Smav        }
195229997Sken        else if (arg instanceof Type) {
196314739Smav            return printer.visit((Type)arg, l);
197314739Smav        }
198314739Smav        else if (arg instanceof Symbol) {
199314739Smav            return printer.visit((Symbol)arg, l);
200229997Sken        }
201229997Sken        else if (arg instanceof JavaFileObject) {
202229997Sken            return ((JavaFileObject)arg).getName();
203268677Smav        }
204273316Smav        else if (arg instanceof Profile) {
205273316Smav            return ((Profile)arg).name;
206229997Sken        }
207229997Sken        else if (arg instanceof Formattable) {
208268677Smav            return ((Formattable)arg).toString(l, messages);
209268677Smav        }
210273318Smav        else {
211268677Smav            return String.valueOf(arg);
212268677Smav        }
213268677Smav    }
214249009Strasz    //where
215268677Smav            private String expr2String(JCExpression tree) {
216249009Strasz                switch(tree.getTag()) {
217313369Smav                    case PARENS:
218229997Sken                        return expr2String(((JCParens)tree).expr);
219229997Sken                    case LAMBDA:
220313369Smav                    case REFERENCE:
221313369Smav                    case CONDEXPR:
222313369Smav                        return Pretty.toSimpleString(tree);
223229997Sken                    default:
224229997Sken                        Assert.error("unexpected tree kind " + tree.getKind());
225313369Smav                        return null;
226268677Smav                }
227229997Sken            }
228229997Sken
229229997Sken    /**
230229997Sken     * Format an iterable argument of a given diagnostic.
231268677Smav     *
232268677Smav     * @param d diagnostic whose argument is to be formatted
233268677Smav     * @param it iterable argument to be formatted
234229997Sken     * @param l locale object to be used for i18n
235313369Smav     * @return string representation of the diagnostic iterable argument
236268677Smav     */
237268677Smav    protected String formatIterable(JCDiagnostic d, Iterable<?> it, Locale l) {
238268677Smav        StringBuilder sbuf = new StringBuilder();
239229997Sken        String sep = "";
240229997Sken        for (Object o : it) {
241229997Sken            sbuf.append(sep);
242229997Sken            sbuf.append(formatArgument(d, o, l));
243229997Sken            sep = ",";
244229997Sken        }
245229997Sken        return sbuf.toString();
246229997Sken    }
247229997Sken
248229997Sken    /**
249229997Sken     * Format all the subdiagnostics attached to a given diagnostic.
250229997Sken     *
251273317Smav     * @param d diagnostic whose subdiagnostics are to be formatted
252229997Sken     * @param l locale object to be used for i18n
253229997Sken     * @return list of all string representations of the subdiagnostics
254229997Sken     */
255229997Sken    protected List<String> formatSubdiagnostics(JCDiagnostic d, Locale l) {
256229997Sken        List<String> subdiagnostics = List.nil();
257273317Smav        int maxDepth = config.getMultilineLimit(MultilineLimit.DEPTH);
258273317Smav        if (maxDepth == -1 || depth < maxDepth) {
259273317Smav            depth++;
260273317Smav            try {
261273317Smav                int maxCount = config.getMultilineLimit(MultilineLimit.LENGTH);
262273317Smav                int count = 0;
263273317Smav                for (JCDiagnostic d2 : d.getSubdiagnostics()) {
264229997Sken                    if (maxCount == -1 || count < maxCount) {
265229997Sken                        subdiagnostics = subdiagnostics.append(formatSubdiagnostic(d, d2, l));
266229997Sken                        count++;
267229997Sken                    }
268229997Sken                    else
269229997Sken                        break;
270229997Sken                }
271268677Smav            }
272229997Sken            finally {
273229997Sken                depth--;
274229997Sken            }
275229997Sken        }
276229997Sken        return subdiagnostics;
277229997Sken    }
278229997Sken
279230033Sken    /**
280229997Sken     * Format a subdiagnostics attached to a given diagnostic.
281229997Sken     *
282230033Sken     * @param parent multiline diagnostic whose subdiagnostics is to be formatted
283229997Sken     * @param sub subdiagnostic to be formatted
284229997Sken     * @param l locale object to be used for i18n
285229997Sken     * @return string representation of the subdiagnostics
286273317Smav     */
287273317Smav    protected String formatSubdiagnostic(JCDiagnostic parent, JCDiagnostic sub, Locale l) {
288273317Smav        return formatMessage(sub, l);
289273317Smav    }
290273317Smav
291273317Smav    /** Format the faulty source code line and point to the error.
292273317Smav     *  @param d The diagnostic for which the error line should be printed
293273317Smav     */
294229997Sken    protected String formatSourceLine(JCDiagnostic d, int nSpaces) {
295229997Sken        StringBuilder buf = new StringBuilder();
296229997Sken        DiagnosticSource source = d.getDiagnosticSource();
297229997Sken        int pos = d.getIntPosition();
298229997Sken        if (d.getIntPosition() == Position.NOPOS)
299273317Smav            throw new AssertionError();
300273317Smav        String line = (source == null ? null : source.getLine(pos));
301229997Sken        if (line == null)
302273317Smav            return "";
303229997Sken        buf.append(indent(line, nSpaces));
304229997Sken        int col = source.getColumnNumber(pos, false);
305229997Sken        if (config.isCaretEnabled()) {
306273317Smav            buf.append("\n");
307288713Smav            for (int i = 0; i < col - 1; i++)  {
308273317Smav                buf.append((line.charAt(i) == '\t') ? "\t" : " ");
309290776Smav            }
310265641Smav            buf.append(indent("^", nSpaces));
311273317Smav        }
312265641Smav        return buf.toString();
313273317Smav    }
314273317Smav
315273317Smav    protected String formatLintCategory(JCDiagnostic d, Locale l) {
316229997Sken        LintCategory lc = d.getLintCategory();
317273317Smav        if (lc == null)
318268677Smav            return "";
319229997Sken        return localize(l, "compiler.warn.lintOption", lc.option);
320229997Sken    }
321229997Sken
322229997Sken    /**
323229997Sken     * Converts a String into a locale-dependent representation accordingly to a given locale.
324268677Smav     *
325268694Smav     * @param l locale object to be used for i18n
326268694Smav     * @param key locale-independent key used for looking up in a resource file
327229997Sken     * @param args localization arguments
328268677Smav     * @return a locale-dependent string
329229997Sken     */
330229997Sken    protected String localize(Locale l, String key, Object... args) {
331314751Smav        return messages.getLocalizedString(l, key, args);
332273317Smav    }
333229997Sken
334229997Sken    public boolean displaySource(JCDiagnostic d) {
335229997Sken        return config.getVisible().contains(DiagnosticPart.SOURCE) &&
336229997Sken                d.getType() != FRAGMENT &&
337229997Sken                d.getIntPosition() != Position.NOPOS;
338273317Smav    }
339273319Smav
340273319Smav    public boolean isRaw() {
341268677Smav        return false;
342268677Smav    }
343273317Smav
344268677Smav    /**
345268677Smav     * Creates a string with a given amount of empty spaces. Useful for
346273317Smav     * indenting the text of a diagnostic message.
347275880Smav     *
348275880Smav     * @param nSpaces the amount of spaces to be added to the result string
349229997Sken     * @return the indentation string
350229997Sken     */
351229997Sken    protected String indentString(int nSpaces) {
352229997Sken        String spaces = "                        ";
353268677Smav        if (nSpaces <= spaces.length())
354268677Smav            return spaces.substring(0, nSpaces);
355288732Smav        else {
356314751Smav            StringBuilder buf = new StringBuilder();
357275493Smav            for (int i = 0 ; i < nSpaces ; i++)
358229997Sken                buf.append(" ");
359268677Smav            return buf.toString();
360229997Sken        }
361273317Smav    }
362273317Smav
363229997Sken    /**
364229997Sken     * Indent a string by prepending a given amount of empty spaces to each line
365229997Sken     * of the string.
366273317Smav     *
367229997Sken     * @param s the string to be indented
368229997Sken     * @param nSpaces the amount of spaces that should be prepended to each line
369229997Sken     * of the string
370245228Sken     * @return an indented string
371245228Sken     */
372245228Sken    protected String indent(String s, int nSpaces) {
373245228Sken        String indent = indentString(nSpaces);
374245228Sken        StringBuilder buf = new StringBuilder();
375245228Sken        String nl = "";
376245228Sken        for (String line : s.split("\n")) {
377245228Sken            buf.append(nl);
378245228Sken            buf.append(indent + line);
379273317Smav            nl = "\n";
380273317Smav        }
381273317Smav        return buf.toString();
382273317Smav    }
383268677Smav
384260387Sscottl    public SimpleConfiguration getConfiguration() {
385245228Sken        return config;
386229997Sken    }
387229997Sken
388229997Sken    static public class SimpleConfiguration implements Configuration {
389229997Sken
390229997Sken        protected Map<MultilineLimit, Integer> multilineLimits;
391229997Sken        protected EnumSet<DiagnosticPart> visibleParts;
392229997Sken        protected boolean caretEnabled;
393229997Sken
394229997Sken        public SimpleConfiguration(Set<DiagnosticPart> parts) {
395229997Sken            multilineLimits = new HashMap<>();
396229997Sken            setVisible(parts);
397273317Smav            setMultilineLimit(MultilineLimit.DEPTH, -1);
398229997Sken            setMultilineLimit(MultilineLimit.LENGTH, -1);
399229997Sken            setCaretEnabled(true);
400229997Sken        }
401236426Smjacob
402229997Sken        @SuppressWarnings("fallthrough")
403229997Sken        public SimpleConfiguration(Options options, Set<DiagnosticPart> parts) {
404229997Sken            this(parts);
405229997Sken            String showSource = null;
406273317Smav            if ((showSource = options.get("diags.showSource")) != null) {
407229997Sken                if (showSource.equals("true"))
408229997Sken                    setVisiblePart(DiagnosticPart.SOURCE, true);
409229997Sken                else if (showSource.equals("false"))
410229997Sken                    setVisiblePart(DiagnosticPart.SOURCE, false);
411229997Sken            }
412229997Sken            String diagOpts = options.get("diags.formatterOptions");
413268692Smav            if (diagOpts != null) {//override -XDshowSource
414268692Smav                Collection<String> args = Arrays.asList(diagOpts.split(","));
415229997Sken                if (args.contains("short")) {
416268692Smav                    setVisiblePart(DiagnosticPart.DETAILS, false);
417268692Smav                    setVisiblePart(DiagnosticPart.SUBDIAGNOSTICS, false);
418229997Sken                }
419229997Sken                if (args.contains("source"))
420268692Smav                    setVisiblePart(DiagnosticPart.SOURCE, true);
421229997Sken                if (args.contains("-source"))
422229997Sken                    setVisiblePart(DiagnosticPart.SOURCE, false);
423229997Sken            }
424268677Smav            String multiPolicy = null;
425229997Sken            if ((multiPolicy = options.get("diags.multilinePolicy")) != null) {
426229997Sken                if (multiPolicy.equals("disabled"))
427229997Sken                    setVisiblePart(DiagnosticPart.SUBDIAGNOSTICS, false);
428229997Sken                else if (multiPolicy.startsWith("limit:")) {
429229997Sken                    String limitString = multiPolicy.substring("limit:".length());
430229997Sken                    String[] limits = limitString.split(":");
431229997Sken                    try {
432229997Sken                        switch (limits.length) {
433229997Sken                            case 2: {
434229997Sken                                if (!limits[1].equals("*"))
435229997Sken                                    setMultilineLimit(MultilineLimit.DEPTH, Integer.parseInt(limits[1]));
436229997Sken                            }
437229997Sken                            case 1: {
438229997Sken                                if (!limits[0].equals("*"))
439229997Sken                                    setMultilineLimit(MultilineLimit.LENGTH, Integer.parseInt(limits[0]));
440229997Sken                            }
441229997Sken                        }
442229997Sken                    }
443229997Sken                    catch(NumberFormatException ex) {
444229997Sken                        setMultilineLimit(MultilineLimit.DEPTH, -1);
445229997Sken                        setMultilineLimit(MultilineLimit.LENGTH, -1);
446229997Sken                    }
447315889Smav                }
448229997Sken            }
449229997Sken            String showCaret = null;
450229997Sken            if (((showCaret = options.get("diags.showCaret")) != null) &&
451229997Sken                showCaret.equals("false"))
452229997Sken                    setCaretEnabled(false);
453229997Sken            else
454315887Smav                setCaretEnabled(true);
455315889Smav        }
456315889Smav
457229997Sken        public int getMultilineLimit(MultilineLimit limit) {
458229997Sken            return multilineLimits.get(limit);
459229997Sken        }
460315939Smav
461315939Smav        public EnumSet<DiagnosticPart> getVisible() {
462315939Smav            return EnumSet.copyOf(visibleParts);
463315939Smav        }
464315939Smav
465315939Smav        public void setMultilineLimit(MultilineLimit limit, int value) {
466315939Smav            multilineLimits.put(limit, value < -1 ? -1 : value);
467315939Smav        }
468315939Smav
469315939Smav
470315889Smav        public void setVisible(Set<DiagnosticPart> diagParts) {
471315889Smav            visibleParts = EnumSet.copyOf(diagParts);
472315889Smav        }
473315889Smav
474315889Smav        public void setVisiblePart(DiagnosticPart diagParts, boolean enabled) {
475315889Smav            if (enabled)
476315889Smav                visibleParts.add(diagParts);
477229997Sken            else
478229997Sken                visibleParts.remove(diagParts);
479315889Smav        }
480229997Sken
481229997Sken        /**
482229997Sken         * Shows a '^' sign under the source line displayed by the formatter
483229997Sken         * (if applicable).
484229997Sken         *
485229997Sken         * @param caretEnabled if true enables caret
486229997Sken         */
487275878Smav        public void setCaretEnabled(boolean caretEnabled) {
488288723Smav            this.caretEnabled = caretEnabled;
489229997Sken        }
490229997Sken
491236426Smjacob        /**
492229997Sken         * Tells whether the caret display is active or not.
493229997Sken         *
494229997Sken         * @return true if the caret is enabled
495229997Sken         */
496275878Smav        public boolean isCaretEnabled() {
497275878Smav            return caretEnabled;
498275878Smav        }
499275878Smav    }
500275878Smav
501275878Smav    public Printer getPrinter() {
502288723Smav        return printer;
503288723Smav    }
504288723Smav
505288723Smav    public void setPrinter(Printer printer) {
506288723Smav        this.printer = printer;
507288723Smav    }
508288723Smav
509288723Smav    /**
510312585Smav     * An enhanced printer for formatting types/symbols used by
511284793Smav     * AbstractDiagnosticFormatter. Provides alternate numbering of captured
512275878Smav     * types (they are numbered starting from 1 on each new diagnostic, instead
513315889Smav     * of relying on the underlying hashcode() method which generates unstable
514275878Smav     * output). Also detects cycles in wildcard messages (e.g. if the wildcard
515229997Sken     * type referred by a given captured type C contains C itself) which might
516229997Sken     * lead to infinite loops.
517229997Sken     */
518260387Sscottl    protected Printer printer = new Printer() {
519229997Sken
520229997Sken        @Override
521229997Sken        protected String localize(Locale locale, String key, Object... args) {
522288723Smav            return AbstractDiagnosticFormatter.this.localize(locale, key, args);
523275878Smav        }
524229997Sken        @Override
525229997Sken        protected String capturedVarId(CapturedType t, Locale locale) {
526229997Sken            return "" + (allCaptured.indexOf(t) + 1);
527229997Sken        }
528229997Sken        @Override
529229997Sken        public String visitCapturedType(CapturedType t, Locale locale) {
530229997Sken            if (!allCaptured.contains(t)) {
531229997Sken                allCaptured = allCaptured.append(t);
532229997Sken            }
533229997Sken            return super.visitCapturedType(t, locale);
534229997Sken        }
535229997Sken    };
536229997Sken}
537229997Sken