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