blob: f2296eb709a46dcc238e35ecdd0d2698a277dfb0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.junit;
import org.netbeans.modules.junit.api.JUnitVersion;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.WorkingCopy;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
/**
*
* @author Marian Petras
*/
final class JUnit5TestGenerator extends AbstractTestGenerator {
/** */
static final String ANN_BEFORE_CLASS = "org.junit.jupiter.api.BeforeAll"; //NOI18N
/** */
static final String ANN_AFTER_CLASS = "org.junit.jupiter.api.AfterAll"; //NOI18N
/** */
static final String ANN_BEFORE = "org.junit.jupiter.api.BeforeEach"; //NOI18N
/** */
static final String ANN_AFTER = "org.junit.jupiter.api.AfterEach"; //NOI18N
/** */
static final String ANN_TEST = "org.junit.jupiter.api.Test"; //NOI18N
/** */
private static final String BEFORE_CLASS_METHOD_NAME = "setUpClass";//NOI18N
/** */
private static final String AFTER_CLASS_METHOD_NAME = "tearDownClass";//NOI18N
/** */
private static final String BEFORE_METHOD_NAME = "setUp"; //NOI18N
/** */
private static final String AFTER_METHOD_NAME = "tearDown"; //NOI18N
/**
*/
JUnit5TestGenerator(TestGeneratorSetup setup) {
super(setup, JUnitVersion.JUNIT5);
}
/**
*/
JUnit5TestGenerator(TestGeneratorSetup setup,
List<ElementHandle<TypeElement>> srcTopClassHandles,
List<String>suiteMembers,
boolean isNewTestClass) {
super(setup, srcTopClassHandles, suiteMembers, isNewTestClass, JUnitVersion.JUNIT5);
}
/**
*/
@Override
protected ClassTree composeNewTestClass(WorkingCopy workingCopy,
String name,
List<? extends Tree> members) {
final TreeMaker maker = workingCopy.getTreeMaker();
ModifiersTree modifiers = maker.Modifiers(
Collections.<Modifier>singleton(PUBLIC));
return maker.Class(
modifiers, //modifiers
name, //name
Collections.<TypeParameterTree>emptyList(),//type params
null, //extends
Collections.<ExpressionTree>emptyList(), //implements
members); //members
}
/**
*/
@Override
protected List<? extends Tree> generateInitMembers(WorkingCopy workingCopy) {
if (!setup.isGenerateBefore() && !setup.isGenerateAfter()
&& !setup.isGenerateBeforeClass() && !setup.isGenerateAfterClass()) {
return Collections.<Tree>emptyList();
}
List<MethodTree> result = new ArrayList<MethodTree>(4);
if (setup.isGenerateBeforeClass()) {
result.add(
generateInitMethod(BEFORE_CLASS_METHOD_NAME, ANN_BEFORE_CLASS, true, workingCopy));
}
if (setup.isGenerateAfterClass()) {
result.add(
generateInitMethod(AFTER_CLASS_METHOD_NAME, ANN_AFTER_CLASS, true, workingCopy));
}
if (setup.isGenerateBefore()) {
result.add(
generateInitMethod(BEFORE_METHOD_NAME, ANN_BEFORE, false, workingCopy));
}
if (setup.isGenerateAfter()) {
result.add(
generateInitMethod(AFTER_METHOD_NAME, ANN_AFTER, false, workingCopy));
}
return result;
}
/**
*/
@Override
protected ClassTree generateMissingInitMembers(ClassTree tstClass,
TreePath tstClassTreePath,
WorkingCopy workingCopy) {
if (!setup.isGenerateBefore() && !setup.isGenerateAfter()
&& !setup.isGenerateBeforeClass() && !setup.isGenerateAfterClass()) {
return tstClass;
}
ClassMap classMap = ClassMap.forClass(tstClass, tstClassTreePath,
workingCopy.getTrees());
if ((!setup.isGenerateBefore() || classMap.containsBefore())
&& (!setup.isGenerateAfter() || classMap.containsAfter())
&& (!setup.isGenerateBeforeClass() || classMap.containsBeforeClass())
&& (!setup.isGenerateAfterClass() || classMap.containsAfterClass())) {
return tstClass;
}
final TreeMaker maker = workingCopy.getTreeMaker();
List<? extends Tree> tstMembersOrig = tstClass.getMembers();
List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 4);
tstMembers.addAll(tstMembersOrig);
generateMissingInitMembers(tstMembers, classMap, workingCopy);
ClassTree newClass = maker.Class(
tstClass.getModifiers(),
tstClass.getSimpleName(),
tstClass.getTypeParameters(),
tstClass.getExtendsClause(),
(List<? extends ExpressionTree>) tstClass.getImplementsClause(),
tstMembers);
return newClass;
}
/**
*/
@Override
protected boolean generateMissingInitMembers(List<Tree> tstMembers,
ClassMap clsMap,
WorkingCopy workingCopy) {
boolean modified = false;
if (setup.isGenerateBeforeClass() && !clsMap.containsBeforeClass()) {
int targetIndex;
if (clsMap.containsAfterClass()) {
targetIndex = clsMap.getAfterClassIndex();
} else {
int beforeIndex = clsMap.getBeforeIndex();
int afterIndex = clsMap.getAfterIndex();
if ((beforeIndex != -1) && (afterIndex != -1)) {
targetIndex = Math.min(beforeIndex, afterIndex);
} else {
targetIndex = Math.max(beforeIndex, afterIndex);
}
}
addInitMethod(BEFORE_CLASS_METHOD_NAME,
ANN_BEFORE_CLASS,
true,
targetIndex,
tstMembers,
clsMap,
workingCopy);
modified = true;
}
if (setup.isGenerateAfterClass() && !clsMap.containsAfterClass()) {
int targetIndex;
if (clsMap.containsBeforeClass()) {
targetIndex = clsMap.getBeforeClassIndex() + 1;
} else {
int beforeIndex = clsMap.getBeforeIndex();
int afterIndex = clsMap.getAfterIndex();
if ((beforeIndex != -1) && (afterIndex != -1)) {
targetIndex = Math.min(beforeIndex, afterIndex);
} else {
targetIndex = Math.max(beforeIndex, afterIndex);
}
}
addInitMethod(AFTER_CLASS_METHOD_NAME,
ANN_AFTER_CLASS,
true,
targetIndex,
tstMembers,
clsMap,
workingCopy);
modified = true;
}
if (setup.isGenerateBefore() && !clsMap.containsBefore()) {
int targetIndex;
if (clsMap.containsAfter()) {
targetIndex = clsMap.getAfterIndex();
} else {
int beforeClassIndex = clsMap.getBeforeClassIndex();
int afterClassIndex = clsMap.getAfterClassIndex();
targetIndex = Math.max(beforeClassIndex, afterClassIndex);
if (targetIndex != -1) {
targetIndex++;
}
}
addInitMethod(BEFORE_METHOD_NAME,
ANN_BEFORE,
false,
targetIndex,
tstMembers,
clsMap,
workingCopy);
modified = true;
}
if (setup.isGenerateAfter() && !clsMap.containsAfter()) {
int targetIndex;
if (clsMap.containsBefore()) {
targetIndex = clsMap.getBeforeIndex() + 1;
} else {
int beforeClassIndex = clsMap.getBeforeClassIndex();
int afterClassIndex = clsMap.getAfterClassIndex();
targetIndex = Math.max(beforeClassIndex, afterClassIndex);
if (targetIndex != -1) {
targetIndex++;
}
}
addInitMethod(AFTER_METHOD_NAME,
ANN_AFTER,
false,
targetIndex,
tstMembers,
clsMap,
workingCopy);
modified = true;
}
return modified;
}
/**
*/
private void addInitMethod(String methodName,
String annotationClassName,
boolean isStatic,
int targetIndex,
List<Tree> clsMembers,
ClassMap clsMap,
WorkingCopy workingCopy) {
MethodTree initMethod = generateInitMethod(methodName,
annotationClassName,
isStatic,
workingCopy);
if (targetIndex == -1) {
targetIndex = getPlaceForFirstInitMethod(clsMap);
}
if (targetIndex != -1) {
clsMembers.add(targetIndex, initMethod);
} else {
clsMembers.add(initMethod);
}
clsMap.addNoArgMethod(methodName, annotationClassName, targetIndex);
}
/**
* Generates a set-up or a tear-down method.
* The generated method will have no arguments, void return type
* and a declaration that it may throw {@code java.lang.Exception}.
* The method will have a declared protected member access.
* The method contains call of the corresponding super method, i.e.
* {@code super.setUp()} or {@code super.tearDown()}.
*
* @param methodName name of the method to be created
* @return created method
* @see http://junit.sourceforge.net/javadoc/junit/framework/TestCase.html
* methods {@code setUp()} and {@code tearDown()}
*/
private MethodTree generateInitMethod(String methodName,
String annotationClassName,
boolean isStatic,
WorkingCopy workingCopy) {
Set<Modifier> methodModifiers
= isStatic ? createModifierSet(PUBLIC, STATIC)
: Collections.<Modifier>singleton(PUBLIC);
ModifiersTree modifiers = createModifiersTree(annotationClassName,
methodModifiers,
workingCopy);
TreeMaker maker = workingCopy.getTreeMaker();
BlockTree methodBody = maker.Block(
Collections.<StatementTree>emptyList(),
false);
MethodTree method = maker.Method(
modifiers, // modifiers
methodName, // name
maker.PrimitiveType(TypeKind.VOID), // return type
Collections.<TypeParameterTree>emptyList(), // type params
Collections.<VariableTree>emptyList(), // parameters
Collections.<ExpressionTree>singletonList(
maker.Identifier("Exception")), // throws...//NOI18N
methodBody,
null); // default value
return method;
}
/**
*/
@Override
protected void generateMissingPostInitMethods(TreePath tstClassTreePath,
List<Tree> tstMembers,
ClassMap clsMap,
WorkingCopy workingCopy) {
/* no post-init methods */
}
/**
*/
@Override
protected MethodTree composeNewTestMethod(String testMethodName,
BlockTree testMethodBody,
List<ExpressionTree> throwsList,
WorkingCopy workingCopy) {
TreeMaker maker = workingCopy.getTreeMaker();
return maker.Method(
createModifiersTree(ANN_TEST,
createModifierSet(PUBLIC),
workingCopy),
testMethodName,
maker.PrimitiveType(TypeKind.VOID),
Collections.<TypeParameterTree>emptyList(),
Collections.<VariableTree>emptyList(),
throwsList,
testMethodBody,
null); //default value - used by annotations
}
/**
*/
@Override
protected ClassTree finishSuiteClass(ClassTree tstClass,
TreePath tstClassTreePath,
List<Tree> tstMembers,
List<String> suiteMembers,
boolean membersChanged,
ClassMap classMap,
WorkingCopy workingCopy) {
return tstClass;
}
/**
* Returns fully qualified class name of a class given to an annotation
* as (the only) argument.
*
* @param annValue annotation value
* @return fully qualified name of a class represented by the given
* annotation value, or {@code null} if the annotation value
* does not represent a class
*/
private Name getAnnotationValueClassName(AnnotationValue annValue,
Types types) {
Object value = annValue.getValue();
if (value instanceof TypeMirror) {
TypeMirror typeMirror = (TypeMirror) value;
Element typeElement = types.asElement(typeMirror);
if (typeElement.getKind() == ElementKind.CLASS) {
return ((TypeElement) typeElement).getQualifiedName();
}
}
return null;
}
}