1/*
2 * Copyright (c) 2014, 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.ClassFile;
26import com.sun.tools.classfile.InnerClasses_attribute;
27import com.sun.tools.classfile.InnerClasses_attribute.Info;
28
29import java.nio.file.Paths;
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.Collections;
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.List;
36import java.util.Map;
37import java.util.Set;
38import java.util.stream.Collectors;
39
40/**
41 * Base class for tests of inner classes attribute.
42 * The scenario of tests:
43 *   1. set possible values of class modifiers.
44 *   2. according to set class modifiers, a test generates sources
45 * and golden data with {@code generateTestCases}.
46 *   3. a test loops through all test cases and checks InnerClasses
47 * attribute with {@code test}.
48 *
49 * Example, possible flags for outer class are {@code Modifier.PRIVATE and Modifier.PUBLIC},
50 * possible flags for inner class are {@code Modifier.EMPTY}.
51 * At the second step the test generates two test cases:
52 *   1. public class A {
53 *        public class B {
54 *          class C {}
55 *        }
56 *      }
57 *   2. public class A {
58 *        private class B {
59 *          class C {}
60 *        }
61 *      }
62 */
63public abstract class InnerClassesTestBase extends TestResult {
64
65    private Modifier[] outerAccessModifiers = {Modifier.EMPTY, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC};
66    private Modifier[] outerOtherModifiers = {Modifier.EMPTY, Modifier.STATIC, Modifier.FINAL, Modifier.ABSTRACT};
67    private Modifier[] innerAccessModifiers = outerAccessModifiers;
68    private Modifier[] innerOtherModifiers = outerOtherModifiers;
69    private boolean isForbiddenWithoutStaticInOuterMods = false;
70
71    private ClassType outerClassType;
72    private ClassType innerClassType;
73    private boolean hasSyntheticClass;
74    private String prefix = "";
75    private String suffix = "";
76
77    /**
78     * Sets properties.
79     *
80     * Returns generated list of test cases. Method is called in {@code test()}.
81     */
82    public abstract void setProperties();
83
84    /**
85     * Runs the test.
86     *
87     * @param classToTest expected name of outer class
88     * @param skipClasses classes that names should not be checked
89     */
90    public void test(String classToTest, String...skipClasses) throws TestFailedException {
91        try {
92            String testName = getClass().getName();
93            List<TestCase> testCases = generateTestCases();
94            for (int i = 0; i < testCases.size(); ++i) {
95                TestCase test = testCases.get(i);
96                String testCaseName = testName + i + ".java";
97                addTestCase(testCaseName);
98                writeToFileIfEnabled(Paths.get(testCaseName), test.getSource());
99                test(classToTest, test, skipClasses);
100            }
101        } catch (Exception e) {
102            addFailure(e);
103        } finally {
104            checkStatus();
105        }
106    }
107
108    /**
109     * If {@code flag} is {@code true} an outer class can not have static modifier.
110     *
111     * @param flag if {@code true} the outer class can not have static modifier
112     */
113    public void setForbiddenWithoutStaticInOuterMods(boolean flag) {
114        isForbiddenWithoutStaticInOuterMods = flag;
115    }
116
117    /**
118     * Sets the possible access flags of an outer class.
119     *
120     * @param mods the possible access flags of an outer class
121     */
122    public void setOuterAccessModifiers(Modifier...mods) {
123        outerAccessModifiers = mods;
124    }
125
126    /**
127     * Sets the possible flags of an outer class.
128     *
129     * @param mods the possible flags of an outer class
130     */
131    public void setOuterOtherModifiers(Modifier...mods) {
132        outerOtherModifiers = mods;
133    }
134
135    /**
136     * Sets the possible access flags of an inner class.
137     *
138     * @param mods the possible access flags of an inner class
139     */
140    public void setInnerAccessModifiers(Modifier...mods) {
141        innerAccessModifiers = mods;
142    }
143
144    /**
145     * Sets the possible flags of an inner class.
146     *
147     * @param mods the possible flags of an inner class
148     */
149    public void setInnerOtherModifiers(Modifier...mods) {
150        innerOtherModifiers = mods;
151    }
152
153    /**
154     * Sets the suffix for the generated source.
155     *
156     * @param suffix a suffix
157     */
158    public void setSuffix(String suffix) {
159        this.suffix = suffix;
160    }
161
162    /**
163     * Sets the prefix for the generated source.
164     *
165     * @param prefix a prefix
166     */
167    public void setPrefix(String prefix) {
168        this.prefix = prefix;
169    }
170
171    /**
172     * If {@code true} synthetic class is generated.
173     *
174     * @param hasSyntheticClass if {@code true} synthetic class is generated
175     */
176    public void setHasSyntheticClass(boolean hasSyntheticClass) {
177        this.hasSyntheticClass = hasSyntheticClass;
178    }
179
180    /**
181     * Sets the inner class type.
182     *
183     * @param innerClassType the inner class type
184     */
185    public void setInnerClassType(ClassType innerClassType) {
186        this.innerClassType = innerClassType;
187    }
188
189    /**
190     * Sets the outer class type.
191     *
192     * @param outerClassType the outer class type
193     */
194    public void setOuterClassType(ClassType outerClassType) {
195        this.outerClassType = outerClassType;
196    }
197
198    private void test(String classToTest, TestCase test, String...skipClasses) {
199        printf("Testing :\n%s\n", test.getSource());
200        try {
201            Map<String, Set<String>> class2Flags = test.getFlags();
202            ClassFile cf = readClassFile(compile(test.getSource())
203                    .getClasses().get(classToTest));
204            InnerClasses_attribute innerClasses = (InnerClasses_attribute)
205                    cf.getAttribute(Attribute.InnerClasses);
206            int count = 0;
207            for (Attribute a : cf.attributes.attrs) {
208                if (a instanceof InnerClasses_attribute) {
209                    ++count;
210                }
211            }
212            checkEquals(1, count, "Number of inner classes attribute");
213            if (!checkNotNull(innerClasses, "InnerClasses attribute should not be null")) {
214                return;
215            }
216            checkEquals(cf.constant_pool.
217                    getUTF8Info(innerClasses.attribute_name_index).value, "InnerClasses",
218                    "innerClasses.attribute_name_index");
219            // Inner Classes attribute consists of length (2 bytes)
220            // and 8 bytes for each inner class's entry.
221            checkEquals(innerClasses.attribute_length,
222                    2 + 8 * class2Flags.size(), "innerClasses.attribute_length");
223            checkEquals(innerClasses.number_of_classes,
224                    class2Flags.size(), "innerClasses.number_of_classes");
225            Set<String> visitedClasses = new HashSet<>();
226            for (Info e : innerClasses.classes) {
227                String baseName = cf.constant_pool.getClassInfo(
228                        e.inner_class_info_index).getBaseName();
229                if (cf.major_version >= 51 && e.inner_name_index == 0) {
230                    checkEquals(e.outer_class_info_index, 0,
231                            "outer_class_info_index "
232                                    + "in case of inner_name_index is zero : "
233                                    + baseName);
234                }
235                String className = baseName.replaceFirst(".*\\$", "");
236                checkTrue(class2Flags.containsKey(className),
237                        className);
238                checkTrue(visitedClasses.add(className),
239                        "there are no duplicates in attribute : " + className);
240                checkEquals(e.inner_class_access_flags.getInnerClassFlags(),
241                        class2Flags.get(className),
242                        "inner_class_access_flags " + className);
243                if (!Arrays.asList(skipClasses).contains(className)) {
244                        checkEquals(
245                                cf.constant_pool.getClassInfo(e.inner_class_info_index).getBaseName(),
246                                classToTest + "$" + className,
247                                "inner_class_info_index of " + className);
248                    if (e.outer_class_info_index > 0) {
249                        checkEquals(
250                                cf.constant_pool.getClassInfo(e.outer_class_info_index).getName(),
251                                classToTest,
252                                "outer_class_info_index of " + className);
253                    }
254                }
255            }
256        } catch (Exception e) {
257            addFailure(e);
258        }
259    }
260
261    /**
262     * Methods generates list of test cases. Method generates all possible combinations
263     * of acceptable flags for nested inner classes.
264     *
265     * @return generated list of test cases
266     */
267    protected List<TestCase> generateTestCases() {
268        setProperties();
269        List<TestCase> list = new ArrayList<>();
270
271        List<List<Modifier>> outerMods = getAllCombinations(outerAccessModifiers, outerOtherModifiers);
272        List<List<Modifier>> innerMods = getAllCombinations(innerAccessModifiers, innerOtherModifiers);
273
274        for (List<Modifier> outerMod : outerMods) {
275            if (isForbiddenWithoutStaticInOuterMods && !outerMod.contains(Modifier.STATIC)) {
276                continue;
277            }
278            StringBuilder sb = new StringBuilder();
279            sb.append("public class InnerClassesSrc {")
280                    .append(toString(outerMod)).append(' ')
281                    .append(outerClassType).append(' ')
282                    .append(prefix).append(' ').append('\n');
283            int count = 0;
284            Map<String, Set<String>> class2Flags = new HashMap<>();
285            List<String> syntheticClasses = new ArrayList<>();
286            for (List<Modifier> innerMod : innerMods) {
287                ++count;
288                String privateConstructor = "";
289                if (hasSyntheticClass && !innerMod.contains(Modifier.ABSTRACT)) {
290                    privateConstructor = "private A" + count + "() {}";
291                    syntheticClasses.add("new A" + count + "();");
292                }
293                sb.append(toString(innerMod)).append(' ');
294                sb.append(String.format("%s A%d {%s}\n", innerClassType, count, privateConstructor));
295                Set<String> flags = getFlags(innerClassType, innerMod);
296                class2Flags.put("A" + count, flags);
297            }
298            if (hasSyntheticClass) {
299                // Source to generate synthetic classes
300                sb.append(syntheticClasses.stream().collect(Collectors.joining(" ", "{", "}")));
301                class2Flags.put("1", new HashSet<>(Arrays.asList("ACC_STATIC", "ACC_SYNTHETIC")));
302            }
303            sb.append(suffix).append("\n}");
304            getAdditionalFlags(class2Flags, outerClassType, outerMod.toArray(new Modifier[outerMod.size()]));
305            list.add(new TestCase(sb.toString(), class2Flags));
306        }
307        return list;
308    }
309
310    /**
311     * Methods returns flags which must have type.
312     *
313     * @param type class, interface, enum or annotation
314     * @param mods modifiers
315     * @return set of access flags
316     */
317    protected Set<String> getFlags(ClassType type, List<Modifier> mods) {
318        Set<String> flags = mods.stream()
319                .map(Modifier::getString)
320                .filter(str -> !str.isEmpty())
321                .map(str -> "ACC_" + str.toUpperCase())
322                .collect(Collectors.toSet());
323        type.addSpecificFlags(flags);
324        return flags;
325    }
326
327    private List<List<Modifier>> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) {
328        List<List<Modifier>> list = new ArrayList<>();
329        for (Modifier access : accessModifiers) {
330            for (int i = 0; i < otherModifiers.length; ++i) {
331                Modifier mod1 = otherModifiers[i];
332                for (int j = i + 1; j < otherModifiers.length; ++j) {
333                    Modifier mod2 = otherModifiers[j];
334                    if (isForbidden(mod1, mod2)) {
335                        continue;
336                    }
337                    list.add(Arrays.asList(access, mod1, mod2));
338                }
339                if (mod1 == Modifier.EMPTY) {
340                    list.add(Collections.singletonList(access));
341                }
342            }
343        }
344        return list;
345    }
346
347    private boolean isForbidden(Modifier mod1, Modifier mod2) {
348        return mod1 == Modifier.FINAL && mod2 == Modifier.ABSTRACT
349                || mod1 == Modifier.ABSTRACT && mod2 == Modifier.FINAL;
350    }
351
352    private String toString(List<Modifier> mods) {
353        return mods.stream()
354                .map(Modifier::getString)
355                .filter(s -> !s.isEmpty())
356                .collect(Collectors.joining(" "));
357    }
358
359    /**
360     * Method is called in generateTestCases().
361     * If you need to add additional access flags, you should override this method.
362     *
363     *
364     * @param class2Flags map with flags
365     * @param type class, interface, enum or @annotation
366     * @param mods modifiers
367     */
368    public void getAdditionalFlags(Map<String, Set<String>> class2Flags, ClassType type, Modifier...mods) {
369        class2Flags.values().forEach(type::addFlags);
370    }
371
372    public enum ClassType {
373        CLASS("class") {
374            @Override
375            public void addSpecificFlags(Set<String> flags) {
376            }
377        },
378        INTERFACE("interface") {
379            @Override
380            public void addFlags(Set<String> flags) {
381                flags.add("ACC_STATIC");
382                flags.add("ACC_PUBLIC");
383            }
384
385            @Override
386            public void addSpecificFlags(Set<String> flags) {
387                flags.add("ACC_INTERFACE");
388                flags.add("ACC_ABSTRACT");
389                flags.add("ACC_STATIC");
390            }
391        },
392        ANNOTATION("@interface") {
393            @Override
394            public void addFlags(Set<String> flags) {
395                flags.add("ACC_STATIC");
396                flags.add("ACC_PUBLIC");
397            }
398
399            @Override
400            public void addSpecificFlags(Set<String> flags) {
401                flags.add("ACC_INTERFACE");
402                flags.add("ACC_ABSTRACT");
403                flags.add("ACC_STATIC");
404                flags.add("ACC_ANNOTATION");
405            }
406        },
407        ENUM("enum") {
408            @Override
409            public void addSpecificFlags(Set<String> flags) {
410                flags.add("ACC_ENUM");
411                flags.add("ACC_FINAL");
412                flags.add("ACC_STATIC");
413            }
414        },
415        OTHER("") {
416            @Override
417            public void addSpecificFlags(Set<String> flags) {
418            }
419        };
420
421        private final String classType;
422
423        ClassType(String clazz) {
424            this.classType = clazz;
425        }
426
427        public abstract void addSpecificFlags(Set<String> flags);
428
429        public String toString() {
430            return classType;
431        }
432
433        public void addFlags(Set<String> set) {
434        }
435    }
436
437    public enum Modifier {
438        PUBLIC("public"), PRIVATE("private"),
439        PROTECTED("protected"), DEFAULT("default"),
440        FINAL("final"), ABSTRACT("abstract"),
441        STATIC("static"), EMPTY("");
442
443        private final String str;
444
445        Modifier(String str) {
446            this.str = str;
447        }
448
449        public String getString() {
450            return str;
451        }
452    }
453}
454