3789983e89
Reviewed-by: darcy, ihse
367 lines
13 KiB
Java
367 lines
13 KiB
Java
/*
|
|
* Copyright (c) 2014, 2015, 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
|
|
* @modules jdk.compiler/com.sun.tools.javac.api
|
|
* jdk.compiler/com.sun.tools.javac.code
|
|
* jdk.compiler/com.sun.tools.javac.file
|
|
* jdk.compiler/com.sun.tools.javac.tree
|
|
* jdk.compiler/com.sun.tools.javac.util
|
|
* @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 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;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|