2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.junit;
44
import com.sun.source.tree.BlockTree;
45
import com.sun.source.tree.ClassTree;
46
import com.sun.source.tree.ExpressionTree;
47
import com.sun.source.tree.MethodInvocationTree;
48
import com.sun.source.tree.MethodTree;
49
import com.sun.source.tree.ModifiersTree;
50
import com.sun.source.tree.ReturnTree;
51
import com.sun.source.tree.StatementTree;
52
import com.sun.source.tree.Tree;
53
import com.sun.source.tree.TypeParameterTree;
54
import com.sun.source.tree.VariableTree;
55
import com.sun.source.util.TreePath;
56
import com.sun.source.util.Trees;
57
import java.util.ArrayList;
58
import java.util.Collections;
59
import java.util.List;
61
import javax.lang.model.element.Element;
62
import javax.lang.model.element.ExecutableElement;
63
import javax.lang.model.element.Modifier;
64
import javax.lang.model.element.TypeElement;
65
import javax.lang.model.type.TypeKind;
66
import javax.lang.model.type.TypeMirror;
67
import javax.lang.model.util.ElementFilter;
68
import javax.lang.model.util.Elements;
69
import javax.lang.model.util.Types;
70
import org.netbeans.api.java.source.ElementHandle;
71
import org.netbeans.api.java.source.TreeMaker;
72
import org.netbeans.api.java.source.WorkingCopy;
73
import static javax.lang.model.element.Modifier.ABSTRACT;
74
import static javax.lang.model.element.Modifier.PRIVATE;
75
import static javax.lang.model.element.Modifier.PROTECTED;
76
import static javax.lang.model.element.Modifier.PUBLIC;
77
import static javax.lang.model.element.Modifier.STATIC;
81
* @author Marian Petras
84
final class JUnit3TestGenerator extends AbstractTestGenerator {
86
private static final String TEST = "junit.framework.Test";
87
private static final String TEST_CASE = "junit.framework.TestCase";
88
private static final String TEST_SUITE = "junit.framework.TestSuite";
89
private static final String OVERRIDE = "java.lang.Override";
91
/** whether Java annotations should be generated */
92
private final boolean useAnnotations;
96
JUnit3TestGenerator(TestGeneratorSetup setup, String sourceLevel) {
98
useAnnotations = TestUtil.areAnnotationsSupported(sourceLevel);
103
JUnit3TestGenerator(TestGeneratorSetup setup,
104
List<ElementHandle<TypeElement>> srcTopClassHandles,
105
List<String>suiteMembers,
106
boolean isNewTestClass,
107
String sourceLevel) {
108
super(setup, srcTopClassHandles, suiteMembers, isNewTestClass);
109
useAnnotations = TestUtil.areAnnotationsSupported(sourceLevel);
115
protected ClassTree composeNewTestClass(WorkingCopy workingCopy,
117
List<? extends Tree> members) {
118
final TreeMaker maker = workingCopy.getTreeMaker();
119
ModifiersTree modifiers = maker.Modifiers(
120
Collections.<Modifier>singleton(PUBLIC));
121
Tree extendsClause = getClassIdentifierTree(TEST_CASE, workingCopy);
123
modifiers, //modifiers
125
Collections.<TypeParameterTree>emptyList(),//type params
126
extendsClause, //extends
127
Collections.<ExpressionTree>emptyList(), //implements
133
protected List<? extends Tree> generateInitMembers(WorkingCopy workingCopy) {
134
if (!setup.isGenerateSetUp() && !setup.isGenerateTearDown()) {
135
return Collections.<Tree>emptyList();
138
final TreeMaker maker = workingCopy.getTreeMaker();
139
List<MethodTree> result = new ArrayList<MethodTree>(2);
140
if (setup.isGenerateSetUp()) {
141
result.add(generateInitMethod("setUp", maker, workingCopy)); //NOI18N
143
if (setup.isGenerateTearDown()) {
144
result.add(generateInitMethod("tearDown", maker, workingCopy)); //NOI18N
151
protected ClassTree generateMissingInitMembers(ClassTree tstClass,
152
TreePath tstClassTreePath,
153
WorkingCopy workingCopy) {
154
if (!setup.isGenerateSetUp() && !setup.isGenerateTearDown()
155
&& !setup.isGenerateSuiteClasses()) {
159
ClassMap classMap = ClassMap.forClass(tstClass, tstClassTreePath, workingCopy.getTrees());
161
if ((!setup.isGenerateSetUp() || classMap.containsSetUp())
162
&& (!setup.isGenerateTearDown() || classMap.containsTearDown())
163
&& (!setup.isGenerateSuiteClasses() || classMap.containsNoArgMethod("suite"))) {//NOI18N
167
List<? extends Tree> tstMembersOrig = tstClass.getMembers();
168
List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 2);
169
tstMembers.addAll(tstMembersOrig);
171
generateMissingInitMembers(tstMembers, classMap, workingCopy);
172
generateTestClassSuiteMethod(tstClassTreePath, tstMembers, classMap,
175
ClassTree newClass = workingCopy.getTreeMaker().Class(
176
tstClass.getModifiers(),
177
tstClass.getSimpleName(),
178
tstClass.getTypeParameters(),
179
tstClass.getExtendsClause(),
180
(List<? extends ExpressionTree>) tstClass.getImplementsClause(),
187
protected boolean generateMissingInitMembers(List<Tree> tstMembers,
189
WorkingCopy workingCopy) {
190
TreeMaker treeMaker = workingCopy.getTreeMaker();
192
boolean modified = false;
193
if (setup.isGenerateSetUp() && !clsMap.containsSetUp()) {
194
addInitMethod("setUp", //NOI18N
195
clsMap.getTearDownIndex(),
202
if (setup.isGenerateTearDown() && !clsMap.containsTearDown()) {
203
int setUpIndex = clsMap.getSetUpIndex();
204
addInitMethod("tearDown", //NOI18N
205
(setUpIndex != -1) ? setUpIndex + 1 : -1,
216
* Creates a new init method ({@code setUp()}, {@code tearDown()}.
217
* When the method is created, it is added to the passed
218
* {@code List<Tree>} of class members and the passed {@code ClassMap}
219
* is updated appropriately.
221
* @param methodName name of the init method to be added
222
* @param targetIndex position in the list of members where the new
223
* init method should be put; or {@code -1} if this
224
* is the first init method to be added and
225
* the position should be determined automatically
226
* @param clsMembers list of class members to which the created init
227
* method should be added
228
* @param clsMap map of the current class members (will be updated)
229
* @param treeMaker maker to be used for creation of the init method
231
private void addInitMethod(String methodName,
233
List<Tree> clsMembers,
236
WorkingCopy workingCopy) {
237
MethodTree initMethod = generateInitMethod(methodName,
241
if (targetIndex == -1) {
242
targetIndex = getPlaceForFirstInitMethod(clsMap);
245
if (targetIndex != -1) {
246
clsMembers.add(targetIndex, initMethod);
248
clsMembers.add(initMethod);
250
clsMap.addNoArgMethod(methodName, targetIndex);
254
* Generates a set-up or a tear-down method.
255
* The generated method will have no arguments, void return type
256
* and a declaration that it may throw {@code java.lang.Exception}.
257
* The method will have a declared protected member access.
258
* The method contains call of the corresponding super method, i.e.
259
* {@code super.setUp()} or {@code super.tearDown()}.
261
* @param methodName name of the method to be created
262
* @return created method
263
* @see http://junit.sourceforge.net/javadoc/junit/framework/TestCase.html
264
* methods {@code setUp()} and {@code tearDown()}
266
protected MethodTree generateInitMethod(String methodName,
268
WorkingCopy workingCopy) {
269
Set<Modifier> modifiers = Collections.<Modifier>singleton(PROTECTED);
270
ModifiersTree modifiersTree
272
? createModifiersTree(OVERRIDE, modifiers, workingCopy)
273
: maker.Modifiers(modifiers);
274
ExpressionTree superMethodCall = maker.MethodInvocation(
275
Collections.<ExpressionTree>emptyList(), // type params.
277
maker.Identifier("super"), methodName), //NOI18N
278
Collections.<ExpressionTree>emptyList());
279
BlockTree methodBody = maker.Block(
280
Collections.<StatementTree>singletonList(
281
maker.ExpressionStatement(superMethodCall)),
283
MethodTree method = maker.Method(
284
modifiersTree, // modifiers
286
maker.PrimitiveType(TypeKind.VOID), // return type
287
Collections.<TypeParameterTree>emptyList(), // type params
288
Collections.<VariableTree>emptyList(), // parameters
289
Collections.<ExpressionTree>singletonList(
290
maker.Identifier("Exception")), // throws...//NOI18N
292
null); // default value
298
protected void generateMissingPostInitMethods(TreePath tstClassTreePath,
299
List<Tree> tstMembers,
301
WorkingCopy workingCopy) {
302
if (setup.isGenerateSuiteClasses()) {
303
generateTestClassSuiteMethod(tstClassTreePath,
313
protected String createTestMethodName(String smName) {
314
return "test" //NOI18N
315
+ smName.substring(0,1).toUpperCase() + smName.substring(1);
320
protected MethodTree composeNewTestMethod(String testMethodName,
321
BlockTree testMethodBody,
322
List<ExpressionTree> throwsList,
323
WorkingCopy workingCopy) {
324
TreeMaker maker = workingCopy.getTreeMaker();
326
maker.Modifiers(createModifierSet(PUBLIC)),
328
maker.PrimitiveType(TypeKind.VOID),
329
Collections.<TypeParameterTree>emptyList(),
330
Collections.<VariableTree>emptyList(),
333
null); //default value - used by annotations
338
protected ClassTree finishSuiteClass(ClassTree tstClass,
339
TreePath tstClassTreePath,
340
List<Tree> tstMembers,
341
List<String> suiteMembers,
342
boolean membersChanged,
344
WorkingCopy workingCopy) {
345
MethodTree suiteMethod = generateSuiteMethod(
346
tstClass.getSimpleName().toString(),
349
if (suiteMethod != null) {
350
int suiteMethodIndex = classMap.findNoArgMethod("suite"); //NOI18N
351
if (suiteMethodIndex != -1) {
352
tstMembers.set(suiteMethodIndex, suiteMethod); //replace method
355
if (classMap.containsInitializers()) {
356
targetIndex = classMap.getLastInitializerIndex() + 1;
357
} else if (classMap.containsMethods()) {
358
targetIndex = classMap.getFirstMethodIndex();
359
} else if (classMap.containsNestedClasses()) {
360
targetIndex = classMap.getFirstNestedClassIndex();
362
targetIndex = classMap.size();
364
if (targetIndex == classMap.size()) {
365
tstMembers.add(suiteMethod);
367
tstMembers.add(targetIndex, suiteMethod);
369
classMap.addNoArgMethod("suite", targetIndex); //NOI18N
371
membersChanged = true;
374
//PENDING - generating main(String[]) method:
375
//if (generateMainMethod && !TestUtil.hasMainMethod(tstClass)) {
376
// addMainMethod(tstClass);
379
if (!membersChanged) {
383
return workingCopy.getTreeMaker().Class(
384
tstClass.getModifiers(),
385
tstClass.getSimpleName(),
386
tstClass.getTypeParameters(),
387
tstClass.getExtendsClause(),
388
(List<? extends ExpressionTree>) tstClass.getImplementsClause(),
394
* @return object representing body of the suite() method,
395
* or {@code null} if an error occured while creating the body
397
private MethodTree generateSuiteMethod(String suiteName,
398
List<String> members,
399
WorkingCopy workingCopy) {
400
final Types types = workingCopy.getTypes();
401
final Elements elements = workingCopy.getElements();
402
final TreeMaker maker = workingCopy.getTreeMaker();
404
ExpressionTree testSuiteIdentifier
405
= getClassIdentifierTree(TEST_SUITE, workingCopy);
407
TypeElement testTypeElem = getTestTypeElem(elements);
408
if (testTypeElem == null) {
411
TypeMirror testType = testTypeElem.asType();
413
List<StatementTree> bodyContent
414
= new ArrayList<StatementTree>(members.size() + 2);
416
/* TestSuite suite = new TestSuite("ClassName") */
418
VariableTree suiteObjInit = maker.Variable(
419
maker.Modifiers(noModifiers()),
423
null, //enclosing instance
424
Collections.<ExpressionTree>emptyList(), //type args
425
testSuiteIdentifier, //class name
426
Collections.singletonList( //params
427
maker.Literal(TestUtil.getSimpleName(suiteName))),
430
bodyContent.add(suiteObjInit);
432
for (String className : members) {
433
TypeElement classElem = elements.getTypeElement(className);
434
if ((classElem != null) && containsSuiteMethod(
439
/* suite.addTest(ClassName.suite()) */
441
MethodInvocationTree suiteMethodCall, methodCall;
442
suiteMethodCall = maker.MethodInvocation(
443
Collections.<ExpressionTree>emptyList(),
444
maker.MemberSelect(maker.QualIdent(classElem),
446
Collections.<ExpressionTree>emptyList());
447
methodCall = maker.MethodInvocation(
448
Collections.<ExpressionTree>emptyList(),
449
maker.MemberSelect(maker.Identifier("suite"), //NOI18N
451
Collections.singletonList(suiteMethodCall));
453
bodyContent.add(maker.ExpressionStatement(methodCall));
459
bodyContent.add(maker.Return(maker.Identifier("suite"))); //NOI18N
463
maker.Modifiers(createModifierSet(PUBLIC, STATIC)),
465
maker.QualIdent(testTypeElem), //return type
466
Collections.<TypeParameterTree>emptyList(),//type params
467
Collections.<VariableTree>emptyList(), //params
468
Collections.<ExpressionTree>emptyList(), //throws-list
469
maker.Block(bodyContent, false), //body
470
null); //def. value - only for annotations
474
* Finds whether the given {@code TypeElement} or any of its type
475
* ancestor contains an accessible static no-arg method
478
* @param typeElement {@code TypeElement} to search
479
* @param methodName name of the method to be found
480
* @param elements support instance to be used for the search
481
* @return {@code true} if the given {@code TypeElement} contains,
482
* whether inherited or declared directly,
483
* a static no-argument method of the given name,
484
* {@code false} otherwise
486
private boolean containsSuiteMethod(TypeElement typeElement,
489
TypeMirror testType) {
490
List<ExecutableElement> allMethods
491
= ElementFilter.methodsIn(elements.getAllMembers(typeElement));
492
for (ExecutableElement method : allMethods) {
493
if (method.getSimpleName().contentEquals("suite") //NOI18N
494
&& method.getParameters().isEmpty()) {
495
return method.getModifiers().contains(Modifier.STATIC)
496
&& types.isSameType(method.getReturnType(),
505
private boolean generateTestClassSuiteMethod(TreePath tstClassTreePath,
506
List<Tree> tstMembers,
508
WorkingCopy workingCopy) {
509
if (!setup.isGenerateSuiteClasses()
510
|| clsMap.containsNoArgMethod("suite")) { //NOI18N
514
final TreeMaker maker = workingCopy.getTreeMaker();
515
final Elements elements = workingCopy.getElements();
516
final Trees trees = workingCopy.getTrees();
518
Element tstClassElem = trees.getElement(tstClassTreePath);
519
assert tstClassElem != null;
521
List<StatementTree> bodyContent = new ArrayList<StatementTree>(4);
524
/* TestSuite suite = new TestSuite(MyTestClass.class); */
526
VariableTree suiteVar = maker.Variable(
527
maker.Modifiers(noModifiers()),
529
getClassIdentifierTree(TEST_SUITE, workingCopy),
531
null, //enclosing instance
532
Collections.<ExpressionTree>emptyList(),
533
getClassIdentifierTree(TEST_SUITE, workingCopy),
534
Collections.singletonList(
535
maker.MemberSelect(maker.QualIdent(tstClassElem),
537
null)); //class definition
539
bodyContent.add(suiteVar);
541
/* suite.addTest(NestedClass.suite()); */
542
/* suite.addTest(AnotherNestedClass.suite()); */
545
List<TypeElement> nestedClassElems
546
= ElementFilter.typesIn(tstClassElem.getEnclosedElements());
547
if (!nestedClassElems.isEmpty()) {
548
for (TypeElement nestedClassElem : nestedClassElems) {
549
if (TestUtil.isClassTest(workingCopy, nestedClassElem)) {
551
/* suite.addTest(NestedClass.suite()); */
553
/* NestedClass.suite() */
554
MethodInvocationTree arg = maker.MethodInvocation(
555
Collections.<ExpressionTree>emptyList(),
557
maker.QualIdent(nestedClassElem),
559
Collections.<ExpressionTree>emptyList());
561
/* suite.addTest(...) */
562
MethodInvocationTree methodCall = maker.MethodInvocation(
563
Collections.<ExpressionTree>emptyList(),
565
maker.Identifier("suite"), //NOI18N
567
Collections.singletonList(arg));
569
bodyContent.add(maker.ExpressionStatement(methodCall));
576
ReturnTree returnStmt
577
= maker.Return(maker.Identifier("suite")); //NOI18N
578
bodyContent.add(returnStmt);
580
MethodTree suiteMethod = maker.Method(
581
maker.Modifiers(createModifierSet(PUBLIC, STATIC)),
583
getClassIdentifierTree(TEST, workingCopy),
584
Collections.<TypeParameterTree>emptyList(), //type params
585
Collections.<VariableTree>emptyList(), //parameters
586
Collections.<ExpressionTree>emptyList(), //throws ...
587
maker.Block(bodyContent, false), //body
588
null); //default value
591
if (clsMap.containsMethods()) {
592
targetIndex = clsMap.getFirstMethodIndex(); //before methods
593
} else if (clsMap.containsNestedClasses()) {
594
targetIndex = clsMap.getFirstNestedClassIndex(); //before nested
596
targetIndex = clsMap.size(); //end of the class
599
if (targetIndex == clsMap.size()) {
600
tstMembers.add(suiteMethod);
602
tstMembers.add(targetIndex, suiteMethod);
604
clsMap.addNoArgMethod("suite", targetIndex); //NOI18N
609
/** element representing type {@code junit.framework.Test} */
610
private TypeElement testTypeElem;
614
private TypeElement getTestTypeElem(Elements elements) {
615
if (testTypeElem == null) {
616
testTypeElem = getElemForClassName(
617
"junit.framework.Test", //NOI18N