ExternalResourceRefactorer.java
/*******************************************************************************
* Copyright (c) 2025 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.jdt.internal.corext.fix.helper.lib;
import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnitConstants.*;
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.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.text.edits.TextEditGroup;
import org.sandbox.jdt.internal.corext.util.AnnotationUtils;
import org.sandbox.jdt.internal.corext.util.ASTNavigationUtils;
import org.sandbox.jdt.internal.corext.util.NamingUtils;
/**
* Helper class for refactoring JUnit 4 ExternalResource to JUnit 5 lifecycle callbacks.
* Handles transformation of ExternalResource classes and anonymous instances.
*/
public final class ExternalResourceRefactorer {
// Private constructor to prevent instantiation
private ExternalResourceRefactorer() {
throw new UnsupportedOperationException("Utility class");
}
/**
* Modifies a class that extends ExternalResource to use JUnit 5 extensions instead.
*
* @param node the type declaration to modify
* @param field the field declaration with ExternalResource
* @param fieldStatic whether the field is static (affects callback type)
* @param rewriter the AST rewriter
* @param ast the AST instance
* @param group the text edit group
* @param importRewriter the import rewriter
*/
public static void modifyExternalResourceClass(TypeDeclaration node, FieldDeclaration field, boolean fieldStatic,
ASTRewrite rewriter, AST ast, TextEditGroup group, ImportRewrite importRewriter) {
if (!shouldProcessNode(node)) {
return;
}
CallbackConfig callbackConfig = determineCallbackConfig(fieldStatic);
if (field != null) {
processExternalResourceField(field, rewriter, ast, group, importRewriter);
}
if (isDirectlyExtendingExternalResource(node.resolveBinding())) {
refactorToImplementCallbacks(node, rewriter, ast, group, importRewriter, callbackConfig.beforeCallback,
callbackConfig.afterCallback, callbackConfig.importBeforeCallback,
callbackConfig.importAfterCallback);
}
LifecycleMethodAdapter.updateLifecycleMethodsInClass(node, rewriter, ast, group, importRewriter, METHOD_BEFORE,
METHOD_AFTER, fieldStatic ? METHOD_BEFORE_ALL : METHOD_BEFORE_EACH,
fieldStatic ? METHOD_AFTER_ALL : METHOD_AFTER_EACH);
}
/**
* Processes an ExternalResource field by removing JUnit 4 annotations and adding JUnit 5 equivalents.
*
* @param field the field to process
* @param rewriter the AST rewriter
* @param ast the AST instance
* @param group the text edit group
* @param importRewriter the import rewriter
*/
public static void processExternalResourceField(FieldDeclaration field, ASTRewrite rewriter, AST ast,
TextEditGroup group, ImportRewrite importRewriter) {
String ruleAnnotation = null;
if (AnnotationUtils.isAnnotatedWith(field, ORG_JUNIT_RULE)
&& isExternalResource(field, ORG_JUNIT_RULES_EXTERNAL_RESOURCE)) {
ruleAnnotation = ORG_JUNIT_RULE;
} else if (AnnotationUtils.isAnnotatedWith(field, ORG_JUNIT_CLASS_RULE)
&& isExternalResource(field, ORG_JUNIT_RULES_EXTERNAL_RESOURCE)) {
ruleAnnotation = ORG_JUNIT_CLASS_RULE;
}
if (ruleAnnotation != null) {
removeRuleAnnotation(field, rewriter, group, importRewriter, ruleAnnotation);
addRegisterExtensionAnnotation(field, rewriter, ast, importRewriter, group);
ITypeBinding fieldType = ((VariableDeclarationFragment) field.fragments().get(0)).resolveBinding()
.getType();
adaptExternalResourceHierarchy(fieldType, rewriter, ast, importRewriter, group);
}
}
/**
* Adapts the superclass hierarchy for types extending ExternalResource.
* Walks up the inheritance chain, transforming each type to use JUnit 5 extensions
* until reaching ExternalResource itself.
*
* @param typeBinding the type binding to start from
* @param rewrite the AST rewriter
* @param ast the AST instance
* @param importRewrite the import rewriter
* @param group the text edit group
*/
public static void adaptExternalResourceHierarchy(ITypeBinding typeBinding, ASTRewrite rewrite, AST ast,
ImportRewrite importRewrite, TextEditGroup group) {
while (typeBinding != null) {
// Stop when we reach ExternalResource itself
if (ORG_JUNIT_RULES_EXTERNAL_RESOURCE.equals(typeBinding.getQualifiedName())) {
break;
}
// Process types that extend ExternalResource
if (isExternalResource(typeBinding, ORG_JUNIT_RULES_EXTERNAL_RESOURCE)) {
TypeDeclaration typeDecl = ASTNavigationUtils.findTypeDeclarationInProject(typeBinding);
if (typeDecl != null) {
adaptTypeDeclaration(typeDecl, rewrite, ast, importRewrite, group);
}
}
typeBinding = typeBinding.getSuperclass();
}
}
/**
* Adapts a type declaration that extends ExternalResource to use JUnit 5 lifecycle callbacks.
* Removes the ExternalResource superclass and updates lifecycle methods (before/after).
*
* @param typeDecl the type declaration to adapt
* @param globalRewrite the global AST rewriter (may be different from typeDecl's AST)
* @param ast the AST instance
* @param importRewrite the import rewriter
* @param group the text edit group
*/
public static void adaptTypeDeclaration(TypeDeclaration typeDecl, ASTRewrite globalRewrite, AST ast,
ImportRewrite importRewrite, TextEditGroup group) {
// Create separate rewriters if the type declaration is in a different compilation unit
ASTRewrite rewriteToUse = getASTRewrite(typeDecl, ast, globalRewrite);
ImportRewrite importRewriteToUse = getImportRewrite(typeDecl, ast, importRewrite);
// Remove ExternalResource superclass
removeSuperclassType(typeDecl, rewriteToUse, group);
// Update lifecycle methods: before() -> beforeEach(), after() -> afterEach()
LifecycleMethodAdapter.updateLifecycleMethodsInClass(typeDecl, rewriteToUse, ast, group, importRewriteToUse,
METHOD_BEFORE, METHOD_AFTER, METHOD_BEFORE_EACH, METHOD_AFTER_EACH);
// Add required JUnit 5 callback imports
importRewriteToUse.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_BEFORE_EACH_CALLBACK);
importRewriteToUse.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_AFTER_EACH_CALLBACK);
// If we created a separate rewriter, commit the change
if (rewriteToUse != globalRewrite) {
DocumentHelper.createChangeForRewrite(ASTNavigationUtils.findCompilationUnit(typeDecl), rewriteToUse);
}
}
/**
* Refactors an anonymous ExternalResource class to implement JUnit 5 callback interfaces.
* Converts the anonymous class to a named nested class with before/after callback methods.
*
* @param anonymousClass the anonymous class declaration to refactor
* @param fieldDeclaration the field containing the anonymous class
* @param fieldStatic whether the field is static
* @param rewriter the AST rewriter
* @param ast the AST instance
* @param group the text edit group
* @param importRewriter the import rewriter
*/
public static void refactorAnonymousClassToImplementCallbacks(AnonymousClassDeclaration anonymousClass,
FieldDeclaration fieldDeclaration, boolean fieldStatic, ASTRewrite rewriter, AST ast, TextEditGroup group,
ImportRewrite importRewriter) {
if (anonymousClass == null) {
return;
}
// Access the surrounding ClassInstanceCreation
ASTNode parent = anonymousClass.getParent();
if (parent instanceof ClassInstanceCreation) {
ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation) parent;
ensureClassInstanceRewrite(classInstanceCreation, rewriter, importRewriter, group, fieldStatic);
String fieldName = NamingUtils.extractFieldName(fieldDeclaration);
String nestedClassName = NamingUtils.generateUniqueNestedClassName(anonymousClass, fieldName);
TypeDeclaration nestedClass = createNestedClassFromAnonymous(anonymousClass, nestedClassName, fieldStatic,
rewriter, ast, importRewriter, group);
replaceFieldWithExtensionDeclaration(classInstanceCreation, nestedClassName, fieldStatic, rewriter, ast,
group, importRewriter);
}
}
/**
* Creates a nested class from an anonymous ExternalResource declaration.
* Converts anonymous class lifecycle methods (before/after) to JUnit 5 callback methods
* (beforeEach/afterEach) and implements the appropriate callback interfaces.
*
* @param anonymousClass the anonymous class to convert
* @param className the name for the new nested class
* @param fieldStatic whether the field is static (affects which callbacks to implement)
* @param rewriter the AST rewriter
* @param ast the AST instance
* @param importRewriter the import rewriter
* @param group the text edit group
* @return the newly created nested class declaration
*/
public static TypeDeclaration createNestedClassFromAnonymous(AnonymousClassDeclaration anonymousClass,
String className, boolean fieldStatic, ASTRewrite rewriter, AST ast, ImportRewrite importRewriter,
TextEditGroup group) {
// Create the new TypeDeclaration
TypeDeclaration nestedClass = ast.newTypeDeclaration();
nestedClass.setName(ast.newSimpleName(className));
if (fieldStatic) {
nestedClass.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.STATIC_KEYWORD));
}
// Add JUnit 5 callback interfaces (before/after each or all depending on static)
// Use addImport to get the appropriate name (simple or qualified based on conflicts)
if (fieldStatic) {
String beforeAllName = importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_BEFORE_ALL_CALLBACK);
String afterAllName = importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_AFTER_ALL_CALLBACK);
nestedClass.superInterfaceTypes()
.add(ast.newSimpleType(ast.newName(beforeAllName)));
nestedClass.superInterfaceTypes()
.add(ast.newSimpleType(ast.newName(afterAllName)));
} else {
String beforeEachName = importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_BEFORE_EACH_CALLBACK);
String afterEachName = importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_AFTER_EACH_CALLBACK);
nestedClass.superInterfaceTypes()
.add(ast.newSimpleType(ast.newName(beforeEachName)));
nestedClass.superInterfaceTypes()
.add(ast.newSimpleType(ast.newName(afterEachName)));
}
// Transfer lifecycle methods from anonymous class to new class
ListRewrite bodyRewrite = rewriter.getListRewrite(nestedClass, TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
for (Object decl : anonymousClass.bodyDeclarations()) {
if (decl instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) decl;
// Convert before() -> beforeEach/beforeAll(ExtensionContext) and after() -> afterEach/afterAll(ExtensionContext)
if (isLifecycleMethod(method, METHOD_BEFORE)) {
String beforeMethodName = fieldStatic ? METHOD_BEFORE_ALL : METHOD_BEFORE_EACH;
MethodDeclaration beforeCallbackMethod = LifecycleMethodAdapter.createLifecycleCallbackMethod(ast,
beforeMethodName, EXTENSION_CONTEXT, method.getBody(), group);
bodyRewrite.insertLast(beforeCallbackMethod, group);
} else if (isLifecycleMethod(method, METHOD_AFTER)) {
String afterMethodName = fieldStatic ? METHOD_AFTER_ALL : METHOD_AFTER_EACH;
MethodDeclaration afterCallbackMethod = LifecycleMethodAdapter.createLifecycleCallbackMethod(ast,
afterMethodName, EXTENSION_CONTEXT, method.getBody(), group);
bodyRewrite.insertLast(afterCallbackMethod, group);
}
}
}
// Add the new class to the enclosing type
TypeDeclaration parentType = ASTNavigationUtils.findEnclosingTypeDeclaration(anonymousClass);
if (parentType != null) {
ListRewrite enclosingBodyRewrite = rewriter.getListRewrite(parentType,
TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
enclosingBodyRewrite.insertLast(nestedClass, group);
}
return nestedClass;
}
/**
* Ensures that an anonymous ExternalResource class is properly rewritten for JUnit 5.
* Removes the ExternalResource superclass and adds necessary JUnit 5 callback imports.
*
* @param classInstanceCreation the class instance creation containing the anonymous class
* @param rewriter the AST rewriter
* @param importRewriter the import rewriter
* @param group the text edit group
* @param fieldStatic true if the field is static (use BeforeAll/AfterAll), false for instance (use BeforeEach/AfterEach)
*/
public static void ensureClassInstanceRewrite(ClassInstanceCreation classInstanceCreation, ASTRewrite rewriter,
ImportRewrite importRewriter, TextEditGroup group, boolean fieldStatic) {
removeExternalResourceSuperclass(classInstanceCreation, rewriter, importRewriter, group);
// Add required JUnit 5 callback imports
if (fieldStatic) {
importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_BEFORE_ALL_CALLBACK);
importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_AFTER_ALL_CALLBACK);
} else {
importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_BEFORE_EACH_CALLBACK);
importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_AFTER_EACH_CALLBACK);
}
importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_EXTENSION_CONTEXT);
}
/**
* Removes the ExternalResource superclass from an anonymous class.
*
* @param anonymousClass the class instance creation
* @param rewrite the AST rewriter
* @param importRewriter the import rewriter
* @param group the text edit group
*/
private static void removeExternalResourceSuperclass(ClassInstanceCreation anonymousClass, ASTRewrite rewrite,
ImportRewrite importRewriter, TextEditGroup group) {
// Check if the anonymous class inherits from ExternalResource
ITypeBinding typeBinding = anonymousClass.resolveTypeBinding();
if (typeBinding.getSuperclass() != null
&& ORG_JUNIT_RULES_EXTERNAL_RESOURCE.equals(typeBinding.getSuperclass().getQualifiedName())) {
// Remove the superclass by replacing the type in the ClassInstanceCreation
Type type = anonymousClass.getType();
if (type != null) {
rewrite.replace(type, anonymousClass.getAST().newSimpleType(anonymousClass.getAST().newSimpleName("Object")),
group);
}
// Remove the import of the superclass
importRewriter.removeImport(ORG_JUNIT_RULES_EXTERNAL_RESOURCE);
}
}
/**
* Refactors a type to implement JUnit 5 callback interfaces instead of extending ExternalResource.
*
* @param node the type declaration
* @param rewriter the AST rewriter
* @param ast the AST instance
* @param group the text edit group
* @param importRewriter the import rewriter
* @param beforeCallback the before callback simple name
* @param afterCallback the after callback simple name
* @param importBeforeCallback the before callback fully qualified name
* @param importAfterCallback the after callback fully qualified name
*/
private static void refactorToImplementCallbacks(TypeDeclaration node, ASTRewrite rewriter, AST ast,
TextEditGroup group, ImportRewrite importRewriter, String beforeCallback, String afterCallback,
String importBeforeCallback, String importAfterCallback) {
if (node == null || rewriter == null || ast == null || importRewriter == null) {
return;
}
ASTRewrite rewriteToUse = getASTRewrite(node, ast, rewriter);
ImportRewrite importRewriteToUse = getImportRewrite(node, ast, importRewriter);
rewriteToUse.remove(node.getSuperclassType(), group);
importRewriteToUse.removeImport(ORG_JUNIT_RULES_EXTERNAL_RESOURCE);
ListRewrite listRewrite = rewriteToUse.getListRewrite(node, TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY);
ImportHelper.addInterfaceCallback(listRewrite, ast, beforeCallback, group, importRewriteToUse,
importBeforeCallback);
ImportHelper.addInterfaceCallback(listRewrite, ast, afterCallback, group, importRewriteToUse,
importAfterCallback);
if (rewriteToUse != rewriter) {
DocumentHelper.createChangeForRewrite(ASTNavigationUtils.findCompilationUnit(node), rewriteToUse);
}
}
/**
* Removes the superclass type from a type declaration.
* Used when converting ExternalResource subclasses to implement callback interfaces.
*
* @param typeDecl the type declaration to modify
* @param rewrite the AST rewriter
* @param group the text edit group
*/
private static void removeSuperclassType(TypeDeclaration typeDecl, ASTRewrite rewrite, TextEditGroup group) {
if (typeDecl.getSuperclassType() != null) {
rewrite.remove(typeDecl.getSuperclassType(), group);
}
}
/**
* Adds the @RegisterExtension annotation to a field.
* Resolves the field declaration from the given node and delegates to addRegisterExtensionToField.
*
* @param node the AST node (either a FieldDeclaration or a ClassInstanceCreation)
* @param rewrite the AST rewriter
* @param ast the AST instance
* @param importRewrite the import rewriter
* @param group the text edit group
*/
private static void addRegisterExtensionAnnotation(ASTNode node, ASTRewrite rewrite, AST ast,
ImportRewrite importRewrite, TextEditGroup group) {
FieldDeclaration field = resolveFieldDeclaration(node);
if (field != null) {
addRegisterExtensionToField(field, rewrite, ast, importRewrite, group);
}
}
/**
* Resolves the {@link FieldDeclaration} from the given AST node.
*
* @param node an AST node that is either a {@link FieldDeclaration} or a
* {@link ClassInstanceCreation} within a field initializer
* @return the resolved {@link FieldDeclaration}, or {@code null} if not found
*/
private static FieldDeclaration resolveFieldDeclaration(ASTNode node) {
if (node instanceof FieldDeclaration) {
return (FieldDeclaration) node;
} else if (node instanceof ClassInstanceCreation) {
return ASTNodes.getParent(node, FieldDeclaration.class);
}
return null;
}
/**
* Adds the {@code @RegisterExtension} annotation to the given field if not already present.
*/
private static void addRegisterExtensionToField(FieldDeclaration field, ASTRewrite rewrite, AST ast,
ImportRewrite importRewrite, TextEditGroup group) {
boolean hasRegisterExtension = AnnotationUtils.hasAnnotationBySimpleName(field.modifiers(),
ANNOTATION_REGISTER_EXTENSION);
ListRewrite listRewrite = rewrite.getListRewrite(field, FieldDeclaration.MODIFIERS2_PROPERTY);
boolean hasPendingRegisterExtension = listRewrite.getRewrittenList().stream()
.anyMatch(rewritten -> rewritten instanceof MarkerAnnotation && ((MarkerAnnotation) rewritten)
.getTypeName().getFullyQualifiedName().equals(ANNOTATION_REGISTER_EXTENSION));
if (!hasRegisterExtension && !hasPendingRegisterExtension) {
MarkerAnnotation registerExtensionAnnotation = ast.newMarkerAnnotation();
registerExtensionAnnotation.setTypeName(ast.newName(ANNOTATION_REGISTER_EXTENSION));
listRewrite.insertFirst(registerExtensionAnnotation, group);
importRewrite.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_REGISTER_EXTENSION);
}
}
/**
* Adds the @ExtendWith annotation to a class for JUnit 5 extension integration.
* Used when migrating JUnit 4 @Rule fields to JUnit 5 @RegisterExtension.
*
* @param rewrite the AST rewriter
* @param ast the AST instance
* @param group the text edit group
* @param importRewriter the import rewriter
* @param className the simple name of the extension class
* @param field the field that triggered the need for this annotation
*/
public static void addExtendWithAnnotation(ASTRewrite rewrite, AST ast, TextEditGroup group,
ImportRewrite importRewriter, String className, FieldDeclaration field) {
TypeDeclaration parentClass = getParentTypeDeclaration(field);
if (parentClass == null) {
return;
}
// Create @ExtendWith(ClassName.class) annotation
SingleMemberAnnotation newAnnotation = ast.newSingleMemberAnnotation();
newAnnotation.setTypeName(ast.newName(ANNOTATION_EXTEND_WITH));
TypeLiteral newTypeLiteral = ast.newTypeLiteral();
newTypeLiteral.setType(ast.newSimpleType(ast.newSimpleName(className)));
newAnnotation.setValue(newTypeLiteral);
// Add annotation to class
ListRewrite modifierListRewrite = rewrite.getListRewrite(parentClass, TypeDeclaration.MODIFIERS2_PROPERTY);
modifierListRewrite.insertFirst(newAnnotation, group);
// Add import for @ExtendWith
importRewriter.addImport(ORG_JUNIT_JUPITER_API_EXTENSION_EXTEND_WITH);
}
/**
* Removes a @Rule or @ClassRule annotation from a body declaration.
* Also removes the corresponding import statement.
*
* @param declaration the body declaration to remove the annotation from
* @param rewriter the AST rewriter
* @param group the text edit group
* @param importRewriter the import rewriter
* @param annotationclass the fully qualified annotation class name to remove
*/
private static void removeRuleAnnotation(BodyDeclaration declaration, ASTRewrite rewriter, TextEditGroup group,
ImportRewrite importRewriter, String annotationclass) {
java.util.List<?> modifiers = declaration.modifiers();
for (Object modifier : modifiers) {
if (modifier instanceof Annotation) {
Annotation annotation = (Annotation) modifier;
ITypeBinding binding = annotation.resolveTypeBinding();
if (binding != null && binding.getQualifiedName().equals(annotationclass)) {
rewriter.remove(annotation, group);
importRewriter.removeImport(annotationclass);
break;
}
}
}
}
private static void replaceFieldWithExtensionDeclaration(ClassInstanceCreation classInstanceCreation,
String nestedClassName, boolean fieldStatic, ASTRewrite rewriter, AST ast, TextEditGroup group,
ImportRewrite importRewriter) {
FieldDeclaration fieldDecl = ASTNodes.getParent(classInstanceCreation, FieldDeclaration.class);
if (fieldDecl != null) {
// Remove the @Rule annotation
removeRuleAnnotation(fieldDecl, rewriter, group, importRewriter, ORG_JUNIT_RULE);
// Add the @RegisterExtension annotation
addRegisterExtensionAnnotation(fieldDecl, rewriter, ast, importRewriter, group);
// Change the type of the FieldDeclaration
Type newType = ast.newSimpleType(ast.newName(nestedClassName));
rewriter.replace(fieldDecl.getType(), newType, group);
// Add the initialization
for (Object fragment : fieldDecl.fragments()) {
if (fragment instanceof VariableDeclarationFragment) {
VariableDeclarationFragment fragmentNode = (VariableDeclarationFragment) fragment;
ClassInstanceCreation newInstance = ast.newClassInstanceCreation();
newInstance.setType(ast.newSimpleType(ast.newName(nestedClassName)));
rewriter.replace(fragmentNode.getInitializer(), newInstance, group);
}
}
}
}
/**
* Determines the appropriate callback configuration based on whether the field is static.
*
* @param fieldStatic whether the field is static
* @return the callback configuration with callback names and import paths
*/
private static CallbackConfig determineCallbackConfig(boolean fieldStatic) {
if (fieldStatic) {
return new CallbackConfig(BEFORE_ALL_CALLBACK, AFTER_ALL_CALLBACK,
ORG_JUNIT_JUPITER_API_EXTENSION_BEFORE_ALL_CALLBACK,
ORG_JUNIT_JUPITER_API_EXTENSION_AFTER_ALL_CALLBACK);
} else {
return new CallbackConfig(BEFORE_EACH_CALLBACK, AFTER_EACH_CALLBACK,
ORG_JUNIT_JUPITER_API_EXTENSION_BEFORE_EACH_CALLBACK,
ORG_JUNIT_JUPITER_API_EXTENSION_AFTER_EACH_CALLBACK);
}
}
/**
* Configuration holder for callback names and import paths.
*/
private static class CallbackConfig {
final String beforeCallback;
final String afterCallback;
final String importBeforeCallback;
final String importAfterCallback;
CallbackConfig(String beforeCallback, String afterCallback, String importBeforeCallback,
String importAfterCallback) {
this.beforeCallback = beforeCallback;
this.afterCallback = afterCallback;
this.importBeforeCallback = importBeforeCallback;
this.importAfterCallback = importAfterCallback;
}
}
private static boolean shouldProcessNode(TypeDeclaration node) {
ITypeBinding binding = node.resolveBinding();
return binding != null && isExternalResource(binding, ORG_JUNIT_RULES_EXTERNAL_RESOURCE);
}
private static boolean isExternalResource(FieldDeclaration field, String typeToLookup) {
ITypeBinding binding = ((VariableDeclarationFragment) field.fragments().get(0)).resolveBinding().getType();
return org.sandbox.jdt.internal.corext.util.TypeCheckingUtils.isTypeOrSubtype(binding, typeToLookup);
}
private static boolean isExternalResource(ITypeBinding typeBinding, String typeToLookup) {
return org.sandbox.jdt.internal.corext.util.TypeCheckingUtils.isTypeOrSubtype(typeBinding, typeToLookup);
}
private static boolean isDirectlyExtendingExternalResource(ITypeBinding binding) {
ITypeBinding superclass = binding.getSuperclass();
return superclass != null && ORG_JUNIT_RULES_EXTERNAL_RESOURCE.equals(superclass.getQualifiedName());
}
private static boolean isLifecycleMethod(MethodDeclaration method, String methodName) {
return methodName.equals(method.getName().getIdentifier());
}
private static TypeDeclaration getParentTypeDeclaration(ASTNode node) {
return ASTNavigationUtils.getParentTypeDeclaration(node);
}
private static ASTRewrite getASTRewrite(ASTNode node, AST globalAST, ASTRewrite globalRewrite) {
return (node.getAST() == globalAST) ? globalRewrite : ASTRewrite.create(node.getAST());
}
private static ImportRewrite getImportRewrite(ASTNode node, AST globalAST, ImportRewrite globalImportRewrite) {
org.eclipse.jdt.core.dom.CompilationUnit compilationUnit = ASTNavigationUtils.findCompilationUnit(node);
return (node.getAST() == globalAST) ? globalImportRewrite : ImportRewrite.create(compilationUnit, true);
}
}