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
24import com.sun.tools.classfile.*;
25import com.sun.tools.classfile.Field;
26import com.sun.tools.classfile.Method;
27
28import java.io.File;
29import java.io.FilenameFilter;
30import java.lang.reflect.*;
31import java.util.*;
32import java.util.function.Function;
33import java.util.function.Predicate;
34import java.util.function.Supplier;
35import java.util.stream.Collectors;
36import java.util.stream.Stream;
37
38/**
39 * The main class of Signature tests.
40 * Driver reads golden data of each class member that must have a Signature attribute,
41 * after that the class compares expected data with actual one.
42 *
43 * Example of usage Driver:
44 * java Driver Test
45 *
46 * Each member of the class Test should have @ExpectedSignature annotations
47 * if it must have the Signature attribute. Anonymous class cannot be annotated.
48 * So its enclosing class should be annotated and method isAnonymous
49 * of ExpectedSignature must return true.
50 */
51public class Driver extends TestResult {
52
53    private final static String ACC_BRIDGE = "ACC_BRIDGE";
54
55    private final String topLevelClassName;
56    private final File[] files;
57
58    public Driver(String topLevelClassName) {
59        this.topLevelClassName = topLevelClassName;
60        // Get top level class and all inner classes.
61        FilenameFilter filter = (dir, file) ->
62                file.equals(topLevelClassName + ".class")
63                        || file.matches(topLevelClassName + "\\$.*\\.class");
64        files = getClassDir().listFiles(filter);
65    }
66
67    private boolean isAnonymous(String className) {
68        return className.matches(".*\\$\\d+$");
69    }
70
71    private Class<?> getEnclosingClass(String className) throws ClassNotFoundException {
72        return Class.forName(className.replaceFirst("\\$\\d+$", ""));
73    }
74
75    private ExpectedSignature getExpectedClassSignature(String className, Class<?> clazz)
76            throws ClassNotFoundException {
77        // anonymous class cannot be annotated, so information about anonymous class
78        // is located in its enclosing class.
79        boolean isAnonymous = isAnonymous(className);
80        clazz = isAnonymous ? getEnclosingClass(className) : clazz;
81        return Stream.of(clazz.getAnnotationsByType(ExpectedSignature.class))
82                .filter(s -> s.isAnonymous() == isAnonymous)
83                .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity()))
84                .get(className);
85    }
86
87    // Class.getName() cannot be used here, because the method can rely on signature attribute.
88    private Map<String, ExpectedSignature> getClassExpectedSignature(String className, Class<?> clazz)
89            throws ClassNotFoundException {
90        Map<String, ExpectedSignature> classSignatures = new HashMap<>();
91        ExpectedSignature classSignature = getExpectedClassSignature(className, clazz);
92        if (classSignature != null) {
93            classSignatures.put(className, classSignature);
94        }
95        return classSignatures;
96    }
97
98    private Map<String, ExpectedSignature> getExpectedExecutableSignatures(Executable[] executables,
99                                                                           Predicate<Executable> filterBridge) {
100        return Stream.of(executables)
101                .filter(filterBridge)
102                .map(e -> e.getAnnotation(ExpectedSignature.class))
103                .filter(Objects::nonNull)
104                .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity()));
105    }
106
107    private Map<String, ExpectedSignature> getExpectedMethodSignatures(Class<?> clazz) {
108        Map<String, ExpectedSignature> methodSignatures =
109                getExpectedExecutableSignatures(clazz.getDeclaredMethods(),
110                        m -> !((java.lang.reflect.Method) m).isBridge());
111        methodSignatures.putAll(
112                getExpectedExecutableSignatures(clazz.getDeclaredConstructors(),
113                        m -> true));
114        return methodSignatures;
115    }
116
117    private Map<String, ExpectedSignature> getExpectedFieldSignatures(Class<?> clazz) {
118        return Stream.of(clazz.getDeclaredFields())
119                .map(f -> f.getAnnotation(ExpectedSignature.class))
120                .filter(Objects::nonNull)
121                .collect(Collectors.toMap(ExpectedSignature::descriptor, Function.identity()));
122    }
123
124    public void test() throws TestFailedException {
125        try {
126            addTestCase("Source is " + topLevelClassName + ".java");
127            assertTrue(files.length > 0, "No class files found");
128            for (File file : files) {
129                try {
130                    String className = file.getName().replace(".class", "");
131                    Class<?> clazz = Class.forName(className);
132                    printf("Testing class %s\n", className);
133                    ClassFile classFile = readClassFile(file);
134
135                    // test class signature
136                    testAttribute(
137                            className,
138                            classFile,
139                            () -> (Signature_attribute) classFile.getAttribute(Attribute.Signature),
140                            getClassExpectedSignature(className, clazz).get(className));
141
142                    testFields(getExpectedFieldSignatures(clazz), classFile);
143
144                    testMethods(getExpectedMethodSignatures(clazz), classFile);
145                } catch (Exception e) {
146                    addFailure(e);
147                }
148            }
149        } catch (Exception e) {
150            addFailure(e);
151        } finally {
152            checkStatus();
153        }
154    }
155
156    private void checkAllMembersFound(Set<String> found, Map<String, ExpectedSignature> signatures, String message) {
157        if (signatures != null) {
158            checkContains(found,
159                    signatures.values().stream()
160                            .map(ExpectedSignature::descriptor)
161                            .collect(Collectors.toSet()),
162                    message);
163        }
164    }
165
166    private void testMethods(Map<String, ExpectedSignature> expectedSignatures, ClassFile classFile)
167            throws ConstantPoolException, Descriptor.InvalidDescriptor {
168        String className = classFile.getName();
169        Set<String> foundMethods = new HashSet<>();
170        for (Method method : classFile.methods) {
171            String methodName = getMethodName(classFile, method);
172            printf("Testing method %s\n", methodName);
173            if (method.access_flags.getMethodFlags().contains(ACC_BRIDGE)) {
174                printf("Bridge method is skipped : %s\n", methodName);
175                continue;
176            }
177            testAttribute(
178                    methodName,
179                    classFile,
180                    () -> (Signature_attribute) method.attributes.get(Attribute.Signature),
181                    expectedSignatures.get(methodName));
182            foundMethods.add(methodName);
183        }
184        checkAllMembersFound(foundMethods, expectedSignatures,
185                "Checking that all methods of class " + className + " with Signature attribute found");
186    }
187
188    private String getMethodName(ClassFile classFile, Method method)
189            throws ConstantPoolException, Descriptor.InvalidDescriptor {
190        return String.format("%s%s",
191                method.getName(classFile.constant_pool),
192                method.descriptor.getParameterTypes(classFile.constant_pool));
193    }
194
195    private void testFields(Map<String, ExpectedSignature> expectedSignatures, ClassFile classFile)
196            throws ConstantPoolException {
197        String className = classFile.getName();
198        Set<String> foundFields = new HashSet<>();
199        for (Field field : classFile.fields) {
200            String fieldName = field.getName(classFile.constant_pool);
201            printf("Testing field %s\n", fieldName);
202            testAttribute(
203                    fieldName,
204                    classFile,
205                    () -> (Signature_attribute) field.attributes.get(Attribute.Signature),
206                    expectedSignatures.get(fieldName));
207            foundFields.add(fieldName);
208        }
209        checkAllMembersFound(foundFields, expectedSignatures,
210                "Checking that all fields of class " + className + " with Signature attribute found");
211    }
212
213    private void testAttribute(
214            String memberName,
215            ClassFile classFile,
216            Supplier<Signature_attribute> sup,
217            ExpectedSignature expectedSignature)
218            throws ConstantPoolException {
219
220        Signature_attribute attribute = sup.get();
221        if (expectedSignature != null && checkNotNull(attribute, memberName + " must have attribute")) {
222            checkEquals(classFile.constant_pool.getUTF8Value(attribute.attribute_name_index),
223                    "Signature", "Attribute's name : " + memberName);
224            checkEquals(attribute.attribute_length, 2, "Attribute's length : " + memberName);
225            checkEquals(attribute.getSignature(classFile.constant_pool),
226                    expectedSignature.signature(),
227                    "Testing signature of : " + memberName);
228        } else {
229            checkNull(attribute, memberName + " must not have attribute");
230        }
231    }
232
233    public static void main(String[] args) throws TestFailedException {
234        if (args.length != 1) {
235            throw new IllegalArgumentException("Usage: Driver <class-name>");
236        }
237        new Driver(args[0]).test();
238    }
239}