1 /*
   2  * Copyright (c) 2010, 2018, 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 
  41 import java.awt.BorderLayout;
  42 import java.awt.Color;
  43 import java.awt.Dimension;
  44 import java.awt.EventQueue;
  45 import java.awt.Font;
  46 import java.awt.GridBagConstraints;
  47 import java.awt.GridBagLayout;
  48 import java.awt.Rectangle;
  49 import java.awt.event.ActionEvent;
  50 import java.awt.event.ActionListener;
  51 import java.awt.event.MouseAdapter;
  52 import java.awt.event.MouseEvent;
  53 import java.io.File;
  54 import java.io.IOException;
  55 import java.io.PrintStream;
  56 import java.io.PrintWriter;
  57 import java.io.StringWriter;
  58 import java.lang.reflect.Field;
  59 import java.nio.file.FileVisitResult;
  60 import java.nio.file.Files;
  61 import java.nio.file.Path;
  62 import java.nio.file.SimpleFileVisitor;
  63 import java.nio.file.attribute.BasicFileAttributes;
  64 import java.util.ArrayList;
  65 import java.util.Arrays;
  66 import java.util.HashSet;
  67 import java.util.List;
  68 import java.util.Set;
  69 import java.util.concurrent.atomic.AtomicInteger;
  70 import java.util.function.BiConsumer;
  71 
  72 import javax.lang.model.element.Element;
  73 import javax.swing.DefaultComboBoxModel;
  74 import javax.swing.JComboBox;
  75 import javax.swing.JComponent;
  76 import javax.swing.JFrame;
  77 import javax.swing.JLabel;
  78 import javax.swing.JPanel;
  79 import javax.swing.JScrollPane;
  80 import javax.swing.JTextArea;
  81 import javax.swing.JTextField;
  82 import javax.swing.SwingUtilities;
  83 import javax.swing.event.CaretEvent;
  84 import javax.swing.event.CaretListener;
  85 import javax.swing.text.BadLocationException;
  86 import javax.swing.text.DefaultHighlighter;
  87 import javax.swing.text.Highlighter;
  88 import javax.tools.JavaFileObject;
  89 
  90 import com.sun.source.tree.CompilationUnitTree;
  91 import com.sun.source.util.TaskEvent;
  92 import com.sun.source.util.TaskEvent.Kind;
  93 import com.sun.source.util.TaskListener;
  94 import com.sun.tools.javac.code.Symbol;
  95 import com.sun.tools.javac.code.Type;
  96 import com.sun.tools.javac.tree.EndPosTable;
  97 import com.sun.tools.javac.tree.JCTree;
  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=at=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                 endPosTable = cut.endPositions;
 370                 encl = new Info(tree, endPosTable);
 371                 tree.accept(this);
 372             }
 373 
 374             @Override
 375             public void scan(JCTree tree) {
 376                 if (tree == null ||
 377                         excludeTags.contains(treeUtil.nameFromTag(tree.getTag()))) {
 378                     return;
 379                 }
 380 
 381                 Info self = new Info(tree, endPosTable);
 382                 if (mandatoryType(tree)) {
 383                     check(tree.type != null,
 384                             "'null' field 'type' found in tree ", self);
 385                     if (tree.type==null)
 386                         Thread.dumpStack();
 387                 }
 388 
 389                 Field errField = checkFields(tree);
 390                 if (errField!=null) {
 391                     check(false,
 392                             "'null' field '" + errField.getName() + "' found in tree ", self);
 393                 }
 394 
 395                 Info prevEncl = encl;
 396                 encl = self;
 397                 tree.accept(this);
 398                 encl = prevEncl;
 399             }
 400 
 401             private boolean mandatoryType(JCTree that) {
 402                 return that instanceof JCTree.JCExpression ||
 403                         that.hasTag(VARDEF) ||
 404                         that.hasTag(METHODDEF) ||
 405                         that.hasTag(CLASSDEF);
 406             }
 407 
 408             private final List<String> excludedFields = Arrays.asList("varargsElement", "targetType");
 409 
 410             void check(boolean ok, String label, Info self) {
 411                 if (!ok) {
 412                     if (gui) {
 413                         if (viewer == null)
 414                             viewer = new Viewer();
 415                         viewer.addEntry(sourcefile, label, encl, self);
 416                     }
 417                     error(label + self.toString() + " encl: " + encl.toString() +
 418                             " in file: " + sourcefile + "  " + self.tree);
 419                 }
 420             }
 421 
 422             Field checkFields(JCTree t) {
 423                 List<Field> fieldsToCheck = treeUtil.getFieldsOfType(t,
 424                         excludedFields,
 425                         Symbol.class,
 426                         Type.class);
 427                 for (Field f : fieldsToCheck) {
 428                     try {
 429                         if (f.get(t) == null) {
 430                             return f;
 431                         }
 432                     }
 433                     catch (IllegalAccessException e) {
 434                         System.err.println("Cannot read field: " + f);
 435                         //swallow it
 436                     }
 437                 }
 438                 return null;
 439             }
 440 
 441             @Override
 442             public void visitImport(JCImport tree) { }
 443 
 444             @Override
 445             public void visitTopLevel(JCCompilationUnit tree) {
 446                 scan(tree.defs);
 447             }
 448 
 449             JavaFileObject sourcefile;
 450             EndPosTable endPosTable;
 451             Info encl;
 452         }
 453     }
 454 
 455     // See CR:  6982992 Tests CheckAttributedTree.java, JavacTreeScannerTest.java, and SourceTreeeScannerTest.java timeout
 456     StringWriter sw = new StringWriter();
 457     PrintWriter pw = new PrintWriter(sw);
 458 
 459     StringWriter errSWriter = new StringWriter();
 460     PrintWriter errWriter = new PrintWriter(errSWriter);
 461 
 462     /** Flag: don't report irrelevant files. */
 463     boolean quiet;
 464     /** Flag: show errors in GUI viewer. */
 465     boolean gui;
 466     /** The GUI viewer for errors. */
 467     Viewer viewer;
 468     /** Flag: report files as they are processed. */
 469     boolean verbose;
 470     /** Option: encoding for test files. */
 471     String encoding;
 472     /** The set of tags for tree nodes to be analyzed; if empty, all tree nodes
 473      * are analyzed. */
 474     Set<String> tags = new HashSet<String>();
 475     /** Set of files and directories to be excluded from analysis. */
 476     Set<File> excludeFiles = new HashSet<File>();
 477     /** Set of tag names to be excluded from analysis. */
 478     Set<String> excludeTags = new HashSet<String>();
 479     /** Utility class for trees */
 480     TreeUtil treeUtil = new TreeUtil();
 481 
 482     /**
 483      * Utility class providing easy access to position and other info for a tree node.
 484      */
 485     private class Info {
 486         Info() {
 487             tree = null;
 488             tag = ERRONEOUS;
 489             start = 0;
 490             pos = 0;
 491             end = Integer.MAX_VALUE;
 492         }
 493 
 494         Info(JCTree tree, EndPosTable endPosTable) {
 495             this.tree = tree;
 496             tag = tree.getTag();
 497             start = TreeInfo.getStartPos(tree);
 498             pos = tree.pos;
 499             end = TreeInfo.getEndPos(tree, endPosTable);
 500         }
 501 
 502         @Override
 503         public String toString() {
 504             return treeUtil.nameFromTag(tree.getTag()) + "[start:" + start + ",pos:" + pos + ",end:" + end + "]";
 505         }
 506 
 507         final JCTree tree;
 508         final JCTree.Tag tag;
 509         final int start;
 510         final int pos;
 511         final int end;
 512     }
 513 
 514     /**
 515      * Names for tree tags.
 516      */
 517     private static class TreeUtil {
 518         String nameFromTag(JCTree.Tag tag) {
 519             String name = tag.name();
 520             return (name == null) ? "??" : name;
 521         }
 522 
 523         List<Field> getFieldsOfType(JCTree t, List<String> excludeNames, Class<?>... types) {
 524             List<Field> buf = new ArrayList<Field>();
 525             for (Field f : t.getClass().getDeclaredFields()) {
 526                 if (!excludeNames.contains(f.getName())) {
 527                     for (Class<?> type : types) {
 528                         if (type.isAssignableFrom(f.getType())) {
 529                             f.setAccessible(true);
 530                             buf.add(f);
 531                             break;
 532                         }
 533                     }
 534                 }
 535             }
 536             return buf;
 537         }
 538     }
 539 
 540     /**
 541      * GUI viewer for issues found by TreePosTester. The viewer provides a drop
 542      * down list for selecting error conditions, a header area providing details
 543      * about an error, and a text area with the ranges of text highlighted as
 544      * appropriate.
 545      */
 546     private class Viewer extends JFrame {
 547         /**
 548          * Create a viewer.
 549          */
 550         Viewer() {
 551             initGUI();
 552         }
 553 
 554         /**
 555          * Add another entry to the list of errors.
 556          * @param file The file containing the error
 557          * @param check The condition that was being tested, and which failed
 558          * @param encl the enclosing tree node
 559          * @param self the tree node containing the error
 560          */
 561         void addEntry(JavaFileObject file, String check, Info encl, Info self) {
 562             Entry e = new Entry(file, check, encl, self);
 563             DefaultComboBoxModel m = (DefaultComboBoxModel) entries.getModel();
 564             m.addElement(e);
 565             if (m.getSize() == 1)
 566                 entries.setSelectedItem(e);
 567         }
 568 
 569         /**
 570          * Initialize the GUI window.
 571          */
 572         private void initGUI() {
 573             JPanel head = new JPanel(new GridBagLayout());
 574             GridBagConstraints lc = new GridBagConstraints();
 575             GridBagConstraints fc = new GridBagConstraints();
 576             fc.anchor = GridBagConstraints.WEST;
 577             fc.fill = GridBagConstraints.HORIZONTAL;
 578             fc.gridwidth = GridBagConstraints.REMAINDER;
 579 
 580             entries = new JComboBox();
 581             entries.addActionListener(new ActionListener() {
 582                 public void actionPerformed(ActionEvent e) {
 583                     showEntry((Entry) entries.getSelectedItem());
 584                 }
 585             });
 586             fc.insets.bottom = 10;
 587             head.add(entries, fc);
 588             fc.insets.bottom = 0;
 589             head.add(new JLabel("check:"), lc);
 590             head.add(checkField = createTextField(80), fc);
 591             fc.fill = GridBagConstraints.NONE;
 592             head.add(setBackground(new JLabel("encl:"), enclColor), lc);
 593             head.add(enclPanel = new InfoPanel(), fc);
 594             head.add(setBackground(new JLabel("self:"), selfColor), lc);
 595             head.add(selfPanel = new InfoPanel(), fc);
 596             add(head, BorderLayout.NORTH);
 597 
 598             body = new JTextArea();
 599             body.setFont(Font.decode(Font.MONOSPACED));
 600             body.addCaretListener(new CaretListener() {
 601                 public void caretUpdate(CaretEvent e) {
 602                     int dot = e.getDot();
 603                     int mark = e.getMark();
 604                     if (dot == mark)
 605                         statusText.setText("dot: " + dot);
 606                     else
 607                         statusText.setText("dot: " + dot + ", mark:" + mark);
 608                 }
 609             });
 610             JScrollPane p = new JScrollPane(body,
 611                     JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
 612                     JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
 613             p.setPreferredSize(new Dimension(640, 480));
 614             add(p, BorderLayout.CENTER);
 615 
 616             statusText = createTextField(80);
 617             add(statusText, BorderLayout.SOUTH);
 618 
 619             pack();
 620             setLocationRelativeTo(null); // centered on screen
 621             setVisible(true);
 622             setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 623         }
 624 
 625         /** Show an entry that has been selected. */
 626         private void showEntry(Entry e) {
 627             try {
 628                 // update simple fields
 629                 setTitle(e.file.getName());
 630                 checkField.setText(e.check);
 631                 enclPanel.setInfo(e.encl);
 632                 selfPanel.setInfo(e.self);
 633                 // show file text with highlights
 634                 body.setText(e.file.getCharContent(true).toString());
 635                 Highlighter highlighter = body.getHighlighter();
 636                 highlighter.removeAllHighlights();
 637                 addHighlight(highlighter, e.encl, enclColor);
 638                 addHighlight(highlighter, e.self, selfColor);
 639                 scroll(body, getMinPos(enclPanel.info, selfPanel.info));
 640             } catch (IOException ex) {
 641                 body.setText("Cannot read " + e.file.getName() + ": " + e);
 642             }
 643         }
 644 
 645         /** Create a test field. */
 646         private JTextField createTextField(int width) {
 647             JTextField f = new JTextField(width);
 648             f.setEditable(false);
 649             f.setBorder(null);
 650             return f;
 651         }
 652 
 653         /** Add a highlighted region based on the positions in an Info object. */
 654         private void addHighlight(Highlighter h, Info info, Color c) {
 655             int start = info.start;
 656             int end = info.end;
 657             if (start == -1 && end == -1)
 658                 return;
 659             if (start == -1)
 660                 start = end;
 661             if (end == -1)
 662                 end = start;
 663             try {
 664                 h.addHighlight(info.start, info.end,
 665                         new DefaultHighlighter.DefaultHighlightPainter(c));
 666                 if (info.pos != -1) {
 667                     Color c2 = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int)(.4f * 255)); // 40%
 668                     h.addHighlight(info.pos, info.pos + 1,
 669                         new DefaultHighlighter.DefaultHighlightPainter(c2));
 670                 }
 671             } catch (BadLocationException e) {
 672                 e.printStackTrace();
 673             }
 674         }
 675 
 676         /** Get the minimum valid position in a set of info objects. */
 677         private int getMinPos(Info... values) {
 678             int i = Integer.MAX_VALUE;
 679             for (Info info: values) {
 680                 if (info.start >= 0) i = Math.min(i, info.start);
 681                 if (info.pos   >= 0) i = Math.min(i, info.pos);
 682                 if (info.end   >= 0) i = Math.min(i, info.end);
 683             }
 684             return (i == Integer.MAX_VALUE) ? 0 : i;
 685         }
 686 
 687         /** Set the background on a component. */
 688         private JComponent setBackground(JComponent comp, Color c) {
 689             comp.setOpaque(true);
 690             comp.setBackground(c);
 691             return comp;
 692         }
 693 
 694         /** Scroll a text area to display a given position near the middle of the visible area. */
 695         private void scroll(final JTextArea t, final int pos) {
 696             // Using invokeLater appears to give text a chance to sort itself out
 697             // before the scroll happens; otherwise scrollRectToVisible doesn't work.
 698             // Maybe there's a better way to sync with the text...
 699             EventQueue.invokeLater(new Runnable() {
 700                 public void run() {
 701                     try {
 702                         Rectangle r = t.modelToView(pos);
 703                         JScrollPane p = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, t);
 704                         r.y = Math.max(0, r.y - p.getHeight() * 2 / 5);
 705                         r.height += p.getHeight() * 4 / 5;
 706                         t.scrollRectToVisible(r);
 707                     } catch (BadLocationException ignore) {
 708                     }
 709                 }
 710             });
 711         }
 712 
 713         private JComboBox entries;
 714         private JTextField checkField;
 715         private InfoPanel enclPanel;
 716         private InfoPanel selfPanel;
 717         private JTextArea body;
 718         private JTextField statusText;
 719 
 720         private Color selfColor = new Color(0.f, 1.f, 0.f, 0.2f); // 20% green
 721         private Color enclColor = new Color(1.f, 0.f, 0.f, 0.2f); // 20% red
 722 
 723         /** Panel to display an Info object. */
 724         private class InfoPanel extends JPanel {
 725             InfoPanel() {
 726                 add(tagName = createTextField(20));
 727                 add(new JLabel("start:"));
 728                 add(addListener(start = createTextField(6)));
 729                 add(new JLabel("pos:"));
 730                 add(addListener(pos = createTextField(6)));
 731                 add(new JLabel("end:"));
 732                 add(addListener(end = createTextField(6)));
 733             }
 734 
 735             void setInfo(Info info) {
 736                 this.info = info;
 737                 tagName.setText(treeUtil.nameFromTag(info.tag));
 738                 start.setText(String.valueOf(info.start));
 739                 pos.setText(String.valueOf(info.pos));
 740                 end.setText(String.valueOf(info.end));
 741             }
 742 
 743             JTextField addListener(final JTextField f) {
 744                 f.addMouseListener(new MouseAdapter() {
 745                     @Override
 746                     public void mouseClicked(MouseEvent e) {
 747                         body.setCaretPosition(Integer.valueOf(f.getText()));
 748                         body.getCaret().setVisible(true);
 749                     }
 750                 });
 751                 return f;
 752             }
 753 
 754             Info info;
 755             JTextField tagName;
 756             JTextField start;
 757             JTextField pos;
 758             JTextField end;
 759         }
 760 
 761         /** Object to record information about an error to be displayed. */
 762         private class Entry {
 763             Entry(JavaFileObject file, String check, Info encl, Info self) {
 764                 this.file = file;
 765                 this.check = check;
 766                 this.encl = encl;
 767                 this.self= self;
 768             }
 769 
 770             @Override
 771             public String toString() {
 772                 return file.getName() + " " + check + " " + getMinPos(encl, self);
 773             }
 774 
 775             final JavaFileObject file;
 776             final String check;
 777             final Info encl;
 778             final Info self;
 779         }
 780     }
 781 
 782     /** Number of files that have been analyzed. */
 783     static AtomicInteger fileCount = new AtomicInteger();
 784 
 785 }