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