blob: 1faf22f34337ea6d51b67486534828701ce72b04 [file] [log] [blame]
/*
* 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<TestCase> 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<String, Set<String>> 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<String> 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<TestCase> generateTestCases() {
setProperties();
List<TestCase> list = new ArrayList<>();
List<List<Modifier>> outerMods = getAllCombinations(outerAccessModifiers, outerOtherModifiers);
List<List<Modifier>> innerMods = getAllCombinations(innerAccessModifiers, innerOtherModifiers);
for (List<Modifier> 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<String, Set<String>> class2Flags = new HashMap<>();
List<String> syntheticClasses = new ArrayList<>();
for (List<Modifier> 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<String> 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<String> getFlags(ClassType type, List<Modifier> mods) {
Set<String> 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<String> getCompileOptions() {
return Collections.emptyList();
}
private List<List<Modifier>> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) {
List<List<Modifier>> 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<Modifier> 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<String, Set<String>> class2Flags, ClassType type, Modifier...mods) {
class2Flags.values().forEach(type::addFlags);
}
public enum ClassType {
CLASS("class") {
@Override
public void addSpecificFlags(Set<String> flags) {
}
},
INTERFACE("interface") {
@Override
public void addFlags(Set<String> flags) {
flags.add("ACC_STATIC");
flags.add("ACC_PUBLIC");
}
@Override
public void addSpecificFlags(Set<String> flags) {
flags.add("ACC_INTERFACE");
flags.add("ACC_ABSTRACT");
flags.add("ACC_STATIC");
}
},
ANNOTATION("@interface") {
@Override
public void addFlags(Set<String> flags) {
flags.add("ACC_STATIC");
flags.add("ACC_PUBLIC");
}
@Override
public void addSpecificFlags(Set<String> flags) {
flags.add("ACC_INTERFACE");
flags.add("ACC_ABSTRACT");
flags.add("ACC_STATIC");
flags.add("ACC_ANNOTATION");
}
},
ENUM("enum") {
@Override
public void addSpecificFlags(Set<String> flags) {
flags.add("ACC_ENUM");
flags.add("ACC_FINAL");
flags.add("ACC_STATIC");
}
},
OTHER("") {
@Override
public void addSpecificFlags(Set<String> flags) {
}
};
private final String classType;
ClassType(String clazz) {
this.classType = clazz;
}
public abstract void addSpecificFlags(Set<String> flags);
public String toString() {
return classType;
}
public void addFlags(Set<String> 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;
}
}
}