3789983e89
Reviewed-by: darcy, ihse
460 lines
18 KiB
Java
460 lines
18 KiB
Java
/*
|
|
* Copyright (c) 2015, 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 java.lang.annotation.RetentionPolicy;
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
public class TestCase {
|
|
|
|
/**
|
|
* The top-level classes of the test case.
|
|
*/
|
|
public final Map<String, TestClassInfo> classes = new LinkedHashMap<>();
|
|
|
|
/**
|
|
* Constructs a test class info with {@code classType} as top-level class,
|
|
* with {@code outerClassName} as name and {@code mods} as modifiers.
|
|
*
|
|
* @param classType a class type
|
|
* @param outerClassName a name
|
|
* @param mods an array of modifiers
|
|
*/
|
|
public TestClassInfo addClassInfo(ClassType classType, String outerClassName, String...mods) {
|
|
return addClassInfo(null, classType, outerClassName, mods);
|
|
}
|
|
|
|
/**
|
|
* Constructs a test class info with {@code classType} as top-level class,
|
|
* with {@code outerClassName} as name, {@code parent} class name
|
|
* as parent class and {@code mods} as modifiers.
|
|
*
|
|
* @param classType a class type
|
|
* @param outerClassName a name
|
|
* @param mods an array of modifiers
|
|
*/
|
|
public TestClassInfo addClassInfo(String parent, ClassType classType, String outerClassName, String...mods) {
|
|
TestClassInfo clazz = new TestClassInfo(classType, outerClassName, parent, mods);
|
|
if (classes.put(outerClassName, clazz) != null) {
|
|
throw new IllegalArgumentException("Duplicate class name: " + outerClassName);
|
|
}
|
|
return clazz;
|
|
}
|
|
|
|
public String generateSource() {
|
|
return classes.values().stream()
|
|
.map(TestMemberInfo::generateSource)
|
|
.collect(Collectors.joining("\n"));
|
|
}
|
|
|
|
/**
|
|
* Returns {@code TestClassInfo} by class signature.
|
|
* Example, {@code getTestClassInfo("Test$1Local")}
|
|
* returns local inner class of class {@code Test}.
|
|
*
|
|
* @param classSignature a class signature
|
|
* @return {@code TestClassInfo} by class signature
|
|
*/
|
|
public TestClassInfo getTestClassInfo(String classSignature) {
|
|
String[] cs = classSignature.split("\\$");
|
|
if (cs.length > 0 && classes.containsKey(cs[0])) {
|
|
// check signature corresponds to top level class
|
|
if (cs.length == 1) {
|
|
return classes.get(cs[0]);
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException("Cannot find class : " + classSignature);
|
|
}
|
|
TestClassInfo current = classes.get(cs[0]);
|
|
// find class info in the inner classes
|
|
for (int i = 1; i < cs.length; ++i) {
|
|
Map<String, TestClassInfo> innerClasses = current.innerClasses;
|
|
Map<String, TestMethodInfo> methods = current.methods;
|
|
current = innerClasses.get(cs[i]);
|
|
// if current is null then class info does not exist or the class is local
|
|
if (current == null) {
|
|
if (!cs[i].isEmpty()) {
|
|
// the class is local, remove leading digit
|
|
String className = cs[i].substring(1);
|
|
Optional<TestClassInfo> opt = methods.values().stream()
|
|
.flatMap(c -> c.localClasses.values().stream())
|
|
.filter(c -> c.name.equals(className)).findAny();
|
|
if (opt.isPresent()) {
|
|
current = opt.get();
|
|
// continue analysis of local class
|
|
continue;
|
|
}
|
|
}
|
|
throw new IllegalArgumentException("Cannot find class : " + classSignature);
|
|
}
|
|
}
|
|
return current;
|
|
}
|
|
|
|
/**
|
|
* Class represents a program member.
|
|
*/
|
|
public static abstract class TestMemberInfo {
|
|
// next two fields are used for formatting
|
|
protected final int indention;
|
|
protected final ClassType containerType;
|
|
public final List<String> mods;
|
|
public final String name;
|
|
public final Map<String, TestAnnotationInfo> annotations;
|
|
|
|
TestMemberInfo(int indention, ClassType containerType, String name, String... mods) {
|
|
this.indention = indention;
|
|
this.containerType = containerType;
|
|
this.mods = Arrays.asList(mods);
|
|
this.name = name;
|
|
this.annotations = new HashMap<>();
|
|
}
|
|
|
|
public abstract String generateSource();
|
|
|
|
public boolean isAnnotated(RetentionPolicy policy) {
|
|
return annotations.values().stream()
|
|
.filter(a -> a.policy == policy)
|
|
.findAny().isPresent();
|
|
}
|
|
|
|
public Set<String> getRuntimeVisibleAnnotations() {
|
|
return getRuntimeAnnotations(RetentionPolicy.RUNTIME);
|
|
}
|
|
|
|
public Set<String> getRuntimeInvisibleAnnotations() {
|
|
return getRuntimeAnnotations(RetentionPolicy.CLASS);
|
|
}
|
|
|
|
private Set<String> getRuntimeAnnotations(RetentionPolicy policy) {
|
|
return annotations.values().stream()
|
|
.filter(e -> e.policy == policy)
|
|
.map(a -> a.annotationName)
|
|
.distinct()
|
|
.collect(Collectors.toSet());
|
|
}
|
|
|
|
/**
|
|
* Generates source for annotations.
|
|
*
|
|
* @param prefix a leading text
|
|
* @param suffix a trailing text
|
|
* @param joining a text between annotations
|
|
* @return source for annotations
|
|
*/
|
|
protected String generateSourceForAnnotations(String prefix, String suffix, String joining) {
|
|
StringBuilder sb = new StringBuilder();
|
|
for (TestAnnotationInfo annotation : annotations.values()) {
|
|
sb.append(prefix);
|
|
if (annotation.isContainer) {
|
|
// the annotation is repeatable
|
|
// container consists of an array of annotations
|
|
TestAnnotationInfo.TestArrayElementValue containerElementValue =
|
|
(TestAnnotationInfo.TestArrayElementValue) annotation.elementValues.get(0).elementValue;
|
|
// concatenate sources of repeatable annotations
|
|
sb.append(containerElementValue.values.stream()
|
|
.map(TestAnnotationInfo.TestElementValue::toString)
|
|
.collect(Collectors.joining(joining)));
|
|
} else {
|
|
sb.append(annotation);
|
|
}
|
|
sb.append(suffix);
|
|
}
|
|
String src = sb.toString();
|
|
return src.trim().isEmpty() ? "" : src;
|
|
|
|
}
|
|
|
|
/**
|
|
* Generates source for annotations.
|
|
*
|
|
* @return source for annotations
|
|
*/
|
|
public String generateSourceForAnnotations() {
|
|
return generateSourceForAnnotations(indention(), "\n", "\n" + indention());
|
|
}
|
|
|
|
/**
|
|
* Adds annotation info to the member.
|
|
*
|
|
* @param anno an annotation info
|
|
*/
|
|
public void addAnnotation(TestAnnotationInfo anno) {
|
|
String containerName = anno.annotationName + "Container";
|
|
TestAnnotationInfo annotation = annotations.get(anno.annotationName);
|
|
TestAnnotationInfo containerAnnotation = annotations.get(containerName);
|
|
|
|
if (annotation == null) {
|
|
// if annotation is null then either it is first adding of the annotation to the member
|
|
// or there is the container of the annotation.
|
|
if (containerAnnotation == null) {
|
|
// first adding to the member
|
|
annotations.put(anno.annotationName, anno);
|
|
} else {
|
|
// add annotation to container
|
|
TestAnnotationInfo.TestArrayElementValue containerElementValue =
|
|
((TestAnnotationInfo.TestArrayElementValue) containerAnnotation.elementValues.get(0).elementValue);
|
|
containerElementValue.values.add(new TestAnnotationInfo.TestAnnotationElementValue(anno.annotationName, anno));
|
|
}
|
|
} else {
|
|
// remove previously added annotation and add new container of repeatable annotation
|
|
// which contains previously added and new annotation
|
|
annotations.remove(anno.annotationName);
|
|
containerAnnotation = new TestAnnotationInfo(
|
|
containerName,
|
|
anno.policy,
|
|
true,
|
|
new TestAnnotationInfo.Pair("value",
|
|
new TestAnnotationInfo.TestArrayElementValue(
|
|
new TestAnnotationInfo.TestAnnotationElementValue(anno.annotationName, annotation),
|
|
new TestAnnotationInfo.TestAnnotationElementValue(anno.annotationName, anno))));
|
|
annotations.put(containerName, containerAnnotation);
|
|
}
|
|
}
|
|
|
|
public String indention() {
|
|
char[] a = new char[4 * indention];
|
|
Arrays.fill(a, ' ');
|
|
return new String(a);
|
|
}
|
|
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The class represents a class.
|
|
*/
|
|
public static class TestClassInfo extends TestMemberInfo {
|
|
public final ClassType classType;
|
|
public final String parent;
|
|
public final Map<String, TestClassInfo> innerClasses;
|
|
public final Map<String, TestMethodInfo> methods;
|
|
public final Map<String, TestFieldInfo> fields;
|
|
|
|
TestClassInfo(int indention, ClassType classType, String className, String... mods) {
|
|
this(indention, classType, className, null, mods);
|
|
}
|
|
|
|
TestClassInfo(ClassType classType, String className, String parent, String... mods) {
|
|
this(0, classType, className, parent, mods);
|
|
}
|
|
|
|
TestClassInfo(int indention, ClassType classType, String className, String parent, String... mods) {
|
|
super(indention, null, className, mods);
|
|
this.classType = classType;
|
|
this.parent = parent;
|
|
innerClasses = new LinkedHashMap<>();
|
|
methods = new LinkedHashMap<>();
|
|
fields = new LinkedHashMap<>();
|
|
}
|
|
|
|
/**
|
|
* Generates source which represents the class.
|
|
*
|
|
* @return source which represents the class
|
|
*/
|
|
@Override
|
|
public String generateSource() {
|
|
String sourceForAnnotations = generateSourceForAnnotations();
|
|
String classModifiers = mods.stream().collect(Collectors.joining(" "));
|
|
return sourceForAnnotations
|
|
+ String.format("%s%s %s %s %s {%n",
|
|
indention(),
|
|
classModifiers,
|
|
classType.getDescription(),
|
|
name,
|
|
parent == null ? "" : "extends " + parent)
|
|
+ classType.collectFields(fields.values())
|
|
+ classType.collectMethods(methods.values())
|
|
+ classType.collectInnerClasses(innerClasses.values())
|
|
+ indention() + "}";
|
|
}
|
|
|
|
/**
|
|
* Adds a new inner class to the class.
|
|
*
|
|
* @param classType a class type
|
|
* @param className a class name
|
|
* @param mods modifiers
|
|
* @return a new added inner class to the class
|
|
*/
|
|
public TestClassInfo addInnerClassInfo(ClassType classType, String className, String... mods) {
|
|
TestClassInfo testClass = new TestClassInfo(indention + 1, classType, className, mods);
|
|
if (innerClasses.put(className, testClass) != null) {
|
|
throw new IllegalArgumentException("Duplicated class : " + className);
|
|
}
|
|
return testClass;
|
|
}
|
|
|
|
/**
|
|
* Adds a new method to the class.
|
|
*
|
|
* @param methodName a method name
|
|
* @param mods modifiers
|
|
* @return a new inner class to the class
|
|
*/
|
|
public TestMethodInfo addMethodInfo(String methodName, String... mods) {
|
|
return addMethodInfo(methodName, false, mods);
|
|
}
|
|
|
|
/**
|
|
* Adds a new method to the class.
|
|
*
|
|
* @param methodName a method name
|
|
* @param isSynthetic if {@code true} the method is synthetic
|
|
* @param mods modifiers
|
|
* @return a new method added to the class
|
|
*/
|
|
public TestMethodInfo addMethodInfo(String methodName, boolean isSynthetic, String... mods) {
|
|
boolean isConstructor = methodName.contains("<init>");
|
|
if (isConstructor) {
|
|
methodName = methodName.replace("<init>", name);
|
|
}
|
|
TestMethodInfo testMethod = new TestMethodInfo(indention + 1, classType, methodName, isConstructor, isSynthetic, mods);
|
|
if (methods.put(methodName, testMethod) != null) {
|
|
throw new IllegalArgumentException("Duplicated method : " + methodName);
|
|
}
|
|
return testMethod;
|
|
}
|
|
|
|
/**
|
|
* Adds a new field to the class.
|
|
*
|
|
* @param fieldName a method name
|
|
* @param mods modifiers
|
|
* @return a new field added to the class
|
|
*/
|
|
public TestFieldInfo addFieldInfo(String fieldName, String... mods) {
|
|
TestFieldInfo field = new TestFieldInfo(indention + 1, classType, fieldName, mods);
|
|
if (fields.put(fieldName, field) != null) {
|
|
throw new IllegalArgumentException("Duplicated field : " + fieldName);
|
|
}
|
|
return field;
|
|
}
|
|
|
|
public TestMethodInfo getTestMethodInfo(String methodName) {
|
|
return methods.get(methodName);
|
|
}
|
|
|
|
public TestFieldInfo getTestFieldInfo(String fieldName) {
|
|
return fields.get(fieldName);
|
|
}
|
|
}
|
|
|
|
public static class TestMethodInfo extends TestMemberInfo {
|
|
public final boolean isConstructor;
|
|
public final boolean isSynthetic;
|
|
public final Map<String, TestClassInfo> localClasses;
|
|
public final List<TestParameterInfo> parameters;
|
|
|
|
TestMethodInfo(int indention, ClassType containerType, String methodName,
|
|
boolean isConstructor, boolean isSynthetic, String... mods) {
|
|
super(indention, containerType, methodName, mods);
|
|
this.isSynthetic = isSynthetic;
|
|
this.localClasses = new LinkedHashMap<>();
|
|
this.parameters = new ArrayList<>();
|
|
this.isConstructor = isConstructor;
|
|
}
|
|
|
|
public boolean isParameterAnnotated(RetentionPolicy policy) {
|
|
return parameters.stream()
|
|
.filter(p -> p.isAnnotated(policy))
|
|
.findFirst().isPresent();
|
|
}
|
|
|
|
public TestParameterInfo addParameter(String type, String name) {
|
|
TestParameterInfo testParameter = new TestParameterInfo(type, name);
|
|
parameters.add(testParameter);
|
|
return testParameter;
|
|
}
|
|
|
|
/**
|
|
* Adds a local class to the method.
|
|
*
|
|
* @param className a class name
|
|
* @param mods modifiers
|
|
* @return a local class added to the method
|
|
*/
|
|
public TestClassInfo addLocalClassInfo(String className, String... mods) {
|
|
TestClassInfo testClass = new TestClassInfo(indention + 1, ClassType.CLASS, className, mods);
|
|
if (localClasses.put(className, testClass) != null) {
|
|
throw new IllegalArgumentException("Duplicated class : " + className);
|
|
}
|
|
return testClass;
|
|
}
|
|
|
|
@Override
|
|
public String generateSource() {
|
|
if (isSynthetic) {
|
|
return "";
|
|
}
|
|
return generateSourceForAnnotations() +
|
|
containerType.methodToString(this);
|
|
}
|
|
|
|
@Override
|
|
public String getName() {
|
|
return name.replaceAll("\\(.*\\)", "");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The class represents a method parameter.
|
|
*/
|
|
public static class TestParameterInfo extends TestMemberInfo {
|
|
public final String type;
|
|
|
|
TestParameterInfo(String type, String name) {
|
|
super(0, null, name);
|
|
this.type = type;
|
|
}
|
|
|
|
@Override
|
|
public String generateSource() {
|
|
return generateSourceForAnnotations() + type + " " + name;
|
|
}
|
|
|
|
public String generateSourceForAnnotations() {
|
|
return generateSourceForAnnotations("", " ", " ");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The class represents a field.
|
|
*/
|
|
public static class TestFieldInfo extends TestMemberInfo {
|
|
|
|
TestFieldInfo(int indention, ClassType containerType, String fieldName, String... mods) {
|
|
super(indention, containerType, fieldName, mods);
|
|
}
|
|
|
|
@Override
|
|
public String generateSource() {
|
|
return generateSourceForAnnotations() +
|
|
containerType.fieldToString(this);
|
|
}
|
|
}
|
|
}
|