package org.testng.eclipse.ui.conversion;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.common.collect.Maps;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.testng.eclipse.TestNGPlugin;
import org.testng.eclipse.util.PreferenceStoreUtil.SuiteMethodTreatment;
/**
* A rewriter that will convert the current JUnit file to TestNG
* using JDK5 annotations
*
* @author C�dric Beust <cedric@beust.com>
*/
public class AnnotationRewriter implements IRewriteProvider
{
private static final Set<String> IMPORTS_TO_REMOVE = new HashSet<String>() {{
add("junit.framework.Assert");
add("junit.framework.Test");
add("junit.framework.TestCase");
add("junit.framework.TestSuite");
add("org.junit.After");
add("org.junit.AfterClass");
add("org.junit.Before");
add("org.junit.BeforeClass");
add("org.junit.Ignore");
add("org.junit.Test");
add("org.junit.runner.RunWith");
add("org.junit.runners.Parameterized");
}};
private static final Set<String> STATIC_IMPORTS_TO_REMOVE = new HashSet<String>() {{
add("org.junit.Assert");
}};
public ASTRewrite createRewriter(CompilationUnit astRoot, AST ast) {
final ASTRewrite result = ASTRewrite.create(astRoot.getAST());
JUnitVisitor visitor = new JUnitVisitor();
astRoot.accept(visitor);
//
// Remove some JUnit imports.
//
List<ImportDeclaration> oldImports = visitor.getJUnitImports();
for (int i = 0; i < oldImports.size(); i++) {
Name importName = oldImports.get(i).getName();
String fqn = importName.getFullyQualifiedName();
if (IMPORTS_TO_REMOVE.contains(fqn)) {
result.remove(oldImports.get(i), null);
}
for (String s : STATIC_IMPORTS_TO_REMOVE) {
if (fqn.contains(s)) {
result.remove(oldImports.get(i), null);
}
}
}
//
// Add imports as needed
//
maybeAddImport(ast, result, astRoot, visitor.hasAsserts(), "org.testng.AssertJUnit");
maybeAddImport(ast, result, astRoot, visitor.hasFail(), "org.testng.Assert");
maybeAddImport(ast, result, astRoot, !visitor.getBeforeClasses().isEmpty(),
"org.testng.annotations.BeforeClass");
maybeAddImport(ast, result, astRoot, !visitor.getBeforeMethods().isEmpty(),
"org.testng.annotations.BeforeMethod");
maybeAddImport(ast, result, astRoot, visitor.hasTestMethods(), "org.testng.annotations.Test");
maybeAddImport(ast, result, astRoot, !visitor.getAfterMethods().isEmpty(),
"org.testng.annotations.AfterMethod");
maybeAddImport(ast, result, astRoot, !visitor.getAfterClasses().isEmpty(),
"org.testng.annotations.AfterClass");
//
// Add static imports
//
Set<String> staticImports = visitor.getStaticImports();
for (String si : staticImports) {
addImport(ast, result, astRoot, "org.testng.AssertJUnit." + si, true /* static import */);
}
//
// Remove "extends TestCase"
//
SimpleType td = visitor.getTestCase();
if (null != td) {
result.remove(td, null);
}
//
// Addd the annotations as needed
//
maybeAddAnnotations(ast, visitor, result, visitor.getTestMethods(), "Test", null, null);
maybeAddAnnotations(ast, visitor, result, visitor.getDisabledTestMethods(), "Test", null,
createDisabledAttribute(ast));
maybeAddAnnotations(ast, visitor, result, visitor.getBeforeMethods(), "BeforeMethod",
"@Before" /* annotation to remove */);
maybeAddAnnotations(ast, visitor, result, visitor.getAfterMethods(), "AfterMethod",
"@After" /* annotation to remove */);
//
// suite() method: remove, comment out or leave untouched, depending on the setting
//
SuiteMethodTreatment smt = TestNGPlugin.getPluginPreferenceStore().getSuiteMethodTreatement();
MethodDeclaration suiteMethod = visitor.getSuite();
if (smt != SuiteMethodTreatment.DONT_TOUCH && suiteMethod != null) {
if (smt == SuiteMethodTreatment.REMOVE) {
// Remove suite()
result.remove(suiteMethod, null);
} else {
// Comment out suite()
TypeDeclaration type = visitor.getType();
ListRewrite lr = result.getListRewrite(type, TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
lr.insertBefore(result.createStringPlaceholder("/*", ASTNode.METHOD_DECLARATION),
suiteMethod, null);
lr.insertAfter(result.createStringPlaceholder("*/", ASTNode.METHOD_DECLARATION),
suiteMethod, null);
}
}
//
// Remove all the nodes that need to be removed
//
for (ASTNode n : visitor.getNodesToRemove()) {
result.remove(n, null);
}
//
// Replace @Ignore with @Test(enabled = false)
//
for (Map.Entry<MethodDeclaration, Annotation> e : visitor.getIgnoredMethods().entrySet()) {
MethodDeclaration md = e.getKey();
Annotation ignored = e.getValue();
// Add the @Test(enabled = false)
NormalAnnotation test = ast.newNormalAnnotation();
test.setTypeName(ast.newName("Test"));
MemberValuePair mvp = ast.newMemberValuePair();
mvp.setName(ast.newSimpleName("enabled"));
mvp.setValue(ast.newBooleanLiteral(false));
test.values().add(mvp);
result.remove(ignored, null);
ListRewrite lr = result.getListRewrite(md, MethodDeclaration.MODIFIERS2_PROPERTY);
lr.insertFirst(test, null);
}
//
// Replace "Assert" with "AssertJUnit", unless the method is already imported statically.
//
Set<MethodInvocation> asserts = visitor.getAsserts();
for (MethodInvocation m : asserts) {
if (! staticImports.contains(m.getName().toString())) {
Expression exp = m.getExpression();
Name name = ast.newName("AssertJUnit");
if (exp != null) {
result.replace(exp, name, null);
} else {
result.set(m, MethodInvocation.EXPRESSION_PROPERTY, name, null);
}
}
}
//
// Replace "fail()" with "Assert.fail()"
//
for (MethodInvocation fail : visitor.getFails()) {
SimpleName exp = ast.newSimpleName("Assert");
result.set(fail, MethodInvocation.EXPRESSION_PROPERTY, exp, null);
}
//
// Replace @Test(expected) with @Test(expectedExceptions)
// and @Test(timeout) with @Test(timeOut)
//
for (Map.Entry<MemberValuePair, String> pair : visitor.getTestsWithExpected().entrySet()) {
result.replace(pair.getKey().getName(), ast.newSimpleName(pair.getValue()), null);
}
//
// Remove super invocation in the constructor
//
SuperConstructorInvocation sci = visitor.getSuperConstructorInvocation();
if (sci != null) {
result.remove(sci, null);
}
//
// Convert @RunWith(Parameterized.class)
//
SingleMemberAnnotation runWith = visitor.getRunWithParameterized();
if (runWith != null) {
// Remove @RunWith
result.remove(runWith, null);
// Add imports
addImport(ast, result, astRoot, "org.testng.ConversionUtils.wrapDataProvider",
true /* static import */);
addImport(ast, result, astRoot, "org.testng.annotations.Factory", false /* not static */);
// Add the factory method
MethodDeclaration parameterMethod = visitor.getParametersMethod();
ListRewrite lr = result.getListRewrite(visitor.getType(),
TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
MethodDeclaration md = ast.newMethodDeclaration();
md.setName(ast.newSimpleName("factory" + capitalize(parameterMethod.getName().toString())));
// Add the "Factory" annotation
MarkerAnnotation factory = ast.newMarkerAnnotation();
factory.setTypeName(ast.newName("Factory"));
md.modifiers().add(factory);
// Make the method public
md.modifiers().addAll(ast.newModifiers(Modifier.PUBLIC | Modifier.STATIC));
ArrayType returnType = ast.newArrayType(ast.newSimpleType(ast.newName("Object")));
md.setReturnType2(returnType);
// Create the method invocation "ConversionUtils.wrapDataProvider(Foo.class, data())"
MethodInvocation mi = ast.newMethodInvocation();
mi.setName(ast.newSimpleName("wrapDataProvider"));
// Add parameters to wrapDataProvider()
// 1) the current class
TypeLiteral tl = ast.newTypeLiteral();
tl.setType(ast.newSimpleType(ast.newSimpleName(visitor.getType().getName().toString())));
mi.arguments().add(tl);
// 2) the call to the @Parameters method
MethodInvocation pmi = ast.newMethodInvocation();
pmi.setName(ast.newSimpleName(parameterMethod.getName().getFullyQualifiedName()));
mi.arguments().add(pmi);
// Create the return statement
ReturnStatement returnStatement = ast.newReturnStatement();
returnStatement.setExpression(mi);
Block block = ast.newBlock();
block.statements().add(returnStatement);
md.setBody(block);
lr.insertFirst(md, null);
}
return result;
}
private String capitalize(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
private Map<String, Boolean> createDisabledAttribute(AST ast) {
Map<String, Boolean> result = Maps.newHashMap();
result.put("enabled", false);
return result;
}
private void maybeAddImport(AST ast, ASTRewrite rewriter, CompilationUnit astRoot, boolean add,
String imp) {
if (add) {
addImport(ast, rewriter, astRoot, imp);
}
}
private void addImport(AST ast, ASTRewrite rewriter, CompilationUnit astRoot, String imp) {
addImport(ast, rewriter, astRoot, imp, false /* non static import */);
}
private void addImport(AST ast, ASTRewrite rewriter, CompilationUnit astRoot, String imp,
boolean isStatic) {
ListRewrite lr = rewriter.getListRewrite(astRoot, CompilationUnit.IMPORTS_PROPERTY);
ImportDeclaration id = ast.newImportDeclaration();
id.setStatic(isStatic);
id.setName(ast.newName(imp));
lr.insertFirst(id, null);
}
/**
* Add the given annotation if the method is non null
*/
private void maybeAddAnnotation(AST ast, JUnitVisitor visitor, ASTRewrite rewriter,
MethodDeclaration method, String annotation, String annotationToRemove,
Map<String, Boolean> attributes)
{
if (method != null) {
addAnnotation(ast, visitor, rewriter, method, createAnnotation(ast, annotation, attributes),
annotationToRemove);
}
}
/**
* @return a NormalAnnotation if the annotation to create has attributes or a
* MarkerAnnotation otherwise.
*/
private Annotation createAnnotation(AST ast, String name, Map<String, Boolean> attributes) {
Annotation result = null;
NormalAnnotation normalAnnotation = null;
if (attributes != null && attributes.size() > 0) {
normalAnnotation = ast.newNormalAnnotation();
result = normalAnnotation;
} else {
result = ast.newMarkerAnnotation();
}
result.setTypeName(ast.newName(name));
if (attributes != null) {
for (Entry<String, Boolean> a : attributes.entrySet()) {
MemberValuePair mvp = ast.newMemberValuePair();
mvp.setName(ast.newSimpleName(a.getKey()));
mvp.setValue(ast.newBooleanLiteral(a.getValue()));
normalAnnotation.values().add(mvp);
}
}
return result;
}
/**
* Add the given annotation if the method is non null
*/
private void maybeAddAnnotations(AST ast, JUnitVisitor visitor, ASTRewrite rewriter,
Collection<MethodDeclaration> methods, String annotation, String annotationToRemove) {
maybeAddAnnotations(ast, visitor, rewriter, methods, annotation, annotationToRemove, null);
}
private void maybeAddAnnotations(AST ast, JUnitVisitor visitor,
ASTRewrite rewriter, Collection<MethodDeclaration> methods, String annotation,
String annotationToRemove, Map<String, Boolean> attributes) {
for (MethodDeclaration method : methods) {
maybeAddAnnotation(ast, visitor, rewriter, method, annotation, annotationToRemove,
attributes);
}
}
private void addAnnotation(AST ast, JUnitVisitor visitor, ASTRewrite rewriter,
MethodDeclaration md, Annotation a, String annotationToRemove)
{
ListRewrite lr = rewriter.getListRewrite(md, MethodDeclaration.MODIFIERS2_PROPERTY);
// Remove the annotation if applicable
if (annotationToRemove != null) {
List modifiers = md.modifiers();
for (int k = 0; k < modifiers.size(); k++) {
Object old = modifiers.get(k);
if (old instanceof Annotation) {
String oldAnnotation = old.toString();
if (oldAnnotation.equals(annotationToRemove) || "@Override".equals(oldAnnotation)) {
lr.remove((Annotation) old, null);
break;
}
}
}
}
// Add the annotation
lr.insertFirst(a, null);
}
public String getName() {
return "Convert to TestNG (Annotations)";
}
}