DependenciesTest.java revision 3066:820841f0e8bd
1/*
2 * Copyright (c) 2014, 2015, 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 7101822
27 * @summary Verify that the processing of classes in TypeEnter runs in the correct order.
28 * @library /tools/lib
29 * @modules jdk.compiler/com.sun.tools.javac.api
30 *          jdk.compiler/com.sun.tools.javac.code
31 *          jdk.compiler/com.sun.tools.javac.file
32 *          jdk.compiler/com.sun.tools.javac.tree
33 *          jdk.compiler/com.sun.tools.javac.util
34 * @build annotations.TriggersComplete annotations.TriggersCompleteRepeat annotations.Phase
35 * @build DependenciesTest
36 * @run main DependenciesTest
37 */
38
39import java.io.IOException;
40import java.net.URI;
41import java.nio.file.Files;
42import java.nio.file.Path;
43import java.nio.file.Paths;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Collection;
47import java.util.Collections;
48import java.util.HashMap;
49import java.util.HashSet;
50import java.util.List;
51import java.util.Map;
52import java.util.Map.Entry;
53import java.util.Objects;
54import java.util.Set;
55import java.util.Stack;
56import java.util.stream.Stream;
57
58import javax.lang.model.element.AnnotationMirror;
59import javax.lang.model.element.AnnotationValue;
60import javax.lang.model.element.Element;
61import javax.lang.model.element.ExecutableElement;
62import javax.lang.model.element.Name;
63import javax.lang.model.element.TypeElement;
64import javax.lang.model.type.DeclaredType;
65import javax.lang.model.type.TypeMirror;
66import javax.lang.model.util.Elements;
67import javax.tools.JavaFileObject;
68import javax.tools.SimpleJavaFileObject;
69
70import annotations.*;
71import com.sun.source.tree.AnnotationTree;
72
73import com.sun.source.tree.ClassTree;
74import com.sun.source.tree.CompilationUnitTree;
75import com.sun.source.tree.ImportTree;
76import com.sun.source.tree.Tree;
77import com.sun.source.util.JavacTask;
78import com.sun.source.util.SourcePositions;
79import com.sun.source.util.TreePathScanner;
80import com.sun.source.util.Trees;
81import com.sun.tools.javac.api.JavacTool;
82import com.sun.tools.javac.api.JavacTrees;
83import com.sun.tools.javac.code.Symbol.ClassSymbol;
84import com.sun.tools.javac.file.JavacFileManager;
85import com.sun.tools.javac.tree.JCTree;
86import com.sun.tools.javac.util.Context;
87import com.sun.tools.javac.util.Context.Factory;
88import com.sun.tools.javac.util.Dependencies;
89
90
91public class DependenciesTest {
92    public static void main(String... args) throws IOException {
93        new DependenciesTest().run();
94    }
95
96    void run() throws IOException {
97        Path src = Paths.get(System.getProperty("test.src"), "tests");
98
99        try (Stream<Path> tests = Files.list(src)) {
100            tests.map(p -> Files.isRegularFile(p) ? Stream.of(p) : silentWalk(p))
101                 .forEach(this :: runTest);
102        }
103    }
104
105    Stream<Path> silentWalk(Path src) {
106        try {
107            return Files.walk(src).filter(Files :: isRegularFile);
108        } catch (IOException ex) {
109            throw new IllegalStateException(ex);
110        }
111    }
112
113    void runTest(Stream<Path> inputs) {
114        JavacTool tool = JavacTool.create();
115        try (JavacFileManager fm = tool.getStandardFileManager(null, null, null)) {
116            Path classes = Paths.get(System.getProperty("test.classes"));
117            Iterable<? extends JavaFileObject> reconFiles =
118                    fm.getJavaFileObjectsFromFiles(inputs.sorted().map(p -> p.toFile()) :: iterator);
119            List<String> options = Arrays.asList("-classpath", classes.toAbsolutePath().toString());
120            JavacTask reconTask = tool.getTask(null, fm, null, options, null, reconFiles);
121            Iterable<? extends CompilationUnitTree> reconUnits = reconTask.parse();
122            JavacTrees reconTrees = JavacTrees.instance(reconTask);
123            SearchAnnotations scanner = new SearchAnnotations(reconTrees,
124                                                              reconTask.getElements());
125            List<JavaFileObject> validateFiles = new ArrayList<>();
126
127            reconTask.analyze();
128            scanner.scan(reconUnits, null);
129
130            for (CompilationUnitTree cut : reconUnits) {
131                validateFiles.add(ClearAnnotations.clearAnnotations(reconTrees, cut));
132            }
133
134            Context validateContext = new Context();
135            TestDependencies.preRegister(validateContext);
136            JavacTask validateTask =
137                    tool.getTask(null, fm, null, options, null, validateFiles, validateContext);
138
139            validateTask.analyze();
140
141            TestDependencies deps = (TestDependencies) Dependencies.instance(validateContext);
142
143            if (!scanner.topLevel2Expected.equals(deps.topLevel2Completing)) {
144                throw new IllegalStateException(  "expected=" + scanner.topLevel2Expected +
145                                                "; actual=" + deps.topLevel2Completing);
146            }
147        } catch (IOException ex) {
148            throw new IllegalStateException(ex);
149        } finally {
150            inputs.close();
151        }
152    }
153
154    static final class TestDependencies extends Dependencies {
155
156        public static void preRegister(Context context) {
157            context.put(dependenciesKey, (Factory<Dependencies>) TestDependencies :: new);
158        }
159
160        public TestDependencies(Context context) {
161            super(context);
162        }
163
164        final Stack<PhaseDescription> inProcess = new Stack<>();
165
166        String topLevelMemberEnter;
167        Map<String, Set<PhaseDescription>> topLevel2Completing = new HashMap<>();
168
169        @Override
170        public void push(ClassSymbol s, CompletionCause phase) {
171            String flatname = s.flatName().toString();
172            for (Phase p : Phase.values()) {
173                if (phase == p.cause) {
174                    inProcess.push(new PhaseDescription(flatname, p));
175                    return ;
176                }
177            }
178            if (phase == CompletionCause.MEMBER_ENTER) {
179                if (inProcess.isEmpty()) {
180                    topLevelMemberEnter = flatname;
181                } else {
182                    for (PhaseDescription running : inProcess) {
183                        if (running == null)
184                            continue;
185
186                        Set<PhaseDescription> completing =
187                                topLevel2Completing.computeIfAbsent(running.flatname, $ -> new HashSet<>());
188
189                        completing.add(new PhaseDescription(flatname, running.phase));
190                    }
191                }
192            }
193            inProcess.push(null);
194        }
195
196        @Override
197        public void pop() {
198            inProcess.pop();
199        }
200
201    }
202
203    static final class SearchAnnotations extends TreePathScanner<Void, Void> {
204        final Trees trees;
205        final Elements elements;
206        final TypeElement triggersCompleteAnnotation;
207        final TypeElement triggersCompleteRepeatAnnotation;
208        final Map<String, Set<PhaseDescription>> topLevel2Expected =
209                new HashMap<>();
210
211        public SearchAnnotations(Trees trees, Elements elements) {
212            this.trees = trees;
213            this.elements = elements;
214            this.triggersCompleteAnnotation =
215                    elements.getTypeElement(TriggersComplete.class.getName());
216            this.triggersCompleteRepeatAnnotation =
217                    elements.getTypeElement(TriggersCompleteRepeat.class.getName());
218        }
219
220        @Override
221        public Void visitClass(ClassTree node, Void p) {
222            TypeElement te = (TypeElement) trees.getElement(getCurrentPath());
223            Set<PhaseDescription> expected = new HashSet<>();
224
225            for (AnnotationMirror am : getTriggersCompleteAnnotation(te)) {
226                TypeMirror of = (TypeMirror) findAttribute(am, "of").getValue();
227                Name ofName = elements.getBinaryName((TypeElement) ((DeclaredType) of).asElement());
228                Element at = (Element) findAttribute(am, "at").getValue();
229                Phase phase = Phase.valueOf(at.getSimpleName().toString());
230                expected.add(new PhaseDescription(ofName.toString(), phase));
231            }
232
233            if (!expected.isEmpty())
234                topLevel2Expected.put(elements.getBinaryName(te).toString(), expected);
235
236            return super.visitClass(node, p);
237        }
238
239        Collection<AnnotationMirror> getTriggersCompleteAnnotation(TypeElement te) {
240            for (AnnotationMirror am : te.getAnnotationMirrors()) {
241                if (triggersCompleteAnnotation.equals(am.getAnnotationType().asElement())) {
242                    return Collections.singletonList(am);
243                }
244                if (triggersCompleteRepeatAnnotation.equals(am.getAnnotationType().asElement())) {
245                    return (Collection<AnnotationMirror>) findAttribute(am, "value").getValue();
246                }
247            }
248            return Collections.emptyList();
249        }
250
251        AnnotationValue findAttribute(AnnotationMirror mirror, String name) {
252            for (Entry<? extends ExecutableElement, ? extends AnnotationValue> e :
253                    mirror.getElementValues().entrySet()) {
254                if (e.getKey().getSimpleName().contentEquals(name)) {
255                    return e.getValue();
256                }
257            }
258
259            throw new IllegalStateException("Could not find " + name + " in " + mirror);
260        }
261    }
262
263    static final class ClearAnnotations extends TreePathScanner<Void, Void> {
264        final SourcePositions positions;
265        final List<int[]> spans2Clear = new ArrayList<>();
266
267        ClearAnnotations(Trees trees) {
268            this.positions = trees.getSourcePositions();
269        }
270
271        @Override
272        public Void visitAnnotation(AnnotationTree node, Void p) {
273            removeCurrentNode();
274            return null;
275        }
276
277        @Override
278        public Void visitImport(ImportTree node, Void p) {
279            if (node.getQualifiedIdentifier().toString().startsWith("annotations.")) {
280                removeCurrentNode();
281                return null;
282            }
283            return super.visitImport(node, p);
284        }
285
286        void removeCurrentNode() {
287            CompilationUnitTree topLevel = getCurrentPath().getCompilationUnit();
288            Tree node = getCurrentPath().getLeaf();
289            spans2Clear.add(new int[] {(int) positions.getStartPosition(topLevel, node),
290                                       (int) positions.getEndPosition(topLevel, node)});
291        }
292
293        static JavaFileObject clearAnnotations(Trees trees, CompilationUnitTree cut)
294                throws IOException {
295            ClearAnnotations a = new ClearAnnotations(trees);
296            a.scan(cut, null);
297            Collections.sort(a.spans2Clear, (s1, s2) -> s2[0] - s1[0]);
298            StringBuilder result = new StringBuilder(cut.getSourceFile().getCharContent(true));
299            for (int[] toClear : a.spans2Clear) {
300                result.delete(toClear[0], toClear[1]);
301            }
302            return new TestJavaFileObject(cut.getSourceFile().toUri(), result.toString());
303        }
304
305    }
306
307    static final class PhaseDescription {
308        final String flatname;
309        final Phase phase;
310
311        public PhaseDescription(String flatname, Phase phase) {
312            this.flatname = flatname;
313            this.phase = phase;
314        }
315
316        @Override
317        public String toString() {
318            return "@annotations.TriggersComplete(of=" + flatname + ".class," +
319                   "at=annotations.Phase." + phase + ')';
320        }
321
322        @Override
323        public int hashCode() {
324            int hash = 7;
325            hash = 89 * hash + Objects.hashCode(this.flatname);
326            hash = 89 * hash + Objects.hashCode(this.phase);
327            return hash;
328        }
329
330        @Override
331        public boolean equals(Object obj) {
332            if (obj == null) {
333                return false;
334            }
335            if (getClass() != obj.getClass()) {
336                return false;
337            }
338            final PhaseDescription other = (PhaseDescription) obj;
339            if (!Objects.equals(this.flatname, other.flatname)) {
340                return false;
341            }
342            if (this.phase != other.phase) {
343                return false;
344            }
345            return true;
346        }
347
348    }
349
350    static final class TestJavaFileObject extends SimpleJavaFileObject {
351        private final String content;
352
353        public TestJavaFileObject(URI uri, String content) {
354            super(uri, Kind.SOURCE);
355            this.content = content;
356        }
357
358        @Override
359        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
360            return content;
361        }
362
363    }
364}
365
366
367