--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/importscope/dependencies/DependenciesTest.java Wed Dec 03 13:46:12 2014 +0100
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 7101822
+ * @summary Verify that the processing of classes in TypeEnter runs in the correct order.
+ * @library /tools/lib
+ * @build annotations.TriggersComplete annotations.TriggersCompleteRepeat annotations.Phase
+ * @build DependenciesTest
+ * @run main DependenciesTest
+ */
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Stack;
+import java.util.stream.Stream;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+import annotations.*;
+import com.sun.source.tree.AnnotationTree;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePathScanner;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.api.JavacTrees;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Context.Factory;
+import com.sun.tools.javac.util.Dependencies;
+
+
+public class DependenciesTest {
+ public static void main(String... args) throws IOException {
+ new DependenciesTest().run();
+ }
+
+ void run() throws IOException {
+ Path src = Paths.get(System.getProperty("test.src"), "tests");
+
+ try (Stream<Path> tests = Files.list(src)) {
+ tests.map(p -> Files.isRegularFile(p) ? Stream.of(p) : silentWalk(p))
+ .forEach(this :: runTest);
+ }
+ }
+
+ Stream<Path> silentWalk(Path src) {
+ try {
+ return Files.walk(src).filter(Files :: isRegularFile);
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ void runTest(Stream<Path> inputs) {
+ JavacTool tool = JavacTool.create();
+ try (JavacFileManager fm = tool.getStandardFileManager(null, null, null)) {
+ Path classes = Paths.get(System.getProperty("test.classes"));
+ Iterable<? extends JavaFileObject> reconFiles =
+ fm.getJavaFileObjectsFromFiles(inputs.sorted().map(p -> p.toFile()) :: iterator);
+ List<String> options = Arrays.asList("-classpath", classes.toAbsolutePath().toString());
+ JavacTask reconTask = tool.getTask(null, fm, null, options, null, reconFiles);
+ Iterable<? extends CompilationUnitTree> reconUnits = reconTask.parse();
+ JavacTrees reconTrees = JavacTrees.instance(reconTask);
+ SearchAnnotations scanner = new SearchAnnotations(reconTrees,
+ reconTask.getElements());
+ List<JavaFileObject> validateFiles = new ArrayList<>();
+
+ reconTask.analyze();
+ scanner.scan(reconUnits, null);
+
+ for (CompilationUnitTree cut : reconUnits) {
+ validateFiles.add(ClearAnnotations.clearAnnotations(reconTrees, cut));
+ }
+
+ Context validateContext = new Context();
+ TestDependencies.preRegister(validateContext);
+ JavacTask validateTask =
+ tool.getTask(null, fm, null, options, null, validateFiles, validateContext);
+
+ validateTask.analyze();
+
+ TestDependencies deps = (TestDependencies) Dependencies.instance(validateContext);
+
+ if (!scanner.topLevel2Expected.equals(deps.topLevel2Completing)) {
+ throw new IllegalStateException( "expected=" + scanner.topLevel2Expected +
+ "; actual=" + deps.topLevel2Completing);
+ }
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ } finally {
+ inputs.close();
+ }
+ }
+
+ static final class TestDependencies extends Dependencies {
+
+ public static void preRegister(Context context) {
+ context.put(dependenciesKey, (Factory<Dependencies>) TestDependencies :: new);
+ }
+
+ public TestDependencies(Context context) {
+ super(context);
+ }
+
+ final Stack<PhaseDescription> inProcess = new Stack<>();
+
+ String topLevelMemberEnter;
+ Map<String, Set<PhaseDescription>> topLevel2Completing = new HashMap<>();
+
+ @Override
+ public void push(ClassSymbol s, CompletionCause phase) {
+ String flatname = s.flatName().toString();
+ for (Phase p : Phase.values()) {
+ if (phase == p.cause) {
+ inProcess.push(new PhaseDescription(flatname, p));
+ return ;
+ }
+ }
+ if (phase == CompletionCause.MEMBER_ENTER) {
+ if (inProcess.isEmpty()) {
+ topLevelMemberEnter = flatname;
+ } else {
+ for (PhaseDescription running : inProcess) {
+ if (running == null)
+ continue;
+
+ Set<PhaseDescription> completing =
+ topLevel2Completing.computeIfAbsent(running.flatname, $ -> new HashSet<>());
+
+ completing.add(new PhaseDescription(flatname, running.phase));
+ }
+ }
+ }
+ inProcess.push(null);
+ }
+
+ @Override
+ public void push(AttributionKind ak, JCTree t) {
+ inProcess.push(null);
+ }
+
+ @Override
+ public void pop() {
+ inProcess.pop();
+ }
+
+ }
+
+ static final class SearchAnnotations extends TreePathScanner<Void, Void> {
+ final Trees trees;
+ final Elements elements;
+ final TypeElement triggersCompleteAnnotation;
+ final TypeElement triggersCompleteRepeatAnnotation;
+ final Map<String, Set<PhaseDescription>> topLevel2Expected =
+ new HashMap<>();
+
+ public SearchAnnotations(Trees trees, Elements elements) {
+ this.trees = trees;
+ this.elements = elements;
+ this.triggersCompleteAnnotation =
+ elements.getTypeElement(TriggersComplete.class.getName());
+ this.triggersCompleteRepeatAnnotation =
+ elements.getTypeElement(TriggersCompleteRepeat.class.getName());
+ }
+
+ @Override
+ public Void visitClass(ClassTree node, Void p) {
+ TypeElement te = (TypeElement) trees.getElement(getCurrentPath());
+ Set<PhaseDescription> expected = new HashSet<>();
+
+ for (AnnotationMirror am : getTriggersCompleteAnnotation(te)) {
+ TypeMirror of = (TypeMirror) findAttribute(am, "of").getValue();
+ Name ofName = elements.getBinaryName((TypeElement) ((DeclaredType) of).asElement());
+ Element at = (Element) findAttribute(am, "at").getValue();
+ Phase phase = Phase.valueOf(at.getSimpleName().toString());
+ expected.add(new PhaseDescription(ofName.toString(), phase));
+ }
+
+ if (!expected.isEmpty())
+ topLevel2Expected.put(elements.getBinaryName(te).toString(), expected);
+
+ return super.visitClass(node, p);
+ }
+
+ Collection<AnnotationMirror> getTriggersCompleteAnnotation(TypeElement te) {
+ for (AnnotationMirror am : te.getAnnotationMirrors()) {
+ if (triggersCompleteAnnotation.equals(am.getAnnotationType().asElement())) {
+ return Collections.singletonList(am);
+ }
+ if (triggersCompleteRepeatAnnotation.equals(am.getAnnotationType().asElement())) {
+ return (Collection<AnnotationMirror>) findAttribute(am, "value").getValue();
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ AnnotationValue findAttribute(AnnotationMirror mirror, String name) {
+ for (Entry<? extends ExecutableElement, ? extends AnnotationValue> e :
+ mirror.getElementValues().entrySet()) {
+ if (e.getKey().getSimpleName().contentEquals(name)) {
+ return e.getValue();
+ }
+ }
+
+ throw new IllegalStateException("Could not find " + name + " in " + mirror);
+ }
+ }
+
+ static final class ClearAnnotations extends TreePathScanner<Void, Void> {
+ final SourcePositions positions;
+ final List<int[]> spans2Clear = new ArrayList<>();
+
+ ClearAnnotations(Trees trees) {
+ this.positions = trees.getSourcePositions();
+ }
+
+ @Override
+ public Void visitAnnotation(AnnotationTree node, Void p) {
+ removeCurrentNode();
+ return null;
+ }
+
+ @Override
+ public Void visitImport(ImportTree node, Void p) {
+ if (node.getQualifiedIdentifier().toString().startsWith("annotations.")) {
+ removeCurrentNode();
+ return null;
+ }
+ return super.visitImport(node, p);
+ }
+
+ void removeCurrentNode() {
+ CompilationUnitTree topLevel = getCurrentPath().getCompilationUnit();
+ Tree node = getCurrentPath().getLeaf();
+ spans2Clear.add(new int[] {(int) positions.getStartPosition(topLevel, node),
+ (int) positions.getEndPosition(topLevel, node)});
+ }
+
+ static JavaFileObject clearAnnotations(Trees trees, CompilationUnitTree cut)
+ throws IOException {
+ ClearAnnotations a = new ClearAnnotations(trees);
+ a.scan(cut, null);
+ Collections.sort(a.spans2Clear, (s1, s2) -> s2[0] - s1[0]);
+ StringBuilder result = new StringBuilder(cut.getSourceFile().getCharContent(true));
+ for (int[] toClear : a.spans2Clear) {
+ result.delete(toClear[0], toClear[1]);
+ }
+ return new TestJavaFileObject(cut.getSourceFile().toUri(), result.toString());
+ }
+
+ }
+
+ static final class PhaseDescription {
+ final String flatname;
+ final Phase phase;
+
+ public PhaseDescription(String flatname, Phase phase) {
+ this.flatname = flatname;
+ this.phase = phase;
+ }
+
+ @Override
+ public String toString() {
+ return "@annotations.TriggersComplete(of=" + flatname + ".class," +
+ "at=annotations.Phase." + phase + ')';
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 89 * hash + Objects.hashCode(this.flatname);
+ hash = 89 * hash + Objects.hashCode(this.phase);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PhaseDescription other = (PhaseDescription) obj;
+ if (!Objects.equals(this.flatname, other.flatname)) {
+ return false;
+ }
+ if (this.phase != other.phase) {
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+ static final class TestJavaFileObject extends SimpleJavaFileObject {
+ private final String content;
+
+ public TestJavaFileObject(URI uri, String content) {
+ super(uri, Kind.SOURCE);
+ this.content = content;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return content;
+ }
+
+ }
+}
+
+