/* * Copyright (c) 2014, 2018, 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.Attribute; import com.sun.tools.classfile.ClassFile; import com.sun.tools.classfile.InnerClasses_attribute; import com.sun.tools.classfile.InnerClasses_attribute.Info; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Base class for tests of inner classes attribute. * The scenario of tests: * 1. set possible values of class modifiers. * 2. according to set class modifiers, a test generates sources * and golden data with {@code generateTestCases}. * 3. a test loops through all test cases and checks InnerClasses * attribute with {@code test}. * * Example, possible flags for outer class are {@code Modifier.PRIVATE and Modifier.PUBLIC}, * possible flags for inner class are {@code Modifier.EMPTY}. * At the second step the test generates two test cases: * 1. public class A { * public class B { * class C {} * } * } * 2. public class A { * private class B { * class C {} * } * } */ public abstract class InnerClassesTestBase extends TestResult { private Modifier[] outerAccessModifiers = {Modifier.EMPTY, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC}; private Modifier[] outerOtherModifiers = {Modifier.EMPTY, Modifier.STATIC, Modifier.FINAL, Modifier.ABSTRACT}; private Modifier[] innerAccessModifiers = outerAccessModifiers; private Modifier[] innerOtherModifiers = outerOtherModifiers; private boolean isForbiddenWithoutStaticInOuterMods = false; private ClassType outerClassType; private ClassType innerClassType; private boolean hasSyntheticClass; private String prefix = ""; private String suffix = ""; /** * Sets properties. * * Returns generated list of test cases. Method is called in {@code test()}. */ public abstract void setProperties(); /** * Runs the test. * * @param classToTest expected name of outer class * @param skipClasses classes that names should not be checked */ public void test(String classToTest, String...skipClasses) throws TestFailedException { try { String testName = getClass().getName(); List testCases = generateTestCases(); for (int i = 0; i < testCases.size(); ++i) { TestCase test = testCases.get(i); String testCaseName = testName + i + ".java"; addTestCase(testCaseName); writeToFileIfEnabled(Paths.get(testCaseName), test.getSource()); test(classToTest, test, skipClasses); } } catch (Exception e) { addFailure(e); } finally { checkStatus(); } } /** * If {@code flag} is {@code true} an outer class can not have static modifier. * * @param flag if {@code true} the outer class can not have static modifier */ public void setForbiddenWithoutStaticInOuterMods(boolean flag) { isForbiddenWithoutStaticInOuterMods = flag; } /** * Sets the possible access flags of an outer class. * * @param mods the possible access flags of an outer class */ public void setOuterAccessModifiers(Modifier...mods) { outerAccessModifiers = mods; } /** * Sets the possible flags of an outer class. * * @param mods the possible flags of an outer class */ public void setOuterOtherModifiers(Modifier...mods) { outerOtherModifiers = mods; } /** * Sets the possible access flags of an inner class. * * @param mods the possible access flags of an inner class */ public void setInnerAccessModifiers(Modifier...mods) { innerAccessModifiers = mods; } /** * Sets the possible flags of an inner class. * * @param mods the possible flags of an inner class */ public void setInnerOtherModifiers(Modifier...mods) { innerOtherModifiers = mods; } /** * Sets the suffix for the generated source. * * @param suffix a suffix */ public void setSuffix(String suffix) { this.suffix = suffix; } /** * Sets the prefix for the generated source. * * @param prefix a prefix */ public void setPrefix(String prefix) { this.prefix = prefix; } /** * If {@code true} synthetic class is generated. * * @param hasSyntheticClass if {@code true} synthetic class is generated */ public void setHasSyntheticClass(boolean hasSyntheticClass) { this.hasSyntheticClass = hasSyntheticClass; } /** * Sets the inner class type. * * @param innerClassType the inner class type */ public void setInnerClassType(ClassType innerClassType) { this.innerClassType = innerClassType; } /** * Sets the outer class type. * * @param outerClassType the outer class type */ public void setOuterClassType(ClassType outerClassType) { this.outerClassType = outerClassType; } private void test(String classToTest, TestCase test, String...skipClasses) { printf("Testing :\n%s\n", test.getSource()); try { Map> class2Flags = test.getFlags(); ClassFile cf = readClassFile(compile(getCompileOptions(), test.getSource()) .getClasses().get(classToTest)); InnerClasses_attribute innerClasses = (InnerClasses_attribute) cf.getAttribute(Attribute.InnerClasses); int count = 0; for (Attribute a : cf.attributes.attrs) { if (a instanceof InnerClasses_attribute) { ++count; } } checkEquals(1, count, "Number of inner classes attribute"); if (!checkNotNull(innerClasses, "InnerClasses attribute should not be null")) { return; } checkEquals(cf.constant_pool. getUTF8Info(innerClasses.attribute_name_index).value, "InnerClasses", "innerClasses.attribute_name_index"); // Inner Classes attribute consists of length (2 bytes) // and 8 bytes for each inner class's entry. checkEquals(innerClasses.attribute_length, 2 + 8 * class2Flags.size(), "innerClasses.attribute_length"); checkEquals(innerClasses.number_of_classes, class2Flags.size(), "innerClasses.number_of_classes"); Set visitedClasses = new HashSet<>(); for (Info e : innerClasses.classes) { String baseName = cf.constant_pool.getClassInfo( e.inner_class_info_index).getBaseName(); if (cf.major_version >= 51 && e.inner_name_index == 0) { checkEquals(e.outer_class_info_index, 0, "outer_class_info_index " + "in case of inner_name_index is zero : " + baseName); } String className = baseName.replaceFirst(".*\\$", ""); checkTrue(class2Flags.containsKey(className), className); checkTrue(visitedClasses.add(className), "there are no duplicates in attribute : " + className); checkEquals(e.inner_class_access_flags.getInnerClassFlags(), class2Flags.get(className), "inner_class_access_flags " + className); if (!Arrays.asList(skipClasses).contains(className)) { checkEquals( cf.constant_pool.getClassInfo(e.inner_class_info_index).getBaseName(), classToTest + "$" + className, "inner_class_info_index of " + className); if (e.outer_class_info_index > 0) { checkEquals( cf.constant_pool.getClassInfo(e.outer_class_info_index).getName(), classToTest, "outer_class_info_index of " + className); } } } } catch (Exception e) { addFailure(e); } } /** * Methods generates list of test cases. Method generates all possible combinations * of acceptable flags for nested inner classes. * * @return generated list of test cases */ protected List generateTestCases() { setProperties(); List list = new ArrayList<>(); List> outerMods = getAllCombinations(outerAccessModifiers, outerOtherModifiers); List> innerMods = getAllCombinations(innerAccessModifiers, innerOtherModifiers); for (List outerMod : outerMods) { if (isForbiddenWithoutStaticInOuterMods && !outerMod.contains(Modifier.STATIC)) { continue; } StringBuilder sb = new StringBuilder(); sb.append("public class InnerClassesSrc {") .append(toString(outerMod)).append(' ') .append(outerClassType).append(' ') .append(prefix).append(' ').append('\n'); int count = 0; Map> class2Flags = new HashMap<>(); List syntheticClasses = new ArrayList<>(); for (List innerMod : innerMods) { ++count; String privateConstructor = ""; if (hasSyntheticClass && !innerMod.contains(Modifier.ABSTRACT)) { privateConstructor = "private A" + count + "() {}"; syntheticClasses.add("new A" + count + "();"); } sb.append(toString(innerMod)).append(' '); sb.append(String.format("%s A%d {%s}\n", innerClassType, count, privateConstructor)); Set flags = getFlags(innerClassType, innerMod); class2Flags.put("A" + count, flags); } if (hasSyntheticClass) { // Source to generate synthetic classes sb.append(syntheticClasses.stream().collect(Collectors.joining(" ", "{", "}"))); class2Flags.put("1", new HashSet<>(Arrays.asList("ACC_STATIC", "ACC_SYNTHETIC"))); } sb.append(suffix).append("\n}"); getAdditionalFlags(class2Flags, outerClassType, outerMod.toArray(new Modifier[outerMod.size()])); list.add(new TestCase(sb.toString(), class2Flags)); } return list; } /** * Methods returns flags which must have type. * * @param type class, interface, enum or annotation * @param mods modifiers * @return set of access flags */ protected Set getFlags(ClassType type, List mods) { Set flags = mods.stream() .map(Modifier::getString) .filter(str -> !str.isEmpty()) .map(str -> "ACC_" + str.toUpperCase()) .collect(Collectors.toSet()); type.addSpecificFlags(flags); return flags; } protected List getCompileOptions() { return Collections.emptyList(); } private List> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) { List> list = new ArrayList<>(); for (Modifier access : accessModifiers) { for (int i = 0; i < otherModifiers.length; ++i) { Modifier mod1 = otherModifiers[i]; for (int j = i + 1; j < otherModifiers.length; ++j) { Modifier mod2 = otherModifiers[j]; if (isForbidden(mod1, mod2)) { continue; } list.add(Arrays.asList(access, mod1, mod2)); } if (mod1 == Modifier.EMPTY) { list.add(Collections.singletonList(access)); } } } return list; } private boolean isForbidden(Modifier mod1, Modifier mod2) { return mod1 == Modifier.FINAL && mod2 == Modifier.ABSTRACT || mod1 == Modifier.ABSTRACT && mod2 == Modifier.FINAL; } private String toString(List mods) { return mods.stream() .map(Modifier::getString) .filter(s -> !s.isEmpty()) .collect(Collectors.joining(" ")); } /** * Method is called in generateTestCases(). * If you need to add additional access flags, you should override this method. * * * @param class2Flags map with flags * @param type class, interface, enum or @annotation * @param mods modifiers */ public void getAdditionalFlags(Map> class2Flags, ClassType type, Modifier...mods) { class2Flags.values().forEach(type::addFlags); } public enum ClassType { CLASS("class") { @Override public void addSpecificFlags(Set flags) { } }, INTERFACE("interface") { @Override public void addFlags(Set flags) { flags.add("ACC_STATIC"); flags.add("ACC_PUBLIC"); } @Override public void addSpecificFlags(Set flags) { flags.add("ACC_INTERFACE"); flags.add("ACC_ABSTRACT"); flags.add("ACC_STATIC"); } }, ANNOTATION("@interface") { @Override public void addFlags(Set flags) { flags.add("ACC_STATIC"); flags.add("ACC_PUBLIC"); } @Override public void addSpecificFlags(Set flags) { flags.add("ACC_INTERFACE"); flags.add("ACC_ABSTRACT"); flags.add("ACC_STATIC"); flags.add("ACC_ANNOTATION"); } }, ENUM("enum") { @Override public void addSpecificFlags(Set flags) { flags.add("ACC_ENUM"); flags.add("ACC_FINAL"); flags.add("ACC_STATIC"); } }, OTHER("") { @Override public void addSpecificFlags(Set flags) { } }; private final String classType; ClassType(String clazz) { this.classType = clazz; } public abstract void addSpecificFlags(Set flags); public String toString() { return classType; } public void addFlags(Set set) { } } public enum Modifier { PUBLIC("public"), PRIVATE("private"), PROTECTED("protected"), DEFAULT("default"), FINAL("final"), ABSTRACT("abstract"), STATIC("static"), EMPTY(""); private final String str; Modifier(String str) { this.str = str; } public String getString() { return str; } } }