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