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.Attribute;
25import com.sun.tools.classfile.ConstantPoolException;
26import com.sun.tools.classfile.Descriptor;
27
28import javax.tools.JavaFileObject;
29import java.io.IOException;
30import java.lang.annotation.RetentionPolicy;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.util.*;
34import java.util.stream.Collectors;
35import java.util.stream.IntStream;
36import java.util.stream.Stream;
37
38public abstract class AnnotationsTestBase extends TestResult {
39
40    /**
41     * Element values which are used in generation of annotations.
42     */
43    private static final TestAnnotationInfo.Pair[] elementValues = {
44            new TestAnnotationInfo.Pair("booleanValue", new TestAnnotationInfo.TestBooleanElementValue(true)),
45            new TestAnnotationInfo.Pair("byteValue", new TestAnnotationInfo.TestIntegerElementValue('B', 83)),
46            new TestAnnotationInfo.Pair("charValue", new TestAnnotationInfo.TestCharElementValue('H')),
47            new TestAnnotationInfo.Pair("shortValue", new TestAnnotationInfo.TestIntegerElementValue('S', 14)),
48            new TestAnnotationInfo.Pair("intValue", new TestAnnotationInfo.TestIntegerElementValue('I', 18)),
49            new TestAnnotationInfo.Pair("longValue", new TestAnnotationInfo.TestLongElementValue(14)),
50            new TestAnnotationInfo.Pair("floatValue", new TestAnnotationInfo.TestFloatElementValue(-1)),
51            new TestAnnotationInfo.Pair("doubleValue", new TestAnnotationInfo.TestDoubleElementValue(-83)),
52            new TestAnnotationInfo.Pair("stringValue", new TestAnnotationInfo.TestStringElementValue("///")),
53            new TestAnnotationInfo.Pair("arrayValue1", new TestAnnotationInfo.TestArrayElementValue(
54                    new TestAnnotationInfo.TestIntegerElementValue('I', 1),
55                    new TestAnnotationInfo.TestIntegerElementValue('I', 4),
56                    new TestAnnotationInfo.TestIntegerElementValue('I', 8),
57                    new TestAnnotationInfo.TestIntegerElementValue('I', 3))),
58            new TestAnnotationInfo.Pair("arrayValue2", new TestAnnotationInfo.TestArrayElementValue(
59                    new TestAnnotationInfo.TestStringElementValue("AAA"),
60                    new TestAnnotationInfo.TestStringElementValue("BBB"))),
61            new TestAnnotationInfo.Pair("enumValue", new TestAnnotationInfo.TestEnumElementValue("EnumValue", "VALUE2")),
62            new TestAnnotationInfo.Pair("classValue1", new TestAnnotationInfo.TestClassElementValue("void.class")),
63            new TestAnnotationInfo.Pair("classValue2", new TestAnnotationInfo.TestClassElementValue("Character.class")),
64            new TestAnnotationInfo.Pair("annoValue", new TestAnnotationInfo.TestAnnotationElementValue("AnnotationValue",
65                    new TestAnnotationInfo("AnnotationValue", RetentionPolicy.CLASS,
66                            new TestAnnotationInfo.Pair("stringValue",
67                                    new TestAnnotationInfo.TestStringElementValue("StringValue1"))))),
68            new TestAnnotationInfo.Pair("annoArrayValue", new TestAnnotationInfo.TestArrayElementValue(
69                    new TestAnnotationInfo.TestAnnotationElementValue("AnnotationValue",
70                            new TestAnnotationInfo("AnnotationValue", RetentionPolicy.CLASS,
71                                new TestAnnotationInfo.Pair("stringValue",
72                                        new TestAnnotationInfo.TestStringElementValue("StringValue1")))),
73                    new TestAnnotationInfo.TestAnnotationElementValue("AnnotationValue",
74                            new TestAnnotationInfo("AnnotationValue", RetentionPolicy.CLASS,
75                                    new TestAnnotationInfo.Pair("stringValue",
76                                            new TestAnnotationInfo.TestStringElementValue("StringValue1"))))))
77    };
78
79    /**
80     * Masks which are used in generation of annotations.
81     * E.g. mask 0 corresponds to an annotation without element values.
82     */
83    private static final int[] elementValuesCombinations;
84
85    static {
86        List<Integer> combinations = new ArrayList<>();
87        combinations.add(0);
88        for (int i = 0; i < elementValues.length; ++i) {
89            combinations.add(1 << i);
90        }
91        // pairs int value and another value
92        for (int i = 0; i < elementValues.length; ++i) {
93            combinations.add((1 << 5) | (1 << i));
94        }
95        combinations.add((1 << elementValues.length) - 1);
96        elementValuesCombinations = combinations.stream().mapToInt(Integer::intValue).toArray();
97    }
98
99    /**
100     * Method generates a list of test cases.
101     * Method is called in the method {@code call()}.
102     *
103     * @return a list of test cases
104     */
105    public abstract List<TestCase> generateTestCases();
106
107    public abstract void test(TestCase testCase, Map<String, ? extends JavaFileObject> classes)
108            throws IOException, ConstantPoolException, Descriptor.InvalidDescriptor;
109
110    /**
111     * The method is used to create a repeatable annotation.
112     */
113    private TestAnnotationInfo createSomeAnnotation(String annotationName) {
114        return new TestAnnotationInfo(annotationName, getRetentionPolicy(annotationName),
115                new TestAnnotationInfo.Pair("booleanValue",
116                        new TestAnnotationInfo.TestBooleanElementValue(true)),
117                new TestAnnotationInfo.Pair("intValue",
118                        new TestAnnotationInfo.TestIntegerElementValue('I', 1)),
119                new TestAnnotationInfo.Pair("enumValue",
120                        new TestAnnotationInfo.TestEnumElementValue("EnumValue", "VALUE1")));
121    }
122
123    private TestAnnotationInfo getAnnotationByMask(String annotationName, int mask) {
124        List<TestAnnotationInfo.Pair> pairs = new ArrayList<>();
125        for (int i = 0; i < elementValues.length; ++i) {
126            if ((mask & (1 << i)) != 0) {
127                pairs.add(elementValues[i]);
128            }
129        }
130        return new TestAnnotationInfo(
131                annotationName,
132                getRetentionPolicy(annotationName),
133                pairs.toArray(new TestAnnotationInfo.Pair[pairs.size()]));
134    }
135
136    /**
137     * Class represents annotations which will be applied to one method.
138     */
139    public static class TestAnnotationInfos {
140        public final List<TestAnnotationInfo> annotations;
141
142        public TestAnnotationInfos(List<TestAnnotationInfo> a) {
143            this.annotations = a;
144        }
145
146        public void annotate(TestCase.TestMemberInfo memberInfo) {
147            annotations.forEach(memberInfo::addAnnotation);
148        }
149    }
150
151    /**
152     * Convenience method to group test cases.
153     * Increases speed of tests.
154     */
155    public List<List<TestAnnotationInfos>> groupAnnotations(List<TestAnnotationInfos> annotations) {
156        List<List<TestAnnotationInfos>> groupedAnnotations = new ArrayList<>();
157        int size = 32;
158        List<TestAnnotationInfos> current = null;
159        for (TestAnnotationInfos infos : annotations) {
160            if (current == null || current.size() == size) {
161                current = new ArrayList<>();
162                groupedAnnotations.add(current);
163            }
164            current.add(infos);
165        }
166        return groupedAnnotations;
167    }
168
169    public List<TestAnnotationInfos> getAllCombinationsOfAnnotations() {
170        List<TestAnnotationInfos> combinations = new ArrayList<>();
171        for (Annotations annotationName1 : Annotations.values()) {
172            List<TestAnnotationInfo> list = IntStream.of(elementValuesCombinations)
173                    .mapToObj(e -> getAnnotationByMask(annotationName1.getAnnotationName(), e))
174                    .collect(Collectors.toList());
175            // add cases with a single annotation
176            combinations.addAll(list.stream()
177                    .map(Collections::singletonList)
178                    .map(TestAnnotationInfos::new)
179                    .collect(Collectors.toList()));
180
181            // add cases with a repeatable annotation
182            for (Annotations annotationName2 : Annotations.values()) {
183                if (annotationName1 == annotationName2 && !annotationName1.isRepeatable()) {
184                    continue;
185                }
186                TestAnnotationInfo annotation2 = createSomeAnnotation(annotationName2.getAnnotationName());
187                for (TestAnnotationInfo annotation1 : list) {
188                    List<TestAnnotationInfo> list1 = new ArrayList<>();
189                    Collections.addAll(list1, annotation1, annotation2);
190                    combinations.add(new TestAnnotationInfos(list1));
191                }
192            }
193        }
194        return combinations;
195    }
196
197    protected RetentionPolicy getRetentionPolicy(String name) {
198        if (name.contains("Visible")) {
199            return RetentionPolicy.RUNTIME;
200        } else if (name.contains("Invisible")) {
201            return RetentionPolicy.CLASS;
202        }
203        throw new IllegalArgumentException(name);
204    }
205
206    protected long countNumberOfAttributes(Attribute[] attrs,
207                                               Class<? extends Attribute> clazz) {
208        return Stream.of(attrs)
209                .filter(clazz::isInstance)
210                .count();
211    }
212
213    public void test() throws TestFailedException {
214        try {
215            List<TestCase> testCases = generateTestCases();
216            for (int i = 0; i < testCases.size(); ++i) {
217                TestCase testCase = testCases.get(i);
218                String source = testCase.generateSource();
219                Path sourceFile = Paths.get(getClass().getSimpleName() + i + ".java");
220                addTestCase(sourceFile.toAbsolutePath().toString());
221                writeToFileIfEnabled(sourceFile, source);
222                echo("Testing: " + sourceFile.toString());
223                try {
224                    test(testCase, compile(source).getClasses());
225                } catch (Exception e) {
226                    addFailure(e);
227                }
228            }
229        } catch (RuntimeException | IOException e) {
230            addFailure(e);
231        } finally {
232            checkStatus();
233        }
234    }
235
236    public enum Annotations {
237        RUNTIME_INVISIBLE_REPEATABLE("RuntimeInvisibleRepeatable", true),
238        RUNTIME_INVISIBLE_NOT_REPEATABLE("RuntimeInvisibleNotRepeatable", false),
239        RUNTIME_VISIBLE_REPEATABLE("RuntimeVisibleRepeatable", true),
240        RUNTIME_VISIBLE_NOT_REPEATABLE("RuntimeVisibleNotRepeatable", false);
241
242        private final String annotationName;
243        private final boolean isRepeatable;
244
245        Annotations(String annotationName, boolean isRepeatable) {
246            this.annotationName = annotationName;
247            this.isRepeatable = isRepeatable;
248        }
249
250        public String getAnnotationName() {
251            return annotationName;
252        }
253
254        public boolean isRepeatable() {
255            return isRepeatable;
256        }
257    }
258}
259