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