| /* |
| * 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.JUnitSettings; |
| import org.netbeans.modules.junit.api.JUnitTestUtil; |
| import org.netbeans.api.java.source.TreeUtilities; |
| import org.openide.filesystems.FileObject; |
| import com.sun.source.tree.AnnotationTree; |
| import com.sun.source.tree.BlockTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.CompilationUnitTree; |
| import com.sun.source.tree.ExpressionStatementTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.LiteralTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| 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 com.sun.source.util.Trees; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.MissingResourceException; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.Name; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.TypeParameterElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.ExecutableType; |
| 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.classpath.ClassPath; |
| import org.netbeans.api.java.source.CancellableTask; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.Comment; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.ElementUtilities.ElementAcceptor; |
| import org.netbeans.api.java.source.JavaSource.Phase; |
| import org.netbeans.api.java.source.TreeMaker; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import org.openide.ErrorManager; |
| import org.openide.util.NbBundle; |
| import static javax.lang.model.element.Modifier.ABSTRACT; |
| import static javax.lang.model.element.Modifier.PRIVATE; |
| import static javax.lang.model.element.Modifier.PUBLIC; |
| import static javax.lang.model.element.Modifier.STATIC; |
| import javax.lang.model.type.WildcardType; |
| import static org.netbeans.modules.junit.TestCreator.ACCESS_MODIFIERS; |
| |
| /** |
| * A base class for generators of JUnit test classes and test methods. |
| * |
| * @author Marian Petras |
| */ |
| abstract class AbstractTestGenerator implements CancellableTask<WorkingCopy>{ |
| |
| /** |
| * name of the 'instance' variable in the generated test method skeleton |
| * |
| * @see #RESULT_VAR_NAME |
| * @see #EXP_RESULT_VAR_NAME |
| */ |
| private static final String INSTANCE_VAR_NAME = "instance"; //NOI18N |
| /** |
| * name of the 'result' variable in the generated test method skeleton |
| * |
| * @see #EXP_RESULT_VAR_NAME |
| */ |
| private static final String RESULT_VAR_NAME = "result"; //NOI18N |
| /** |
| * name of the 'expected result' variable in the generated test method |
| * skeleton |
| * |
| * @see #RESULT_VAR_NAME |
| */ |
| private static final String EXP_RESULT_VAR_NAME = "expResult"; //NOI18N |
| /** |
| * base for artificial names of variables |
| * (if there is no name to derive from) |
| */ |
| private static final String ARTIFICAL_VAR_NAME_BASE = "arg"; //NOI18N |
| /** |
| * name of the stub test method |
| */ |
| private static final String STUB_TEST_NAME = "testSomeMethod"; //NOI18N |
| /** |
| * name of the 'container' variable in the generated ejb test method skeleton |
| */ |
| private static final String CONTAINER_VAR_NAME = "container"; //NOI18N |
| |
| /** */ |
| private static final EnumSet<Modifier> NO_MODIFIERS |
| = EnumSet.noneOf(Modifier.class); |
| |
| /** |
| * Returns {@literal EnumSet} of all access modifiers. |
| * |
| * @return {@literal EnumSet} of all access modifiers; |
| * it is guaranteed that the returned set always contains |
| * the same set of {@literal Modifier}s, but the returned |
| * instance may not always be the same |
| */ |
| protected static EnumSet<Modifier> accessModifiers() { |
| /* |
| * An alternative would be to create an instance of |
| * unmodifiable Set<Modifier> (e.g. Collections.unmodifiableSet(...)) |
| * and always return this instance. But the instance would not be an |
| * instance of (subclass of) EnumSet which would significantly slow down |
| * many operations performed on it. |
| */ |
| return EnumSet.copyOf(ACCESS_MODIFIERS); |
| } |
| |
| /** |
| * Returns an empty {@literal EnumSet} of {@literal Modifier}s. |
| * |
| * @return empty {@literal EnumSet} of all {@literal Modifier}s; |
| * it is guaranteed that the returned set is always empty |
| * but the returned instance may not always be the same |
| */ |
| protected static EnumSet<Modifier> noModifiers() { |
| /* |
| * An alternative would be to create an instance of |
| * unmodifiable Set<Modifier> (e.g. Collections.<Modifier>emptySet()) |
| * and always return that instance. But the instance would not be an |
| * instance of (subclass of) EnumSet which would significantly slow down |
| * many operations performed on it. |
| */ |
| return EnumSet.copyOf(NO_MODIFIERS); |
| } |
| |
| /** */ |
| protected final TestGeneratorSetup setup; |
| |
| private final List<ElementHandle<TypeElement>> srcTopClassElemHandles; |
| |
| private final List<String> suiteMembers; |
| |
| private final boolean isNewTestClass; |
| |
| private List<String>processedClassNames; |
| |
| /** |
| * cached value of <code>JUnitSettings.getGenerateMainMethodBody()</code> |
| */ |
| private String initialMainMethodBody; |
| |
| private volatile boolean cancelled = false; |
| private JUnitVersion junitVersion; |
| private static final Logger LOG = Logger.getLogger(AbstractTestGenerator.class.getName()); |
| |
| |
| /** |
| * Used when creating a new empty test class. |
| */ |
| protected AbstractTestGenerator(TestGeneratorSetup setup, JUnitVersion version) { |
| this.setup = setup; |
| this.srcTopClassElemHandles = null; |
| this.suiteMembers = null; |
| this.isNewTestClass = true; //value not used |
| this.junitVersion = version; |
| } |
| |
| /** |
| * Used when creating a test class for a given source class |
| * or when creating a test suite. |
| */ |
| protected AbstractTestGenerator( |
| TestGeneratorSetup setup, |
| List<ElementHandle<TypeElement>> srcTopClassHandles, |
| List<String>suiteMembers, |
| boolean isNewTestClass, |
| JUnitVersion version) { |
| this.setup = setup; |
| this.srcTopClassElemHandles = srcTopClassHandles; |
| this.suiteMembers = suiteMembers; |
| this.isNewTestClass = isNewTestClass; |
| this.junitVersion = version; |
| } |
| |
| /** |
| */ |
| public void run(WorkingCopy workingCopy) throws IOException { |
| |
| workingCopy.toPhase(Phase.ELEMENTS_RESOLVED); |
| |
| CompilationUnitTree compUnit = workingCopy.getCompilationUnit(); |
| List<ClassTree> tstTopClasses = TopClassFinder.findTopClasses( |
| compUnit, |
| workingCopy.getTreeUtilities()); |
| TreePath compUnitPath = new TreePath(compUnit); |
| |
| List<TypeElement> srcTopClassElems |
| = resolveHandles(workingCopy, srcTopClassElemHandles); |
| |
| if ((srcTopClassElems != null) && !srcTopClassElems.isEmpty()) { |
| |
| final ClassPath classPath = |
| workingCopy.getClasspathInfo() |
| .getClassPath(ClasspathInfo.PathKind.SOURCE); |
| final FileObject fileObject = workingCopy.getFileObject(); |
| final String className = |
| classPath.getResourceName(fileObject, '.', false); |
| |
| // #188060 |
| assert className != null : |
| "Unknown class name. Test can't be generated. Bug #188060." |
| + "/nPlease, if possible, provide a sample project." |
| + "/n workingCopy=" + workingCopy |
| + "/n workingCopy.getClasspathInfo()=" + |
| workingCopy.getClasspathInfo() |
| + "/n classPath=" + classPath |
| + "/n fileObject=" + fileObject |
| + "/n classPath.findOwnerRoot(fileObject)=" + |
| classPath.findOwnerRoot(fileObject); |
| |
| /* Create/update a test class for each testable source class: */ |
| for (TypeElement srcTopClass : srcTopClassElems) { |
| createOrUpdateTestClass(srcTopClass, |
| tstTopClasses, |
| className, |
| compUnitPath, |
| workingCopy); |
| } |
| } else if (suiteMembers != null) { //test suite |
| for (ClassTree tstClass : tstTopClasses) { |
| TreePath tstClassTreePath = new TreePath(compUnitPath, |
| tstClass); |
| ClassTree origTstTopClass = tstClass; |
| ClassTree tstTopClass = generateMissingSuiteClassMembers( |
| tstClass, |
| tstClassTreePath, |
| suiteMembers, |
| isNewTestClass, |
| workingCopy); |
| if (tstTopClass != origTstTopClass) { |
| workingCopy.rewrite(origTstTopClass, |
| tstTopClass); |
| } |
| classProcessed(tstClass); |
| } |
| } else if (srcTopClassElems == null) { //new empty test class |
| for (ClassTree tstClass : tstTopClasses) { |
| ClassTree origTstTopClass = tstClass; |
| ClassTree tstTopClass = generateMissingInitMembers( |
| tstClass, |
| new TreePath(compUnitPath, |
| tstClass), |
| workingCopy); |
| if (tstTopClass != origTstTopClass) { |
| workingCopy.rewrite(origTstTopClass, |
| tstTopClass); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Creates or updates a test class for a given source class. |
| * |
| * @param srcTopClass source class for which a test class should be |
| * created or updated |
| * @param tstTopClasses list of top-level classes that are present |
| * in the test source code |
| * @param testClassName desired name of the test class corresponding |
| * to the given source class; if a test class of the |
| * given name is not found, it is created |
| * @param compUnitPath tree-path to the compilation unit of the test |
| * source file |
| * @param workingCopy working copy of the test file's structure |
| */ |
| private void createOrUpdateTestClass(TypeElement srcTopClass, |
| List<ClassTree> tstTopClasses, |
| String testClassName, |
| TreePath compUnitPath, |
| WorkingCopy workingCopy) { |
| List<ExecutableElement> srcMethods |
| = findTestableMethods(workingCopy, srcTopClass); |
| boolean srcHasTestableMethods = !srcMethods.isEmpty(); |
| |
| final String testClassSimpleName = JUnitTestUtil.getSimpleName(testClassName); |
| |
| ClassTree tstTopClass = findClass(testClassSimpleName, tstTopClasses); |
| |
| if (tstTopClass != null) { //if the test class already exists |
| TreePath tstTopClassTreePath = new TreePath(compUnitPath, |
| tstTopClass); |
| |
| ClassTree origTstTopClass = tstTopClass; |
| if (srcHasTestableMethods) { |
| tstTopClass = generateMissingTestMethods( |
| srcTopClass, |
| srcMethods, |
| tstTopClass, |
| tstTopClassTreePath, |
| true, |
| workingCopy); |
| } else if (isNewTestClass) { |
| tstTopClass = generateMissingInitMembers( |
| tstTopClass, |
| tstTopClassTreePath, |
| workingCopy); |
| // #175201 |
| tstTopClass = generateStubTestMethod( |
| tstTopClass, |
| STUB_TEST_NAME, |
| workingCopy); |
| |
| } |
| if (tstTopClass != origTstTopClass) { |
| workingCopy.rewrite(origTstTopClass, |
| tstTopClass); |
| } |
| } else { //it does not exist - it must be created |
| if (srcHasTestableMethods && testClassSimpleName != null) { |
| tstTopClass = generateNewTestClass(workingCopy, |
| testClassSimpleName, |
| srcTopClass, |
| srcMethods); |
| //PENDING - add the top class to the CompilationUnit |
| |
| //PENDING - generate suite method |
| } |
| } |
| } |
| |
| /** |
| * Returns default body for the test method. The generated body will |
| * contains the following lines: |
| * <pre><code> |
| * // TODO review the generated test code and remove the default call to fail. |
| * fail("The test case is a prototype."); |
| * </code></pre> |
| * @param maker the tree maker |
| * @return an {@literal ExpressionStatementTree} for the generated body. |
| * @throws MissingResourceException |
| * @throws IllegalStateException |
| */ |
| private ExpressionStatementTree generateDefMethodBody(TreeMaker maker) |
| throws MissingResourceException, IllegalStateException { |
| String failMsg = NbBundle.getMessage(TestCreator.class, |
| "TestCreator.variantMethods.defaultFailMsg"); |
| MethodInvocationTree failMethodCall = |
| maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), |
| maker.Identifier("fail"), |
| Collections.<ExpressionTree>singletonList( |
| maker.Literal(failMsg))); |
| ExpressionStatementTree exprStatement = |
| maker.ExpressionStatement(failMethodCall); |
| if (setup.isGenerateMethodBodyComment()) { |
| Comment comment = |
| Comment.create(Comment.Style.LINE, -2, -2, -2, |
| NbBundle.getMessage(AbstractTestGenerator.class, |
| "TestCreator.variantMethods.defaultComment")); |
| maker.addComment(exprStatement, comment, true); |
| } |
| return exprStatement; |
| } |
| |
| /** |
| * Generates a new test class for the given source class. |
| * |
| * @param name name of the class to be created |
| * @param srcClass source class for which the test class should be created |
| * @param srcMethods methods inside the source class for which |
| * corresponding test methods should be created |
| * @return generated test class |
| */ |
| private ClassTree generateNewTestClass(WorkingCopy workingCopy, |
| String name, |
| TypeElement srcClass, |
| List<ExecutableElement> srcMethods) { |
| |
| String instanceClassName = null; |
| ClassTree abstractClassImpl = null; |
| if (srcClass.getModifiers().contains(ABSTRACT) |
| && hasInstanceMethods(srcMethods)) { |
| instanceClassName = getAbstractClassImplName(srcClass.getSimpleName()); |
| abstractClassImpl = generateAbstractClassImpl(srcClass, |
| instanceClassName, |
| workingCopy); |
| } |
| |
| List<MethodTree> testMethods = generateTestMethods(srcClass, |
| instanceClassName, |
| srcMethods, |
| workingCopy); |
| |
| List<? extends Tree> members; |
| if (abstractClassImpl == null) { |
| members = testMethods; |
| } else if (testMethods.isEmpty()) { |
| members = Collections.singletonList(abstractClassImpl); |
| } else { |
| members = new ArrayList<Tree>(testMethods.size() + 1); |
| ((List<Tree>) members).addAll(testMethods); |
| ((List<Tree>) members).add(abstractClassImpl); |
| } |
| return composeNewTestClass(workingCopy, name, members); |
| } |
| |
| /** |
| * Generates a trivial non-abstract subclass of a given abstract class. |
| * |
| * @param srcClass abstract class for which an impl. class is to be made |
| * @param name desired name of the subclass |
| */ |
| private ClassTree generateAbstractClassImpl(TypeElement srcClass, |
| CharSequence name, |
| WorkingCopy workingCopy) { |
| |
| int membersCount = 0; |
| MethodTree constructor = generateAbstractClassImplCtor(srcClass, |
| workingCopy); |
| if (constructor != null) { |
| membersCount++; |
| } |
| |
| List<ExecutableElement> methods |
| = ElementFilter.methodsIn(srcClass.getEnclosedElements()); |
| List<ExecutableElement> abstractMethods = findAbstractMethods(methods); |
| if (!abstractMethods.isEmpty()) { |
| membersCount += abstractMethods.size(); |
| } |
| |
| List<MethodTree> members; |
| if (membersCount == 0) { |
| members = Collections.emptyList(); |
| } else if (membersCount == 1) { |
| if (constructor != null) { |
| members = Collections.singletonList(constructor); |
| } else { |
| members = Collections.singletonList( |
| generateAbstractMethodImpl(abstractMethods.get(0), |
| workingCopy)); |
| } |
| } else { |
| members = new ArrayList(membersCount); |
| if (constructor != null) { |
| members.add(constructor); |
| } |
| for (ExecutableElement abstractMethod : abstractMethods) { |
| members.add(generateAbstractMethodImpl(abstractMethod, |
| workingCopy)); |
| } |
| |
| } |
| |
| final TreeMaker maker = workingCopy.getTreeMaker(); |
| |
| switch(srcClass.getKind()) { |
| case INTERFACE: |
| case ANNOTATION_TYPE: |
| List<ExpressionTree> implemetnts = |
| new ArrayList<ExpressionTree>(); |
| implemetnts.add(maker.QualIdent(srcClass)); |
| return maker.Class( |
| maker.Modifiers(Collections.singleton(PUBLIC)), |
| name, //name |
| Collections.<TypeParameterTree>emptyList(), //type params |
| null, //extends |
| implemetnts, //implements |
| members); //members |
| default: // should be never happen. We'll generate a class anyway. |
| case CLASS: |
| case ENUM: |
| return maker.Class( |
| maker.Modifiers(Collections.singleton(PUBLIC)), |
| name, //name |
| Collections.<TypeParameterTree>emptyList(), //type params |
| maker.QualIdent(srcClass), //extends |
| Collections.<ExpressionTree>emptyList(), //implements |
| members); //members |
| } |
| } |
| |
| /** |
| * Makes a name for a simple implementation of an abstract class. |
| * |
| * @param baseName name of the abstract class |
| * @return name for the simple class |
| */ |
| private static String getAbstractClassImplName(CharSequence baseName) { |
| return baseName + "Impl"; //NOI18N |
| } |
| |
| /** |
| * Generates a constructor for a simple subclass of a given abstract class, |
| * if necessary. |
| * |
| * @param srcClass abstract class for which a subclass is being created |
| * @param name |
| * @param workingCopy working copy of the class being created |
| * @return constructor for the class being generated, |
| * or {@literal null} if no explicit constructor is necessary |
| */ |
| private static MethodTree generateAbstractClassImplCtor( |
| TypeElement srcClass, |
| WorkingCopy workingCopy) { |
| |
| List<? extends VariableElement> superCtorParams; |
| |
| ExecutableElement superConstructor = findAccessibleConstructor(srcClass); |
| if ((superConstructor == null) |
| || (superCtorParams = superConstructor.getParameters()).isEmpty() |
| && !throwsNonRuntimeExceptions(workingCopy, superConstructor)) { |
| return null; |
| } |
| |
| final TreeMaker maker = workingCopy.getTreeMaker(); |
| |
| List<? extends VariableElement> defaultCtorParams |
| = new ArrayList(superCtorParams); |
| |
| List<ExpressionTree> throwsList = |
| (superConstructor != null) |
| ? generateThrowsList(superConstructor, workingCopy, maker, false) |
| : Collections.<ExpressionTree>emptyList(); |
| |
| BlockTree body = maker.Block( |
| Collections.singletonList( |
| maker.ExpressionStatement( |
| maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), |
| maker.Identifier("super"), //NOI18N |
| generateDefaultParamValues( |
| defaultCtorParams, maker)))), |
| false); |
| |
| return maker.Constructor( |
| maker.Modifiers(Collections.singleton(PUBLIC)), |
| Collections.<TypeParameterTree>emptyList(), |
| Collections.<VariableTree>emptyList(), |
| throwsList, |
| body); |
| } |
| |
| /** |
| * Generates default values (to be used in a method invocation) |
| * for the given list of formal method parameters. |
| * |
| * @param params formal parameters to create default values for |
| * @param maker tree maker to use for generating the default values |
| * @return list of default values for the given formal parameters |
| */ |
| private static List<ExpressionTree> generateDefaultParamValues( |
| List<? extends VariableElement> params, |
| TreeMaker maker) { |
| if (params.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| |
| List<ExpressionTree> result = new ArrayList(params.size()); |
| for (VariableElement param : params) { |
| result.add(getDefaultValue(maker, param.asType())); |
| } |
| return result; |
| } |
| |
| /** |
| * Finds an non-private constructor in the given class. |
| * It tries to find the following types of constructors, in this order: |
| * <ol> |
| * <li>no-arg constructor</li> |
| * <li>one-argument constructor without throws-clause</li> |
| * <li>one-argument constructor with a throws-clause</li> |
| * <li>two-argument constructor without throws-clause</li> |
| * <li>two-argument constructor with a throws-clause</li> |
| * <li>etc.</li> |
| * </ol> |
| * |
| * @param clazz class to find constructor in |
| * @return accessible (non-private) constructor, |
| * or {@literal null} if none was found |
| */ |
| private static ExecutableElement findAccessibleConstructor(TypeElement clazz) { |
| List<ExecutableElement> ctors |
| = ElementFilter.constructorsIn(clazz.getEnclosedElements()); |
| if (ctors.isEmpty()) { |
| return null; |
| } |
| |
| ExecutableElement best = null; |
| int bestArgsCount = -1; |
| boolean bestHasThrown = false; |
| |
| for (ExecutableElement ctor : ctors) { |
| if (ctor.getModifiers().contains(PRIVATE)) { |
| continue; |
| } |
| |
| List args = ctor.getParameters(); |
| if (args.isEmpty()) { |
| return ctor; //this is the best constructor we could find |
| } |
| |
| int argsCount = args.size(); |
| boolean hasThrown = !ctor.getThrownTypes().isEmpty(); |
| if ((best == null) |
| || (argsCount < bestArgsCount) |
| || (argsCount == bestArgsCount) && bestHasThrown && !hasThrown) { |
| best = ctor; |
| bestArgsCount = argsCount; |
| bestHasThrown = hasThrown; |
| } |
| } |
| return best; |
| } |
| |
| /** |
| * Generates a simple implementation of an abstract method declared |
| * in a supertype. |
| * |
| * @param abstractMethod the method whose implementation is to be generated |
| */ |
| private static MethodTree generateAbstractMethodImpl( |
| ExecutableElement abstractMethod, |
| WorkingCopy workingCopy) { |
| final TreeMaker maker = workingCopy.getTreeMaker(); |
| |
| TypeMirror returnType = abstractMethod.getReturnType(); |
| List<? extends StatementTree> content; |
| if (returnType.getKind() == TypeKind.VOID) { |
| content = Collections.<StatementTree>emptyList(); |
| } else { |
| content = Collections.singletonList( |
| maker.Return(getDefaultValue(maker, returnType))); |
| } |
| BlockTree body = maker.Block(content, false); |
| |
| return maker.Method( |
| maker.Modifiers(Collections.singleton(PUBLIC)), |
| abstractMethod.getSimpleName(), |
| maker.Type(returnType), |
| makeTypeParamsCopy(abstractMethod.getTypeParameters(), maker), |
| makeParamsCopy(abstractMethod.getParameters(), maker), |
| makeDeclaredTypesCopy((List<? extends DeclaredType>) |
| abstractMethod.getThrownTypes(), |
| maker), |
| body, |
| null); |
| } |
| |
| /** |
| * Makes a list of trees representing the given type parameter elements. |
| */ |
| private static List<TypeParameterTree> makeTypeParamsCopy( |
| List<? extends TypeParameterElement> typeParams, |
| TreeMaker maker) { |
| if (typeParams.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| |
| int size = typeParams.size(); |
| if (size == 1) { |
| return Collections.singletonList(makeCopy(typeParams.get(0), maker)); |
| } |
| |
| List<TypeParameterTree> result = new ArrayList(size); |
| for (TypeParameterElement typeParam : typeParams) { |
| result.add(makeCopy(typeParam, maker)); |
| } |
| return result; |
| } |
| |
| private static List<VariableTree> makeParamsCopy( |
| List<? extends VariableElement> params, |
| TreeMaker maker) { |
| if (params.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| |
| int size = params.size(); |
| if (size == 1) { |
| return Collections.singletonList(makeCopy(params.get(0), maker)); |
| } |
| |
| List<VariableTree> result = new ArrayList(size); |
| for (VariableElement param : params) { |
| result.add(makeCopy(param, maker)); |
| } |
| return result; |
| } |
| |
| /** |
| * Makes a tree representing the given {@literal TypeParameterElement}. |
| * |
| * @param typeParamElem {@literal TypeParameterElement} to make a copy of |
| * @param maker tree maker to use when for creation of the copy |
| * @return {@literal Tree} respresenting the given |
| * {@literal TypeParameterElement} |
| */ |
| private static TypeParameterTree makeCopy(TypeParameterElement typeParamElem, |
| TreeMaker maker) { |
| return maker.TypeParameter( |
| typeParamElem.getSimpleName(), |
| makeDeclaredTypesCopy((List<? extends DeclaredType>) |
| typeParamElem.getBounds(), |
| maker)); |
| } |
| |
| private static List<ExpressionTree> makeDeclaredTypesCopy( |
| List<? extends DeclaredType> typeList, |
| TreeMaker maker) { |
| if (typeList.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| |
| int size = typeList.size(); |
| if (size == 1) { |
| DeclaredType bound = typeList.get(0); |
| return isRootObjectType(bound) //java.lang.Object |
| ? Collections.<ExpressionTree>emptyList() |
| : Collections.singletonList((ExpressionTree) maker.Type(bound)); |
| } else { |
| List<ExpressionTree> result = new ArrayList(size); |
| for (DeclaredType type : typeList) { |
| result.add((ExpressionTree) maker.Type(type)); |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * Checks whether the given type object represents type |
| * {@literal java.lang.Object}. |
| * |
| * @param type type to be checked |
| * @return {@literal true} if the passed type object represents type |
| * {@literal java.lang.Object}, {@literal false} otherwise |
| */ |
| private static boolean isRootObjectType(DeclaredType type) { |
| if (type.getKind() != TypeKind.DECLARED) { |
| return false; |
| } |
| |
| TypeElement elem = (TypeElement) type.asElement(); |
| return (elem.getKind() == ElementKind.CLASS) |
| && (elem.getSuperclass().getKind() == TypeKind.NONE); |
| } |
| |
| /** |
| * Makes a tree representing the given {@literal VariableElement}. |
| * |
| * @param paramElem {@literal VariableElement} to make a copy of |
| * @param maker tree maker to use when for creation of the copy |
| * @return {@literal Tree} respresenting the given {@literal VariableElement} |
| */ |
| private static VariableTree makeCopy(VariableElement paramElem, |
| TreeMaker maker) { |
| return maker.Variable( |
| maker.Modifiers(Collections.<Modifier>emptySet()), |
| paramElem.getSimpleName(), |
| maker.Type(paramElem.asType()), |
| null); |
| } |
| |
| /** |
| * Finds abstract methods in the given class. |
| * |
| * @param methods method to find abstract methods among |
| * @return sublist of the passed list of methods, containing only |
| * abstract methods |
| */ |
| private static List<ExecutableElement> findAbstractMethods( |
| List<ExecutableElement> methods) { |
| if (methods.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| |
| ArrayList<ExecutableElement> result = null; |
| int remainingCount = methods.size(); |
| for (ExecutableElement method : methods) { |
| Set<Modifier> modifiers = method.getModifiers(); |
| if (modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC)) { |
| if (result == null) { |
| result = new ArrayList(remainingCount); |
| } |
| result.add(method); |
| } |
| remainingCount--; |
| } |
| |
| if (result != null) { |
| result.trimToSize(); |
| } |
| return (result == null) ? Collections.<ExecutableElement>emptyList() |
| : result; |
| } |
| |
| /** |
| * Generates a new test class containing the given list of test methods. |
| * |
| * @param name desired name of the test class |
| * @param members desired content of the test class |
| * @return generated test class |
| */ |
| protected abstract ClassTree composeNewTestClass( |
| WorkingCopy workingCopy, |
| String name, |
| List<? extends Tree> members); |
| |
| /** |
| */ |
| protected abstract List<? extends Tree> generateInitMembers(WorkingCopy workingCopy); |
| |
| /** |
| * Generates missing set-up and tear-down methods in the given test class. |
| * |
| * @param tstClass test class in which the methods should be generated |
| * @return a class tree with the missing methods added; |
| * or the passed class tree if no method was missing |
| */ |
| protected abstract ClassTree generateMissingInitMembers( |
| ClassTree tstClass, |
| TreePath tstClassTreePath, |
| WorkingCopy workingCopy); |
| |
| /** |
| * Generates missing set-up and tear-down methods and adds them to the list |
| * of class members. |
| * |
| * @param tstMembers current list of test class members |
| * - generated members will be added to it |
| * @param clsMap index of the test class contents |
| * - it will be updated if some members are added |
| * to the list |
| * @return {@literal true} if the list of members was modified, |
| * {@literal false} otherwise |
| */ |
| protected abstract boolean generateMissingInitMembers( |
| List<Tree> tstMembers, |
| ClassMap clsMap, |
| WorkingCopy workingCopy); |
| |
| /** |
| * Finds position for the first init method. |
| * |
| * @param clsMap index of the test class contents |
| * @return index where the first init method should be put, |
| * or {@literal -1} if the method should be put to the end |
| * of the class |
| */ |
| protected int getPlaceForFirstInitMethod(ClassMap clsMap) { |
| int targetIndex; |
| if (clsMap.containsMethods()) { |
| targetIndex = clsMap.getFirstMethodIndex(); |
| } else if (clsMap.containsInitializers()) { |
| targetIndex = clsMap.getLastInitializerIndex() + 1; |
| } else if (clsMap.containsNestedClasses()) { |
| targetIndex = clsMap.getFirstNestedClassIndex(); |
| } else { |
| targetIndex = -1; //end of the class |
| } |
| return targetIndex; |
| } |
| |
| /** |
| * |
| * @param srcMethods methods to create/update tests for |
| * |
| */ |
| protected ClassTree generateMissingTestMethods( |
| TypeElement srcClass, |
| List<ExecutableElement> srcMethods, |
| ClassTree tstClass, |
| TreePath tstClassTreePath, |
| boolean generateMissingInitMembers, |
| WorkingCopy workingCopy) { |
| if (srcMethods.isEmpty()) { |
| return tstClass; |
| } |
| |
| final Trees trees = workingCopy.getTrees(); |
| |
| ClassMap clsMap = ClassMap.forClass(tstClass, |
| tstClassTreePath, |
| trees); |
| |
| List<? extends Tree> tstMembersOrig = tstClass.getMembers(); |
| List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 4); |
| tstMembers.addAll(tstMembersOrig); |
| |
| if (generateMissingInitMembers) { |
| generateMissingInitMembers(tstMembers, |
| clsMap, |
| workingCopy); |
| } |
| generateMissingPostInitMethods(tstClassTreePath, |
| tstMembers, |
| clsMap, |
| workingCopy); |
| |
| /* Generate test method names: */ |
| // TypeElement tstClassElem |
| // = (TypeElement) trees.getElement(tstClassTreePath); |
| List<String> testMethodNames |
| = TestMethodNameGenerator.getTestMethodNames(srcMethods, |
| // passing null's to get the names as for newly created class to avoid creating all test methods every time we generating the tests |
| null, null, |
| workingCopy); |
| |
| Iterator<ExecutableElement> srcMethodsIt = srcMethods.iterator(); |
| Iterator<String> tstMethodNamesIt = testMethodNames.iterator(); |
| |
| CharSequence instanceClassName = null; |
| ClassTree newAbstractClassImpl = null; |
| if (srcClass.getModifiers().contains(ABSTRACT) |
| && hasInstanceMethods(srcMethods)) { |
| String prefInstanceClassName |
| = getAbstractClassImplName(srcClass.getSimpleName()); |
| instanceClassName = findAbstractClassImplName(srcClass, |
| tstClass, |
| tstClassTreePath, |
| clsMap, |
| prefInstanceClassName, |
| trees, |
| workingCopy.getTypes()); |
| if (instanceClassName == null) { |
| instanceClassName = prefInstanceClassName; |
| newAbstractClassImpl = generateAbstractClassImpl(srcClass, |
| instanceClassName, |
| workingCopy); |
| } |
| } |
| |
| while (srcMethodsIt.hasNext()) { |
| assert tstMethodNamesIt.hasNext(); |
| |
| ExecutableElement srcMethod = srcMethodsIt.next(); |
| String testMethodName = tstMethodNamesIt.next(); |
| int testMethodIndex = clsMap.findNoArgMethod(testMethodName); |
| if (testMethodIndex != -1) { |
| continue; //corresponding test method already exists |
| } |
| |
| MethodTree newTestMethod = generateTestMethod( |
| srcClass, |
| srcMethod, |
| testMethodName, |
| instanceClassName, |
| workingCopy); |
| |
| tstMembers.add(newTestMethod); |
| clsMap.addNoArgMethod(newTestMethod.getName().toString()); |
| } |
| assert !tstMethodNamesIt.hasNext(); |
| |
| if (newAbstractClassImpl != null) { |
| tstMembers.add(newAbstractClassImpl); |
| clsMap.addNestedClass(instanceClassName.toString()); |
| } |
| |
| if (tstMembers.size() == tstMembersOrig.size()) { //no test method added |
| return tstClass; |
| } |
| |
| ClassTree newClass = workingCopy.getTreeMaker().Class( |
| tstClass.getModifiers(), |
| tstClass.getSimpleName(), |
| tstClass.getTypeParameters(), |
| tstClass.getExtendsClause(), |
| (List<? extends ExpressionTree>) tstClass.getImplementsClause(), |
| tstMembers); |
| return newClass; |
| } |
| |
| /** |
| */ |
| protected abstract void generateMissingPostInitMethods( |
| TreePath tstClassTreePath, |
| List<Tree> tstMembers, |
| ClassMap clsMap, |
| WorkingCopy workingCopy); |
| |
| /** |
| * Generates test methods for the given source methods. |
| * The created test methods will be put to a newly created test class. |
| * The test class does not exist at the moment this method is called. |
| * |
| * @param srcClass source class containing the source methods |
| * @param instanceClsName name of a class whose instance should be created |
| * if appropriate |
| * @param srcMethods source methods the test methods should be created for |
| */ |
| private List<MethodTree> generateTestMethods( |
| TypeElement srcClass, |
| CharSequence instanceClsName, |
| List<ExecutableElement> srcMethods, |
| WorkingCopy workingCopy) { |
| if (srcMethods.isEmpty()) { |
| return Collections.<MethodTree>emptyList(); |
| } |
| |
| List<String> testMethodNames |
| = TestMethodNameGenerator.getTestMethodNames(srcMethods, |
| null, |
| null, //reserved |
| workingCopy); |
| |
| Iterator<ExecutableElement> srcMethodsIt = srcMethods.iterator(); |
| Iterator<String> tstMethodNamesIt = testMethodNames.iterator(); |
| |
| List<MethodTree> testMethods = new ArrayList<MethodTree>(srcMethods.size()); |
| while (srcMethodsIt.hasNext()) { |
| assert tstMethodNamesIt.hasNext(); |
| |
| ExecutableElement srcMethod = srcMethodsIt.next(); |
| String testMethodName = tstMethodNamesIt.next(); |
| |
| testMethods.add( |
| generateTestMethod(srcClass, |
| srcMethod, |
| testMethodName, |
| instanceClsName, |
| workingCopy)); |
| } |
| assert !tstMethodNamesIt.hasNext(); |
| return testMethods; |
| } |
| |
| /** |
| * Generates a test methods for the given source method. |
| * |
| * @param srcClass source class - parent of the source method |
| * @param srcMethod source method for which the test method should be |
| * created |
| * @param useNoArgConstrutor whether a no-argument constructor should be |
| * used in the default test method body; |
| * it should not be {@literal true} unless |
| * the source class contains an accessible |
| * no-argument constructor |
| * @param instanceClsName name of a class of which a constructor should be |
| * createc if {@literal useNoArgConstrutor} is |
| * {@literal true} |
| * @return the generated test method |
| */ |
| protected MethodTree generateTestMethod(TypeElement srcClass, |
| ExecutableElement srcMethod, |
| String testMethodName, |
| CharSequence instanceClsName, |
| WorkingCopy workingCopy) { |
| final TreeMaker maker = workingCopy.getTreeMaker(); |
| |
| MethodTree method = composeNewTestMethod( |
| testMethodName, |
| generateTestMethodBody(srcClass, |
| srcMethod, |
| instanceClsName, |
| workingCopy), |
| generateThrowsList(srcMethod, workingCopy, maker, isClassEjb31Bean(workingCopy, srcClass)), |
| workingCopy); |
| |
| if (setup.isGenerateMethodJavadoc()) { |
| String commentText = NbBundle.getMessage( |
| TestCreator.class, |
| "TestCreator.variantMethods.JavaDoc.comment", //NOI18N |
| srcMethod.getSimpleName().toString(), |
| srcClass.getSimpleName().toString()); |
| Comment javadoc = Comment.create(Comment.Style.JAVADOC, |
| -2, -2, -2, |
| commentText); |
| maker.addComment(method, javadoc, true); |
| } |
| |
| return method; |
| } |
| |
| protected ClassTree generateStubTestMethod(ClassTree tstClass, |
| String testMethodName, |
| WorkingCopy workingCopy) { |
| List<? extends Tree> tstMembersOrig = tstClass.getMembers(); |
| List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 4); |
| tstMembers.addAll(tstMembersOrig); |
| |
| List<ExpressionTree> throwsList = Collections.emptyList(); |
| MethodTree method = composeNewTestMethod( |
| STUB_TEST_NAME, |
| generateStubTestMethodBody(workingCopy), |
| throwsList, |
| workingCopy); |
| |
| tstMembers.add(method); |
| |
| ClassTree newClass = workingCopy.getTreeMaker().Class( |
| tstClass.getModifiers(), |
| tstClass.getSimpleName(), |
| tstClass.getTypeParameters(), |
| tstClass.getExtendsClause(), |
| (List<? extends ExpressionTree>) tstClass.getImplementsClause(), |
| tstMembers); |
| return newClass; |
| } |
| |
| /** |
| * Generates a {@literal throws}-clause of a method or constructor such that |
| * it declares all non-runtime exceptions and errors that are declared |
| * in the {@literal throws}-clause of the given executable element (method or |
| * constructor). |
| * |
| * @param execElem executable element (method or constructor) that serves |
| * as the model for the generated throws-list |
| * @param compInfo {@literal CompilationInfo} to be used for obtaining |
| * information about the given executable element |
| * @param maker maker to be used for construction of the resulting list |
| * @return list of {@literal ExpressionTree}s representing the throws-list |
| * that can be used for construction of a method with the same |
| * throws-clause as the given executable element |
| */ |
| private static List<ExpressionTree> generateThrowsList( |
| ExecutableElement execElem, |
| CompilationInfo compInfo, |
| TreeMaker maker, |
| boolean forceThrowsClause) { |
| if (forceThrowsClause || throwsNonRuntimeExceptions(compInfo, execElem)) { |
| return Collections.<ExpressionTree>singletonList( |
| maker.Identifier("Exception")); //NOI18N |
| } else { |
| return Collections.emptyList(); |
| } |
| } |
| |
| /** |
| */ |
| protected abstract MethodTree composeNewTestMethod( |
| String testMethodName, |
| BlockTree testMethodBody, |
| List<ExpressionTree> throwsList, |
| WorkingCopy workingCopy); |
| |
| /** |
| */ |
| private ClassTree generateMissingSuiteClassMembers( |
| ClassTree tstClass, |
| TreePath tstClassTreePath, |
| List<String> suiteMembers, |
| boolean isNewTestClass, |
| WorkingCopy workingCopy) { |
| final TreeMaker maker = workingCopy.getTreeMaker(); |
| |
| List<? extends Tree> tstMembersOrig = tstClass.getMembers(); |
| List<Tree> tstMembers = new ArrayList<Tree>(tstMembersOrig.size() + 2); |
| tstMembers.addAll(tstMembersOrig); |
| boolean membersChanged = false; |
| |
| ClassMap classMap = ClassMap.forClass(tstClass, |
| tstClassTreePath, |
| workingCopy.getTrees()); |
| |
| membersChanged |= generateMissingInitMembers(tstMembers, |
| classMap, |
| workingCopy); |
| |
| return finishSuiteClass(tstClass, |
| tstClassTreePath, |
| tstMembers, |
| suiteMembers, |
| membersChanged, |
| classMap, |
| workingCopy); |
| } |
| |
| /** |
| */ |
| protected abstract ClassTree finishSuiteClass( |
| ClassTree tstClass, |
| TreePath tstClassTreePath, |
| List<Tree> tstMembers, |
| List<String> suiteMembers, |
| boolean membersChanged, |
| ClassMap classMap, |
| WorkingCopy workingCopy); |
| |
| // <editor-fold defaultstate="collapsed" desc=" disabled code "> |
| // /** |
| // */ |
| // private void addMainMethod(final ClassTree classTree) { |
| // MethodTree mainMethod = createMainMethod(maker); |
| // if (mainMethod != null) { |
| // maker.addClassMember(classTree, mainMethod); |
| // } |
| // } |
| // |
| // /** |
| // */ |
| // private void fillTestClass(JavaClass srcClass, JavaClass tstClass) { |
| // |
| // fillGeneral(tstClass); |
| // |
| // List innerClasses = JUnitTestUtil.filterFeatures(srcClass, |
| // JavaClass.class); |
| // |
| // /* Create test classes for inner classes: */ |
| // for (Iterator i = innerClasses.iterator(); i.hasNext(); ) { |
| // JavaClass innerCls = (JavaClass) i.next(); |
| // |
| // if (!isClassTestable(innerCls).isTestable()) { |
| // continue; |
| // } |
| // |
| // /* |
| // * Check whether the test class for the inner class exists |
| // * and create one if it does not exist: |
| // */ |
| // String innerTestClsName |
| // = JUnitTestUtil.getTestClassName(innerCls.getSimpleName()); |
| // JavaClass innerTestCls |
| // = JUnitTestUtil.getClassBySimpleName(tstClass, |
| // innerTestClsName); |
| // if (innerTestCls == null) { |
| // innerTestCls = tgtPkg.getJavaClass().createJavaClass(); |
| // innerTestCls.setSimpleName( |
| // tstClass.getName() + '.' + innerTestClsName); |
| // tstClass.getFeatures().add(innerTestCls); |
| // } |
| // |
| // /* Process the tested inner class: */ |
| // fillTestClass(innerCls, innerTestCls); |
| // |
| // /* Make the inner test class testable with JUnit: */ |
| // innerTestCls.setModifiers(innerTestCls.getModifiers() | Modifier.STATIC); |
| // } |
| // |
| // /* Add the suite() method (only if we are supposed to do so): */ |
| // if (generateSuiteClasses && !hasSuiteMethod(tstClass)) { |
| // tstClass.getFeatures().add(createTestClassSuiteMethod(tstClass)); |
| // } |
| // |
| // /* Create missing test methods: */ |
| // List srcMethods = JUnitTestUtil.filterFeatures(srcClass, Method.class); |
| // for (Iterator i = srcMethods.iterator(); i.hasNext(); ) { |
| // Method sm = (Method) i.next(); |
| // if (isMethodAcceptable(sm) && |
| // tstClass.getMethod(createTestMethodName(sm.getName()), |
| // Collections.EMPTY_LIST, |
| // false) |
| // == null) { |
| // Method tm = createTestMethod(srcClass, sm); |
| // tstClass.getFeatures().add(tm); |
| // } |
| // } |
| // |
| // /* Create abstract class implementation: */ |
| // if (!skipAbstractClasses |
| // && (Modifier.isAbstract(srcClass.getModifiers()) |
| // || srcClass.isInterface())) { |
| // createAbstractImpl(srcClass, tstClass); |
| // } |
| // } |
| // </editor-fold> |
| |
| // <editor-fold defaultstate="collapsed" desc=" disabled code "> |
| // /** |
| // */ |
| // private Constructor createTestConstructor(String className) { |
| // Constructor constr = tgtPkg.getConstructor().createConstructor( |
| // className, // name |
| // Collections.EMPTY_LIST, // annotations |
| // Modifier.PUBLIC, // modifiers |
| // null, // Javadoc text |
| // null, // Javadoc - object |
| // null, // body - object |
| // "super(testName);\n", // body - text //NOI18N |
| // Collections.EMPTY_LIST, // type parameters |
| // createTestConstructorParams(), // parameters |
| // null); // exception names |
| // return constr; |
| // } |
| // |
| // /** |
| // */ |
| // private List/*<Parameter>*/ createTestConstructorParams() { |
| // Parameter param = tgtPkg.getParameter().createParameter( |
| // "testName", // parameter name |
| // Collections.EMPTY_LIST, // annotations |
| // false, // not final |
| // JUnitTestUtil.getTypeReference( // type |
| // tgtPkg, "String"), //NOI18N |
| // 0, // dimCount |
| // false); // is not var.arg. |
| // return Collections.singletonList(param); |
| // } |
| // </editor-fold> |
| |
| /** |
| * Creates a public static {@literal main(String[])} method |
| * with the body taken from settings. |
| * |
| * @param maker {@literal TreeMaker} to use for creating the method |
| * @return created {@literal main(...)} method, |
| * or {@literal null} if the method body would be empty |
| */ |
| private MethodTree createMainMethod(TreeMaker maker) { |
| String initialMainMethodBody = getInitialMainMethodBody(); |
| if (initialMainMethodBody.length() == 0) { |
| return null; |
| } |
| |
| ModifiersTree modifiers = maker.Modifiers( |
| createModifierSet(Modifier.PUBLIC, Modifier.STATIC)); |
| VariableTree param = maker.Variable( |
| maker.Modifiers(Collections.<Modifier>emptySet()), |
| "argList", //NOI18N |
| maker.Identifier("String[]"), //NOI18N |
| null); //initializer - not used in params |
| MethodTree mainMethod = maker.Method( |
| modifiers, //public static |
| "main", //method name "main"//NOI18N |
| maker.PrimitiveType(TypeKind.VOID), //return type "void" |
| Collections.<TypeParameterTree>emptyList(), //type params |
| Collections.<VariableTree>singletonList(param), //method param |
| Collections.<ExpressionTree>emptyList(), //throws-list |
| '{' + initialMainMethodBody + '}', //body text |
| null); //only for annotations |
| |
| return mainMethod; |
| } |
| |
| /** |
| * Generates a default body of a test method. |
| * |
| * @param srcClass class - parent of the tested source method |
| * @param srcMethod source method which should be tested by the test |
| * @param useNoArgConstrutor whether a no-argument constructor should be |
| * used in the body; |
| * it should not be {@literal true} unless |
| * the source class contains an accessible |
| * no-argument constructor |
| * @param instanceClsName name of a class of instance - only used if |
| * {@literal useNoArgConstrutor} is {@literal true} |
| */ |
| protected BlockTree generateTestMethodBody(TypeElement srcClass, |
| ExecutableElement srcMethod, |
| CharSequence instanceClsName, |
| WorkingCopy workingCopy) { |
| TreeMaker maker = workingCopy.getTreeMaker(); |
| |
| ExecutableType srcMethodExType = (ExecutableType)srcMethod.asType(); |
| try { |
| srcMethodExType = (ExecutableType)workingCopy.getTypes().asMemberOf((DeclaredType)srcClass.asType(), srcMethod); |
| } catch (IllegalArgumentException iae) { |
| } |
| |
| |
| boolean isStatic = srcMethod.getModifiers().contains(Modifier.STATIC); |
| List<StatementTree> statements = new ArrayList<StatementTree>(8); |
| |
| if (setup.isGenerateDefMethodBody()) { |
| StatementTree sout = generateSystemOutPrintln( |
| maker, |
| srcMethod.getSimpleName().toString()); |
| List<VariableTree> paramVariables = generateParamVariables( |
| workingCopy, |
| srcMethodExType, |
| getTestSkeletonVarNames(srcMethod.getParameters())); |
| statements.add(sout); |
| if(paramVariables != null) { |
| statements.addAll(paramVariables); |
| } |
| |
| if (!isStatic) { |
| |
| boolean useNoArgConstructor = hasAccessibleNoArgConstructor(srcClass); |
| |
| if (isClassEjb31Bean(workingCopy, srcClass) && isMethodInContainerLookup(srcClass, srcMethod)) { |
| statements.addAll(generateEJBLookupCode(maker, srcClass, srcMethod)); |
| } else { |
| VariableTree instanceVarInit = maker.Variable( |
| maker.Modifiers(Collections.<Modifier>emptySet()), |
| INSTANCE_VAR_NAME, |
| maker.QualIdent(srcClass), |
| useNoArgConstructor |
| ? generateNoArgConstructorCall(maker, |
| srcClass, |
| instanceClsName) |
| : maker.Literal(null)); |
| statements.add(instanceVarInit); |
| } |
| } |
| |
| MethodInvocationTree methodCall = maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), //type args. |
| maker.MemberSelect( |
| isStatic ? maker.QualIdent(srcClass) |
| : maker.Identifier(INSTANCE_VAR_NAME), |
| srcMethod.getSimpleName()), |
| createIdentifiers(maker, paramVariables)); |
| |
| TypeMirror retType = srcMethodExType.getReturnType(); |
| TypeKind retTypeKind = retType.getKind(); |
| |
| switch(retTypeKind){ |
| case VOID: |
| case ERROR: { |
| StatementTree methodCallStmt = maker.ExpressionStatement(methodCall); |
| statements.add(methodCallStmt); |
| break; |
| } |
| case TYPEVAR:{ |
| retType = getSuperType(workingCopy, retType); |
| retTypeKind = retType.getKind(); |
| } |
| default: |
| retType = getTypeMirror(workingCopy, retType); |
| retTypeKind = retType.getKind(); |
| Tree retTypeTree = maker.Type(retType); |
| VariableTree expectedValue = maker.Variable( |
| maker.Modifiers(NO_MODIFIERS), |
| EXP_RESULT_VAR_NAME, |
| retTypeTree, |
| getDefaultValue(maker, retType)); |
| VariableTree actualValue = maker.Variable( |
| maker.Modifiers(NO_MODIFIERS), |
| RESULT_VAR_NAME, |
| retTypeTree, |
| methodCall); |
| |
| List<ExpressionTree> comparisonArgs = new ArrayList<ExpressionTree>(2); |
| comparisonArgs.add(maker.Identifier(expectedValue.getName().toString())); |
| comparisonArgs.add(maker.Identifier(actualValue.getName().toString())); |
| if ((retTypeKind == TypeKind.DOUBLE) || (retTypeKind == TypeKind.FLOAT)){ |
| comparisonArgs.add(maker.Identifier(new Double(0).toString())); |
| } |
| |
| MethodInvocationTree comparison = maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), //type args. |
| (retTypeKind == TypeKind.ARRAY && (junitVersion == JUnitVersion.JUNIT4 || junitVersion == JUnitVersion.JUNIT5)) ? maker.Identifier("assertArrayEquals") : maker.Identifier("assertEquals"), //NOI18N |
| comparisonArgs); |
| StatementTree comparisonStmt = maker.ExpressionStatement( |
| comparison); |
| |
| statements.add(expectedValue); |
| statements.add(actualValue); |
| statements.add(comparisonStmt); |
| } |
| |
| // close EJBContainer if searching in container lookup was called |
| if (isClassEjb31Bean(workingCopy, srcClass) && isMethodInContainerLookup(srcClass, srcMethod)) { |
| statements.add(generateEJBCleanUpCode(maker)); |
| } |
| } |
| |
| //PENDING - source code hints |
| // if (generateSourceCodeHints) { |
| // // generate comments to bodies |
| // if (needsEmptyLine) { |
| // newBody.append('\n'); |
| // needsEmptyLine = false; |
| // } |
| // newBody.append(NbBundle.getMessage( |
| // TestCreator.class, |
| // generateDefMethodBody |
| // ? "TestCreator.variantMethods.defaultComment"//NOI18N |
| // : "TestCreator.variantMethods.onlyComment")) //NOI18N |
| // .append('\n'); |
| // } |
| |
| if (setup.isGenerateDefMethodBody()) { |
| ExpressionStatementTree exprStatementTree = |
| generateDefMethodBody(maker); |
| statements.add(exprStatementTree); |
| } |
| |
| return maker.Block(statements, false); |
| } |
| |
| private TypeMirror getTypeMirror(WorkingCopy workingCopy, TypeMirror retType) { |
| if (retType.getKind() == TypeKind.DECLARED) { |
| List<? extends TypeMirror> typeArguments = ((DeclaredType) retType).getTypeArguments(); |
| for (TypeMirror argument : typeArguments) { |
| if (argument.getKind() == TypeKind.WILDCARD) { |
| TypeMirror extendsBound = ((WildcardType) argument).getExtendsBound(); |
| TypeMirror superBound = ((WildcardType) argument).getSuperBound(); |
| if(extendsBound == null && superBound == null) { |
| return workingCopy.getTypes().erasure(retType); |
| } |
| if (extendsBound != null) { |
| if (shouldApplyErasure(extendsBound)) { |
| return workingCopy.getTypes().erasure(retType); |
| } |
| } |
| if (superBound != null) { |
| if (shouldApplyErasure(superBound)) { |
| return workingCopy.getTypes().erasure(retType); |
| } |
| } |
| } else if (argument.getKind() == TypeKind.DECLARED) { |
| if (((DeclaredType) argument).asElement().getModifiers().contains(Modifier.PRIVATE)) { |
| return workingCopy.getTypes().erasure(retType); |
| } |
| } else { |
| return workingCopy.getTypes().erasure(retType); |
| } |
| } |
| } else { |
| return workingCopy.getTypes().erasure(retType); |
| } |
| return retType; |
| } |
| |
| private boolean shouldApplyErasure(TypeMirror argument) { |
| if (argument.getKind() == TypeKind.DECLARED) { |
| if (((DeclaredType) argument).asElement().getModifiers().contains(Modifier.PRIVATE)) { |
| return true; |
| } |
| } else { |
| return true; |
| } |
| return false; |
| } |
| |
| protected BlockTree generateStubTestMethodBody(WorkingCopy workingCopy) { |
| TreeMaker maker = workingCopy.getTreeMaker(); |
| List<StatementTree> statements = new ArrayList<StatementTree>(8); |
| |
| |
| if (setup.isGenerateDefMethodBody()) { |
| ExpressionStatementTree exprStatementTree = |
| generateDefMethodBody(maker); |
| statements.add(exprStatementTree); |
| } |
| |
| return maker.Block(statements, false); |
| } |
| |
| /** |
| */ |
| private StatementTree generateSystemOutPrintln(TreeMaker maker, |
| String arg) { |
| MethodInvocationTree methodInvocation = maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), //type args |
| maker.MemberSelect( |
| maker.MemberSelect( |
| maker.Identifier("System"), "out"), "println"),//NOI18N |
| Collections.<LiteralTree>singletonList( |
| maker.Literal(arg))); //args. |
| return maker.ExpressionStatement(methodInvocation); |
| } |
| |
| /** |
| */ |
| private List<VariableTree> generateParamVariables( |
| WorkingCopy workingCopy, |
| ExecutableType srcMethod, |
| String[] varNames) { |
| TreeMaker maker = workingCopy.getTreeMaker(); |
| List<? extends TypeMirror> params = srcMethod.getParameterTypes(); |
| if ((params == null) || params.isEmpty()) { |
| return Collections.<VariableTree>emptyList(); |
| } |
| |
| Set<Modifier> noModifiers = Collections.<Modifier>emptySet(); |
| List<VariableTree> paramVariables = new ArrayList<VariableTree>(params.size()); |
| int index = 0; |
| for (TypeMirror param : params) { |
| if (param.getKind() == TypeKind.TYPEVAR){ |
| param = getSuperType(workingCopy, param); |
| } |
| if (param.getKind() == TypeKind.DECLARED) { |
| boolean typeVar = containsTypeVar((DeclaredType) param, false); |
| if (typeVar) { |
| return null; |
| } |
| } |
| param = getTypeMirror(workingCopy, param); |
| paramVariables.add( |
| maker.Variable(maker.Modifiers(noModifiers), |
| varNames[index++], |
| maker.Type(param), |
| getDefaultValue(maker, param))); |
| } |
| return paramVariables; |
| } |
| |
| private boolean containsTypeVar(DeclaredType dt, boolean typeVar) { |
| List<? extends TypeMirror> args = dt.getTypeArguments(); |
| for (TypeMirror arg : args) { |
| if (arg.getKind() == TypeKind.TYPEVAR) { |
| typeVar = true; |
| } |
| if (arg.getKind() == TypeKind.DECLARED) { |
| typeVar = typeVar || containsTypeVar((DeclaredType) arg, typeVar); |
| } |
| } |
| return typeVar; |
| } |
| |
| /** |
| * Returns supertype of the provided type or provided type if no supertypes were found |
| */ |
| |
| private TypeMirror getSuperType(WorkingCopy workingCopy, TypeMirror type){ |
| List<? extends TypeMirror> superTypes = workingCopy.getTypes().directSupertypes(type); |
| if (!superTypes.isEmpty()){ |
| return superTypes.get(0); |
| } |
| return type; |
| } |
| /** |
| */ |
| private List<IdentifierTree> createIdentifiers( |
| TreeMaker maker, |
| List<VariableTree> variables) { |
| List<IdentifierTree> identifiers; |
| if (variables == null) { |
| identifiers = new ArrayList<IdentifierTree>(1); |
| identifiers.add(maker.Identifier("null".toString())); |
| } else if (variables.isEmpty()) { |
| identifiers = Collections.<IdentifierTree>emptyList(); |
| } else { |
| identifiers = new ArrayList<IdentifierTree>(variables.size()); |
| for (VariableTree var : variables) { |
| identifiers.add(maker.Identifier(var.getName().toString())); |
| } |
| } |
| return identifiers; |
| } |
| |
| /** |
| * Builds list of variable names for use in a test method skeleton. |
| * By default, names of variables are same as names of tested method's |
| * declared parameters. There are three variable names reserved |
| * for variables holding the instance the tested method will be called on, |
| * the expected result and the actual result returned |
| * by the tested method. This method resolves a potential conflict |
| * if some of the tested method's parameter's name is one of these |
| * reserved names - in this case, the variable name used is a slight |
| * modification of the declared parameter's name. The method also resolves |
| * cases that some or all parameters are without name - in this case, |
| * an arbitrary name is assigned to each of these unnamed parameters. |
| * The goal is to ensure that all of the used variable names are unique. |
| * |
| * @param sourceMethodParams |
| * list of tested method's parameters (items are of type |
| * <code>org.netbeans.jmi.javamodel.TypeParameter</code>) |
| * @return variable names used for default values of the tested method's |
| * parameters (the reserved variable names are not included) |
| */ |
| private String[] getTestSkeletonVarNames( |
| final List<? extends VariableElement> sourceMethodParams) { |
| |
| /* Handle the trivial case: */ |
| if (sourceMethodParams.isEmpty()) { |
| return new String[0]; |
| } |
| |
| final int count = sourceMethodParams.size(); |
| String[] varNames = new String[count]; |
| boolean[] conflicts = new boolean[count]; |
| boolean issueFound = false; |
| |
| Set<String> varNamesSet = new HashSet<String>((int) ((count + 2) * 1.4)); |
| varNamesSet.add(INSTANCE_VAR_NAME); |
| varNamesSet.add(RESULT_VAR_NAME); |
| varNamesSet.add(EXP_RESULT_VAR_NAME); |
| |
| Iterator<? extends VariableElement> it = sourceMethodParams.iterator(); |
| for (int i = 0; i < count; i++) { |
| String paramName = it.next().getSimpleName().toString(); |
| varNames[i] = paramName; |
| |
| if (paramName == null) { |
| issueFound = true; |
| } else if (!varNamesSet.add(paramName)) { |
| conflicts[i] = true; |
| issueFound = true; |
| } else { |
| conflicts[i] = false; |
| } |
| } |
| |
| if (issueFound) { |
| for (int i = 0; i < count; i++) { |
| String paramName; |
| if (varNames[i] == null) { |
| paramName = ARTIFICAL_VAR_NAME_BASE + i; |
| if (varNamesSet.add(paramName)) { |
| varNames[i] = paramName; |
| continue; |
| } else { |
| conflicts[i] = true; |
| } |
| } |
| if (conflicts[i]) { |
| String paramNamePrefix = varNames[i] + '_'; |
| |
| int index = 2; |
| while (!varNamesSet.add( |
| paramName = (paramNamePrefix + (index++)))); |
| varNames[i] = paramName; |
| } |
| } |
| } |
| |
| return varNames; |
| } |
| |
| /** |
| */ |
| private static ExpressionTree getDefaultValue(TreeMaker maker, |
| TypeMirror type) { |
| ExpressionTree defValue; |
| TypeKind typeKind = type.getKind(); |
| if (typeKind.isPrimitive()) { |
| switch (typeKind) { |
| case BOOLEAN: |
| defValue = maker.Literal(Boolean.FALSE); |
| break; |
| case CHAR: |
| defValue = maker.Literal(new Character(' ')); |
| break; |
| case BYTE: |
| defValue = maker.Literal(new Byte((byte) 0)); |
| break; |
| case SHORT: |
| defValue = maker.Literal(new Short((short) 0)); |
| break; |
| case INT: |
| defValue = maker.Literal(new Integer(0)); |
| break; |
| case FLOAT: |
| defValue = maker.Literal(new Float(0.0F)); |
| break; |
| case LONG: |
| defValue = maker.Literal(new Long(0L)); |
| break; |
| case DOUBLE: |
| defValue = maker.Literal(new Double(0.0)); |
| break; |
| default: |
| assert false : "unknown primitive type"; //NOI18N |
| defValue = maker.Literal(new Integer(0)); |
| break; |
| } |
| } else if ((typeKind == TypeKind.DECLARED) |
| && type.toString().equals("java.lang.String")) { //NOI18N |
| defValue = maker.Literal(""); //NOI18N |
| } else { |
| defValue = maker.Literal(null); |
| } |
| return defValue; |
| } |
| |
| /** |
| */ |
| private ExpressionTree generateNoArgConstructorCall(TreeMaker maker, |
| TypeElement cls, |
| CharSequence instanceClsName) { |
| return maker.NewClass( |
| null, //enclosing instance |
| Collections.<ExpressionTree>emptyList(),//type arguments |
| instanceClsName != null |
| ? maker.Identifier(instanceClsName) |
| : maker.QualIdent(cls), //class identifier |
| Collections.<ExpressionTree>emptyList(),//arguments list |
| null); //class body |
| } |
| |
| /** |
| */ |
| private List<ExecutableElement> findTestableMethods(WorkingCopy wc, TypeElement classElem) { |
| boolean isEJB = isClassEjb31Bean(wc, classElem); |
| final Types types = wc.getTypes(); |
| |
| Iterable<? extends Element> elements; |
| if (isEJB){ |
| final TypeMirror typeObject = wc.getElements().getTypeElement("java.lang.Object").asType(); //NOI18N |
| ElementAcceptor acceptor = new ElementAcceptor(){ |
| public boolean accept(Element e, TypeMirror type) { |
| return !types.isSameType(typeObject, e.getEnclosingElement().asType()); |
| } |
| }; |
| elements = wc.getElementUtilities().getMembers(classElem.asType(), acceptor); |
| } else { |
| elements = classElem.getEnclosedElements(); |
| } |
| List<ExecutableElement> methods = ElementFilter.methodsIn(elements); |
| |
| if (methods.isEmpty()) { |
| return Collections.<ExecutableElement>emptyList(); |
| } |
| |
| List<ExecutableElement> testableMethods = null; |
| |
| int skippedCount = 0; |
| for (ExecutableElement method : methods) { |
| if (isTestableMethod(method) && |
| (!isEJB || (isEJB && isTestableEJBMethod(method)))) { |
| if (testableMethods == null) { |
| testableMethods = new ArrayList<ExecutableElement>( |
| methods.size() - skippedCount); |
| } |
| testableMethods.add(method); |
| } else { |
| skippedCount++; |
| } |
| } |
| |
| return (testableMethods != null) |
| ? testableMethods |
| : Collections.<ExecutableElement>emptyList(); |
| } |
| |
| /** |
| */ |
| private boolean isTestableMethod(ExecutableElement method) { |
| if (method.getKind() != ElementKind.METHOD) { |
| throw new IllegalArgumentException(); |
| } |
| |
| return setup.isMethodTestable(method); |
| } |
| |
| private boolean isTestableEJBMethod(ExecutableElement method){ |
| Set<Modifier> modifiers = method.getModifiers(); |
| |
| return !(modifiers.isEmpty() || !EnumSet.copyOf(modifiers).removeAll(ACCESS_MODIFIERS)) && |
| !modifiers.contains(Modifier.PROTECTED); |
| } |
| /** |
| * Finds a non-abstract, direct or indirect subclass of a given source class |
| * among nested classes of the given test class. Both static nested classes |
| * and inner classes are taken into account. Anonymous inner classes and |
| * classes defined inside code blocks are ignored. If there are multiple |
| * nested/inner classes the one having the given preferred name, if any, |
| * is chosen. |
| * |
| * @param srcClass abstract class the subclass of which is to be found |
| * @param tstClass test class to search |
| * @param tstClassPath tree-path to the test class |
| * @param tstClassMap content index of the test class |
| * @param preferredName preferred name of the nested/inner class |
| * @return name of the found nested class, or {@literal null} if non was found |
| */ |
| private static CharSequence findAbstractClassImplName(TypeElement srcClass, |
| ClassTree tstClass, |
| TreePath tstClassPath, |
| ClassMap tstClassMap, |
| String preferredName, |
| Trees trees, |
| Types types) { |
| if (!tstClassMap.containsNestedClasses()) { |
| return null; |
| } |
| |
| boolean mayContainPreferred = tstClassMap.getNestedClasses() |
| .contains(preferredName); |
| |
| List<? extends Tree> tstClassMembers = tstClass.getMembers(); |
| int[] nestedClassIndexes = tstClassMap.getNestedClassIndexes(); |
| if(tstClassMembers.size() != nestedClassIndexes.length) { |
| LOG.log(Level.INFO, "Cannot locate subclass of source class {0} in test class {1}", new Object[]{srcClass.getSimpleName(), tstClass.getSimpleName()}); |
| return null; |
| } |
| TypeMirror srcClassType = null; |
| Name firstFound = null; |
| for (int index : nestedClassIndexes) { |
| Tree member = tstClassMembers.get(index); |
| assert TreeUtilities.CLASS_TREE_KINDS.contains(member.getKind()); |
| ClassTree nestedClass = (ClassTree) member; |
| if (nestedClass.getModifiers().getFlags().contains(ABSTRACT)) { |
| continue; |
| } |
| |
| TreePath nestedClassPath = new TreePath(tstClassPath, nestedClass); |
| TypeMirror nestedClassType = trees.getElement(nestedClassPath).asType(); |
| if (srcClassType == null) { |
| srcClassType = srcClass.asType(); |
| } |
| if (types.isSubtype(nestedClassType, srcClassType)) { |
| Name name = nestedClass.getSimpleName(); |
| if (!mayContainPreferred || name.contentEquals(preferredName)) { |
| return name; |
| } else if (firstFound == null) { |
| firstFound = name; |
| } |
| } |
| |
| } |
| return firstFound; //may be null |
| } |
| |
| /** |
| * Checks whether there is an instance (non-static) method among the given |
| * methods. |
| * |
| * @param methods methods to probe |
| * @return {@literal true} if there is at least one non-static method in the |
| * given list of methods, {@literal false} otherwise |
| */ |
| private static boolean hasInstanceMethods(List<ExecutableElement> methods) { |
| if (methods.isEmpty()) { |
| return false; |
| } |
| |
| for (ExecutableElement method : methods) { |
| if (!method.getModifiers().contains(STATIC)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| */ |
| protected boolean hasAccessibleNoArgConstructor(TypeElement srcClass) { |
| boolean answer; |
| |
| List<ExecutableElement> constructors |
| = ElementFilter.constructorsIn(srcClass.getEnclosedElements()); |
| |
| if (constructors.isEmpty()) { |
| answer = true; //no explicit constructor -> synthetic no-arg. constructor |
| } else { |
| answer = false; |
| for (ExecutableElement constructor : constructors) { |
| if (constructor.getParameters().isEmpty()) { |
| answer = !constructor.getModifiers().contains(Modifier.PRIVATE); |
| break; |
| } |
| } |
| } |
| return answer; |
| } |
| |
| /** |
| */ |
| private static boolean throwsNonRuntimeExceptions(CompilationInfo compInfo, |
| ExecutableElement method) { |
| List<? extends TypeMirror> thrownTypes = method.getThrownTypes(); |
| if (thrownTypes.isEmpty()) { |
| return false; |
| } |
| |
| String runtimeExcName = "java.lang.RuntimeException"; //NOI18N |
| TypeElement runtimeExcElement = compInfo.getElements() |
| .getTypeElement(runtimeExcName); |
| if (runtimeExcElement == null) { |
| Logger.getLogger("junit").log( //NOI18N |
| Level.WARNING, |
| "Could not find TypeElement for " //NOI18N |
| + runtimeExcName); |
| return true; |
| } |
| |
| Types types = compInfo.getTypes(); |
| TypeMirror runtimeExcType = runtimeExcElement.asType(); |
| for (TypeMirror exceptionType : thrownTypes) { |
| if (!types.isSubtype(exceptionType, runtimeExcType)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| */ |
| private <T extends Element> List<T> resolveHandles( |
| CompilationInfo compInfo, |
| List<ElementHandle<T>> handles) { |
| if (handles == null) { |
| return null; |
| } |
| if (handles.isEmpty()) { |
| return Collections.<T>emptyList(); |
| } |
| |
| List<T> elements = new ArrayList<T>(handles.size()); |
| for (ElementHandle<T> handle : handles) { |
| T element = handle.resolve(compInfo); |
| if (element != null) { |
| elements.add(element); |
| } else { |
| ErrorManager.getDefault().log( |
| ErrorManager.WARNING, |
| "JUnit: Could not resolve element handle " //NOI18N |
| + handle.getBinaryName()); |
| } |
| } |
| return elements; |
| } |
| |
| /** |
| * Stops this creator - cancels creation of a test class. |
| */ |
| public void cancel() { |
| cancelled = true; |
| } |
| |
| /** |
| */ |
| private void classProcessed(ClassTree cls) { |
| if (processedClassNames == null) { |
| processedClassNames = new ArrayList<String>(4); |
| } |
| processedClassNames.add(cls.getSimpleName().toString()); |
| } |
| |
| /** |
| */ |
| List<String> getProcessedClassNames() { |
| return processedClassNames != null |
| ? processedClassNames |
| : Collections.<String>emptyList(); |
| } |
| |
| /* private methods */ |
| |
| //XXX: retouche |
| // /** |
| // * |
| // * @param cls JavaClass to generate the comment to. |
| // */ |
| // private static void addClassBodyComment(JavaClass cls) { |
| // int off = cls.getEndOffset() - 1; |
| // String theComment1 = NbBundle.getMessage(TestCreator.class, |
| // CLASS_COMMENT_LINE1); |
| // String theComment2 = NbBundle.getMessage(TestCreator.class, |
| // CLASS_COMMENT_LINE2); |
| // String indent = getIndentString(); |
| // DiffElement diff = new DiffElement( |
| // off, |
| // off, |
| // indent + theComment1 + '\n' |
| // + indent + theComment2 + '\n' + '\n'); |
| // ((ResourceImpl) cls.getResource()).addExtDiff(diff); |
| // } |
| |
| /** |
| */ |
| private String getInitialMainMethodBody() { |
| if (initialMainMethodBody == null) { |
| initialMainMethodBody = JUnitSettings.getDefault() |
| .getGenerateMainMethodBody(); |
| if (initialMainMethodBody == null) { |
| /* |
| * set it to a non-null value so that this method does not try |
| * to load it from the settings next time |
| */ |
| initialMainMethodBody = ""; //NOI18N |
| } |
| } |
| return initialMainMethodBody; |
| } |
| |
| /** |
| */ |
| protected ModifiersTree createModifiersTree(String annotationClassName, |
| Set<Modifier> modifiers, |
| WorkingCopy workingCopy) { |
| TreeMaker maker = workingCopy.getTreeMaker(); |
| AnnotationTree annotation = maker.Annotation( |
| getClassIdentifierTree(annotationClassName, workingCopy), |
| Collections.<ExpressionTree>emptyList()); |
| return maker.Modifiers(modifiers, |
| Collections.<AnnotationTree>singletonList( |
| annotation)); |
| } |
| |
| /** */ |
| private Map<String, ExpressionTree> classIdentifiers; |
| |
| /** |
| */ |
| protected ExpressionTree getClassIdentifierTree(String className, |
| WorkingCopy workingCopy) { |
| ExpressionTree classIdentifier; |
| if (classIdentifiers == null) { |
| classIdentifier = null; |
| classIdentifiers = new HashMap<String, ExpressionTree>(13); |
| } else { |
| classIdentifier = classIdentifiers.get(className); |
| } |
| if (classIdentifier == null) { |
| TypeElement typeElement |
| = getElemForClassName(className, workingCopy.getElements()); |
| TreeMaker maker = workingCopy.getTreeMaker(); |
| classIdentifier = (typeElement != null) |
| ? maker.QualIdent(typeElement) |
| : maker.Identifier(className); |
| classIdentifiers.put(className, classIdentifier); |
| } |
| return classIdentifier; |
| } |
| |
| /** |
| */ |
| protected static TypeElement getElemForClassName(String className, |
| Elements elements) { |
| TypeElement elem = elements.getTypeElement(className); |
| if (elem == null) { |
| ErrorManager.getDefault().log( |
| ErrorManager.ERROR, |
| "Could not find TypeElement for " + className); //NOI18N |
| } |
| return elem; |
| } |
| |
| /** |
| * Creates a {@literal Set} of {@literal Modifier}s from the given list |
| * of modifiers. |
| * |
| * @param modifiers modifiers that should be contained in the set |
| * @return set containing exactly the given modifiers |
| */ |
| static Set<Modifier> createModifierSet(Modifier... modifiers) { |
| EnumSet<Modifier> modifierSet = EnumSet.noneOf(Modifier.class); |
| for (Modifier m : modifiers) { |
| modifierSet.add(m); |
| } |
| return modifierSet; |
| } |
| |
| private static boolean isClassEjb31Bean(WorkingCopy wc, TypeElement srcClass) { |
| ClassPath cp = wc.getClasspathInfo().getClassPath(ClasspathInfo.PathKind.COMPILE); |
| if (cp == null || cp.findResource("javax/ejb/embeddable/EJBContainer.class") == null) { |
| // if EJBContainer class is not available on classpath then it is not EJB 3.1 |
| return false; |
| } |
| List<? extends AnnotationMirror> annotations = wc.getElements().getAllAnnotationMirrors(srcClass); |
| for (AnnotationMirror am : annotations) { |
| String annotation = ((TypeElement)am.getAnnotationType().asElement()).getQualifiedName().toString(); |
| if (annotation.equals("javax.ejb.Singleton") || // NOI18N |
| annotation.equals("javax.ejb.Stateless") || // NOI18N |
| annotation.equals("javax.ejb.Stateful")) { // NOI18N |
| // class is an EJB |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks whether is given method declared by no-interface Bean or by interface annotated by |
| * {@literal @javax.ejb.Remote} or {@literal @javax.ejb.Local} |
| * |
| * @param srcClass class for which are generated test cases |
| * @param srcMethod method of interest |
| * @return {@literal true} if the bean is no-interface or method is declared by |
| * respectively annotated interface, {@literal false} otherwise |
| */ |
| private static boolean isMethodInContainerLookup(TypeElement srcClass, ExecutableElement srcMethod) { |
| // check for no-interface LocalBean |
| List<? extends AnnotationMirror> annotations = srcClass.getAnnotationMirrors(); |
| for (AnnotationMirror annotationMirror : annotations) { |
| String annotation = ((TypeElement)annotationMirror.getAnnotationType().asElement()).getQualifiedName().toString(); |
| if (annotation.equals("javax.ejb.LocalBean")) // NOI18N |
| return true; |
| } |
| // check if the class has empty implements clause or given method is declared by @Remote, @Local interface |
| List<? extends TypeMirror> interfaces = srcClass.getInterfaces(); |
| if (interfaces.isEmpty() |
| || areAllowedInterfacesForLocalBean(interfaces) |
| || getEjbInterfaceDeclaringMethod(srcMethod, interfaces) != null) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Checks {@literal List} of interfaces if are all of them allowed for LocalBean, |
| * means if all interfaces are {@literal java.io.Serializable}, {@literal java.io.Externalizable} or |
| * from package {@literal javax.ejb} |
| * |
| * @param interfaces {@literal List} of interfaces which should be checked |
| * @return {@literal true} if all interfaces are allowed for LocalBean, {@literal false} otherwise |
| */ |
| private static boolean areAllowedInterfacesForLocalBean(List<? extends TypeMirror> interfaces) { |
| for (TypeMirror typeMirror : interfaces) { |
| if (typeMirror instanceof DeclaredType) { |
| TypeElement interfaceElement = (TypeElement) ((DeclaredType) typeMirror).asElement(); |
| String interfaceClassName = interfaceElement.getQualifiedName().toString(); |
| if (!interfaceClassName.equals("java.io.Serializable") //NOI18N |
| && !interfaceClassName.equals("java.io.Externalizable") //NOI18N |
| && !interfaceClassName.startsWith("javax.ejb.")) { //NOI18N |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private List<VariableTree> generateEJBLookupCode(TreeMaker maker, TypeElement srcClass, ExecutableElement srcMethod) { |
| final String ejbContainerPackage = "javax.ejb.embeddable.EJBContainer"; // NOI18N |
| List<VariableTree> trees = new ArrayList<VariableTree>(); |
| |
| // TODO: there are probably better ways how to generate code below: |
| IdentifierTree container = maker.Identifier(ejbContainerPackage); |
| MethodInvocationTree invocation = maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), |
| maker.MemberSelect(container, "createEJBContainer"), // NOI18N |
| Collections.<ExpressionTree>emptyList() |
| ); |
| VariableTree containerVarInit = maker.Variable( |
| maker.Modifiers(Collections.<Modifier>emptySet()), |
| CONTAINER_VAR_NAME, |
| maker.QualIdent(ejbContainerPackage), |
| invocation |
| ); |
| trees.add(containerVarInit); |
| |
| String className = getBeanInterfaceOrImplementationClassName(srcClass, srcMethod); |
| IdentifierTree bean = maker.Identifier("(" + className + ")" + CONTAINER_VAR_NAME); // NOI18N |
| MethodInvocationTree contextInvocation = maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), |
| maker.MemberSelect(bean, "getContext"), // NOI18N |
| Collections.<ExpressionTree>emptyList() |
| ); |
| contextInvocation = maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), |
| maker.MemberSelect(contextInvocation, "lookup"), // NOI18N |
| Collections.<ExpressionTree>singletonList(maker.Literal("java:global/classes/"+srcClass.getSimpleName())) // NOI18N |
| ); |
| VariableTree beanVarInit = maker.Variable( |
| maker.Modifiers(Collections.<Modifier>emptySet()), |
| INSTANCE_VAR_NAME, |
| maker.QualIdent(className), |
| contextInvocation |
| ); |
| trees.add(beanVarInit); |
| |
| return trees; |
| } |
| |
| /** |
| * Get name of the implementation class or interface which should be used |
| * for searching in server container context. |
| * |
| * @param srcClass class for which are generated JUnit tests |
| * @param srcMethod currently generated method |
| * @return {@literal String} which should be used for searching in context |
| */ |
| private static String getBeanInterfaceOrImplementationClassName(TypeElement srcClass, ExecutableElement srcMethod) { |
| String interfaceClassName = getEjbInterfaceDeclaringMethod(srcMethod, srcClass.getInterfaces()); |
| if (interfaceClassName != null) { |
| return interfaceClassName; |
| } else { |
| return srcClass.getSimpleName().toString(); |
| } |
| } |
| |
| /** |
| * Gets interface classname which is annotated as Remote or Local and declares |
| * given method or {@literal null} if no such interface was found |
| * |
| * @param srcMethod method which should be declared |
| * @param interfaces {@literal List} of interfaces to be scanned for {@literal @Local}, |
| * {@literal @Remote} annotation and method declaration |
| * @return interface classname when was found satisfactory interface, {@literal null} otherwise |
| */ |
| private static String getEjbInterfaceDeclaringMethod(ExecutableElement srcMethod, List<? extends TypeMirror> interfaces) { |
| for (TypeMirror typeMirror : interfaces) { |
| if (typeMirror instanceof DeclaredType) { |
| DeclaredType declaredType = (DeclaredType) typeMirror; |
| TypeElement interfaceElement = (TypeElement) declaredType.asElement(); |
| if (isLocalOrRemoteInterface(interfaceElement) |
| && isMethodDeclaredByInterface(interfaceElement, srcMethod)) { |
| return interfaceElement.getSimpleName().toString(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Checks whether is interface annotated as {@literal @javax.ejb.Remote} or {@literal @javax.ejb.Local} |
| * |
| * @param trgInterface interface which should be annotated |
| * @return {@literal true} if the interface is annotated, {@literal false} otherwise |
| */ |
| private static boolean isLocalOrRemoteInterface(TypeElement trgInterface) { |
| List<? extends AnnotationMirror> annotations = trgInterface.getAnnotationMirrors(); |
| for (AnnotationMirror am : annotations) { |
| String annotation = ((TypeElement)am.getAnnotationType().asElement()).getQualifiedName().toString(); |
| if (annotation.equals("javax.ejb.Local") || // NOI18N |
| annotation.equals("javax.ejb.Remote")) { // NOI18N |
| // interface is @Local or @Remote |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks whether the interface declare given method |
| * |
| * @param trgInterface interface to be declaring the method |
| * @param srcMethod method to be declared |
| * @return {@literal true} if the method is declared by the interface, {@literal false} otherwise |
| */ |
| private static boolean isMethodDeclaredByInterface(TypeElement trgInterface, ExecutableElement srcMethod) { |
| List<? extends Element> enclosedElements = trgInterface.getEnclosedElements(); |
| List<? extends VariableElement> methodParameters = srcMethod.getParameters(); |
| for (Element enclosedElement : enclosedElements) { |
| if (enclosedElement instanceof ExecutableElement) { |
| ExecutableElement exElement = (ExecutableElement) enclosedElement; |
| List<? extends VariableElement> elementParameters = exElement.getParameters(); |
| if (srcMethod.getSimpleName() != exElement.getSimpleName() |
| || (methodParameters.size() != elementParameters.size())) { |
| continue; |
| } |
| for (int i = 0; i < methodParameters.size(); i++) { |
| if (!((VariableElement) methodParameters.get(i)).asType().toString().equals( |
| ((VariableElement) elementParameters.get(i)).asType().toString())) { |
| continue; |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private StatementTree generateEJBCleanUpCode(TreeMaker maker) { |
| IdentifierTree container = maker.Identifier(CONTAINER_VAR_NAME); |
| MethodInvocationTree invocation = maker.MethodInvocation( |
| Collections.<ExpressionTree>emptyList(), |
| maker.MemberSelect(container, "close"), // NOI18N |
| Collections.<ExpressionTree>emptyList() |
| ); |
| return maker.ExpressionStatement(invocation); |
| } |
| |
| /** |
| * Finds a class with the specified simple name in the specified list of |
| * classes. |
| * @param simpleName the simple class name. |
| * @param classes the list of the {@literal ClassTree}s. |
| * @return a {@literal ClassTree} of the class if the specified simple class |
| * name is not {@literal null}, and a class with that name exists, |
| * otherwise {@literal null}. |
| */ |
| private ClassTree findClass(String simpleName, List<ClassTree> classes) { |
| if(simpleName == null) { // #180480 |
| return null; |
| } |
| for (ClassTree cls : classes) { |
| if (cls.getSimpleName().contentEquals(simpleName)) { |
| return cls; // class exists |
| } |
| } |
| return null; |
| } |
| |
| } |