blob: 1d6e3c4133f2d20503abe8c92a27192792a6a6a2 [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 org.netbeans.modules.junit.api.JUnitTestUtil;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.ReturnTree;
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 com.sun.source.util.Trees;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
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.ABSTRACT;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PROTECTED;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
/**
*
* @author Marian Petras
* @author vstejskal
*/
final class JUnit3TestGenerator extends AbstractTestGenerator {
private static final String TEST = "junit.framework.Test";
private static final String TEST_CASE = "junit.framework.TestCase";
private static final String TEST_SUITE = "junit.framework.TestSuite";
private static final String OVERRIDE = "java.lang.Override";
/** whether Java annotations should be generated */
private final boolean useAnnotations;
/**
*/
JUnit3TestGenerator(TestGeneratorSetup setup, String sourceLevel) {
super(setup, JUnitVersion.JUNIT3);
useAnnotations = JUnitTestUtil.areAnnotationsSupported(sourceLevel);
}
/**
*/
JUnit3TestGenerator(TestGeneratorSetup setup,
List<ElementHandle<TypeElement>> srcTopClassHandles,
List<String>suiteMembers,
boolean isNewTestClass,
String sourceLevel) {
super(setup, srcTopClassHandles, suiteMembers, isNewTestClass, JUnitVersion.JUNIT3);
useAnnotations = JUnitTestUtil.areAnnotationsSupported(sourceLevel);
}
/**
*/
protected ClassTree composeNewTestClass(WorkingCopy workingCopy,
String name,
List<? extends Tree> members) {
final TreeMaker maker = workingCopy.getTreeMaker();
ModifiersTree modifiers = maker.Modifiers(
Collections.<Modifier>singleton(PUBLIC));
Tree extendsClause = getClassIdentifierTree(TEST_CASE, workingCopy);
return maker.Class(
modifiers, //modifiers
name, //name
Collections.<TypeParameterTree>emptyList(),//type params
extendsClause, //extends
Collections.<ExpressionTree>emptyList(), //implements
members); //members
}
/**
*/
protected List<? extends Tree> generateInitMembers(WorkingCopy workingCopy) {
if (!setup.isGenerateSetUp() && !setup.isGenerateTearDown()) {
return Collections.<Tree>emptyList();
}
final TreeMaker maker = workingCopy.getTreeMaker();
List<MethodTree> result = new ArrayList<MethodTree>(2);
if (setup.isGenerateSetUp()) {
result.add(generateInitMethod("setUp", maker, workingCopy)); //NOI18N
}
if (setup.isGenerateTearDown()) {
result.add(generateInitMethod("tearDown", maker, workingCopy)); //NOI18N
}
return result;
}
/**
*/
protected ClassTree generateMissingInitMembers(ClassTree tstClass,
TreePath tstClassTreePath,
WorkingCopy workingCopy) {
if (!setup.isGenerateSetUp() && !setup.isGenerateTearDown()
&& !setup.isGenerateSuiteClasses()) {
return tstClass;
}
ClassMap classMap = ClassMap.forClass(tstClass, tstClassTreePath, workingCopy.getTrees());
if ((!setup.isGenerateSetUp() || classMap.containsSetUp())
&& (!setup.isGenerateTearDown() || classMap.containsTearDown())
&& (!setup.isGenerateSuiteClasses() || classMap.containsNoArgMethod("suite"))) {//NOI18N
return tstClass;
}
List<? extends Tree> tstMembersOrig = tstClass.getMembers();
List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 2);
tstMembers.addAll(tstMembersOrig);
generateMissingInitMembers(tstMembers, classMap, workingCopy);
generateTestClassSuiteMethod(tstClassTreePath, tstMembers, classMap,
workingCopy);
ClassTree newClass = workingCopy.getTreeMaker().Class(
tstClass.getModifiers(),
tstClass.getSimpleName(),
tstClass.getTypeParameters(),
tstClass.getExtendsClause(),
(List<? extends ExpressionTree>) tstClass.getImplementsClause(),
tstMembers);
return newClass;
}
/**
*/
protected boolean generateMissingInitMembers(List<Tree> tstMembers,
ClassMap clsMap,
WorkingCopy workingCopy) {
TreeMaker treeMaker = workingCopy.getTreeMaker();
boolean modified = false;
if (setup.isGenerateSetUp() && !clsMap.containsSetUp()) {
addInitMethod("setUp", //NOI18N
clsMap.getTearDownIndex(),
tstMembers,
clsMap,
treeMaker,
workingCopy);
modified = true;
}
if (setup.isGenerateTearDown() && !clsMap.containsTearDown()) {
int setUpIndex = clsMap.getSetUpIndex();
addInitMethod("tearDown", //NOI18N
(setUpIndex != -1) ? setUpIndex + 1 : -1,
tstMembers,
clsMap,
treeMaker,
workingCopy);
modified = true;
}
return modified;
}
/**
* Creates a new init method ({@code setUp()}, {@code tearDown()}.
* When the method is created, it is added to the passed
* {@code List<Tree>} of class members and the passed {@code ClassMap}
* is updated appropriately.
*
* @param methodName name of the init method to be added
* @param targetIndex position in the list of members where the new
* init method should be put; or {@code -1} if this
* is the first init method to be added and
* the position should be determined automatically
* @param clsMembers list of class members to which the created init
* method should be added
* @param clsMap map of the current class members (will be updated)
* @param treeMaker maker to be used for creation of the init method
*/
private void addInitMethod(String methodName,
int targetIndex,
List<Tree> clsMembers,
ClassMap clsMap,
TreeMaker treeMaker,
WorkingCopy workingCopy) {
MethodTree initMethod = generateInitMethod(methodName,
treeMaker,
workingCopy);
if (targetIndex == -1) {
targetIndex = getPlaceForFirstInitMethod(clsMap);
}
if (targetIndex != -1) {
clsMembers.add(targetIndex, initMethod);
} else {
clsMembers.add(initMethod);
}
clsMap.addNoArgMethod(methodName, 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()}
*/
protected MethodTree generateInitMethod(String methodName,
TreeMaker maker,
WorkingCopy workingCopy) {
Set<Modifier> modifiers = Collections.<Modifier>singleton(PROTECTED);
ModifiersTree modifiersTree
= useAnnotations
? createModifiersTree(OVERRIDE, modifiers, workingCopy)
: maker.Modifiers(modifiers);
ExpressionTree superMethodCall = maker.MethodInvocation(
Collections.<ExpressionTree>emptyList(), // type params.
maker.MemberSelect(
maker.Identifier("super"), methodName), //NOI18N
Collections.<ExpressionTree>emptyList());
BlockTree methodBody = maker.Block(
Collections.<StatementTree>singletonList(
maker.ExpressionStatement(superMethodCall)),
false);
MethodTree method = maker.Method(
modifiersTree, // 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;
}
/**
*/
protected void generateMissingPostInitMethods(TreePath tstClassTreePath,
List<Tree> tstMembers,
ClassMap clsMap,
WorkingCopy workingCopy) {
if (setup.isGenerateSuiteClasses()) {
generateTestClassSuiteMethod(tstClassTreePath,
tstMembers,
clsMap,
workingCopy);
}
}
/**
*/
protected MethodTree composeNewTestMethod(String testMethodName,
BlockTree testMethodBody,
List<ExpressionTree> throwsList,
WorkingCopy workingCopy) {
TreeMaker maker = workingCopy.getTreeMaker();
return maker.Method(
maker.Modifiers(createModifierSet(PUBLIC)),
testMethodName,
maker.PrimitiveType(TypeKind.VOID),
Collections.<TypeParameterTree>emptyList(),
Collections.<VariableTree>emptyList(),
throwsList,
testMethodBody,
null); //default value - used by annotations
}
/**
*/
protected ClassTree finishSuiteClass(ClassTree tstClass,
TreePath tstClassTreePath,
List<Tree> tstMembers,
List<String> suiteMembers,
boolean membersChanged,
ClassMap classMap,
WorkingCopy workingCopy) {
MethodTree suiteMethod = generateSuiteMethod(
tstClass.getSimpleName().toString(),
suiteMembers,
workingCopy);
if (suiteMethod != null) {
int suiteMethodIndex = classMap.findNoArgMethod("suite"); //NOI18N
if (suiteMethodIndex != -1) {
tstMembers.set(suiteMethodIndex, suiteMethod); //replace method
} else {
int targetIndex;
if (classMap.containsInitializers()) {
targetIndex = classMap.getLastInitializerIndex() + 1;
} else if (classMap.containsMethods()) {
targetIndex = classMap.getFirstMethodIndex();
} else if (classMap.containsNestedClasses()) {
targetIndex = classMap.getFirstNestedClassIndex();
} else {
targetIndex = classMap.size();
}
if (targetIndex == classMap.size()) {
tstMembers.add(suiteMethod);
} else {
tstMembers.add(targetIndex, suiteMethod);
}
classMap.addNoArgMethod("suite", targetIndex); //NOI18N
}
membersChanged = true;
}
//PENDING - generating main(String[]) method:
//if (generateMainMethod && !JUnitTestUtil.hasMainMethod(tstClass)) {
// addMainMethod(tstClass);
//}
if (!membersChanged) {
return tstClass;
}
return workingCopy.getTreeMaker().Class(
tstClass.getModifiers(),
tstClass.getSimpleName(),
tstClass.getTypeParameters(),
tstClass.getExtendsClause(),
(List<? extends ExpressionTree>) tstClass.getImplementsClause(),
tstMembers);
}
/**
*
* @return object representing body of the suite() method,
* or {@code null} if an error occured while creating the body
*/
private MethodTree generateSuiteMethod(String suiteName,
List<String> members,
WorkingCopy workingCopy) {
final Types types = workingCopy.getTypes();
final Elements elements = workingCopy.getElements();
final TreeMaker maker = workingCopy.getTreeMaker();
ExpressionTree testSuiteIdentifier
= getClassIdentifierTree(TEST_SUITE, workingCopy);
TypeElement testTypeElem = getTestTypeElem(elements);
if (testTypeElem == null) {
return null;
}
TypeMirror testType = testTypeElem.asType();
List<StatementTree> bodyContent
= new ArrayList<StatementTree>(members.size() + 2);
/* TestSuite suite = new TestSuite("ClassName") */
VariableTree suiteObjInit = maker.Variable(
maker.Modifiers(noModifiers()),
"suite", //NOI18N
testSuiteIdentifier,
maker.NewClass(
null, //enclosing instance
Collections.<ExpressionTree>emptyList(), //type args
testSuiteIdentifier, //class name
Collections.singletonList( //params
maker.Literal(JUnitTestUtil.getSimpleName(suiteName))),
null)); //class body
bodyContent.add(suiteObjInit);
for (String className : members) {
TypeElement classElem = elements.getTypeElement(className);
if ((classElem != null) && containsSuiteMethod(
classElem,
elements, types,
testType)) {
/* suite.addTest(ClassName.suite()) */
MethodInvocationTree suiteMethodCall, methodCall;
suiteMethodCall = maker.MethodInvocation(
Collections.<ExpressionTree>emptyList(),
maker.MemberSelect(maker.QualIdent(classElem),
"suite"), //NOI18N
Collections.<ExpressionTree>emptyList());
methodCall = maker.MethodInvocation(
Collections.<ExpressionTree>emptyList(),
maker.MemberSelect(maker.Identifier("suite"), //NOI18N
"addTest"), //NOI18N
Collections.singletonList(suiteMethodCall));
bodyContent.add(maker.ExpressionStatement(methodCall));
}
}
/* return suite; */
bodyContent.add(maker.Return(maker.Identifier("suite"))); //NOI18N
return maker.Method(
maker.Modifiers(createModifierSet(PUBLIC, STATIC)),
"suite", //NOI18N
maker.QualIdent(testTypeElem), //return type
Collections.<TypeParameterTree>emptyList(),//type params
Collections.<VariableTree>emptyList(), //params
Collections.<ExpressionTree>emptyList(), //throws-list
maker.Block(bodyContent, false), //body
null); //def. value - only for annotations
}
/**
* Finds whether the given {@code TypeElement} or any of its type
* ancestor contains an accessible static no-arg method
* of the given name.
*
* @param typeElement {@code TypeElement} to search
* @param methodName name of the method to be found
* @param elements support instance to be used for the search
* @return {@code true} if the given {@code TypeElement} contains,
* whether inherited or declared directly,
* a static no-argument method of the given name,
* {@code false} otherwise
*/
private boolean containsSuiteMethod(TypeElement typeElement,
Elements elements,
Types types,
TypeMirror testType) {
List<ExecutableElement> allMethods
= ElementFilter.methodsIn(elements.getAllMembers(typeElement));
for (ExecutableElement method : allMethods) {
if (method.getSimpleName().contentEquals("suite") //NOI18N
&& method.getParameters().isEmpty()) {
return method.getModifiers().contains(Modifier.STATIC)
&& types.isSameType(method.getReturnType(),
testType);
}
}
return false;
}
/**
*/
private boolean generateTestClassSuiteMethod(TreePath tstClassTreePath,
List<Tree> tstMembers,
ClassMap clsMap,
WorkingCopy workingCopy) {
if (!setup.isGenerateSuiteClasses()
|| clsMap.containsNoArgMethod("suite")) { //NOI18N
return false;
}
final TreeMaker maker = workingCopy.getTreeMaker();
final Elements elements = workingCopy.getElements();
final Trees trees = workingCopy.getTrees();
Element tstClassElem = trees.getElement(tstClassTreePath);
assert tstClassElem != null;
List<StatementTree> bodyContent = new ArrayList<StatementTree>(4);
/* TestSuite suite = new TestSuite(MyTestClass.class); */
VariableTree suiteVar = maker.Variable(
maker.Modifiers(noModifiers()),
"suite", //NOI18N
getClassIdentifierTree(TEST_SUITE, workingCopy),
maker.NewClass(
null, //enclosing instance
Collections.<ExpressionTree>emptyList(),
getClassIdentifierTree(TEST_SUITE, workingCopy),
Collections.singletonList(
maker.MemberSelect(maker.QualIdent(tstClassElem),
"class")), //NOI18N
null)); //class definition
bodyContent.add(suiteVar);
/* suite.addTest(NestedClass.suite()); */
/* suite.addTest(AnotherNestedClass.suite()); */
/* ... */
List<TypeElement> nestedClassElems
= ElementFilter.typesIn(tstClassElem.getEnclosedElements());
if (!nestedClassElems.isEmpty()) {
for (TypeElement nestedClassElem : nestedClassElems) {
if (JUnitTestUtil.isClassTest(workingCopy, nestedClassElem)) {
/* suite.addTest(NestedClass.suite()); */
/* NestedClass.suite() */
MethodInvocationTree arg = maker.MethodInvocation(
Collections.<ExpressionTree>emptyList(),
maker.MemberSelect(
maker.QualIdent(nestedClassElem),
"suite"), //NOI18N
Collections.<ExpressionTree>emptyList());
/* suite.addTest(...) */
MethodInvocationTree methodCall = maker.MethodInvocation(
Collections.<ExpressionTree>emptyList(),
maker.MemberSelect(
maker.Identifier("suite"), //NOI18N
"addTest"), //NOI18N
Collections.singletonList(arg));
bodyContent.add(maker.ExpressionStatement(methodCall));
}
}
}
/* return suite; */
ReturnTree returnStmt
= maker.Return(maker.Identifier("suite")); //NOI18N
bodyContent.add(returnStmt);
MethodTree suiteMethod = maker.Method(
maker.Modifiers(createModifierSet(PUBLIC, STATIC)),
"suite", //NOI18N
getClassIdentifierTree(TEST, workingCopy),
Collections.<TypeParameterTree>emptyList(), //type params
Collections.<VariableTree>emptyList(), //parameters
Collections.<ExpressionTree>emptyList(), //throws ...
maker.Block(bodyContent, false), //body
null); //default value
int targetIndex;
if (clsMap.containsMethods()) {
targetIndex = clsMap.getFirstMethodIndex(); //before methods
} else if (clsMap.containsNestedClasses()) {
targetIndex = clsMap.getFirstNestedClassIndex(); //before nested
} else {
targetIndex = clsMap.size(); //end of the class
}
if (targetIndex == clsMap.size()) {
tstMembers.add(suiteMethod);
} else {
tstMembers.add(targetIndex, suiteMethod);
}
clsMap.addNoArgMethod("suite", targetIndex); //NOI18N
return true;
}
/** element representing type {@code junit.framework.Test} */
private TypeElement testTypeElem;
/**
*/
private TypeElement getTestTypeElem(Elements elements) {
if (testTypeElem == null) {
testTypeElem = getElemForClassName(
"junit.framework.Test", //NOI18N
elements);
}
return testTypeElem;
}
}