--- /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);
+ }
+ }
+}