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.AnnotationTree;
45
import com.sun.source.tree.BlockTree;
46
import com.sun.source.tree.ClassTree;
47
import com.sun.source.tree.ExpressionTree;
48
import com.sun.source.tree.MethodTree;
49
import com.sun.source.tree.ModifiersTree;
50
import com.sun.source.tree.StatementTree;
51
import com.sun.source.tree.Tree;
52
import com.sun.source.tree.TypeParameterTree;
53
import com.sun.source.tree.VariableTree;
54
import com.sun.source.util.TreePath;
55
import com.sun.source.util.Trees;
56
import java.util.ArrayList;
57
import java.util.Collections;
58
import java.util.EnumSet;
59
import java.util.Iterator;
60
import java.util.List;
63
import javax.lang.model.element.AnnotationMirror;
64
import javax.lang.model.element.AnnotationValue;
65
import javax.lang.model.element.Element;
66
import javax.lang.model.element.ElementKind;
67
import javax.lang.model.element.ExecutableElement;
68
import javax.lang.model.element.Modifier;
69
import javax.lang.model.element.Name;
70
import javax.lang.model.element.TypeElement;
71
import javax.lang.model.type.TypeKind;
72
import javax.lang.model.type.TypeMirror;
73
import javax.lang.model.util.Types;
74
import org.netbeans.api.java.source.ElementHandle;
75
import org.netbeans.api.java.source.TreeMaker;
76
import org.netbeans.api.java.source.WorkingCopy;
77
import static javax.lang.model.element.Modifier.PUBLIC;
78
import static javax.lang.model.element.Modifier.PROTECTED;
79
import static javax.lang.model.element.Modifier.PRIVATE;
80
import static javax.lang.model.element.Modifier.STATIC;
84
* @author Marian Petras
86
final class JUnit4TestGenerator extends AbstractTestGenerator {
89
static final String ANN_BEFORE_CLASS = "org.junit.BeforeClass"; //NOI18N
91
static final String ANN_AFTER_CLASS = "org.junit.AfterClass"; //NOI18N
93
static final String ANN_BEFORE = "org.junit.Before"; //NOI18N
95
static final String ANN_AFTER = "org.junit.After"; //NOI18N
97
static final String ANN_TEST = "org.junit.Test"; //NOI18N
99
private static final String ANN_RUN_WITH = "org.junit.runner.RunWith";//NOI18N
101
private static final String ANN_SUITE = "org.junit.runners.Suite"; //NOI18N
103
private static final String ANN_SUITE_MEMBERS = "SuiteClasses"; //NOI18N
105
private static final String BEFORE_CLASS_METHOD_NAME = "setUpClass";//NOI18N
107
private static final String AFTER_CLASS_METHOD_NAME = "tearDownClass";//NOI18N
109
private static final String BEFORE_METHOD_NAME = "setUp"; //NOI18N
111
private static final String AFTER_METHOD_NAME = "tearDown"; //NOI18N
115
JUnit4TestGenerator(TestGeneratorSetup setup) {
121
JUnit4TestGenerator(TestGeneratorSetup setup,
122
List<ElementHandle<TypeElement>> srcTopClassHandles,
123
List<String>suiteMembers,
124
boolean isNewTestClass) {
125
super(setup, srcTopClassHandles, suiteMembers, isNewTestClass);
131
protected ClassTree composeNewTestClass(WorkingCopy workingCopy,
133
List<? extends Tree> members) {
134
final TreeMaker maker = workingCopy.getTreeMaker();
135
ModifiersTree modifiers = maker.Modifiers(
136
Collections.<Modifier>singleton(PUBLIC));
138
modifiers, //modifiers
140
Collections.<TypeParameterTree>emptyList(),//type params
142
Collections.<ExpressionTree>emptyList(), //implements
148
protected List<? extends Tree> generateInitMembers(WorkingCopy workingCopy) {
149
if (!setup.isGenerateBefore() && !setup.isGenerateAfter()
150
&& !setup.isGenerateBeforeClass() && !setup.isGenerateAfterClass()) {
151
return Collections.<Tree>emptyList();
154
List<MethodTree> result = new ArrayList<MethodTree>(4);
155
if (setup.isGenerateBeforeClass()) {
157
generateInitMethod(BEFORE_CLASS_METHOD_NAME, ANN_BEFORE_CLASS, true, workingCopy));
159
if (setup.isGenerateAfterClass()) {
161
generateInitMethod(AFTER_CLASS_METHOD_NAME, ANN_AFTER_CLASS, true, workingCopy));
163
if (setup.isGenerateBefore()) {
165
generateInitMethod(BEFORE_METHOD_NAME, ANN_BEFORE, false, workingCopy));
167
if (setup.isGenerateAfter()) {
169
generateInitMethod(AFTER_METHOD_NAME, ANN_AFTER, false, workingCopy));
176
protected ClassTree generateMissingInitMembers(ClassTree tstClass,
177
TreePath tstClassTreePath,
178
WorkingCopy workingCopy) {
179
if (!setup.isGenerateBefore() && !setup.isGenerateAfter()
180
&& !setup.isGenerateBeforeClass() && !setup.isGenerateAfterClass()) {
184
ClassMap classMap = ClassMap.forClass(tstClass, tstClassTreePath,
185
workingCopy.getTrees());
187
if ((!setup.isGenerateBefore() || classMap.containsBefore())
188
&& (!setup.isGenerateAfter() || classMap.containsAfter())
189
&& (!setup.isGenerateBeforeClass() || classMap.containsBeforeClass())
190
&& (!setup.isGenerateAfterClass() || classMap.containsAfterClass())) {
194
final TreeMaker maker = workingCopy.getTreeMaker();
196
List<? extends Tree> tstMembersOrig = tstClass.getMembers();
197
List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 4);
198
tstMembers.addAll(tstMembersOrig);
200
generateMissingInitMembers(tstMembers, classMap, workingCopy);
202
ClassTree newClass = maker.Class(
203
tstClass.getModifiers(),
204
tstClass.getSimpleName(),
205
tstClass.getTypeParameters(),
206
tstClass.getExtendsClause(),
207
(List<? extends ExpressionTree>) tstClass.getImplementsClause(),
214
protected boolean generateMissingInitMembers(List<Tree> tstMembers,
216
WorkingCopy workingCopy) {
217
boolean modified = false;
219
if (setup.isGenerateBeforeClass() && !clsMap.containsBeforeClass()) {
221
if (clsMap.containsAfterClass()) {
222
targetIndex = clsMap.getAfterClassIndex();
224
int beforeIndex = clsMap.getBeforeIndex();
225
int afterIndex = clsMap.getAfterIndex();
226
if ((beforeIndex != -1) && (afterIndex != -1)) {
227
targetIndex = Math.min(beforeIndex, afterIndex);
230
* if (beforeIndex != -1)
231
* targetIndex = beforeIndex;
232
* else if (afterIndex != -1)
233
* targetIndex = afterIndex;
237
targetIndex = Math.max(beforeIndex, afterIndex);
240
addInitMethod(BEFORE_CLASS_METHOD_NAME,
249
if (setup.isGenerateAfterClass() && !clsMap.containsAfterClass()) {
251
if (clsMap.containsBeforeClass()) {
252
targetIndex = clsMap.getBeforeClassIndex() + 1;
254
int beforeIndex = clsMap.getBeforeIndex();
255
int afterIndex = clsMap.getAfterIndex();
256
if ((beforeIndex != -1) && (afterIndex != -1)) {
257
targetIndex = Math.min(beforeIndex, afterIndex);
259
targetIndex = Math.max(beforeIndex, afterIndex);
262
addInitMethod(AFTER_CLASS_METHOD_NAME,
271
if (setup.isGenerateBefore() && !clsMap.containsBefore()) {
273
if (clsMap.containsAfter()) {
274
targetIndex = clsMap.getAfterIndex();
276
int beforeClassIndex = clsMap.getBeforeClassIndex();
277
int afterClassIndex = clsMap.getAfterClassIndex();
280
* if ((beforeClassIndex != -1) && (afterClassIndex != -1))
281
* targetIndex = Math.max(beforeClassIndex, afterClassIndex) + 1;
282
* else if (beforeClassIndex != -1)
283
* targetIndex = beforeClassIndex + 1;
284
* else if (afterClassIndex != -1)
285
* targetIndex = afterClassIndex + 1;
289
targetIndex = Math.max(beforeClassIndex, afterClassIndex);
290
if (targetIndex != -1) {
294
addInitMethod(BEFORE_METHOD_NAME,
303
if (setup.isGenerateAfter() && !clsMap.containsAfter()) {
305
if (clsMap.containsBefore()) {
306
targetIndex = clsMap.getBeforeIndex() + 1;
308
int beforeClassIndex = clsMap.getBeforeClassIndex();
309
int afterClassIndex = clsMap.getAfterClassIndex();
310
targetIndex = Math.max(beforeClassIndex, afterClassIndex);
311
if (targetIndex != -1) {
315
addInitMethod(AFTER_METHOD_NAME,
330
private void addInitMethod(String methodName,
331
String annotationClassName,
334
List<Tree> clsMembers,
336
WorkingCopy workingCopy) {
337
MethodTree initMethod = generateInitMethod(methodName,
342
if (targetIndex == -1) {
343
targetIndex = getPlaceForFirstInitMethod(clsMap);
346
if (targetIndex != -1) {
347
clsMembers.add(targetIndex, initMethod);
349
clsMembers.add(initMethod);
351
clsMap.addNoArgMethod(methodName, annotationClassName, targetIndex);
355
* Generates a set-up or a tear-down method.
356
* The generated method will have no arguments, void return type
357
* and a declaration that it may throw {@code java.lang.Exception}.
358
* The method will have a declared protected member access.
359
* The method contains call of the corresponding super method, i.e.
360
* {@code super.setUp()} or {@code super.tearDown()}.
362
* @param methodName name of the method to be created
363
* @return created method
364
* @see http://junit.sourceforge.net/javadoc/junit/framework/TestCase.html
365
* methods {@code setUp()} and {@code tearDown()}
367
private MethodTree generateInitMethod(String methodName,
368
String annotationClassName,
370
WorkingCopy workingCopy) {
371
Set<Modifier> methodModifiers
372
= isStatic ? createModifierSet(PUBLIC, STATIC)
373
: Collections.<Modifier>singleton(PUBLIC);
374
ModifiersTree modifiers = createModifiersTree(annotationClassName,
377
TreeMaker maker = workingCopy.getTreeMaker();
378
BlockTree methodBody = maker.Block(
379
Collections.<StatementTree>emptyList(),
381
MethodTree method = maker.Method(
382
modifiers, // modifiers
384
maker.PrimitiveType(TypeKind.VOID), // return type
385
Collections.<TypeParameterTree>emptyList(), // type params
386
Collections.<VariableTree>emptyList(), // parameters
387
Collections.<ExpressionTree>singletonList(
388
maker.Identifier("Exception")), // throws...//NOI18N
390
null); // default value
396
protected void generateMissingPostInitMethods(TreePath tstClassTreePath,
397
List<Tree> tstMembers,
399
WorkingCopy workingCopy) {
400
/* no post-init methods */
406
protected String createTestMethodName(String smName) {
412
protected MethodTree composeNewTestMethod(String testMethodName,
413
BlockTree testMethodBody,
414
List<ExpressionTree> throwsList,
415
WorkingCopy workingCopy) {
416
TreeMaker maker = workingCopy.getTreeMaker();
418
createModifiersTree(ANN_TEST,
419
createModifierSet(PUBLIC),
422
maker.PrimitiveType(TypeKind.VOID),
423
Collections.<TypeParameterTree>emptyList(),
424
Collections.<VariableTree>emptyList(),
427
null); //default value - used by annotations
432
protected ClassTree finishSuiteClass(ClassTree tstClass,
433
TreePath tstClassTreePath,
434
List<Tree> tstMembers,
435
List<String> suiteMembers,
436
boolean membersChanged,
438
WorkingCopy workingCopy) {
440
ModifiersTree currModifiers = tstClass.getModifiers();
441
ModifiersTree modifiers = fixSuiteClassModifiers(tstClass,
446
if (!membersChanged) {
447
if (modifiers != currModifiers) {
448
workingCopy.rewrite(currModifiers, modifiers);
453
return workingCopy.getTreeMaker().Class(
455
tstClass.getSimpleName(),
456
tstClass.getTypeParameters(),
457
tstClass.getExtendsClause(),
458
(List<? extends ExpressionTree>) tstClass.getImplementsClause(),
463
* Keeps or modifies annotations and modifiers of the given suite class.
464
* Modifiers are modified such that the class is public.
465
* The list of annotations is modified such that it contains
466
* the following annotations:
467
* <pre><code>RunWith(Suite.class)
468
* @SuiteRunner.Suite({...})</code></pre>
469
* with members of the suite in place of the <code>{...}</code> list.
471
* @param tstClass class whose modifiers and anntations are to be modified
472
* @param tstClassTreePath tree path to the class from the compilation unit
473
* @param modifiers current modifiers and annotations
474
* @param suiteMembers list of class names that should be contained
476
* @return {@code ModifiersTree} object containing the modified set
477
* of class modifiers and annotations, or {@code null}
478
* if no modifications were necessary
480
private ModifiersTree fixSuiteClassModifiers(ClassTree tstClass,
481
TreePath tstClassTreePath,
482
ModifiersTree modifiers,
483
List<String> suiteMembers,
484
WorkingCopy workingCopy) {
485
boolean flagsModified = false;
487
Set<Modifier> currFlags = modifiers.getFlags();
488
Set<Modifier> flags = EnumSet.copyOf(currFlags);
489
flagsModified |= flags.remove(PRIVATE);
490
flagsModified |= flags.remove(PROTECTED);
491
flagsModified |= flags.add(PUBLIC);
492
if (!flagsModified) {
497
boolean annotationListModified = false;
499
List<? extends AnnotationTree> currAnnotations = modifiers.getAnnotations();
500
List<? extends AnnotationTree> annotations;
501
if (currAnnotations.isEmpty()) {
502
List<AnnotationTree> newAnnotations = new ArrayList<AnnotationTree>(2);
503
newAnnotations.add(createRunWithSuiteAnnotation(workingCopy));
504
newAnnotations.add(createSuiteAnnotation(suiteMembers, workingCopy));
505
annotations = newAnnotations;
507
annotationListModified = true;
509
Trees trees = workingCopy.getTrees();
510
Element classElement = trees.getElement(tstClassTreePath);
511
List<? extends AnnotationMirror> annMirrors
512
= classElement.getAnnotationMirrors();
513
assert annMirrors.size() == currAnnotations.size();
516
int index = -1, runWithIndex = -1, suiteClassesIndex = -1;
517
for (AnnotationMirror annMirror : annMirrors) {
519
Element annElement = annMirror.getAnnotationType().asElement();
520
assert annElement instanceof TypeElement;
521
TypeElement annTypeElem = (TypeElement) annElement;
522
Name annFullName = annTypeElem.getQualifiedName();
524
if ((runWithIndex == -1) && annFullName.contentEquals(ANN_RUN_WITH)) {
525
runWithIndex = index;
526
} else if ((suiteClassesIndex == -1)
527
&& annFullName.contentEquals(ANN_SUITE + '.' + ANN_SUITE_MEMBERS)) {
528
suiteClassesIndex = index;
532
AnnotationTree runWithSuiteAnn;
533
if ((runWithIndex == -1) || !checkRunWithSuiteAnnotation(
534
annMirrors.get(runWithIndex),
536
runWithSuiteAnn = createRunWithSuiteAnnotation(workingCopy);
538
runWithSuiteAnn = currAnnotations.get(runWithIndex);
541
AnnotationTree suiteClassesAnn;
542
if ((suiteClassesIndex == -1) || !checkSuiteMembersAnnotation(
543
annMirrors.get(suiteClassesIndex),
546
suiteClassesAnn = createSuiteAnnotation(suiteMembers, workingCopy);
548
suiteClassesAnn = currAnnotations.get(suiteClassesIndex);
551
if ((runWithIndex != -1) && (suiteClassesIndex != -1)) {
552
if (runWithSuiteAnn != currAnnotations.get(runWithIndex)) {
554
currAnnotations.get(runWithIndex),
557
if (suiteClassesAnn != currAnnotations.get(suiteClassesIndex)) {
559
currAnnotations.get(suiteClassesIndex),
562
annotations = currAnnotations;
564
List<AnnotationTree> newAnnotations
565
= new ArrayList<AnnotationTree>(currAnnotations.size() + 2);
566
if ((runWithIndex == -1) && (suiteClassesIndex == -1)) {
569
* put the @RunWith(...) and @Suite.SuiteClasses(...)
570
* annotations in front of other annotations
572
newAnnotations.add(runWithSuiteAnn);
573
newAnnotations.add(suiteClassesAnn);
574
if (!currAnnotations.isEmpty()) {
575
newAnnotations.addAll(currAnnotations);
578
newAnnotations.addAll(currAnnotations);
579
if (runWithIndex == -1) {
580
assert suiteClassesIndex != 1;
583
* put the @RunWith(...) annotation
584
* just before the Suite.SuiteClasses(...) annotation
586
newAnnotations.add(suiteClassesIndex, runWithSuiteAnn);
588
assert runWithIndex != -1;
591
* put the @Suite.SuiteClasses(...) annotation
592
* just after the @RunWith(...) annotation
594
newAnnotations.add(runWithIndex + 1, suiteClassesAnn);
597
annotations = newAnnotations;
599
annotationListModified = true;
603
if (!flagsModified && !annotationListModified) {
607
return workingCopy.getTreeMaker().Modifiers(flags, annotations);
611
* Checks that the given annotation is of type
612
* <code>{@value #ANN_RUN_WITH}</code> and contains argument
613
* <code>{@value #ANN_SUITE}{@literal .class}</code>.
615
* @param annMirror annotation to be checked
616
* @return {@code true} if the annotation meets the described criteria,
617
* {@code false} otherwise
619
private boolean checkRunWithSuiteAnnotation(AnnotationMirror annMirror,
620
WorkingCopy workingCopy) {
621
Map<? extends ExecutableElement,? extends AnnotationValue> annParams
622
= annMirror.getElementValues();
624
if (annParams.size() != 1) {
628
AnnotationValue annValue = annParams.values().iterator().next();
629
Name annValueClsName = getAnnotationValueClassName(annValue,
630
workingCopy.getTypes());
631
return annValueClsName != null
632
? annValueClsName.contentEquals(ANN_SUITE)
637
* Checks that the given annotation is of type
638
* <code>{@value #ANN_SUITE}.{@value #ANN_SUITE_MEMBERS}</code>
639
* and contains the given list of classes as (the only) argument,
642
* @param annMirror annotation to be checked
643
* @param suiteMembers list of fully qualified class names denoting
644
* content of the test suite
645
* @return {@code true} if the annotation meets the described criteria,
646
* {@code false} otherwise
648
private boolean checkSuiteMembersAnnotation(AnnotationMirror annMirror,
649
List<String> suiteMembers,
650
WorkingCopy workingCopy) {
651
Map<? extends ExecutableElement,? extends AnnotationValue> annParams
652
= annMirror.getElementValues();
654
if (annParams.size() != 1) {
658
AnnotationValue annValue = annParams.values().iterator().next();
659
Object value = annValue.getValue();
660
if (value instanceof java.util.List) {
661
List<? extends AnnotationValue> items
662
= (List<? extends AnnotationValue>) value;
664
if (items.size() != suiteMembers.size()) {
668
Types types = workingCopy.getTypes();
669
Iterator<String> suiteMembersIt = suiteMembers.iterator();
670
for (AnnotationValue item : items) {
671
Name suiteMemberName = getAnnotationValueClassName(item, types);
672
if (suiteMemberName == null) {
675
if (!suiteMemberName.contentEquals(suiteMembersIt.next())) {
686
* Returns fully qualified class name of a class given to an annotation
687
* as (the only) argument.
689
* @param annValue annotation value
690
* @return fully qualified name of a class represented by the given
691
* annotation value, or {@code null} if the annotation value
692
* does not represent a class
694
private Name getAnnotationValueClassName(AnnotationValue annValue,
696
Object value = annValue.getValue();
697
if (value instanceof TypeMirror) {
698
TypeMirror typeMirror = (TypeMirror) value;
699
Element typeElement = types.asElement(typeMirror);
700
if (typeElement.getKind() == ElementKind.CLASS) {
701
return ((TypeElement) typeElement).getQualifiedName();
708
* Creates annotation <code>@org.junit.runner.RunWith</code>.
710
* @return created annotation
712
private AnnotationTree createRunWithSuiteAnnotation(
713
WorkingCopy workingCopy) {
714
TreeMaker maker = workingCopy.getTreeMaker();
716
/* @RunWith(Suite.class) */
717
return maker.Annotation(
718
getClassIdentifierTree(ANN_RUN_WITH, workingCopy),
719
Collections.<ExpressionTree>singletonList(
721
getClassIdentifierTree(ANN_SUITE, workingCopy),
727
* <code>@org.junit.runners.Suite.SuiteClasses({...})</code>.
729
* @param suiteMembers fully qualified names of classes to be included
731
* @param created annotation
733
private AnnotationTree createSuiteAnnotation(List<String> suiteMembers,
734
WorkingCopy workingCopy) {
735
final TreeMaker maker = workingCopy.getTreeMaker();
737
List<ExpressionTree> suiteMemberExpressions
738
= new ArrayList<ExpressionTree>(suiteMembers.size());
739
for (String suiteMember : suiteMembers) {
740
suiteMemberExpressions.add(
742
getClassIdentifierTree(suiteMember, workingCopy),
746
/* @Suite.SuiteClasses({TestA.class, TestB.class, ...}) */
747
return maker.Annotation(
749
getClassIdentifierTree(ANN_SUITE, workingCopy),
751
Collections.singletonList(
753
null, //do not generate "new Class[]"
754
Collections.<ExpressionTree>emptyList(),
755
suiteMemberExpressions)));