001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.junit;
043:
044: import com.sun.source.tree.BlockTree;
045: import com.sun.source.tree.ClassTree;
046: import com.sun.source.tree.ExpressionTree;
047: import com.sun.source.tree.MethodInvocationTree;
048: import com.sun.source.tree.MethodTree;
049: import com.sun.source.tree.ModifiersTree;
050: import com.sun.source.tree.ReturnTree;
051: import com.sun.source.tree.StatementTree;
052: import com.sun.source.tree.Tree;
053: import com.sun.source.tree.TypeParameterTree;
054: import com.sun.source.tree.VariableTree;
055: import com.sun.source.util.TreePath;
056: import com.sun.source.util.Trees;
057: import java.util.ArrayList;
058: import java.util.Collections;
059: import java.util.List;
060: import java.util.Set;
061: import javax.lang.model.element.Element;
062: import javax.lang.model.element.ExecutableElement;
063: import javax.lang.model.element.Modifier;
064: import javax.lang.model.element.TypeElement;
065: import javax.lang.model.type.TypeKind;
066: import javax.lang.model.type.TypeMirror;
067: import javax.lang.model.util.ElementFilter;
068: import javax.lang.model.util.Elements;
069: import javax.lang.model.util.Types;
070: import org.netbeans.api.java.source.ElementHandle;
071: import org.netbeans.api.java.source.TreeMaker;
072: import org.netbeans.api.java.source.WorkingCopy;
073: import static javax.lang.model.element.Modifier.ABSTRACT;
074: import static javax.lang.model.element.Modifier.PRIVATE;
075: import static javax.lang.model.element.Modifier.PROTECTED;
076: import static javax.lang.model.element.Modifier.PUBLIC;
077: import static javax.lang.model.element.Modifier.STATIC;
078:
079: /**
080: *
081: * @author Marian Petras
082: * @author vstejskal
083: */
084: final class JUnit3TestGenerator extends AbstractTestGenerator {
085:
086: private static final String TEST = "junit.framework.Test";
087: private static final String TEST_CASE = "junit.framework.TestCase";
088: private static final String TEST_SUITE = "junit.framework.TestSuite";
089: private static final String OVERRIDE = "java.lang.Override";
090:
091: /** whether Java annotations should be generated */
092: private final boolean useAnnotations;
093:
094: /**
095: */
096: JUnit3TestGenerator(TestGeneratorSetup setup, String sourceLevel) {
097: super (setup);
098: useAnnotations = TestUtil.areAnnotationsSupported(sourceLevel);
099: }
100:
101: /**
102: */
103: JUnit3TestGenerator(TestGeneratorSetup setup,
104: List<ElementHandle<TypeElement>> srcTopClassHandles,
105: List<String> suiteMembers, boolean isNewTestClass,
106: String sourceLevel) {
107: super (setup, srcTopClassHandles, suiteMembers, isNewTestClass);
108: useAnnotations = TestUtil.areAnnotationsSupported(sourceLevel);
109: }
110:
111: /**
112: */
113: protected ClassTree composeNewTestClass(WorkingCopy workingCopy,
114: String name, List<? extends Tree> members) {
115: final TreeMaker maker = workingCopy.getTreeMaker();
116: ModifiersTree modifiers = maker.Modifiers(Collections
117: .<Modifier> singleton(PUBLIC));
118: Tree extendsClause = getClassIdentifierTree(TEST_CASE,
119: workingCopy);
120: return maker.Class(modifiers, //modifiers
121: name, //name
122: Collections.<TypeParameterTree> emptyList(),//type params
123: extendsClause, //extends
124: Collections.<ExpressionTree> emptyList(), //implements
125: members); //members
126: }
127:
128: /**
129: */
130: protected List<? extends Tree> generateInitMembers(
131: WorkingCopy workingCopy) {
132: if (!setup.isGenerateSetUp() && !setup.isGenerateTearDown()) {
133: return Collections.<Tree> emptyList();
134: }
135:
136: final TreeMaker maker = workingCopy.getTreeMaker();
137: List<MethodTree> result = new ArrayList<MethodTree>(2);
138: if (setup.isGenerateSetUp()) {
139: result.add(generateInitMethod("setUp", maker, workingCopy)); //NOI18N
140: }
141: if (setup.isGenerateTearDown()) {
142: result.add(generateInitMethod("tearDown", maker,
143: workingCopy)); //NOI18N
144: }
145: return result;
146: }
147:
148: /**
149: */
150: protected ClassTree generateMissingInitMembers(ClassTree tstClass,
151: TreePath tstClassTreePath, WorkingCopy workingCopy) {
152: if (!setup.isGenerateSetUp() && !setup.isGenerateTearDown()
153: && !setup.isGenerateSuiteClasses()) {
154: return tstClass;
155: }
156:
157: ClassMap classMap = ClassMap.forClass(tstClass,
158: tstClassTreePath, workingCopy.getTrees());
159:
160: if ((!setup.isGenerateSetUp() || classMap.containsSetUp())
161: && (!setup.isGenerateTearDown() || classMap
162: .containsTearDown())
163: && (!setup.isGenerateSuiteClasses() || classMap
164: .containsNoArgMethod("suite"))) {//NOI18N
165: return tstClass;
166: }
167:
168: List<? extends Tree> tstMembersOrig = tstClass.getMembers();
169: List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig
170: .size() + 2);
171: tstMembers.addAll(tstMembersOrig);
172:
173: generateMissingInitMembers(tstMembers, classMap, workingCopy);
174: generateTestClassSuiteMethod(tstClassTreePath, tstMembers,
175: classMap, workingCopy);
176:
177: ClassTree newClass = workingCopy.getTreeMaker().Class(
178: tstClass.getModifiers(),
179: tstClass.getSimpleName(),
180: tstClass.getTypeParameters(),
181: tstClass.getExtendsClause(),
182: (List<? extends ExpressionTree>) tstClass
183: .getImplementsClause(), tstMembers);
184: return newClass;
185: }
186:
187: /**
188: */
189: protected boolean generateMissingInitMembers(List<Tree> tstMembers,
190: ClassMap clsMap, WorkingCopy workingCopy) {
191: TreeMaker treeMaker = workingCopy.getTreeMaker();
192:
193: boolean modified = false;
194: if (setup.isGenerateSetUp() && !clsMap.containsSetUp()) {
195: addInitMethod(
196: "setUp", //NOI18N
197: clsMap.getTearDownIndex(), tstMembers, clsMap,
198: treeMaker, workingCopy);
199: modified = true;
200: }
201: if (setup.isGenerateTearDown() && !clsMap.containsTearDown()) {
202: int setUpIndex = clsMap.getSetUpIndex();
203: addInitMethod(
204: "tearDown", //NOI18N
205: (setUpIndex != -1) ? setUpIndex + 1 : -1,
206: tstMembers, clsMap, treeMaker, workingCopy);
207: modified = true;
208: }
209: return modified;
210: }
211:
212: /**
213: * Creates a new init method ({@code setUp()}, {@code tearDown()}.
214: * When the method is created, it is added to the passed
215: * {@code List<Tree>} of class members and the passed {@code ClassMap}
216: * is updated appropriately.
217: *
218: * @param methodName name of the init method to be added
219: * @param targetIndex position in the list of members where the new
220: * init method should be put; or {@code -1} if this
221: * is the first init method to be added and
222: * the position should be determined automatically
223: * @param clsMembers list of class members to which the created init
224: * method should be added
225: * @param clsMap map of the current class members (will be updated)
226: * @param treeMaker maker to be used for creation of the init method
227: */
228: private void addInitMethod(String methodName, int targetIndex,
229: List<Tree> clsMembers, ClassMap clsMap,
230: TreeMaker treeMaker, WorkingCopy workingCopy) {
231: MethodTree initMethod = generateInitMethod(methodName,
232: treeMaker, workingCopy);
233:
234: if (targetIndex == -1) {
235: targetIndex = getPlaceForFirstInitMethod(clsMap);
236: }
237:
238: if (targetIndex != -1) {
239: clsMembers.add(targetIndex, initMethod);
240: } else {
241: clsMembers.add(initMethod);
242: }
243: clsMap.addNoArgMethod(methodName, targetIndex);
244: }
245:
246: /**
247: * Generates a set-up or a tear-down method.
248: * The generated method will have no arguments, void return type
249: * and a declaration that it may throw {@code java.lang.Exception}.
250: * The method will have a declared protected member access.
251: * The method contains call of the corresponding super method, i.e.
252: * {@code super.setUp()} or {@code super.tearDown()}.
253: *
254: * @param methodName name of the method to be created
255: * @return created method
256: * @see http://junit.sourceforge.net/javadoc/junit/framework/TestCase.html
257: * methods {@code setUp()} and {@code tearDown()}
258: */
259: protected MethodTree generateInitMethod(String methodName,
260: TreeMaker maker, WorkingCopy workingCopy) {
261: Set<Modifier> modifiers = Collections
262: .<Modifier> singleton(PROTECTED);
263: ModifiersTree modifiersTree = useAnnotations ? createModifiersTree(
264: OVERRIDE, modifiers, workingCopy)
265: : maker.Modifiers(modifiers);
266: ExpressionTree super MethodCall = maker.MethodInvocation(
267: Collections.<ExpressionTree> emptyList(), // type params.
268: maker.MemberSelect(maker.Identifier("super"),
269: methodName), //NOI18N
270: Collections.<ExpressionTree> emptyList());
271: BlockTree methodBody = maker.Block(Collections
272: .<StatementTree> singletonList(maker
273: .ExpressionStatement(super MethodCall)), false);
274: MethodTree method = maker.Method(modifiersTree, // modifiers
275: methodName, // name
276: maker.PrimitiveType(TypeKind.VOID), // return type
277: Collections.<TypeParameterTree> emptyList(), // type params
278: Collections.<VariableTree> emptyList(), // parameters
279: Collections.<ExpressionTree> singletonList(maker
280: .Identifier("Exception")), // throws...//NOI18N
281: methodBody, null); // default value
282: return method;
283: }
284:
285: /**
286: */
287: protected void generateMissingPostInitMethods(
288: TreePath tstClassTreePath, List<Tree> tstMembers,
289: ClassMap clsMap, WorkingCopy workingCopy) {
290: if (setup.isGenerateSuiteClasses()) {
291: generateTestClassSuiteMethod(tstClassTreePath, tstMembers,
292: clsMap, workingCopy);
293: }
294: }
295:
296: /**
297: */
298: protected MethodTree composeNewTestMethod(String testMethodName,
299: BlockTree testMethodBody, List<ExpressionTree> throwsList,
300: WorkingCopy workingCopy) {
301: TreeMaker maker = workingCopy.getTreeMaker();
302: return maker.Method(maker.Modifiers(createModifierSet(PUBLIC)),
303: testMethodName, maker.PrimitiveType(TypeKind.VOID),
304: Collections.<TypeParameterTree> emptyList(),
305: Collections.<VariableTree> emptyList(), throwsList,
306: testMethodBody, null); //default value - used by annotations
307: }
308:
309: /**
310: */
311: protected ClassTree finishSuiteClass(ClassTree tstClass,
312: TreePath tstClassTreePath, List<Tree> tstMembers,
313: List<String> suiteMembers, boolean membersChanged,
314: ClassMap classMap, WorkingCopy workingCopy) {
315: MethodTree suiteMethod = generateSuiteMethod(tstClass
316: .getSimpleName().toString(), suiteMembers, workingCopy);
317: if (suiteMethod != null) {
318: int suiteMethodIndex = classMap.findNoArgMethod("suite"); //NOI18N
319: if (suiteMethodIndex != -1) {
320: tstMembers.set(suiteMethodIndex, suiteMethod); //replace method
321: } else {
322: int targetIndex;
323: if (classMap.containsInitializers()) {
324: targetIndex = classMap.getLastInitializerIndex() + 1;
325: } else if (classMap.containsMethods()) {
326: targetIndex = classMap.getFirstMethodIndex();
327: } else if (classMap.containsNestedClasses()) {
328: targetIndex = classMap.getFirstNestedClassIndex();
329: } else {
330: targetIndex = classMap.size();
331: }
332: if (targetIndex == classMap.size()) {
333: tstMembers.add(suiteMethod);
334: } else {
335: tstMembers.add(targetIndex, suiteMethod);
336: }
337: classMap.addNoArgMethod("suite", targetIndex); //NOI18N
338: }
339: membersChanged = true;
340: }
341:
342: //PENDING - generating main(String[]) method:
343: //if (generateMainMethod && !TestUtil.hasMainMethod(tstClass)) {
344: // addMainMethod(tstClass);
345: //}
346:
347: if (!membersChanged) {
348: return tstClass;
349: }
350:
351: return workingCopy.getTreeMaker().Class(
352: tstClass.getModifiers(),
353: tstClass.getSimpleName(),
354: tstClass.getTypeParameters(),
355: tstClass.getExtendsClause(),
356: (List<? extends ExpressionTree>) tstClass
357: .getImplementsClause(), tstMembers);
358: }
359:
360: /**
361: *
362: * @return object representing body of the suite() method,
363: * or {@code null} if an error occured while creating the body
364: */
365: private MethodTree generateSuiteMethod(String suiteName,
366: List<String> members, WorkingCopy workingCopy) {
367: final Types types = workingCopy.getTypes();
368: final Elements elements = workingCopy.getElements();
369: final TreeMaker maker = workingCopy.getTreeMaker();
370:
371: ExpressionTree testSuiteIdentifier = getClassIdentifierTree(
372: TEST_SUITE, workingCopy);
373:
374: TypeElement testTypeElem = getTestTypeElem(elements);
375: if (testTypeElem == null) {
376: return null;
377: }
378: TypeMirror testType = testTypeElem.asType();
379:
380: List<StatementTree> bodyContent = new ArrayList<StatementTree>(
381: members.size() + 2);
382:
383: /* TestSuite suite = new TestSuite("ClassName") */
384:
385: VariableTree suiteObjInit = maker.Variable(maker
386: .Modifiers(noModifiers()), "suite", //NOI18N
387: testSuiteIdentifier, maker.NewClass(
388: null, //enclosing instance
389: Collections.<ExpressionTree> emptyList(), //type args
390: testSuiteIdentifier, //class name
391: Collections.singletonList( //params
392: maker.Literal(TestUtil
393: .getSimpleName(suiteName))),
394: null)); //class body
395:
396: bodyContent.add(suiteObjInit);
397:
398: for (String className : members) {
399: TypeElement classElem = elements.getTypeElement(className);
400: if ((classElem != null)
401: && containsSuiteMethod(classElem, elements, types,
402: testType)) {
403:
404: /* suite.addTest(ClassName.suite()) */
405:
406: MethodInvocationTree suiteMethodCall, methodCall;
407: suiteMethodCall = maker.MethodInvocation(Collections
408: .<ExpressionTree> emptyList(), maker
409: .MemberSelect(maker.QualIdent(classElem),
410: "suite"), //NOI18N
411: Collections.<ExpressionTree> emptyList());
412: methodCall = maker.MethodInvocation(Collections
413: .<ExpressionTree> emptyList(), maker
414: .MemberSelect(maker.Identifier("suite"), //NOI18N
415: "addTest"), //NOI18N
416: Collections.singletonList(suiteMethodCall));
417:
418: bodyContent.add(maker.ExpressionStatement(methodCall));
419: }
420: }
421:
422: /* return suite; */
423:
424: bodyContent.add(maker.Return(maker.Identifier("suite"))); //NOI18N
425:
426: return maker.Method(maker.Modifiers(createModifierSet(PUBLIC,
427: STATIC)), "suite", //NOI18N
428: maker.QualIdent(testTypeElem), //return type
429: Collections.<TypeParameterTree> emptyList(),//type params
430: Collections.<VariableTree> emptyList(), //params
431: Collections.<ExpressionTree> emptyList(), //throws-list
432: maker.Block(bodyContent, false), //body
433: null); //def. value - only for annotations
434: }
435:
436: /**
437: * Finds whether the given {@code TypeElement} or any of its type
438: * ancestor contains an accessible static no-arg method
439: * of the given name.
440: *
441: * @param typeElement {@code TypeElement} to search
442: * @param methodName name of the method to be found
443: * @param elements support instance to be used for the search
444: * @return {@code true} if the given {@code TypeElement} contains,
445: * whether inherited or declared directly,
446: * a static no-argument method of the given name,
447: * {@code false} otherwise
448: */
449: private boolean containsSuiteMethod(TypeElement typeElement,
450: Elements elements, Types types, TypeMirror testType) {
451: List<ExecutableElement> allMethods = ElementFilter
452: .methodsIn(elements.getAllMembers(typeElement));
453: for (ExecutableElement method : allMethods) {
454: if (method.getSimpleName().contentEquals("suite") //NOI18N
455: && method.getParameters().isEmpty()) {
456: return method.getModifiers().contains(Modifier.STATIC)
457: && types.isSameType(method.getReturnType(),
458: testType);
459: }
460: }
461: return false;
462: }
463:
464: /**
465: */
466: private boolean generateTestClassSuiteMethod(
467: TreePath tstClassTreePath, List<Tree> tstMembers,
468: ClassMap clsMap, WorkingCopy workingCopy) {
469: if (!setup.isGenerateSuiteClasses()
470: || clsMap.containsNoArgMethod("suite")) { //NOI18N
471: return false;
472: }
473:
474: final TreeMaker maker = workingCopy.getTreeMaker();
475: final Elements elements = workingCopy.getElements();
476: final Trees trees = workingCopy.getTrees();
477:
478: Element tstClassElem = trees.getElement(tstClassTreePath);
479: assert tstClassElem != null;
480:
481: List<StatementTree> bodyContent = new ArrayList<StatementTree>(
482: 4);
483:
484: /* TestSuite suite = new TestSuite(MyTestClass.class); */
485:
486: VariableTree suiteVar = maker
487: .Variable(
488: maker.Modifiers(noModifiers()),
489: "suite", //NOI18N
490: getClassIdentifierTree(TEST_SUITE, workingCopy),
491: maker
492: .NewClass(
493: null, //enclosing instance
494: Collections
495: .<ExpressionTree> emptyList(),
496: getClassIdentifierTree(
497: TEST_SUITE, workingCopy),
498: Collections
499: .singletonList(maker
500: .MemberSelect(
501: maker
502: .QualIdent(tstClassElem),
503: "class")), //NOI18N
504: null)); //class definition
505:
506: bodyContent.add(suiteVar);
507:
508: /* suite.addTest(NestedClass.suite()); */
509: /* suite.addTest(AnotherNestedClass.suite()); */
510: /* ... */
511:
512: List<TypeElement> nestedClassElems = ElementFilter
513: .typesIn(tstClassElem.getEnclosedElements());
514: if (!nestedClassElems.isEmpty()) {
515: for (TypeElement nestedClassElem : nestedClassElems) {
516: if (TestUtil.isClassTest(workingCopy, nestedClassElem)) {
517:
518: /* suite.addTest(NestedClass.suite()); */
519:
520: /* NestedClass.suite() */
521: MethodInvocationTree arg = maker.MethodInvocation(
522: Collections.<ExpressionTree> emptyList(),
523: maker.MemberSelect(maker
524: .QualIdent(nestedClassElem),
525: "suite"), //NOI18N
526: Collections.<ExpressionTree> emptyList());
527:
528: /* suite.addTest(...) */
529: MethodInvocationTree methodCall = maker
530: .MethodInvocation(Collections
531: .<ExpressionTree> emptyList(),
532: maker.MemberSelect(maker
533: .Identifier("suite"), //NOI18N
534: "addTest"), //NOI18N
535: Collections.singletonList(arg));
536:
537: bodyContent.add(maker
538: .ExpressionStatement(methodCall));
539: }
540: }
541: }
542:
543: /* return suite; */
544:
545: ReturnTree returnStmt = maker.Return(maker.Identifier("suite")); //NOI18N
546: bodyContent.add(returnStmt);
547:
548: MethodTree suiteMethod = maker.Method(maker
549: .Modifiers(createModifierSet(PUBLIC, STATIC)), "suite", //NOI18N
550: getClassIdentifierTree(TEST, workingCopy), Collections
551: .<TypeParameterTree> emptyList(), //type params
552: Collections.<VariableTree> emptyList(), //parameters
553: Collections.<ExpressionTree> emptyList(), //throws ...
554: maker.Block(bodyContent, false), //body
555: null); //default value
556:
557: int targetIndex;
558: if (clsMap.containsMethods()) {
559: targetIndex = clsMap.getFirstMethodIndex(); //before methods
560: } else if (clsMap.containsNestedClasses()) {
561: targetIndex = clsMap.getFirstNestedClassIndex(); //before nested
562: } else {
563: targetIndex = clsMap.size(); //end of the class
564: }
565:
566: if (targetIndex == clsMap.size()) {
567: tstMembers.add(suiteMethod);
568: } else {
569: tstMembers.add(targetIndex, suiteMethod);
570: }
571: clsMap.addNoArgMethod("suite", targetIndex); //NOI18N
572:
573: return true;
574: }
575:
576: /** element representing type {@code junit.framework.Test} */
577: private TypeElement testTypeElem;
578:
579: /**
580: */
581: private TypeElement getTestTypeElem(Elements elements) {
582: if (testTypeElem == null) {
583: testTypeElem = getElemForClassName("junit.framework.Test", //NOI18N
584: elements);
585: }
586: return testTypeElem;
587: }
588:
589: }
|