ElementStructureTest.java revision 2973:0e8fa3249327
1/* 2 * Copyright (c) 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 8072480 27 * @summary Check the platform classpath contains the correct elements. 28 * @library /tools/lib 29 * @build ToolBox ElementStructureTest 30 * @run main ElementStructureTest 31 */ 32 33import java.io.BufferedReader; 34import java.io.ByteArrayInputStream; 35import java.io.ByteArrayOutputStream; 36import java.io.File; 37import java.io.IOException; 38import java.io.InputStream; 39import java.io.OutputStream; 40import java.io.OutputStreamWriter; 41import java.io.Reader; 42import java.io.Writer; 43import java.net.URI; 44import java.net.URL; 45import java.net.URLClassLoader; 46import java.nio.file.Files; 47import java.nio.file.Path; 48import java.nio.file.Paths; 49import java.security.MessageDigest; 50import java.util.ArrayList; 51import java.util.Arrays; 52import java.util.Collections; 53import java.util.EnumSet; 54import java.util.HashMap; 55import java.util.Iterator; 56import java.util.List; 57import java.util.Map; 58import java.util.Map.Entry; 59import java.util.Set; 60import java.util.TreeMap; 61import java.util.TreeSet; 62import java.util.function.Predicate; 63import java.util.regex.Pattern; 64import java.util.stream.Stream; 65 66import javax.lang.model.element.AnnotationMirror; 67import javax.lang.model.element.AnnotationValue; 68import javax.lang.model.element.Element; 69import javax.lang.model.element.ElementVisitor; 70import javax.lang.model.element.ExecutableElement; 71import javax.lang.model.element.Modifier; 72import javax.lang.model.element.NestingKind; 73import javax.lang.model.element.PackageElement; 74import javax.lang.model.element.TypeElement; 75import javax.lang.model.element.TypeParameterElement; 76import javax.lang.model.element.VariableElement; 77import javax.lang.model.type.TypeMirror; 78import javax.tools.FileObject; 79import javax.tools.JavaFileManager; 80import javax.tools.JavaFileObject; 81import javax.tools.JavaFileObject.Kind; 82import javax.tools.StandardLocation; 83import javax.tools.ToolProvider; 84 85import com.sun.source.util.JavacTask; 86import com.sun.tools.classfile.ClassFile; 87import com.sun.tools.classfile.ConstantPoolException; 88import com.sun.tools.javac.api.JavacTaskImpl; 89import com.sun.tools.javac.code.Symbol.CompletionFailure; 90import com.sun.tools.javac.platform.PlatformProvider; 91import com.sun.tools.javac.util.ServiceLoader; 92 93/**To generate the hash values for version N, invoke this class like: 94 * 95 * java ElementStructureTest generate-hashes $LANGTOOLS_DIR/make/data/symbols/include.list (<classes-for-N> N)+ 96 * 97 * Where <classes-for-N> is the file produced by make/src/classes/build/tools/symbolgenerator/Probe.java. 98 * So, to produce hashes for 6, 7 and 8, this command can be used: 99 * 100 * java ElementStructureTest generate-hashes classes-6 6 classes-7 7 classes-8 8 101 * 102 * To inspect differences between the actual and expected output for version N, invoke this class like: 103 * 104 * java ElementStructureTest generate-output $LANGTOOLS_DIR/make/data/symbols/include.list (<classes-for-N> N <actual-output-file> <expected-output-file>)+ 105 * 106 * For example, to get the actual and expected output for 6 in /tmp/actual and /tmp/expected, respectively: 107 * 108 * java ElementStructureTest generate-output $LANGTOOLS_DIR/make/data/symbols/include.list classes-6 6 /tmp/actual /tmp/expected 109 */ 110public class ElementStructureTest { 111 112 static final byte[] hash6 = new byte[] { 113 (byte) 0x99, (byte) 0x34, (byte) 0x82, (byte) 0xCF, 114 (byte) 0xE0, (byte) 0x53, (byte) 0xF3, (byte) 0x13, 115 (byte) 0x4E, (byte) 0xCF, (byte) 0x49, (byte) 0x32, 116 (byte) 0xB7, (byte) 0x52, (byte) 0x0F, (byte) 0x68 117 }; 118 static final byte[] hash7 = new byte[] { 119 (byte) 0x6B, (byte) 0xA2, (byte) 0xE9, (byte) 0x8E, 120 (byte) 0xE1, (byte) 0x8E, (byte) 0x60, (byte) 0xBE, 121 (byte) 0x54, (byte) 0xC4, (byte) 0x33, (byte) 0x3E, 122 (byte) 0x0C, (byte) 0x2D, (byte) 0x3A, (byte) 0x7C 123 }; 124 static final byte[] hash8 = new byte[] { 125 (byte) 0x37, (byte) 0x0C, (byte) 0xBA, (byte) 0xCE, 126 (byte) 0xCF, (byte) 0x81, (byte) 0xAE, (byte) 0xA8, 127 (byte) 0x1E, (byte) 0x10, (byte) 0xAB, (byte) 0x72, 128 (byte) 0xF7, (byte) 0xE5, (byte) 0x34, (byte) 0x72 129 }; 130 131 final static Map<String, byte[]> version2Hash = new HashMap<>(); 132 133 static { 134 version2Hash.put("6", hash6); 135 version2Hash.put("7", hash7); 136 version2Hash.put("8", hash8); 137 } 138 139 public static void main(String... args) throws Exception { 140 if (args.length == 0) { 141 new ElementStructureTest().doTest(); 142 return ; 143 } 144 switch (args[0]) { 145 case "generate-hashes": 146 new ElementStructureTest().generateHashes(args); 147 break; 148 case "generate-output": 149 new ElementStructureTest().generateOutput(args); 150 break; 151 default: 152 throw new IllegalStateException("Unrecognized request: " + args[0]); 153 } 154 } 155 156 void doTest() throws Exception { 157 for (PlatformProvider provider : ServiceLoader.load(PlatformProvider.class)) { 158 for (String ver : provider.getSupportedPlatformNames()) { 159 if (!version2Hash.containsKey(ver)) 160 continue; 161 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer output = new OutputStreamWriter(baos, "UTF-8")) { 162 run(output, ver); 163 output.close(); 164 byte[] actual = MessageDigest.getInstance("MD5").digest(baos.toByteArray()); 165 if (!Arrays.equals(version2Hash.get(ver), actual)) 166 throw new AssertionError("Wrong hash: " + toHex(actual) + " for version: " + ver); 167 } 168 } 169 } 170 } 171 172 void generateHashes(String... args) throws Exception { 173 Predicate<String> ignoreList = constructAcceptIgnoreList(args[1]); 174 for (int i = 2; i < args.length; i += 2) { 175 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer output = new OutputStreamWriter(baos, "UTF-8")) { 176 realClasses(args[i], ignoreList, output, args[i + 1]); 177 output.close(); 178 System.err.println("version:" + args[i + 1] + "; " + toHex(MessageDigest.getInstance("MD5").digest(baos.toByteArray()))); 179 } 180 } 181 } 182 183 void generateOutput(String... args) throws Exception { 184 Predicate<String> ignoreList = constructAcceptIgnoreList(args[1]); 185 for (int i = 2; i < args.length; i += 4) { 186 try (Writer actual = Files.newBufferedWriter(Paths.get(args[i + 2])); 187 Writer expected = Files.newBufferedWriter(Paths.get(args[i + 3]))) { 188 run(actual, args[i + 1]); 189 realClasses(args[i], ignoreList, expected, args[i + 1]); 190 } 191 } 192 } 193 194 Predicate<String> constructAcceptIgnoreList(String fromFiles) throws IOException { 195 StringBuilder acceptPattern = new StringBuilder(); 196 StringBuilder rejectPattern = new StringBuilder(); 197 for (String file : fromFiles.split(File.pathSeparator)) { 198 try (Stream<String> lines = Files.lines(Paths.get(file))) { 199 lines.forEach(line -> { 200 if (line.isEmpty()) 201 return; 202 StringBuilder targetPattern; 203 switch (line.charAt(0)) { 204 case '+': 205 targetPattern = acceptPattern; 206 break; 207 case '-': 208 targetPattern = rejectPattern; 209 break; 210 default: 211 return ; 212 } 213 line = line.substring(1); 214 if (line.endsWith("/")) { 215 line += "[^/]*"; 216 } else { 217 line += "|" + line + "$[^/]*"; 218 } 219 line = line.replace("/", "."); 220 if (targetPattern.length() != 0) 221 targetPattern.append("|"); 222 targetPattern.append(line); 223 }); 224 } 225 } 226 Pattern accept = Pattern.compile(acceptPattern.toString()); 227 Pattern reject = Pattern.compile(rejectPattern.toString()); 228 229 return clazzName -> accept.matcher(clazzName).matches() && !reject.matcher(clazzName).matches(); 230 } 231 232 private static String toHex(byte[] bytes) { 233 StringBuilder hex = new StringBuilder(); 234 String delim = ""; 235 236 for (byte b : bytes) { 237 hex.append(delim); 238 hex.append(String.format("(byte) 0x%02X", b)); 239 delim = ", "; 240 } 241 242 return hex.toString(); 243 } 244 245 void run(Writer output, String version) throws Exception { 246 JavacTaskImpl task = (JavacTaskImpl) ToolProvider.getSystemJavaCompiler().getTask(null, null, null, Arrays.asList("-release", version), null, Arrays.asList(new ToolBox.JavaSource("Test", ""))); 247 task.parse(); 248 249 JavaFileManager fm = task.getContext().get(JavaFileManager.class); 250 251 for (String pack : packages(fm)) { 252 PackageElement packEl = task.getElements().getPackageElement(pack); 253 if (packEl == null) { 254 throw new AssertionError("Cannot find package: " + pack); 255 } 256 new ExhaustiveElementScanner(task, output, p -> true).visit(packEl); 257 } 258 } 259 260 void realClasses(String location, Predicate<String> acceptor, Writer output, String version) throws Exception { 261 Path classes = Paths.get(location); 262 Map<String, JavaFileObject> className2File = new HashMap<>(); 263 Map<JavaFileObject, String> file2ClassName = new HashMap<>(); 264 265 try (BufferedReader descIn = Files.newBufferedReader(classes)) { 266 String classFileData; 267 268 while ((classFileData = descIn.readLine()) != null) { 269 ByteArrayOutputStream data = new ByteArrayOutputStream(); 270 for (int i = 0; i < classFileData.length(); i += 2) { 271 data.write(Integer.parseInt(classFileData.substring(i, i + 2), 16)); 272 } 273 JavaFileObject file = new ByteArrayJavaFileObject(data.toByteArray()); 274 try (InputStream in = new ByteArrayInputStream(data.toByteArray())) { 275 String name = ClassFile.read(in).getName().replace("/", "."); 276 className2File.put(name, file); 277 file2ClassName.put(file, name); 278 } catch (IOException | ConstantPoolException ex) { 279 throw new IllegalStateException(ex); 280 } 281 } 282 } 283 284 try (JavaFileManager fm = new TestFileManager(className2File, file2ClassName)) { 285 JavacTaskImpl task = (JavacTaskImpl) ToolProvider.getSystemJavaCompiler().getTask(null, fm, null, Arrays.asList("-source", version), null, Arrays.asList(new ToolBox.JavaSource("Test", ""))); 286 task.parse(); 287 288 PACK: for (String pack : packages(fm)) { 289 PackageElement packEl = task.getElements().getPackageElement(pack); 290 assert packEl != null; 291 new ExhaustiveElementScanner(task, output, acceptor).visit(packEl); 292 } 293 } 294 } 295 296 Set<String> packages(JavaFileManager fm) throws IOException { 297 Set<String> packages = new TreeSet<>(); 298 EnumSet<Kind> kinds = EnumSet.of(JavaFileObject.Kind.CLASS, JavaFileObject.Kind.OTHER); 299 300 for (JavaFileObject file : fm.list(StandardLocation.PLATFORM_CLASS_PATH, "", kinds, true)) { 301 String binary = fm.inferBinaryName(StandardLocation.PLATFORM_CLASS_PATH, file); 302 packages.add(binary.substring(0, binary.lastIndexOf('.'))); 303 } 304 305 return packages; 306 } 307 308 final class ExhaustiveElementScanner implements ElementVisitor<Void, Void> { 309 310 final JavacTask task; 311 final Writer out; 312 final Predicate<String> acceptType; 313 314 public ExhaustiveElementScanner(JavacTask task, Writer out, Predicate<String> acceptType) { 315 this.task = task; 316 this.out = out; 317 this.acceptType = acceptType; 318 } 319 320 @Override 321 public Void visit(Element e, Void p) { 322 return e.accept(this, p); 323 } 324 325 @Override 326 public Void visit(Element e) { 327 return e.accept(this, null); 328 } 329 330 private void write(TypeMirror type) throws IOException { 331 try { 332 out.write(type.toString() 333 .replace("java.lang.invoke.MethodHandle$PolymorphicSignature", "java.lang.invoke.MethodHandle.PolymorphicSignature") 334 .replace("javax.swing.JRootPane$DefaultAction", "javax.swing.JRootPane.DefaultAction") 335 .replace("javax.swing.plaf.metal.MetalFileChooserUI$DirectoryComboBoxRenderer", "javax.swing.plaf.metal.MetalFileChooserUI.DirectoryComboBoxRenderer") 336 ); 337 } catch (CompletionFailure cf) { 338 out.write("cf"); 339 } 340 } 341 342 private void writeTypes(Iterable<? extends TypeMirror> types) throws IOException { 343 String sep = ""; 344 345 for (TypeMirror type : types) { 346 out.write(sep); 347 write(type); 348 sep = ", "; 349 } 350 } 351 352 private void writeAnnotations(Iterable<? extends AnnotationMirror> annotations) throws IOException { 353 for (AnnotationMirror ann : annotations) { 354 out.write("@"); 355 write(ann.getAnnotationType()); 356 if (!ann.getElementValues().isEmpty()) { 357 out.write("("); 358 Map<ExecutableElement, AnnotationValue> valuesMap = new TreeMap<>((a1, a2) -> a1.getSimpleName().toString().compareTo(a2.getSimpleName().toString())); 359 valuesMap.putAll(ann.getElementValues()); 360 for (Entry<? extends ExecutableElement, ? extends AnnotationValue> ev : valuesMap.entrySet()) { 361 out.write(ev.getKey().getSimpleName().toString()); 362 out.write(" = "); 363 out.write(ev.getValue().toString()); 364 } 365 out.write(")"); 366 } 367 } 368 } 369 370 void analyzeElement(Element e) { 371 try { 372 write(e.asType()); 373 writeAnnotations(e.getAnnotationMirrors()); 374 out.write(e.getKind().toString()); 375 out.write(e.getModifiers().toString()); 376 out.write(e.getSimpleName().toString()); 377 } catch (IOException ex) { 378 ex.printStackTrace(); 379 } 380 } 381 382 boolean acceptAccess(Element e) { 383 return e.getModifiers().contains(Modifier.PUBLIC) || e.getModifiers().contains(Modifier.PROTECTED); 384 } 385 386 @Override 387 public Void visitExecutable(ExecutableElement e, Void p) { 388 if (!acceptAccess(e)) 389 return null; 390 try { 391 analyzeElement(e); 392 out.write(String.valueOf(e.getDefaultValue())); 393 for (VariableElement param : e.getParameters()) { 394 visit(param, p); 395 } 396 out.write(String.valueOf(e.getReceiverType())); 397 write(e.getReturnType()); 398 out.write(e.getSimpleName().toString()); 399 writeTypes(e.getThrownTypes()); 400 for (TypeParameterElement param : e.getTypeParameters()) { 401 visit(param, p); 402 } 403 out.write(String.valueOf(e.isDefault())); 404 out.write(String.valueOf(e.isVarArgs())); 405 out.write("\n"); 406 } catch (IOException ex) { 407 ex.printStackTrace(); 408 } 409 return null; 410 } 411 412 @Override 413 public Void visitPackage(PackageElement e, Void p) { 414 List<Element> types = new ArrayList<>(e.getEnclosedElements()); 415 Collections.sort(types, (e1, e2) -> e1.getSimpleName().toString().compareTo(e2.getSimpleName().toString())); 416 for (Element encl : types) { 417 visit(encl, p); 418 } 419 return null; 420 } 421 422 @Override 423 public Void visitType(TypeElement e, Void p) { 424 if (!acceptAccess(e)) 425 return null; 426 writeType(e); 427 return null; 428 } 429 430 void writeType(TypeElement e) { 431 if (!acceptType.test(task.getElements().getBinaryName(e).toString())) 432 return ; 433 try { 434 analyzeElement(e); 435 writeTypes(e.getInterfaces()); 436 out.write(e.getNestingKind().toString()); 437 out.write(e.getQualifiedName().toString()); 438 write(e.getSuperclass()); 439 for (TypeParameterElement param : e.getTypeParameters()) { 440 visit(param, null); 441 } 442 List<Element> defs = new ArrayList<>(e.getEnclosedElements()); //XXX: forcing ordering for members - not completely correct! 443 Collections.sort(defs, (e1, e2) -> e1.toString().compareTo(e2.toString())); 444 for (Element def : defs) { 445 visit(def, null); 446 } 447 out.write("\n"); 448 } catch (IOException ex) { 449 ex.printStackTrace(); 450 } 451 } 452 453 @Override 454 public Void visitVariable(VariableElement e, Void p) { 455 if (!acceptAccess(e)) 456 return null; 457 try { 458 analyzeElement(e); 459 out.write(String.valueOf(e.getConstantValue())); 460 out.write("\n"); 461 } catch (IOException ex) { 462 ex.printStackTrace(); 463 } 464 return null; 465 } 466 467 @Override 468 public Void visitTypeParameter(TypeParameterElement e, Void p) { 469 try { 470 analyzeElement(e); 471 out.write(e.getBounds().toString()); 472 out.write("\n"); 473 } catch (IOException ex) { 474 ex.printStackTrace(); 475 } 476 return null; 477 } 478 479 @Override 480 public Void visitUnknown(Element e, Void p) { 481 throw new IllegalStateException("Should not get here."); 482 } 483 484 } 485 486 final class TestFileManager implements JavaFileManager { 487 488 final Map<String, JavaFileObject> className2File; 489 final Map<JavaFileObject, String> file2ClassName; 490 491 public TestFileManager(Map<String, JavaFileObject> className2File, Map<JavaFileObject, String> file2ClassName) { 492 this.className2File = className2File; 493 this.file2ClassName = file2ClassName; 494 } 495 496 @Override 497 public ClassLoader getClassLoader(Location location) { 498 return new URLClassLoader(new URL[0]); 499 } 500 501 @Override 502 public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse) throws IOException { 503 if (location != StandardLocation.PLATFORM_CLASS_PATH || !kinds.contains(Kind.CLASS)) 504 return Collections.emptyList(); 505 506 if (!packageName.isEmpty()) 507 packageName += "."; 508 509 List<JavaFileObject> result = new ArrayList<>(); 510 511 for (Entry<String, JavaFileObject> e : className2File.entrySet()) { 512 String currentPackage = e.getKey().substring(0, e.getKey().lastIndexOf(".") + 1); 513 if (recurse ? currentPackage.startsWith(packageName) : packageName.equals(currentPackage)) 514 result.add(e.getValue()); 515 } 516 517 return result; 518 } 519 520 @Override 521 public String inferBinaryName(Location location, JavaFileObject file) { 522 return file2ClassName.get(file); 523 } 524 525 @Override 526 public boolean isSameFile(FileObject a, FileObject b) { 527 return a == b; 528 } 529 530 @Override 531 public boolean handleOption(String current, Iterator<String> remaining) { 532 return false; 533 } 534 535 @Override 536 public boolean hasLocation(Location location) { 537 return location == StandardLocation.PLATFORM_CLASS_PATH; 538 } 539 540 @Override 541 public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { 542 if (location != StandardLocation.PLATFORM_CLASS_PATH || kind != Kind.CLASS) 543 return null; 544 545 return className2File.get(className); 546 } 547 548 @Override 549 public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { 550 throw new UnsupportedOperationException(""); 551 } 552 553 @Override 554 public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { 555 return null; 556 } 557 558 @Override 559 public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { 560 throw new UnsupportedOperationException(""); 561 } 562 563 @Override 564 public void flush() throws IOException { 565 } 566 567 @Override 568 public void close() throws IOException { 569 } 570 571 @Override 572 public int isSupportedOption(String option) { 573 return -1; 574 } 575 576 } 577 578 static class ByteArrayJavaFileObject implements JavaFileObject { 579 580 private final byte[] data; 581 582 public ByteArrayJavaFileObject(byte[] data) { 583 this.data = data; 584 } 585 586 @Override 587 public Kind getKind() { 588 return Kind.CLASS; 589 } 590 591 @Override 592 public boolean isNameCompatible(String simpleName, Kind kind) { 593 return true; 594 } 595 596 @Override 597 public NestingKind getNestingKind() { 598 return null; 599 } 600 601 @Override 602 public Modifier getAccessLevel() { 603 return null; 604 } 605 606 @Override 607 public URI toUri() { 608 return null; 609 } 610 611 @Override 612 public String getName() { 613 return null; 614 } 615 616 @Override 617 public InputStream openInputStream() throws IOException { 618 return new ByteArrayInputStream(data); 619 } 620 621 @Override 622 public OutputStream openOutputStream() throws IOException { 623 throw new UnsupportedOperationException(); 624 } 625 626 @Override 627 public Reader openReader(boolean ignoreEncodingErrors) throws IOException { 628 throw new UnsupportedOperationException(); 629 } 630 631 @Override 632 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 633 throw new UnsupportedOperationException(); 634 } 635 636 @Override 637 public Writer openWriter() throws IOException { 638 throw new UnsupportedOperationException(); 639 } 640 641 @Override 642 public long getLastModified() { 643 return 0; 644 } 645 646 @Override 647 public boolean delete() { 648 throw new UnsupportedOperationException(); 649 } 650 } 651 652} 653