DependenciesTest.java revision 2739:9d2192f36e53
1/* 2 * Copyright (c) 2014, 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 * @build annotations.TriggersComplete annotations.TriggersCompleteRepeat annotations.Phase 30 * @build DependenciesTest 31 * @run main DependenciesTest 32 */ 33 34import java.io.IOException; 35import java.net.URI; 36import java.nio.file.Files; 37import java.nio.file.Path; 38import java.nio.file.Paths; 39import java.util.ArrayList; 40import java.util.Arrays; 41import java.util.Collection; 42import java.util.Collections; 43import java.util.HashMap; 44import java.util.HashSet; 45import java.util.List; 46import java.util.Map; 47import java.util.Map.Entry; 48import java.util.Objects; 49import java.util.Set; 50import java.util.Stack; 51import java.util.stream.Stream; 52 53import javax.lang.model.element.AnnotationMirror; 54import javax.lang.model.element.AnnotationValue; 55import javax.lang.model.element.Element; 56import javax.lang.model.element.ExecutableElement; 57import javax.lang.model.element.Name; 58import javax.lang.model.element.TypeElement; 59import javax.lang.model.type.DeclaredType; 60import javax.lang.model.type.TypeMirror; 61import javax.lang.model.util.Elements; 62import javax.tools.JavaFileObject; 63import javax.tools.SimpleJavaFileObject; 64 65import annotations.*; 66import com.sun.source.tree.AnnotationTree; 67 68import com.sun.source.tree.ClassTree; 69import com.sun.source.tree.CompilationUnitTree; 70import com.sun.source.tree.ImportTree; 71import com.sun.source.tree.Tree; 72import com.sun.source.util.JavacTask; 73import com.sun.source.util.SourcePositions; 74import com.sun.source.util.TreePathScanner; 75import com.sun.source.util.Trees; 76import com.sun.tools.javac.api.JavacTool; 77import com.sun.tools.javac.api.JavacTrees; 78import com.sun.tools.javac.code.Symbol.ClassSymbol; 79import com.sun.tools.javac.file.JavacFileManager; 80import com.sun.tools.javac.tree.JCTree; 81import com.sun.tools.javac.util.Context; 82import com.sun.tools.javac.util.Context.Factory; 83import com.sun.tools.javac.util.Dependencies; 84 85 86public class DependenciesTest { 87 public static void main(String... args) throws IOException { 88 new DependenciesTest().run(); 89 } 90 91 void run() throws IOException { 92 Path src = Paths.get(System.getProperty("test.src"), "tests"); 93 94 try (Stream<Path> tests = Files.list(src)) { 95 tests.map(p -> Files.isRegularFile(p) ? Stream.of(p) : silentWalk(p)) 96 .forEach(this :: runTest); 97 } 98 } 99 100 Stream<Path> silentWalk(Path src) { 101 try { 102 return Files.walk(src).filter(Files :: isRegularFile); 103 } catch (IOException ex) { 104 throw new IllegalStateException(ex); 105 } 106 } 107 108 void runTest(Stream<Path> inputs) { 109 JavacTool tool = JavacTool.create(); 110 try (JavacFileManager fm = tool.getStandardFileManager(null, null, null)) { 111 Path classes = Paths.get(System.getProperty("test.classes")); 112 Iterable<? extends JavaFileObject> reconFiles = 113 fm.getJavaFileObjectsFromFiles(inputs.sorted().map(p -> p.toFile()) :: iterator); 114 List<String> options = Arrays.asList("-classpath", classes.toAbsolutePath().toString()); 115 JavacTask reconTask = tool.getTask(null, fm, null, options, null, reconFiles); 116 Iterable<? extends CompilationUnitTree> reconUnits = reconTask.parse(); 117 JavacTrees reconTrees = JavacTrees.instance(reconTask); 118 SearchAnnotations scanner = new SearchAnnotations(reconTrees, 119 reconTask.getElements()); 120 List<JavaFileObject> validateFiles = new ArrayList<>(); 121 122 reconTask.analyze(); 123 scanner.scan(reconUnits, null); 124 125 for (CompilationUnitTree cut : reconUnits) { 126 validateFiles.add(ClearAnnotations.clearAnnotations(reconTrees, cut)); 127 } 128 129 Context validateContext = new Context(); 130 TestDependencies.preRegister(validateContext); 131 JavacTask validateTask = 132 tool.getTask(null, fm, null, options, null, validateFiles, validateContext); 133 134 validateTask.analyze(); 135 136 TestDependencies deps = (TestDependencies) Dependencies.instance(validateContext); 137 138 if (!scanner.topLevel2Expected.equals(deps.topLevel2Completing)) { 139 throw new IllegalStateException( "expected=" + scanner.topLevel2Expected + 140 "; actual=" + deps.topLevel2Completing); 141 } 142 } catch (IOException ex) { 143 throw new IllegalStateException(ex); 144 } finally { 145 inputs.close(); 146 } 147 } 148 149 static final class TestDependencies extends Dependencies { 150 151 public static void preRegister(Context context) { 152 context.put(dependenciesKey, (Factory<Dependencies>) TestDependencies :: new); 153 } 154 155 public TestDependencies(Context context) { 156 super(context); 157 } 158 159 final Stack<PhaseDescription> inProcess = new Stack<>(); 160 161 String topLevelMemberEnter; 162 Map<String, Set<PhaseDescription>> topLevel2Completing = new HashMap<>(); 163 164 @Override 165 public void push(ClassSymbol s, CompletionCause phase) { 166 String flatname = s.flatName().toString(); 167 for (Phase p : Phase.values()) { 168 if (phase == p.cause) { 169 inProcess.push(new PhaseDescription(flatname, p)); 170 return ; 171 } 172 } 173 if (phase == CompletionCause.MEMBER_ENTER) { 174 if (inProcess.isEmpty()) { 175 topLevelMemberEnter = flatname; 176 } else { 177 for (PhaseDescription running : inProcess) { 178 if (running == null) 179 continue; 180 181 Set<PhaseDescription> completing = 182 topLevel2Completing.computeIfAbsent(running.flatname, $ -> new HashSet<>()); 183 184 completing.add(new PhaseDescription(flatname, running.phase)); 185 } 186 } 187 } 188 inProcess.push(null); 189 } 190 191 @Override 192 public void push(AttributionKind ak, JCTree t) { 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