test/hotspot/jtreg/compiler/graalunit/com.oracle.mxtool.junit/com/oracle/mxtool/junit/FindClassesByAnnotatedMethods.java
changeset 50908 7c51db95ccb6
child 58523 fb3d408c7a7e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/compiler/graalunit/com.oracle.mxtool.junit/com/oracle/mxtool/junit/FindClassesByAnnotatedMethods.java	Thu Jun 28 17:07:34 2018 -0700
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.mxtool.junit;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Finds classes in given jar files that contain methods annotated by a given set of annotations.
+ */
+public class FindClassesByAnnotatedMethods {
+
+    /**
+     * Finds classes in a given set of jar files that contain at least one method with an annotation
+     * from a given set of annotations. The qualified name and containing jar file (separated by a
+     * space) is written to {@link System#out} for each matching class.
+     *
+     * @param args jar file names, annotations and snippets patterns. Annotations are those starting
+     *            with "@" and can be either qualified or unqualified annotation class names,
+     *            snippets patterns are those starting with {@code "snippetsPattern:"} and the rest
+     *            are jar file names
+     */
+    public static void main(String... args) throws Throwable {
+        Set<String> qualifiedAnnotations = new HashSet<>();
+        Set<String> unqualifiedAnnotations = new HashSet<>();
+        for (String arg : args) {
+            if (isAnnotationArg(arg)) {
+                String annotation = arg.substring(1);
+                int lastDot = annotation.lastIndexOf('.');
+                if (lastDot != -1) {
+                    qualifiedAnnotations.add(annotation);
+                } else {
+                    String unqualifed = annotation.substring(lastDot + 1);
+                    unqualifiedAnnotations.add(unqualifed);
+                }
+            }
+        }
+
+        for (String jarFilePath : args) {
+            if (isSnippetArg(jarFilePath) || isAnnotationArg(jarFilePath)) {
+                continue;
+            }
+            JarFile jarFile = new JarFile(jarFilePath);
+            Enumeration<JarEntry> e = jarFile.entries();
+            int unsupportedClasses = 0;
+            System.out.print(jarFilePath);
+            while (e.hasMoreElements()) {
+                JarEntry je = e.nextElement();
+                if (je.isDirectory() || !je.getName().endsWith(".class")) {
+                    continue;
+                }
+                Set<String> methodAnnotationTypes = new HashSet<>();
+                DataInputStream stream = new DataInputStream(new BufferedInputStream(jarFile.getInputStream(je), (int) je.getSize()));
+                boolean isSupported = true;
+                try {
+                    readClassfile(stream, methodAnnotationTypes);
+                } catch (UnsupportedClassVersionError ucve) {
+                    isSupported = false;
+                    unsupportedClasses++;
+                }
+                String className = je.getName().substring(0, je.getName().length() - ".class".length()).replaceAll("/", ".");
+                if (!isSupported) {
+                    System.out.print(" !" + className);
+                }
+                for (String annotationType : methodAnnotationTypes) {
+                    if (!qualifiedAnnotations.isEmpty()) {
+                        if (qualifiedAnnotations.contains(annotationType)) {
+                            System.out.print(" " + className);
+                        }
+                    }
+                    if (!unqualifiedAnnotations.isEmpty()) {
+                        final int lastDot = annotationType.lastIndexOf('.');
+                        if (lastDot != -1) {
+                            String simpleName = annotationType.substring(lastDot + 1);
+                            int lastDollar = simpleName.lastIndexOf('$');
+                            if (lastDollar != -1) {
+                                simpleName = simpleName.substring(lastDollar + 1);
+                            }
+                            if (unqualifiedAnnotations.contains(simpleName)) {
+                                System.out.print(" " + className);
+                            }
+                        }
+                    }
+                }
+            }
+            if (unsupportedClasses != 0) {
+                System.err.printf("Warning: %d classes in %s skipped as their class file version is not supported by %s%n", unsupportedClasses, jarFilePath,
+                                FindClassesByAnnotatedMethods.class.getSimpleName());
+            }
+            System.out.println();
+        }
+    }
+
+    private static boolean isAnnotationArg(String arg) {
+        return arg.charAt(0) == '@';
+    }
+
+    private static boolean isSnippetArg(String arg) {
+        return arg.startsWith("snippetsPattern:");
+    }
+
+    /*
+     * Small bytecode parser that extract annotations.
+     */
+    private static final int MAJOR_VERSION_JAVA7 = 51;
+    private static final int MAJOR_VERSION_OFFSET = 44;
+    private static final byte CONSTANT_Utf8 = 1;
+    private static final byte CONSTANT_Integer = 3;
+    private static final byte CONSTANT_Float = 4;
+    private static final byte CONSTANT_Long = 5;
+    private static final byte CONSTANT_Double = 6;
+    private static final byte CONSTANT_Class = 7;
+    private static final byte CONSTANT_Fieldref = 9;
+    private static final byte CONSTANT_String = 8;
+    private static final byte CONSTANT_Methodref = 10;
+    private static final byte CONSTANT_InterfaceMethodref = 11;
+    private static final byte CONSTANT_NameAndType = 12;
+    private static final byte CONSTANT_MethodHandle = 15;
+    private static final byte CONSTANT_MethodType = 16;
+    private static final byte CONSTANT_Dynamic = 17;
+    private static final byte CONSTANT_InvokeDynamic = 18;
+
+    private static void readClassfile(DataInputStream stream, Collection<String> methodAnnotationTypes) throws IOException {
+        // magic
+        int magic = stream.readInt();
+        assert magic == 0xCAFEBABE;
+
+        int minor = stream.readUnsignedShort();
+        int major = stream.readUnsignedShort();
+        if (major < MAJOR_VERSION_JAVA7) {
+            throw new UnsupportedClassVersionError("Unsupported class file version: " + major + "." + minor);
+        }
+        // Starting with JDK8, ignore a classfile that has a newer format than the current JDK.
+        String javaVersion = System.getProperties().get("java.specification.version").toString();
+        int majorJavaVersion;
+        if (javaVersion.startsWith("1.")) {
+            javaVersion = javaVersion.substring(2);
+            majorJavaVersion = Integer.parseInt(javaVersion);
+        } else {
+            majorJavaVersion = Integer.parseInt(javaVersion);
+        }
+        if (major > MAJOR_VERSION_OFFSET + majorJavaVersion) {
+            throw new UnsupportedClassVersionError("Unsupported class file version: " + major + "." + minor);
+        }
+
+        String[] cp = readConstantPool(stream, major, minor);
+
+        // access_flags, this_class, super_class
+        stream.skipBytes(6);
+
+        // interfaces
+        stream.skipBytes(stream.readUnsignedShort() * 2);
+
+        // fields
+        skipFields(stream);
+
+        // methods
+        readMethods(stream, cp, methodAnnotationTypes);
+    }
+
+    private static void skipFully(DataInputStream stream, int n) throws IOException {
+        long skipped = 0;
+        do {
+            long s = stream.skip(n - skipped);
+            skipped += s;
+            if (s == 0 && skipped != n) {
+                // Check for EOF (i.e., truncated class file)
+                if (stream.read() == -1) {
+                    throw new IOException("truncated stream");
+                }
+                skipped++;
+            }
+        } while (skipped != n);
+    }
+
+    private static String[] readConstantPool(DataInputStream stream, int major, int minor) throws IOException {
+        int count = stream.readUnsignedShort();
+        String[] cp = new String[count];
+
+        int i = 1;
+        while (i < count) {
+            byte tag = stream.readByte();
+            switch (tag) {
+                case CONSTANT_Class:
+                case CONSTANT_String:
+                case CONSTANT_MethodType: {
+                    skipFully(stream, 2);
+                    break;
+                }
+                case CONSTANT_InterfaceMethodref:
+                case CONSTANT_Methodref:
+                case CONSTANT_Fieldref:
+                case CONSTANT_NameAndType:
+                case CONSTANT_Float:
+                case CONSTANT_Integer:
+                case CONSTANT_Dynamic:
+                case CONSTANT_InvokeDynamic: {
+                    skipFully(stream, 4);
+                    break;
+                }
+                case CONSTANT_Long:
+                case CONSTANT_Double: {
+                    skipFully(stream, 8);
+                    break;
+                }
+                case CONSTANT_Utf8: {
+                    cp[i] = stream.readUTF();
+                    break;
+                }
+                case CONSTANT_MethodHandle: {
+                    skipFully(stream, 3);
+                    break;
+                }
+                default: {
+                    throw new InternalError(String.format("Invalid constant pool tag: " + tag + ". Maybe %s needs updating for changes introduced by class file version %d.%d?",
+                                    FindClassesByAnnotatedMethods.class, major, minor));
+                }
+            }
+            if ((tag == CONSTANT_Double) || (tag == CONSTANT_Long)) {
+                i += 2;
+            } else {
+                i += 1;
+            }
+        }
+        return cp;
+    }
+
+    private static void skipAttributes(DataInputStream stream) throws IOException {
+        int attributesCount;
+        attributesCount = stream.readUnsignedShort();
+        for (int i = 0; i < attributesCount; i++) {
+            stream.skipBytes(2); // name_index
+            int attributeLength = stream.readInt();
+            skipFully(stream, attributeLength);
+        }
+    }
+
+    private static void readMethodAttributes(DataInputStream stream, String[] cp, Collection<String> methodAnnotationTypes) throws IOException {
+        int attributesCount;
+        attributesCount = stream.readUnsignedShort();
+        for (int i = 0; i < attributesCount; i++) {
+            String attributeName = cp[stream.readUnsignedShort()];
+            int attributeLength = stream.readInt();
+
+            if (attributeName.equals("RuntimeVisibleAnnotations")) {
+                int numAnnotations = stream.readUnsignedShort();
+                for (int a = 0; a != numAnnotations; a++) {
+                    readAnnotation(stream, cp, methodAnnotationTypes);
+                }
+            } else {
+                skipFully(stream, attributeLength);
+            }
+        }
+    }
+
+    private static void readAnnotation(DataInputStream stream, String[] cp, Collection<String> methodAnnotationTypes) throws IOException {
+        int typeIndex = stream.readUnsignedShort();
+        int pairs = stream.readUnsignedShort();
+        String type = cp[typeIndex];
+        String className = type.substring(1, type.length() - 1).replace('/', '.');
+        methodAnnotationTypes.add(className);
+        readAnnotationElements(stream, cp, pairs, true, methodAnnotationTypes);
+    }
+
+    private static void readAnnotationElements(DataInputStream stream, String[] cp, int pairs, boolean withElementName, Collection<String> methodAnnotationTypes) throws IOException {
+        for (int p = 0; p < pairs; p++) {
+            if (withElementName) {
+                skipFully(stream, 2);
+            }
+            int tag = stream.readByte();
+            switch (tag) {
+                case 'B':
+                case 'C':
+                case 'D':
+                case 'F':
+                case 'I':
+                case 'J':
+                case 'S':
+                case 'Z':
+                case 's':
+                case 'c':
+                    skipFully(stream, 2);
+                    break;
+                case 'e':
+                    skipFully(stream, 4);
+                    break;
+                case '@':
+                    readAnnotation(stream, cp, methodAnnotationTypes);
+                    break;
+                case '[': {
+                    int numValues = stream.readUnsignedShort();
+                    readAnnotationElements(stream, cp, numValues, false, methodAnnotationTypes);
+                    break;
+                }
+            }
+        }
+    }
+
+    private static void skipFields(DataInputStream stream) throws IOException {
+        int count = stream.readUnsignedShort();
+        for (int i = 0; i < count; i++) {
+            stream.skipBytes(6); // access_flags, name_index, descriptor_index
+            skipAttributes(stream);
+        }
+    }
+
+    private static void readMethods(DataInputStream stream, String[] cp, Collection<String> methodAnnotationTypes) throws IOException {
+        int count = stream.readUnsignedShort();
+        for (int i = 0; i < count; i++) {
+            skipFully(stream, 6); // access_flags, name_index, descriptor_index
+            readMethodAttributes(stream, cp, methodAnnotationTypes);
+        }
+    }
+}