/* * Copyright (c) 2015, 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. */ import com.sun.tools.classfile.*; import com.sun.tools.classfile.Field; import com.sun.tools.classfile.Method; import java.io.File; import java.io.FilenameFilter; import java.lang.reflect.*; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; /** * The main class of Signature tests. * Driver reads golden data of each class member that must have a Signature attribute, * after that the class compares expected data with actual one. * * Example of usage Driver: * java Driver Test * * Each member of the class Test should have @ExpectedSignature annotations * if it must have the Signature attribute. Anonymous class cannot be annotated. * So its enclosing class should be annotated and method isAnonymous * of ExpectedSignature must return true. */ public class Driver extends TestResult { private final static String ACC_BRIDGE = "ACC_BRIDGE"; private final String topLevelClassName; private final File[] files; public Driver(String topLevelClassName) { this.topLevelClassName = topLevelClassName; // Get top level class and all inner classes. FilenameFilter filter = (dir, file) -> file.equals(topLevelClassName + ".class") || file.matches(topLevelClassName + "\\$.*\\.class"); files = getClassDir().listFiles(filter); } private boolean isAnonymous(String className) { return className.matches(".*\\$\\d+$"); } private Class<?> getEnclosingClass(String className) throws ClassNotFoundException { return Class.forName(className.replaceFirst("\\$\\d+$", "")); } private ExpectedSignature getExpectedClassSignature(String className, Class<?> clazz) throws ClassNotFoundException { // anonymous class cannot be annotated, so information about anonymous class // is located in its enclosing class. boolean isAnonymous = isAnonymous(className); clazz = isAnonymous ? getEnclosingClass(className) : clazz; return Stream.of(clazz.getAnnotationsByType(ExpectedSignature.class)) .filter(s -> s.isAnonymous() == isAnonymous) .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity())) .get(className); } // Class.getName() cannot be used here, because the method can rely on signature attribute. private Map<String, ExpectedSignature> getClassExpectedSignature(String className, Class<?> clazz) throws ClassNotFoundException { Map<String, ExpectedSignature> classSignatures = new HashMap<>(); ExpectedSignature classSignature = getExpectedClassSignature(className, clazz); if (classSignature != null) { classSignatures.put(className, classSignature); } return classSignatures; } private Map<String, ExpectedSignature> getExpectedExecutableSignatures(Executable[] executables, Predicate<Executable> filterBridge) { return Stream.of(executables) .filter(filterBridge) .map(e -> e.getAnnotation(ExpectedSignature.class)) .filter(Objects::nonNull) .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity())); } private Map<String, ExpectedSignature> getExpectedMethodSignatures(Class<?> clazz) { Map<String, ExpectedSignature> methodSignatures = getExpectedExecutableSignatures(clazz.getDeclaredMethods(), m -> !((java.lang.reflect.Method) m).isBridge()); methodSignatures.putAll( getExpectedExecutableSignatures(clazz.getDeclaredConstructors(), m -> true)); return methodSignatures; } private Map<String, ExpectedSignature> getExpectedFieldSignatures(Class<?> clazz) { return Stream.of(clazz.getDeclaredFields()) .map(f -> f.getAnnotation(ExpectedSignature.class)) .filter(Objects::nonNull) .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity())); } public void test() throws TestFailedException { try { addTestCase("Source is " + topLevelClassName + ".java"); assertTrue(files.length > 0, "No class files found"); for (File file : files) { try { String className = file.getName().replace(".class", ""); Class<?> clazz = Class.forName(className); printf("Testing class %s\n", className); ClassFile classFile = readClassFile(file); // test class signature testAttribute( className, classFile, () -> (Signature_attribute) classFile.getAttribute(Attribute.Signature), getClassExpectedSignature(className, clazz).get(className)); testFields(getExpectedFieldSignatures(clazz), classFile); testMethods(getExpectedMethodSignatures(clazz), classFile); } catch (Exception e) { addFailure(e); } } } catch (Exception e) { addFailure(e); } finally { checkStatus(); } } private void checkAllMembersFound(Set<String> found, Map<String, ExpectedSignature> signatures, String message) { if (signatures != null) { checkContains(found, signatures.values().stream() .map(ExpectedSignature::descriptor) .collect(Collectors.toSet()), message); } } private void testMethods(Map<String, ExpectedSignature> expectedSignatures, ClassFile classFile) throws ConstantPoolException, Descriptor.InvalidDescriptor { String className = classFile.getName(); Set<String> foundMethods = new HashSet<>(); for (Method method : classFile.methods) { String methodName = getMethodName(classFile, method); printf("Testing method %s\n", methodName); if (method.access_flags.getMethodFlags().contains(ACC_BRIDGE)) { printf("Bridge method is skipped : %s\n", methodName); continue; } testAttribute( methodName, classFile, () -> (Signature_attribute) method.attributes.get(Attribute.Signature), expectedSignatures.get(methodName)); foundMethods.add(methodName); } checkAllMembersFound(foundMethods, expectedSignatures, "Checking that all methods of class " + className + " with Signature attribute found"); } private String getMethodName(ClassFile classFile, Method method) throws ConstantPoolException, Descriptor.InvalidDescriptor { return String.format("%s%s", method.getName(classFile.constant_pool), method.descriptor.getParameterTypes(classFile.constant_pool)); } private void testFields(Map<String, ExpectedSignature> expectedSignatures, ClassFile classFile) throws ConstantPoolException { String className = classFile.getName(); Set<String> foundFields = new HashSet<>(); for (Field field : classFile.fields) { String fieldName = field.getName(classFile.constant_pool); printf("Testing field %s\n", fieldName); testAttribute( fieldName, classFile, () -> (Signature_attribute) field.attributes.get(Attribute.Signature), expectedSignatures.get(fieldName)); foundFields.add(fieldName); } checkAllMembersFound(foundFields, expectedSignatures, "Checking that all fields of class " + className + " with Signature attribute found"); } private void testAttribute( String memberName, ClassFile classFile, Supplier<Signature_attribute> sup, ExpectedSignature expectedSignature) throws ConstantPoolException { Signature_attribute attribute = sup.get(); if (expectedSignature != null && checkNotNull(attribute, memberName + " must have attribute")) { checkEquals(classFile.constant_pool.getUTF8Value(attribute.attribute_name_index), "Signature", "Attribute's name : " + memberName); checkEquals(attribute.attribute_length, 2, "Attribute's length : " + memberName); checkEquals(attribute.getSignature(classFile.constant_pool), expectedSignature.signature(), "Testing signature of : " + memberName); } else { checkNull(attribute, memberName + " must not have attribute"); } } public static void main(String[] args) throws TestFailedException { if (args.length != 1) { throw new IllegalArgumentException("Usage: Driver <class-name>"); } new Driver(args[0]).test(); } }