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