jjg@695: /* jjg@695: * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. jjg@695: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@695: * jjg@695: * This code is free software; you can redistribute it and/or modify it jjg@695: * under the terms of the GNU General Public License version 2 only, as jjg@695: * published by the Free Software Foundation. jjg@695: * jjg@695: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@695: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@695: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@695: * version 2 for more details (a copy is included in the LICENSE file that jjg@695: * accompanied this code). jjg@695: * jjg@695: * You should have received a copy of the GNU General Public License version jjg@695: * 2 along with this work; if not, write to the Free Software Foundation, jjg@695: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@695: * jjg@695: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jjg@695: * or visit www.oracle.com if you need additional information or have any jjg@695: * questions. jjg@695: */ jjg@695: jjg@695: /* jjg@695: * @test jjg@696: * @bug 6985205 6986246 jjg@695: * @summary access to tree positions and doc comments may be lost across annotation processing rounds jjg@695: * @build TreePosRoundsTest jjg@695: * @compile -proc:only -processor TreePosRoundsTest TreePosRoundsTest.java jjg@695: * @run main TreePosRoundsTest jjg@695: */ jjg@695: jjg@695: import java.io.*; jjg@695: import java.util.*; jjg@695: import javax.annotation.processing.*; jjg@695: import javax.lang.model.*; jjg@695: import javax.lang.model.element.*; jjg@695: import javax.tools.*; jjg@695: jjg@695: import com.sun.source.tree.*; jjg@695: import com.sun.source.util.*; jjg@695: import javax.tools.JavaCompiler.CompilationTask; jjg@695: jjg@695: // This test is an annotation processor that performs multiple rounds of jjg@695: // processing, and on each round, it checks that source positions are jjg@695: // available and correct. jjg@695: // jjg@695: // The test can be run directly as a processor from the javac command line jjg@695: // or via JSR 199 by invoking the main program. jjg@695: jjg@695: @SupportedAnnotationTypes("*") jjg@695: public class TreePosRoundsTest extends AbstractProcessor { jjg@695: public static void main(String... args) throws Exception { jjg@695: String testSrc = System.getProperty("test.src"); jjg@695: String testClasses = System.getProperty("test.classes"); jjg@695: JavaCompiler c = ToolProvider.getSystemJavaCompiler(); jjg@695: StandardJavaFileManager fm = c.getStandardFileManager(null, null, null); jjg@695: String thisName = TreePosRoundsTest.class.getName(); jjg@695: File thisFile = new File(testSrc, thisName + ".java"); jjg@695: Iterable files = fm.getJavaFileObjects(thisFile); jjg@695: List options = Arrays.asList( jjg@695: "-proc:only", jjg@695: "-processor", thisName, jjg@695: "-processorpath", testClasses); jjg@695: CompilationTask t = c.getTask(null, fm, null, options, null, files); jjg@695: boolean ok = t.call(); jjg@695: if (!ok) jjg@695: throw new Exception("processing failed"); jjg@695: } jjg@695: jjg@695: Filer filer; jjg@695: Messager messager; jjg@696: Trees trees; jjg@695: jjg@695: @Override jjg@695: public SourceVersion getSupportedSourceVersion() { jjg@695: return SourceVersion.latest(); jjg@695: } jjg@695: jjg@695: @Override jjg@695: public void init(ProcessingEnvironment pEnv) { jjg@695: super.init(pEnv); jjg@695: filer = pEnv.getFiler(); jjg@695: messager = pEnv.getMessager(); jjg@696: trees = Trees.instance(pEnv); jjg@695: } jjg@695: jjg@695: int round = 0; jjg@695: jjg@695: @Override jjg@695: public boolean process(Set annotations, RoundEnvironment roundEnv) { jjg@695: round++; jjg@695: jjg@695: // Scan trees for elements, verifying source tree positions jjg@695: for (Element e: roundEnv.getRootElements()) { jjg@695: try { jjg@695: TreePath p = trees.getPath(e); jjg@695: new TestTreeScanner(p.getCompilationUnit(), trees).scan(trees.getPath(e), null); jjg@695: } catch (IOException ex) { jjg@695: messager.printMessage(Diagnostic.Kind.ERROR, jjg@695: "Cannot get source: " + ex, e); jjg@695: } jjg@695: } jjg@695: jjg@695: final int MAXROUNDS = 3; jjg@695: if (round < MAXROUNDS) jjg@695: generateSource("Gen" + round); jjg@695: jjg@695: return true; jjg@695: } jjg@695: jjg@695: void generateSource(String name) { jjg@695: StringBuilder text = new StringBuilder(); jjg@695: text.append("class ").append(name).append("{\n"); jjg@695: text.append(" int one = 1;\n"); jjg@695: text.append(" int two = 2;\n"); jjg@695: text.append(" int three = one + two;\n"); jjg@695: text.append("}\n"); jjg@695: jjg@695: try { jjg@695: JavaFileObject fo = filer.createSourceFile(name); jjg@695: Writer out = fo.openWriter(); jjg@695: try { jjg@695: out.write(text.toString()); jjg@695: } finally { jjg@695: out.close(); jjg@695: } jjg@695: } catch (IOException e) { jjg@695: throw new Error(e); jjg@695: } jjg@695: } jjg@695: jjg@695: class TestTreeScanner extends TreePathScanner { jjg@695: TestTreeScanner(CompilationUnitTree unit, Trees trees) throws IOException { jjg@695: this.unit = unit; jjg@695: JavaFileObject sf = unit.getSourceFile(); jjg@695: source = sf.getCharContent(true).toString(); jjg@695: sourcePositions = trees.getSourcePositions(); jjg@695: } jjg@695: jjg@695: @Override jjg@695: public Void visitVariable(VariableTree tree, Void _) { jjg@695: check(getCurrentPath()); jjg@695: return super.visitVariable(tree, _); jjg@695: } jjg@695: jjg@695: void check(TreePath tp) { jjg@695: Tree tree = tp.getLeaf(); jjg@695: jjg@695: String expect = tree.toString(); jjg@695: if (tree.getKind() == Tree.Kind.VARIABLE) { jjg@695: // tree.toString() does not know enough context to add ";", jjg@695: // so deal with that manually... jjg@695: Tree.Kind enclKind = tp.getParentPath().getLeaf().getKind(); jjg@695: //System.err.println(" encl: " +enclKind); jjg@695: if (enclKind == Tree.Kind.CLASS || enclKind == Tree.Kind.BLOCK) jjg@695: expect += ";"; jjg@695: } jjg@695: //System.err.println("expect: " + expect); jjg@695: jjg@695: int start = (int)sourcePositions.getStartPosition(unit, tree); jjg@695: if (start == Diagnostic.NOPOS) { jjg@695: messager.printMessage(Diagnostic.Kind.ERROR, "start pos not set for " + trim(tree)); jjg@695: return; jjg@695: } jjg@695: jjg@695: int end = (int)sourcePositions.getEndPosition(unit, tree); jjg@695: if (end == Diagnostic.NOPOS) { jjg@695: messager.printMessage(Diagnostic.Kind.ERROR, "end pos not set for " + trim(tree)); jjg@695: return; jjg@695: } jjg@695: jjg@695: String found = source.substring(start, end); jjg@695: //System.err.println(" found: " + found); jjg@695: jjg@695: // allow for long lines, in which case just compare beginning and jjg@695: // end of the strings jjg@695: boolean equal; jjg@695: if (found.contains("\n")) { jjg@695: String head = found.substring(0, found.indexOf("\n")); jjg@695: String tail = found.substring(found.lastIndexOf("\n")).trim(); jjg@695: equal = expect.startsWith(head) && expect.endsWith(tail); jjg@695: } else { jjg@695: equal = expect.equals(found); jjg@695: } jjg@695: jjg@695: if (!equal) { jjg@695: messager.printMessage(Diagnostic.Kind.ERROR, jjg@695: "unexpected value found: '" + found + "'; expected: '" + expect + "'"); jjg@695: } jjg@695: } jjg@695: jjg@695: String trim(Tree tree) { jjg@695: final int MAXLEN = 32; jjg@695: String s = tree.toString().replaceAll("\\s+", " ").trim(); jjg@695: return (s.length() < MAXLEN) ? s : s.substring(0, MAXLEN); jjg@695: jjg@695: } jjg@695: jjg@695: CompilationUnitTree unit; jjg@695: SourcePositions sourcePositions; jjg@695: String source; jjg@695: } jjg@695: jjg@695: }