/* * 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); } } }