CheckAttributedTree.java revision 1279:5c0b3faeb0b0
190792Sgshapiro/*
2261363Sgshapiro * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
390792Sgshapiro * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
490792Sgshapiro *
590792Sgshapiro * This code is free software; you can redistribute it and/or modify it
690792Sgshapiro * under the terms of the GNU General Public License version 2 only, as
790792Sgshapiro * published by the Free Software Foundation.
890792Sgshapiro *
9266692Sgshapiro * This code is distributed in the hope that it will be useful, but WITHOUT
1090792Sgshapiro * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1190792Sgshapiro * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1290792Sgshapiro * version 2 for more details (a copy is included in the LICENSE file that
1390792Sgshapiro * accompanied this code).
1490792Sgshapiro *
1590792Sgshapiro * You should have received a copy of the GNU General Public License version
1690792Sgshapiro * 2 along with this work; if not, write to the Free Software Foundation,
1790792Sgshapiro * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1890792Sgshapiro *
1990792Sgshapiro * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2090792Sgshapiro * or visit www.oracle.com if you need additional information or have any
2190792Sgshapiro * questions.
2290792Sgshapiro */
2390792Sgshapiro
2490792Sgshapiroimport com.sun.source.util.TaskEvent;
2590792Sgshapiroimport java.awt.BorderLayout;
2690792Sgshapiroimport java.awt.Color;
2790792Sgshapiroimport java.awt.Dimension;
2890792Sgshapiroimport java.awt.EventQueue;
2990792Sgshapiroimport java.awt.Font;
3090792Sgshapiroimport java.awt.GridBagConstraints;
3190792Sgshapiroimport java.awt.GridBagLayout;
3290792Sgshapiroimport java.awt.Rectangle;
3390792Sgshapiroimport java.awt.event.ActionEvent;
3490792Sgshapiroimport java.awt.event.ActionListener;
35import java.awt.event.MouseAdapter;
36import java.awt.event.MouseEvent;
37import javax.swing.DefaultComboBoxModel;
38import javax.swing.JComboBox;
39import javax.swing.JComponent;
40import javax.swing.JFrame;
41import javax.swing.JLabel;
42import javax.swing.JPanel;
43import javax.swing.JScrollPane;
44import javax.swing.JTextArea;
45import javax.swing.JTextField;
46import javax.swing.SwingUtilities;
47import javax.swing.event.CaretEvent;
48import javax.swing.event.CaretListener;
49import javax.swing.text.BadLocationException;
50import javax.swing.text.DefaultHighlighter;
51import javax.swing.text.Highlighter;
52import java.io.File;
53import java.io.IOException;
54import java.io.PrintStream;
55import java.io.PrintWriter;
56import java.io.StringWriter;
57import java.lang.reflect.Field;
58import java.util.ArrayList;
59import java.util.List;
60import javax.tools.Diagnostic;
61import javax.tools.DiagnosticListener;
62import javax.tools.JavaFileObject;
63import javax.tools.StandardJavaFileManager;
64
65import com.sun.source.tree.CompilationUnitTree;
66import com.sun.source.util.JavacTask;
67import com.sun.source.util.TaskListener;
68import com.sun.tools.javac.api.JavacTool;
69import com.sun.tools.javac.code.Symbol;
70import com.sun.tools.javac.code.Type;
71import com.sun.tools.javac.tree.EndPosTable;
72import com.sun.tools.javac.tree.JCTree;
73import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
74import com.sun.tools.javac.tree.JCTree.JCImport;
75import com.sun.tools.javac.tree.TreeInfo;
76import com.sun.tools.javac.tree.TreeScanner;
77import com.sun.tools.javac.util.Pair;
78
79import java.util.Arrays;
80import java.util.HashSet;
81import java.util.Set;
82import javax.lang.model.element.Element;
83
84import static com.sun.tools.javac.tree.JCTree.Tag.*;
85
86/**
87 * Utility and test program to check validity of tree positions for tree nodes.
88 * The program can be run standalone, or as a jtreg test.  In standalone mode,
89 * errors can be displayed in a gui viewer. For info on command line args,
90 * run program with no args.
91 *
92 * <p>
93 * jtreg: Note that by using the -r switch in the test description below, this test
94 * will process all java files in the langtools/test directory, thus implicitly
95 * covering any new language features that may be tested in this test suite.
96 */
97
98/*
99 * @test
100 * @bug 6970584
101 * @summary assorted position errors in compiler syntax trees
102 * @run main CheckAttributedTree -q -r -et ERRONEOUS .
103 */
104public class CheckAttributedTree {
105    /**
106     * Main entry point.
107     * If test.src is set, program runs in jtreg mode, and will throw an Error
108     * if any errors arise, otherwise System.exit will be used, unless the gui
109     * viewer is being used. In jtreg mode, the default base directory for file
110     * args is the value of ${test.src}. In jtreg mode, the -r option can be
111     * given to change the default base directory to the root test directory.
112     */
113    public static void main(String... args) {
114        String testSrc = System.getProperty("test.src");
115        File baseDir = (testSrc == null) ? null : new File(testSrc);
116        boolean ok = new CheckAttributedTree().run(baseDir, args);
117        if (!ok) {
118            if (testSrc != null)  // jtreg mode
119                throw new Error("failed");
120            else
121                System.exit(1);
122        }
123    }
124
125    /**
126     * Run the program. A base directory can be provided for file arguments.
127     * In jtreg mode, the -r option can be given to change the default base
128     * directory to the test root directory. For other options, see usage().
129     * @param baseDir base directory for any file arguments.
130     * @param args command line args
131     * @return true if successful or in gui mode
132     */
133    boolean run(File baseDir, String... args) {
134        if (args.length == 0) {
135            usage(System.out);
136            return true;
137        }
138
139        List<File> files = new ArrayList<File>();
140        for (int i = 0; i < args.length; i++) {
141            String arg = args[i];
142            if (arg.equals("-encoding") && i + 1 < args.length)
143                encoding = args[++i];
144            else if (arg.equals("-gui"))
145                gui = true;
146            else if (arg.equals("-q"))
147                quiet = true;
148            else if (arg.equals("-v"))
149                verbose = true;
150            else if (arg.equals("-t") && i + 1 < args.length)
151                tags.add(args[++i]);
152            else if (arg.equals("-ef") && i + 1 < args.length)
153                excludeFiles.add(new File(baseDir, args[++i]));
154            else if (arg.equals("-et") && i + 1 < args.length)
155                excludeTags.add(args[++i]);
156            else if (arg.equals("-r")) {
157                if (excludeFiles.size() > 0)
158                    throw new Error("-r must be used before -ef");
159                File d = baseDir;
160                while (!new File(d, "TEST.ROOT").exists()) {
161                    if (d == null)
162                        throw new Error("cannot find TEST.ROOT");
163                    d = d.getParentFile();
164                }
165                baseDir = d;
166            }
167            else if (arg.startsWith("-"))
168                throw new Error("unknown option: " + arg);
169            else {
170                while (i < args.length)
171                    files.add(new File(baseDir, args[i++]));
172            }
173        }
174
175        for (File file: files) {
176            if (file.exists())
177                test(file);
178            else
179                error("File not found: " + file);
180        }
181
182        if (fileCount != 1)
183            System.err.println(fileCount + " files read");
184        if (errors > 0)
185            System.err.println(errors + " errors");
186
187        return (gui || errors == 0);
188    }
189
190    /**
191     * Print command line help.
192     * @param out output stream
193     */
194    void usage(PrintStream out) {
195        out.println("Usage:");
196        out.println("  java CheckAttributedTree options... files...");
197        out.println("");
198        out.println("where options include:");
199        out.println("-q        Quiet: don't report on inapplicable files");
200        out.println("-gui      Display returns in a GUI viewer");
201        out.println("-v        Verbose: report on files as they are being read");
202        out.println("-t tag    Limit checks to tree nodes with this tag");
203        out.println("          Can be repeated if desired");
204        out.println("-ef file  Exclude file or directory");
205        out.println("-et tag   Exclude tree nodes with given tag name");
206        out.println("");
207        out.println("files may be directories or files");
208        out.println("directories will be scanned recursively");
209        out.println("non java files, or java files which cannot be parsed, will be ignored");
210        out.println("");
211    }
212
213    /**
214     * Test a file. If the file is a directory, it will be recursively scanned
215     * for java files.
216     * @param file the file or directory to test
217     */
218    void test(File file) {
219        if (excludeFiles.contains(file)) {
220            if (!quiet)
221                error("File " + file + " excluded");
222            return;
223        }
224
225        if (file.isDirectory()) {
226            for (File f: file.listFiles()) {
227                test(f);
228            }
229            return;
230        }
231
232        if (file.isFile() && file.getName().endsWith(".java")) {
233            try {
234                if (verbose)
235                    System.err.println(file);
236                fileCount++;
237                NPETester p = new NPETester();
238                p.test(read(file));
239            } catch (AttributionException e) {
240                if (!quiet) {
241                    error("Error attributing " + file + "\n" + e.getMessage());
242                }
243            } catch (IOException e) {
244                error("Error reading " + file + ": " + e);
245            }
246            return;
247        }
248
249        if (!quiet)
250            error("File " + file + " ignored");
251    }
252
253    // See CR:  6982992 Tests CheckAttributedTree.java, JavacTreeScannerTest.java, and SourceTreeeScannerTest.java timeout
254    StringWriter sw = new StringWriter();
255    PrintWriter pw = new PrintWriter(sw);
256    Reporter r = new Reporter(pw);
257    JavacTool tool = JavacTool.create();
258    StandardJavaFileManager fm = tool.getStandardFileManager(r, null, null);
259
260    /**
261     * Read a file.
262     * @param file the file to be read
263     * @return the tree for the content of the file
264     * @throws IOException if any IO errors occur
265     * @throws TreePosTest.ParseException if any errors occur while parsing the file
266     */
267    List<Pair<JCCompilationUnit, JCTree>> read(File file) throws IOException, AttributionException {
268        JavacTool tool = JavacTool.create();
269        r.errors = 0;
270        Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(file);
271        String[] opts = { "-XDshouldStopPolicy=ATTR", "-XDverboseCompilePolicy" };
272        JavacTask task = tool.getTask(pw, fm, r, Arrays.asList(opts), null, files);
273        final List<Element> analyzedElems = new ArrayList<>();
274        task.setTaskListener(new TaskListener() {
275            public void started(TaskEvent e) {
276                if (e.getKind() == TaskEvent.Kind.ANALYZE)
277                        analyzedElems.add(e.getTypeElement());
278            }
279            public void finished(TaskEvent e) { }
280        });
281
282        try {
283            Iterable<? extends CompilationUnitTree> trees = task.parse();
284            task.analyze();
285            List<Pair<JCCompilationUnit, JCTree>> res = new ArrayList<>();
286            //System.out.println("Try to add pairs. Elems are " + analyzedElems);
287            for (CompilationUnitTree t : trees) {
288               JCCompilationUnit cu = (JCCompilationUnit)t;
289               for (JCTree def : cu.defs) {
290                   if (def.hasTag(CLASSDEF) &&
291                           analyzedElems.contains(((JCTree.JCClassDecl)def).sym)) {
292                       //System.out.println("Adding pair...");
293                       res.add(new Pair<>(cu, def));
294                   }
295               }
296            }
297            return res;
298        }
299        catch (Throwable t) {
300            throw new AttributionException("Exception while attributing file: " + file);
301        }
302    }
303
304    /**
305     * Report an error. When the program is complete, the program will either
306     * exit or throw an Error if any errors have been reported.
307     * @param msg the error message
308     */
309    void error(String msg) {
310        System.err.println(msg);
311        errors++;
312    }
313
314    /** Number of files that have been analyzed. */
315    int fileCount;
316    /** Number of errors reported. */
317    int errors;
318    /** Flag: don't report irrelevant files. */
319    boolean quiet;
320    /** Flag: show errors in GUI viewer. */
321    boolean gui;
322    /** The GUI viewer for errors. */
323    Viewer viewer;
324    /** Flag: report files as they are processed. */
325    boolean verbose;
326    /** Option: encoding for test files. */
327    String encoding;
328    /** The set of tags for tree nodes to be analyzed; if empty, all tree nodes
329     * are analyzed. */
330    Set<String> tags = new HashSet<String>();
331    /** Set of files and directories to be excluded from analysis. */
332    Set<File> excludeFiles = new HashSet<File>();
333    /** Set of tag names to be excluded from analysis. */
334    Set<String> excludeTags = new HashSet<String>();
335    /** Utility class for trees */
336    TreeUtil treeUtil = new TreeUtil();
337
338    /**
339     * Main class for testing assertions concerning types/symbol
340     * left uninitialized after attribution
341     */
342    private class NPETester extends TreeScanner {
343        void test(List<Pair<JCCompilationUnit, JCTree>> trees) {
344            for (Pair<JCCompilationUnit, JCTree> p : trees) {
345                sourcefile = p.fst.sourcefile;
346                endPosTable = p.fst.endPositions;
347                encl = new Info(p.snd, endPosTable);
348                p.snd.accept(this);
349            }
350        }
351
352        @Override
353        public void scan(JCTree tree) {
354            if (tree == null ||
355                    excludeTags.contains(treeUtil.nameFromTag(tree.getTag()))) {
356                return;
357            }
358
359            Info self = new Info(tree, endPosTable);
360            check(!mandatoryType(tree) ||
361                    (tree.type != null &&
362                    checkFields(tree)),
363                    "'null' found in tree ",
364                    self);
365
366            Info prevEncl = encl;
367            encl = self;
368            tree.accept(this);
369            encl = prevEncl;
370        }
371
372        private boolean mandatoryType(JCTree that) {
373            return that instanceof JCTree.JCExpression ||
374                    that.hasTag(VARDEF) ||
375                    that.hasTag(METHODDEF) ||
376                    that.hasTag(CLASSDEF);
377        }
378
379        private final List<String> excludedFields = Arrays.asList("varargsElement");
380
381        void check(boolean ok, String label, Info self) {
382            if (!ok) {
383                if (gui) {
384                    if (viewer == null)
385                        viewer = new Viewer();
386                    viewer.addEntry(sourcefile, label, encl, self);
387                }
388                error(label + self.toString() + " encl: " + encl.toString() + " in file: " + sourcefile + "  " + self.tree);
389            }
390        }
391
392        boolean checkFields(JCTree t) {
393            List<Field> fieldsToCheck = treeUtil.getFieldsOfType(t,
394                    excludedFields,
395                    Symbol.class,
396                    Type.class);
397            for (Field f : fieldsToCheck) {
398                try {
399                    if (f.get(t) == null) {
400                        return false;
401                    }
402                }
403                catch (IllegalAccessException e) {
404                    System.err.println("Cannot read field: " + f);
405                    //swallow it
406                }
407            }
408            return true;
409        }
410
411        @Override
412        public void visitImport(JCImport tree) { }
413
414        @Override
415        public void visitTopLevel(JCCompilationUnit tree) {
416            scan(tree.defs);
417        }
418
419        JavaFileObject sourcefile;
420        EndPosTable endPosTable;
421        Info encl;
422    }
423
424    /**
425     * Utility class providing easy access to position and other info for a tree node.
426     */
427    private class Info {
428        Info() {
429            tree = null;
430            tag = ERRONEOUS;
431            start = 0;
432            pos = 0;
433            end = Integer.MAX_VALUE;
434        }
435
436        Info(JCTree tree, EndPosTable endPosTable) {
437            this.tree = tree;
438            tag = tree.getTag();
439            start = TreeInfo.getStartPos(tree);
440            pos = tree.pos;
441            end = TreeInfo.getEndPos(tree, endPosTable);
442        }
443
444        @Override
445        public String toString() {
446            return treeUtil.nameFromTag(tree.getTag()) + "[start:" + start + ",pos:" + pos + ",end:" + end + "]";
447        }
448
449        final JCTree tree;
450        final JCTree.Tag tag;
451        final int start;
452        final int pos;
453        final int end;
454    }
455
456    /**
457     * Names for tree tags.
458     */
459    private static class TreeUtil {
460        String nameFromTag(JCTree.Tag tag) {
461            String name = tag.name();
462            return (name == null) ? "??" : name;
463        }
464
465        List<Field> getFieldsOfType(JCTree t, List<String> excludeNames, Class<?>... types) {
466            List<Field> buf = new ArrayList<Field>();
467            for (Field f : t.getClass().getDeclaredFields()) {
468                if (!excludeNames.contains(f.getName())) {
469                    for (Class<?> type : types) {
470                        if (type.isAssignableFrom(f.getType())) {
471                            f.setAccessible(true);
472                            buf.add(f);
473                            break;
474                        }
475                    }
476                }
477            }
478            return buf;
479        }
480    }
481
482    /**
483     * Thrown when errors are found parsing a java file.
484     */
485    private static class ParseException extends Exception {
486        ParseException(String msg) {
487            super(msg);
488        }
489    }
490
491    private static class AttributionException extends Exception {
492        AttributionException(String msg) {
493            super(msg);
494        }
495    }
496
497    /**
498     * DiagnosticListener to report diagnostics and count any errors that occur.
499     */
500    private static class Reporter implements DiagnosticListener<JavaFileObject> {
501        Reporter(PrintWriter out) {
502            this.out = out;
503        }
504
505        public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
506            out.println(diagnostic);
507            switch (diagnostic.getKind()) {
508                case ERROR:
509                    errors++;
510            }
511        }
512        int errors;
513        PrintWriter out;
514    }
515
516    /**
517     * GUI viewer for issues found by TreePosTester. The viewer provides a drop
518     * down list for selecting error conditions, a header area providing details
519     * about an error, and a text area with the ranges of text highlighted as
520     * appropriate.
521     */
522    private class Viewer extends JFrame {
523        /**
524         * Create a viewer.
525         */
526        Viewer() {
527            initGUI();
528        }
529
530        /**
531         * Add another entry to the list of errors.
532         * @param file The file containing the error
533         * @param check The condition that was being tested, and which failed
534         * @param encl the enclosing tree node
535         * @param self the tree node containing the error
536         */
537        void addEntry(JavaFileObject file, String check, Info encl, Info self) {
538            Entry e = new Entry(file, check, encl, self);
539            DefaultComboBoxModel m = (DefaultComboBoxModel) entries.getModel();
540            m.addElement(e);
541            if (m.getSize() == 1)
542                entries.setSelectedItem(e);
543        }
544
545        /**
546         * Initialize the GUI window.
547         */
548        private void initGUI() {
549            JPanel head = new JPanel(new GridBagLayout());
550            GridBagConstraints lc = new GridBagConstraints();
551            GridBagConstraints fc = new GridBagConstraints();
552            fc.anchor = GridBagConstraints.WEST;
553            fc.fill = GridBagConstraints.HORIZONTAL;
554            fc.gridwidth = GridBagConstraints.REMAINDER;
555
556            entries = new JComboBox();
557            entries.addActionListener(new ActionListener() {
558                public void actionPerformed(ActionEvent e) {
559                    showEntry((Entry) entries.getSelectedItem());
560                }
561            });
562            fc.insets.bottom = 10;
563            head.add(entries, fc);
564            fc.insets.bottom = 0;
565            head.add(new JLabel("check:"), lc);
566            head.add(checkField = createTextField(80), fc);
567            fc.fill = GridBagConstraints.NONE;
568            head.add(setBackground(new JLabel("encl:"), enclColor), lc);
569            head.add(enclPanel = new InfoPanel(), fc);
570            head.add(setBackground(new JLabel("self:"), selfColor), lc);
571            head.add(selfPanel = new InfoPanel(), fc);
572            add(head, BorderLayout.NORTH);
573
574            body = new JTextArea();
575            body.setFont(Font.decode(Font.MONOSPACED));
576            body.addCaretListener(new CaretListener() {
577                public void caretUpdate(CaretEvent e) {
578                    int dot = e.getDot();
579                    int mark = e.getMark();
580                    if (dot == mark)
581                        statusText.setText("dot: " + dot);
582                    else
583                        statusText.setText("dot: " + dot + ", mark:" + mark);
584                }
585            });
586            JScrollPane p = new JScrollPane(body,
587                    JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
588                    JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
589            p.setPreferredSize(new Dimension(640, 480));
590            add(p, BorderLayout.CENTER);
591
592            statusText = createTextField(80);
593            add(statusText, BorderLayout.SOUTH);
594
595            pack();
596            setLocationRelativeTo(null); // centered on screen
597            setVisible(true);
598            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
599        }
600
601        /** Show an entry that has been selected. */
602        private void showEntry(Entry e) {
603            try {
604                // update simple fields
605                setTitle(e.file.getName());
606                checkField.setText(e.check);
607                enclPanel.setInfo(e.encl);
608                selfPanel.setInfo(e.self);
609                // show file text with highlights
610                body.setText(e.file.getCharContent(true).toString());
611                Highlighter highlighter = body.getHighlighter();
612                highlighter.removeAllHighlights();
613                addHighlight(highlighter, e.encl, enclColor);
614                addHighlight(highlighter, e.self, selfColor);
615                scroll(body, getMinPos(enclPanel.info, selfPanel.info));
616            } catch (IOException ex) {
617                body.setText("Cannot read " + e.file.getName() + ": " + e);
618            }
619        }
620
621        /** Create a test field. */
622        private JTextField createTextField(int width) {
623            JTextField f = new JTextField(width);
624            f.setEditable(false);
625            f.setBorder(null);
626            return f;
627        }
628
629        /** Add a highlighted region based on the positions in an Info object. */
630        private void addHighlight(Highlighter h, Info info, Color c) {
631            int start = info.start;
632            int end = info.end;
633            if (start == -1 && end == -1)
634                return;
635            if (start == -1)
636                start = end;
637            if (end == -1)
638                end = start;
639            try {
640                h.addHighlight(info.start, info.end,
641                        new DefaultHighlighter.DefaultHighlightPainter(c));
642                if (info.pos != -1) {
643                    Color c2 = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int)(.4f * 255)); // 40%
644                    h.addHighlight(info.pos, info.pos + 1,
645                        new DefaultHighlighter.DefaultHighlightPainter(c2));
646                }
647            } catch (BadLocationException e) {
648                e.printStackTrace();
649            }
650        }
651
652        /** Get the minimum valid position in a set of info objects. */
653        private int getMinPos(Info... values) {
654            int i = Integer.MAX_VALUE;
655            for (Info info: values) {
656                if (info.start >= 0) i = Math.min(i, info.start);
657                if (info.pos   >= 0) i = Math.min(i, info.pos);
658                if (info.end   >= 0) i = Math.min(i, info.end);
659            }
660            return (i == Integer.MAX_VALUE) ? 0 : i;
661        }
662
663        /** Set the background on a component. */
664        private JComponent setBackground(JComponent comp, Color c) {
665            comp.setOpaque(true);
666            comp.setBackground(c);
667            return comp;
668        }
669
670        /** Scroll a text area to display a given position near the middle of the visible area. */
671        private void scroll(final JTextArea t, final int pos) {
672            // Using invokeLater appears to give text a chance to sort itself out
673            // before the scroll happens; otherwise scrollRectToVisible doesn't work.
674            // Maybe there's a better way to sync with the text...
675            EventQueue.invokeLater(new Runnable() {
676                public void run() {
677                    try {
678                        Rectangle r = t.modelToView(pos);
679                        JScrollPane p = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, t);
680                        r.y = Math.max(0, r.y - p.getHeight() * 2 / 5);
681                        r.height += p.getHeight() * 4 / 5;
682                        t.scrollRectToVisible(r);
683                    } catch (BadLocationException ignore) {
684                    }
685                }
686            });
687        }
688
689        private JComboBox entries;
690        private JTextField checkField;
691        private InfoPanel enclPanel;
692        private InfoPanel selfPanel;
693        private JTextArea body;
694        private JTextField statusText;
695
696        private Color selfColor = new Color(0.f, 1.f, 0.f, 0.2f); // 20% green
697        private Color enclColor = new Color(1.f, 0.f, 0.f, 0.2f); // 20% red
698
699        /** Panel to display an Info object. */
700        private class InfoPanel extends JPanel {
701            InfoPanel() {
702                add(tagName = createTextField(20));
703                add(new JLabel("start:"));
704                add(addListener(start = createTextField(6)));
705                add(new JLabel("pos:"));
706                add(addListener(pos = createTextField(6)));
707                add(new JLabel("end:"));
708                add(addListener(end = createTextField(6)));
709            }
710
711            void setInfo(Info info) {
712                this.info = info;
713                tagName.setText(treeUtil.nameFromTag(info.tag));
714                start.setText(String.valueOf(info.start));
715                pos.setText(String.valueOf(info.pos));
716                end.setText(String.valueOf(info.end));
717            }
718
719            JTextField addListener(final JTextField f) {
720                f.addMouseListener(new MouseAdapter() {
721                    @Override
722                    public void mouseClicked(MouseEvent e) {
723                        body.setCaretPosition(Integer.valueOf(f.getText()));
724                        body.getCaret().setVisible(true);
725                    }
726                });
727                return f;
728            }
729
730            Info info;
731            JTextField tagName;
732            JTextField start;
733            JTextField pos;
734            JTextField end;
735        }
736
737        /** Object to record information about an error to be displayed. */
738        private class Entry {
739            Entry(JavaFileObject file, String check, Info encl, Info self) {
740                this.file = file;
741                this.check = check;
742                this.encl = encl;
743                this.self= self;
744            }
745
746            @Override
747            public String toString() {
748                return file.getName() + " " + check + " " + getMinPos(encl, self);
749            }
750
751            final JavaFileObject file;
752            final String check;
753            final Info encl;
754            final Info self;
755        }
756    }
757}
758