1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/test/tools/javac/failover/CheckAttributedTree.java Tue Sep 07 17:33:43 2010 +0100 1.3 @@ -0,0 +1,775 @@ 1.4 +/* 1.5 + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. 1.11 + * 1.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.15 + * version 2 for more details (a copy is included in the LICENSE file that 1.16 + * accompanied this code). 1.17 + * 1.18 + * You should have received a copy of the GNU General Public License version 1.19 + * 2 along with this work; if not, write to the Free Software Foundation, 1.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.21 + * 1.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.23 + * or visit www.oracle.com if you need additional information or have any 1.24 + * questions. 1.25 + */ 1.26 + 1.27 +import com.sun.source.util.TaskEvent; 1.28 +import java.awt.BorderLayout; 1.29 +import java.awt.Color; 1.30 +import java.awt.Dimension; 1.31 +import java.awt.EventQueue; 1.32 +import java.awt.Font; 1.33 +import java.awt.GridBagConstraints; 1.34 +import java.awt.GridBagLayout; 1.35 +import java.awt.Rectangle; 1.36 +import java.awt.event.ActionEvent; 1.37 +import java.awt.event.ActionListener; 1.38 +import java.awt.event.MouseAdapter; 1.39 +import java.awt.event.MouseEvent; 1.40 +import javax.swing.DefaultComboBoxModel; 1.41 +import javax.swing.JComboBox; 1.42 +import javax.swing.JComponent; 1.43 +import javax.swing.JFrame; 1.44 +import javax.swing.JLabel; 1.45 +import javax.swing.JPanel; 1.46 +import javax.swing.JScrollPane; 1.47 +import javax.swing.JTextArea; 1.48 +import javax.swing.JTextField; 1.49 +import javax.swing.SwingUtilities; 1.50 +import javax.swing.event.CaretEvent; 1.51 +import javax.swing.event.CaretListener; 1.52 +import javax.swing.text.BadLocationException; 1.53 +import javax.swing.text.DefaultHighlighter; 1.54 +import javax.swing.text.Highlighter; 1.55 +import java.io.File; 1.56 +import java.io.IOException; 1.57 +import java.io.PrintStream; 1.58 +import java.io.PrintWriter; 1.59 +import java.io.StringWriter; 1.60 +import java.lang.reflect.Field; 1.61 +import java.lang.reflect.Modifier; 1.62 +import java.nio.charset.Charset; 1.63 +import java.util.ArrayList; 1.64 +import java.util.HashMap; 1.65 +import java.util.List; 1.66 +import java.util.Map; 1.67 +import javax.tools.Diagnostic; 1.68 +import javax.tools.DiagnosticListener; 1.69 +import javax.tools.JavaFileObject; 1.70 +import javax.tools.StandardJavaFileManager; 1.71 + 1.72 +import com.sun.source.tree.CompilationUnitTree; 1.73 +import com.sun.source.util.JavacTask; 1.74 +import com.sun.source.util.TaskListener; 1.75 +import com.sun.tools.javac.api.JavacTool; 1.76 +import com.sun.tools.javac.code.Symbol; 1.77 +import com.sun.tools.javac.code.Type; 1.78 +import com.sun.tools.javac.tree.JCTree; 1.79 +import com.sun.tools.javac.tree.JCTree.JCClassDecl; 1.80 +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 1.81 +import com.sun.tools.javac.tree.JCTree.JCImport; 1.82 +import com.sun.tools.javac.tree.TreeInfo; 1.83 +import com.sun.tools.javac.tree.TreeScanner; 1.84 +import com.sun.tools.javac.util.Pair; 1.85 + 1.86 +import java.util.Arrays; 1.87 +import java.util.HashSet; 1.88 +import java.util.Set; 1.89 +import javax.lang.model.element.Element; 1.90 + 1.91 +/** 1.92 + * Utility and test program to check validity of tree positions for tree nodes. 1.93 + * The program can be run standalone, or as a jtreg test. In standalone mode, 1.94 + * errors can be displayed in a gui viewer. For info on command line args, 1.95 + * run program with no args. 1.96 + * 1.97 + * <p> 1.98 + * jtreg: Note that by using the -r switch in the test description below, this test 1.99 + * will process all java files in the langtools/test directory, thus implicitly 1.100 + * covering any new language features that may be tested in this test suite. 1.101 + */ 1.102 + 1.103 +/* 1.104 + * @test 1.105 + * @bug 6970584 1.106 + * @summary assorted position errors in compiler syntax trees 1.107 + * @run main CheckAttributedTree -q -r -et ERRONEOUS . 1.108 + */ 1.109 +public class CheckAttributedTree { 1.110 + /** 1.111 + * Main entry point. 1.112 + * If test.src is set, program runs in jtreg mode, and will throw an Error 1.113 + * if any errors arise, otherwise System.exit will be used, unless the gui 1.114 + * viewer is being used. In jtreg mode, the default base directory for file 1.115 + * args is the value of ${test.src}. In jtreg mode, the -r option can be 1.116 + * given to change the default base directory to the root test directory. 1.117 + */ 1.118 + public static void main(String... args) { 1.119 + String testSrc = System.getProperty("test.src"); 1.120 + File baseDir = (testSrc == null) ? null : new File(testSrc); 1.121 + boolean ok = new CheckAttributedTree().run(baseDir, args); 1.122 + if (!ok) { 1.123 + if (testSrc != null) // jtreg mode 1.124 + throw new Error("failed"); 1.125 + else 1.126 + System.exit(1); 1.127 + } 1.128 + } 1.129 + 1.130 + /** 1.131 + * Run the program. A base directory can be provided for file arguments. 1.132 + * In jtreg mode, the -r option can be given to change the default base 1.133 + * directory to the test root directory. For other options, see usage(). 1.134 + * @param baseDir base directory for any file arguments. 1.135 + * @param args command line args 1.136 + * @return true if successful or in gui mode 1.137 + */ 1.138 + boolean run(File baseDir, String... args) { 1.139 + if (args.length == 0) { 1.140 + usage(System.out); 1.141 + return true; 1.142 + } 1.143 + 1.144 + List<File> files = new ArrayList<File>(); 1.145 + for (int i = 0; i < args.length; i++) { 1.146 + String arg = args[i]; 1.147 + if (arg.equals("-encoding") && i + 1 < args.length) 1.148 + encoding = args[++i]; 1.149 + else if (arg.equals("-gui")) 1.150 + gui = true; 1.151 + else if (arg.equals("-q")) 1.152 + quiet = true; 1.153 + else if (arg.equals("-v")) 1.154 + verbose = true; 1.155 + else if (arg.equals("-t") && i + 1 < args.length) 1.156 + tags.add(args[++i]); 1.157 + else if (arg.equals("-ef") && i + 1 < args.length) 1.158 + excludeFiles.add(new File(baseDir, args[++i])); 1.159 + else if (arg.equals("-et") && i + 1 < args.length) 1.160 + excludeTags.add(args[++i]); 1.161 + else if (arg.equals("-r")) { 1.162 + if (excludeFiles.size() > 0) 1.163 + throw new Error("-r must be used before -ef"); 1.164 + File d = baseDir; 1.165 + while (!new File(d, "TEST.ROOT").exists()) { 1.166 + if (d == null) 1.167 + throw new Error("cannot find TEST.ROOT"); 1.168 + d = d.getParentFile(); 1.169 + } 1.170 + baseDir = d; 1.171 + } 1.172 + else if (arg.startsWith("-")) 1.173 + throw new Error("unknown option: " + arg); 1.174 + else { 1.175 + while (i < args.length) 1.176 + files.add(new File(baseDir, args[i++])); 1.177 + } 1.178 + } 1.179 + 1.180 + for (File file: files) { 1.181 + if (file.exists()) 1.182 + test(file); 1.183 + else 1.184 + error("File not found: " + file); 1.185 + } 1.186 + 1.187 + if (fileCount != 1) 1.188 + System.err.println(fileCount + " files read"); 1.189 + if (errors > 0) 1.190 + System.err.println(errors + " errors"); 1.191 + 1.192 + return (gui || errors == 0); 1.193 + } 1.194 + 1.195 + /** 1.196 + * Print command line help. 1.197 + * @param out output stream 1.198 + */ 1.199 + void usage(PrintStream out) { 1.200 + out.println("Usage:"); 1.201 + out.println(" java CheckAttributedTree options... files..."); 1.202 + out.println(""); 1.203 + out.println("where options include:"); 1.204 + out.println("-q Quiet: don't report on inapplicable files"); 1.205 + out.println("-gui Display returns in a GUI viewer"); 1.206 + out.println("-v Verbose: report on files as they are being read"); 1.207 + out.println("-t tag Limit checks to tree nodes with this tag"); 1.208 + out.println(" Can be repeated if desired"); 1.209 + out.println("-ef file Exclude file or directory"); 1.210 + out.println("-et tag Exclude tree nodes with given tag name"); 1.211 + out.println(""); 1.212 + out.println("files may be directories or files"); 1.213 + out.println("directories will be scanned recursively"); 1.214 + out.println("non java files, or java files which cannot be parsed, will be ignored"); 1.215 + out.println(""); 1.216 + } 1.217 + 1.218 + /** 1.219 + * Test a file. If the file is a directory, it will be recursively scanned 1.220 + * for java files. 1.221 + * @param file the file or directory to test 1.222 + */ 1.223 + void test(File file) { 1.224 + if (excludeFiles.contains(file)) { 1.225 + if (!quiet) 1.226 + error("File " + file + " excluded"); 1.227 + return; 1.228 + } 1.229 + 1.230 + if (file.isDirectory()) { 1.231 + for (File f: file.listFiles()) { 1.232 + test(f); 1.233 + } 1.234 + return; 1.235 + } 1.236 + 1.237 + if (file.isFile() && file.getName().endsWith(".java")) { 1.238 + try { 1.239 + if (verbose) 1.240 + System.err.println(file); 1.241 + fileCount++; 1.242 + NPETester p = new NPETester(); 1.243 + p.test(read(file)); 1.244 + } catch (AttributionException e) { 1.245 + if (!quiet) { 1.246 + error("Error attributing " + file + "\n" + e.getMessage()); 1.247 + } 1.248 + } catch (IOException e) { 1.249 + error("Error reading " + file + ": " + e); 1.250 + } 1.251 + return; 1.252 + } 1.253 + 1.254 + if (!quiet) 1.255 + error("File " + file + " ignored"); 1.256 + } 1.257 + 1.258 + /** 1.259 + * Read a file. 1.260 + * @param file the file to be read 1.261 + * @return the tree for the content of the file 1.262 + * @throws IOException if any IO errors occur 1.263 + * @throws TreePosTest.ParseException if any errors occur while parsing the file 1.264 + */ 1.265 + List<Pair<JCCompilationUnit, JCTree>> read(File file) throws IOException, AttributionException { 1.266 + StringWriter sw = new StringWriter(); 1.267 + PrintWriter pw = new PrintWriter(sw); 1.268 + Reporter r = new Reporter(pw); 1.269 + JavacTool tool = JavacTool.create(); 1.270 + Charset cs = (encoding == null ? null : Charset.forName(encoding)); 1.271 + StandardJavaFileManager fm = tool.getStandardFileManager(r, null, null); 1.272 + Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(file); 1.273 + String[] opts = { "-XDshouldStopPolicy=ATTR", "-XDverboseCompilePolicy" }; 1.274 + JavacTask task = tool.getTask(pw, fm, r, Arrays.asList(opts), null, files); 1.275 + final List<Element> analyzedElems = new ArrayList<>(); 1.276 + task.setTaskListener(new TaskListener() { 1.277 + public void started(TaskEvent e) { 1.278 + if (e.getKind() == TaskEvent.Kind.ANALYZE) 1.279 + analyzedElems.add(e.getTypeElement()); 1.280 + } 1.281 + public void finished(TaskEvent e) { } 1.282 + }); 1.283 + 1.284 + try { 1.285 + Iterable<? extends CompilationUnitTree> trees = task.parse(); 1.286 + task.analyze(); 1.287 + List<Pair<JCCompilationUnit, JCTree>> res = new ArrayList<>(); 1.288 + System.out.println("Try to add pairs. Elems are " + analyzedElems); 1.289 + for (CompilationUnitTree t : trees) { 1.290 + JCCompilationUnit cu = (JCCompilationUnit)t; 1.291 + for (JCTree def : cu.defs) { 1.292 + if (def.getTag() == JCTree.CLASSDEF && 1.293 + analyzedElems.contains(((JCTree.JCClassDecl)def).sym)) { 1.294 + System.out.println("Adding pair..."); 1.295 + res.add(new Pair<>(cu, def)); 1.296 + } 1.297 + } 1.298 + } 1.299 + return res; 1.300 + } 1.301 + catch (Throwable t) { 1.302 + throw new AttributionException("Exception while attributing file: " + file); 1.303 + } 1.304 + } 1.305 + 1.306 + /** 1.307 + * Report an error. When the program is complete, the program will either 1.308 + * exit or throw an Error if any errors have been reported. 1.309 + * @param msg the error message 1.310 + */ 1.311 + void error(String msg) { 1.312 + System.err.println(msg); 1.313 + errors++; 1.314 + } 1.315 + 1.316 + /** Number of files that have been analyzed. */ 1.317 + int fileCount; 1.318 + /** Number of errors reported. */ 1.319 + int errors; 1.320 + /** Flag: don't report irrelevant files. */ 1.321 + boolean quiet; 1.322 + /** Flag: show errors in GUI viewer. */ 1.323 + boolean gui; 1.324 + /** The GUI viewer for errors. */ 1.325 + Viewer viewer; 1.326 + /** Flag: report files as they are processed. */ 1.327 + boolean verbose; 1.328 + /** Option: encoding for test files. */ 1.329 + String encoding; 1.330 + /** The set of tags for tree nodes to be analyzed; if empty, all tree nodes 1.331 + * are analyzed. */ 1.332 + Set<String> tags = new HashSet<String>(); 1.333 + /** Set of files and directories to be excluded from analysis. */ 1.334 + Set<File> excludeFiles = new HashSet<File>(); 1.335 + /** Set of tag names to be excluded from analysis. */ 1.336 + Set<String> excludeTags = new HashSet<String>(); 1.337 + /** Utility class for trees */ 1.338 + TreeUtil treeUtil = new TreeUtil(); 1.339 + 1.340 + /** 1.341 + * Main class for testing assertions concerning types/symbol 1.342 + * left uninitialized after attribution 1.343 + */ 1.344 + private class NPETester extends TreeScanner { 1.345 + void test(List<Pair<JCCompilationUnit, JCTree>> trees) { 1.346 + for (Pair<JCCompilationUnit, JCTree> p : trees) { 1.347 + sourcefile = p.fst.sourcefile; 1.348 + endPosTable = p.fst.endPositions; 1.349 + encl = new Info(p.snd, endPosTable); 1.350 + p.snd.accept(this); 1.351 + } 1.352 + } 1.353 + 1.354 + @Override 1.355 + public void scan(JCTree tree) { 1.356 + if (tree == null || 1.357 + excludeTags.contains(treeUtil.nameFromTag(tree.getTag()))) { 1.358 + return; 1.359 + } 1.360 + 1.361 + Info self = new Info(tree, endPosTable); 1.362 + check(!mandatoryType(tree) || 1.363 + (tree.type != null && 1.364 + checkFields(tree)), 1.365 + "'null' found in tree ", 1.366 + self); 1.367 + 1.368 + Info prevEncl = encl; 1.369 + encl = self; 1.370 + tree.accept(this); 1.371 + encl = prevEncl; 1.372 + } 1.373 + 1.374 + private boolean mandatoryType(JCTree that) { 1.375 + return that instanceof JCTree.JCExpression || 1.376 + that.getTag() == JCTree.VARDEF || 1.377 + that.getTag() == JCTree.METHODDEF || 1.378 + that.getTag() == JCTree.CLASSDEF; 1.379 + } 1.380 + 1.381 + private final List<String> excludedFields = Arrays.asList("varargsElement"); 1.382 + 1.383 + void check(boolean ok, String label, Info self) { 1.384 + if (!ok) { 1.385 + if (gui) { 1.386 + if (viewer == null) 1.387 + viewer = new Viewer(); 1.388 + viewer.addEntry(sourcefile, label, encl, self); 1.389 + } 1.390 + error(label + self.toString() + " encl: " + encl.toString() + " in file: " + sourcefile + " " + self.tree); 1.391 + } 1.392 + } 1.393 + 1.394 + boolean checkFields(JCTree t) { 1.395 + List<Field> fieldsToCheck = treeUtil.getFieldsOfType(t, 1.396 + excludedFields, 1.397 + Symbol.class, 1.398 + Type.class); 1.399 + for (Field f : fieldsToCheck) { 1.400 + try { 1.401 + if (f.get(t) == null) { 1.402 + return false; 1.403 + } 1.404 + } 1.405 + catch (IllegalAccessException e) { 1.406 + System.err.println("Cannot read field: " + f); 1.407 + //swallow it 1.408 + } 1.409 + } 1.410 + return true; 1.411 + } 1.412 + 1.413 + @Override 1.414 + public void visitImport(JCImport tree) { } 1.415 + 1.416 + @Override 1.417 + public void visitTopLevel(JCCompilationUnit tree) { 1.418 + scan(tree.defs); 1.419 + } 1.420 + 1.421 + JavaFileObject sourcefile; 1.422 + Map<JCTree, Integer> endPosTable; 1.423 + Info encl; 1.424 + } 1.425 + 1.426 + /** 1.427 + * Utility class providing easy access to position and other info for a tree node. 1.428 + */ 1.429 + private class Info { 1.430 + Info() { 1.431 + tree = null; 1.432 + tag = JCTree.ERRONEOUS; 1.433 + start = 0; 1.434 + pos = 0; 1.435 + end = Integer.MAX_VALUE; 1.436 + } 1.437 + 1.438 + Info(JCTree tree, Map<JCTree, Integer> endPosTable) { 1.439 + this.tree = tree; 1.440 + tag = tree.getTag(); 1.441 + start = TreeInfo.getStartPos(tree); 1.442 + pos = tree.pos; 1.443 + end = TreeInfo.getEndPos(tree, endPosTable); 1.444 + } 1.445 + 1.446 + @Override 1.447 + public String toString() { 1.448 + return treeUtil.nameFromTag(tree.getTag()) + "[start:" + start + ",pos:" + pos + ",end:" + end + "]"; 1.449 + } 1.450 + 1.451 + final JCTree tree; 1.452 + final int tag; 1.453 + final int start; 1.454 + final int pos; 1.455 + final int end; 1.456 + } 1.457 + 1.458 + /** 1.459 + * Names for tree tags. 1.460 + * javac does not provide an API to convert tag values to strings, so this class uses 1.461 + * reflection to determine names of public static final int values in JCTree. 1.462 + */ 1.463 + private static class TreeUtil { 1.464 + String nameFromTag(int tag) { 1.465 + if (names == null) { 1.466 + names = new HashMap<Integer, String>(); 1.467 + Class c = JCTree.class; 1.468 + for (Field f : c.getDeclaredFields()) { 1.469 + if (f.getType().equals(int.class)) { 1.470 + int mods = f.getModifiers(); 1.471 + if (Modifier.isPublic(mods) && Modifier.isStatic(mods) && Modifier.isFinal(mods)) { 1.472 + try { 1.473 + names.put(f.getInt(null), f.getName()); 1.474 + } catch (IllegalAccessException e) { 1.475 + } 1.476 + } 1.477 + } 1.478 + } 1.479 + } 1.480 + String name = names.get(tag); 1.481 + return (name == null) ? "??" : name; 1.482 + } 1.483 + 1.484 + List<Field> getFieldsOfType(JCTree t, List<String> excludeNames, Class<?>... types) { 1.485 + List<Field> buf = new ArrayList<Field>(); 1.486 + for (Field f : t.getClass().getDeclaredFields()) { 1.487 + if (!excludeNames.contains(f.getName())) { 1.488 + for (Class<?> type : types) { 1.489 + if (type.isAssignableFrom(f.getType())) { 1.490 + f.setAccessible(true); 1.491 + buf.add(f); 1.492 + break; 1.493 + } 1.494 + } 1.495 + } 1.496 + } 1.497 + return buf; 1.498 + } 1.499 + 1.500 + private Map<Integer, String> names; 1.501 + } 1.502 + 1.503 + /** 1.504 + * Thrown when errors are found parsing a java file. 1.505 + */ 1.506 + private static class ParseException extends Exception { 1.507 + ParseException(String msg) { 1.508 + super(msg); 1.509 + } 1.510 + } 1.511 + 1.512 + private static class AttributionException extends Exception { 1.513 + AttributionException(String msg) { 1.514 + super(msg); 1.515 + } 1.516 + } 1.517 + 1.518 + /** 1.519 + * DiagnosticListener to report diagnostics and count any errors that occur. 1.520 + */ 1.521 + private static class Reporter implements DiagnosticListener<JavaFileObject> { 1.522 + Reporter(PrintWriter out) { 1.523 + this.out = out; 1.524 + } 1.525 + 1.526 + public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 1.527 + out.println(diagnostic); 1.528 + switch (diagnostic.getKind()) { 1.529 + case ERROR: 1.530 + errors++; 1.531 + } 1.532 + } 1.533 + int errors; 1.534 + PrintWriter out; 1.535 + } 1.536 + 1.537 + /** 1.538 + * GUI viewer for issues found by TreePosTester. The viewer provides a drop 1.539 + * down list for selecting error conditions, a header area providing details 1.540 + * about an error, and a text area with the ranges of text highlighted as 1.541 + * appropriate. 1.542 + */ 1.543 + private class Viewer extends JFrame { 1.544 + /** 1.545 + * Create a viewer. 1.546 + */ 1.547 + Viewer() { 1.548 + initGUI(); 1.549 + } 1.550 + 1.551 + /** 1.552 + * Add another entry to the list of errors. 1.553 + * @param file The file containing the error 1.554 + * @param check The condition that was being tested, and which failed 1.555 + * @param encl the enclosing tree node 1.556 + * @param self the tree node containing the error 1.557 + */ 1.558 + void addEntry(JavaFileObject file, String check, Info encl, Info self) { 1.559 + Entry e = new Entry(file, check, encl, self); 1.560 + DefaultComboBoxModel m = (DefaultComboBoxModel) entries.getModel(); 1.561 + m.addElement(e); 1.562 + if (m.getSize() == 1) 1.563 + entries.setSelectedItem(e); 1.564 + } 1.565 + 1.566 + /** 1.567 + * Initialize the GUI window. 1.568 + */ 1.569 + private void initGUI() { 1.570 + JPanel head = new JPanel(new GridBagLayout()); 1.571 + GridBagConstraints lc = new GridBagConstraints(); 1.572 + GridBagConstraints fc = new GridBagConstraints(); 1.573 + fc.anchor = GridBagConstraints.WEST; 1.574 + fc.fill = GridBagConstraints.HORIZONTAL; 1.575 + fc.gridwidth = GridBagConstraints.REMAINDER; 1.576 + 1.577 + entries = new JComboBox(); 1.578 + entries.addActionListener(new ActionListener() { 1.579 + public void actionPerformed(ActionEvent e) { 1.580 + showEntry((Entry) entries.getSelectedItem()); 1.581 + } 1.582 + }); 1.583 + fc.insets.bottom = 10; 1.584 + head.add(entries, fc); 1.585 + fc.insets.bottom = 0; 1.586 + head.add(new JLabel("check:"), lc); 1.587 + head.add(checkField = createTextField(80), fc); 1.588 + fc.fill = GridBagConstraints.NONE; 1.589 + head.add(setBackground(new JLabel("encl:"), enclColor), lc); 1.590 + head.add(enclPanel = new InfoPanel(), fc); 1.591 + head.add(setBackground(new JLabel("self:"), selfColor), lc); 1.592 + head.add(selfPanel = new InfoPanel(), fc); 1.593 + add(head, BorderLayout.NORTH); 1.594 + 1.595 + body = new JTextArea(); 1.596 + body.setFont(Font.decode(Font.MONOSPACED)); 1.597 + body.addCaretListener(new CaretListener() { 1.598 + public void caretUpdate(CaretEvent e) { 1.599 + int dot = e.getDot(); 1.600 + int mark = e.getMark(); 1.601 + if (dot == mark) 1.602 + statusText.setText("dot: " + dot); 1.603 + else 1.604 + statusText.setText("dot: " + dot + ", mark:" + mark); 1.605 + } 1.606 + }); 1.607 + JScrollPane p = new JScrollPane(body, 1.608 + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 1.609 + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 1.610 + p.setPreferredSize(new Dimension(640, 480)); 1.611 + add(p, BorderLayout.CENTER); 1.612 + 1.613 + statusText = createTextField(80); 1.614 + add(statusText, BorderLayout.SOUTH); 1.615 + 1.616 + pack(); 1.617 + setLocationRelativeTo(null); // centered on screen 1.618 + setVisible(true); 1.619 + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 1.620 + } 1.621 + 1.622 + /** Show an entry that has been selected. */ 1.623 + private void showEntry(Entry e) { 1.624 + try { 1.625 + // update simple fields 1.626 + setTitle(e.file.getName()); 1.627 + checkField.setText(e.check); 1.628 + enclPanel.setInfo(e.encl); 1.629 + selfPanel.setInfo(e.self); 1.630 + // show file text with highlights 1.631 + body.setText(e.file.getCharContent(true).toString()); 1.632 + Highlighter highlighter = body.getHighlighter(); 1.633 + highlighter.removeAllHighlights(); 1.634 + addHighlight(highlighter, e.encl, enclColor); 1.635 + addHighlight(highlighter, e.self, selfColor); 1.636 + scroll(body, getMinPos(enclPanel.info, selfPanel.info)); 1.637 + } catch (IOException ex) { 1.638 + body.setText("Cannot read " + e.file.getName() + ": " + e); 1.639 + } 1.640 + } 1.641 + 1.642 + /** Create a test field. */ 1.643 + private JTextField createTextField(int width) { 1.644 + JTextField f = new JTextField(width); 1.645 + f.setEditable(false); 1.646 + f.setBorder(null); 1.647 + return f; 1.648 + } 1.649 + 1.650 + /** Add a highlighted region based on the positions in an Info object. */ 1.651 + private void addHighlight(Highlighter h, Info info, Color c) { 1.652 + int start = info.start; 1.653 + int end = info.end; 1.654 + if (start == -1 && end == -1) 1.655 + return; 1.656 + if (start == -1) 1.657 + start = end; 1.658 + if (end == -1) 1.659 + end = start; 1.660 + try { 1.661 + h.addHighlight(info.start, info.end, 1.662 + new DefaultHighlighter.DefaultHighlightPainter(c)); 1.663 + if (info.pos != -1) { 1.664 + Color c2 = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int)(.4f * 255)); // 40% 1.665 + h.addHighlight(info.pos, info.pos + 1, 1.666 + new DefaultHighlighter.DefaultHighlightPainter(c2)); 1.667 + } 1.668 + } catch (BadLocationException e) { 1.669 + e.printStackTrace(); 1.670 + } 1.671 + } 1.672 + 1.673 + /** Get the minimum valid position in a set of info objects. */ 1.674 + private int getMinPos(Info... values) { 1.675 + int i = Integer.MAX_VALUE; 1.676 + for (Info info: values) { 1.677 + if (info.start >= 0) i = Math.min(i, info.start); 1.678 + if (info.pos >= 0) i = Math.min(i, info.pos); 1.679 + if (info.end >= 0) i = Math.min(i, info.end); 1.680 + } 1.681 + return (i == Integer.MAX_VALUE) ? 0 : i; 1.682 + } 1.683 + 1.684 + /** Set the background on a component. */ 1.685 + private JComponent setBackground(JComponent comp, Color c) { 1.686 + comp.setOpaque(true); 1.687 + comp.setBackground(c); 1.688 + return comp; 1.689 + } 1.690 + 1.691 + /** Scroll a text area to display a given position near the middle of the visible area. */ 1.692 + private void scroll(final JTextArea t, final int pos) { 1.693 + // Using invokeLater appears to give text a chance to sort itself out 1.694 + // before the scroll happens; otherwise scrollRectToVisible doesn't work. 1.695 + // Maybe there's a better way to sync with the text... 1.696 + EventQueue.invokeLater(new Runnable() { 1.697 + public void run() { 1.698 + try { 1.699 + Rectangle r = t.modelToView(pos); 1.700 + JScrollPane p = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, t); 1.701 + r.y = Math.max(0, r.y - p.getHeight() * 2 / 5); 1.702 + r.height += p.getHeight() * 4 / 5; 1.703 + t.scrollRectToVisible(r); 1.704 + } catch (BadLocationException ignore) { 1.705 + } 1.706 + } 1.707 + }); 1.708 + } 1.709 + 1.710 + private JComboBox entries; 1.711 + private JTextField checkField; 1.712 + private InfoPanel enclPanel; 1.713 + private InfoPanel selfPanel; 1.714 + private JTextArea body; 1.715 + private JTextField statusText; 1.716 + 1.717 + private Color selfColor = new Color(0.f, 1.f, 0.f, 0.2f); // 20% green 1.718 + private Color enclColor = new Color(1.f, 0.f, 0.f, 0.2f); // 20% red 1.719 + 1.720 + /** Panel to display an Info object. */ 1.721 + private class InfoPanel extends JPanel { 1.722 + InfoPanel() { 1.723 + add(tagName = createTextField(20)); 1.724 + add(new JLabel("start:")); 1.725 + add(addListener(start = createTextField(6))); 1.726 + add(new JLabel("pos:")); 1.727 + add(addListener(pos = createTextField(6))); 1.728 + add(new JLabel("end:")); 1.729 + add(addListener(end = createTextField(6))); 1.730 + } 1.731 + 1.732 + void setInfo(Info info) { 1.733 + this.info = info; 1.734 + tagName.setText(treeUtil.nameFromTag(info.tag)); 1.735 + start.setText(String.valueOf(info.start)); 1.736 + pos.setText(String.valueOf(info.pos)); 1.737 + end.setText(String.valueOf(info.end)); 1.738 + } 1.739 + 1.740 + JTextField addListener(final JTextField f) { 1.741 + f.addMouseListener(new MouseAdapter() { 1.742 + @Override 1.743 + public void mouseClicked(MouseEvent e) { 1.744 + body.setCaretPosition(Integer.valueOf(f.getText())); 1.745 + body.getCaret().setVisible(true); 1.746 + } 1.747 + }); 1.748 + return f; 1.749 + } 1.750 + 1.751 + Info info; 1.752 + JTextField tagName; 1.753 + JTextField start; 1.754 + JTextField pos; 1.755 + JTextField end; 1.756 + } 1.757 + 1.758 + /** Object to record information about an error to be displayed. */ 1.759 + private class Entry { 1.760 + Entry(JavaFileObject file, String check, Info encl, Info self) { 1.761 + this.file = file; 1.762 + this.check = check; 1.763 + this.encl = encl; 1.764 + this.self= self; 1.765 + } 1.766 + 1.767 + @Override 1.768 + public String toString() { 1.769 + return file.getName() + " " + check + " " + getMinPos(encl, self); 1.770 + } 1.771 + 1.772 + final JavaFileObject file; 1.773 + final String check; 1.774 + final Info encl; 1.775 + final Info self; 1.776 + } 1.777 + } 1.778 +}