AbstractRuleFieldPlugin.java

/*******************************************************************************
 * Copyright (c) 2026 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 java.util.Set;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperationWithSourceRange;
import org.sandbox.jdt.internal.common.HelperVisitorFactory;
import org.sandbox.jdt.internal.common.ReferenceHolder;
import org.sandbox.jdt.internal.corext.fix.JUnitCleanUpFixCore;

/**
 * Abstract base class for plugins that migrate @Rule annotated fields.
 * 
 * <p>Subclasses need to implement:</p>
 * <ul>
 *   <li>{@link #getRuleType()} - The JUnit 4 rule type (e.g., "org.junit.rules.TestName")</li>
 *   <li>{@link #process2Rewrite} - The transformation logic</li>
 *   <li>{@link #getPreview(boolean)} - Preview for UI</li>
 * </ul>
 */
public abstract class AbstractRuleFieldPlugin extends AbstractTool<ReferenceHolder<Integer, JunitHolder>> {

	/**
	 * Returns the fully qualified name of the rule type to migrate.
	 * @return e.g., "org.junit.rules.TestName"
	 */
	protected abstract String getRuleType();
	
	/**
	 * Whether to also check for @ClassRule annotation.
	 * Default is false (only @Rule).
	 */
	protected boolean includeClassRule() {
		return false;
	}

	@Override
	public void find(JUnitCleanUpFixCore fixcore, CompilationUnit compilationUnit,
			Set<CompilationUnitRewriteOperationWithSourceRange> operations, Set<ASTNode> nodesprocessed) {
		ReferenceHolder<Integer, JunitHolder> dataHolder = ReferenceHolder.createIndexed();
		
		// Find @Rule fields
		HelperVisitorFactory.forField()
			.withAnnotation(ORG_JUNIT_RULE)
			.ofType(getRuleType())
			.in(compilationUnit)
			.excluding(nodesprocessed)
			.processEach(dataHolder, (visited, aholder) -> 
				processRuleField(fixcore, operations, (FieldDeclaration) visited, aholder));
		
		// Optionally find @ClassRule fields
		if (includeClassRule()) {
			HelperVisitorFactory.forField()
				.withAnnotation(ORG_JUNIT_CLASS_RULE)
				.ofType(getRuleType())
				.in(compilationUnit)
				.excluding(nodesprocessed)
				.processEach(dataHolder, (visited, aholder) -> 
					processRuleField(fixcore, operations, (FieldDeclaration) visited, aholder));
		}
	}

	/**
	 * Processes a rule field. Default implementation creates a JunitHolder and adds a rewrite operation.
	 * Subclasses can override for custom validation.
	 */
	protected boolean processRuleField(JUnitCleanUpFixCore fixcore,
			Set<CompilationUnitRewriteOperationWithSourceRange> operations, FieldDeclaration node,
			ReferenceHolder<Integer, JunitHolder> dataHolder) {
		
		VariableDeclarationFragment fragment = (VariableDeclarationFragment) node.fragments().get(0);
		if (fragment.resolveBinding() == null) {
			return true; // Continue processing
		}
		
		ITypeBinding binding = fragment.resolveBinding().getType();
		if (binding != null && getRuleType().equals(binding.getQualifiedName())) {
			JunitHolder mh = new JunitHolder();
			mh.setMinv(node);
			dataHolder.put(dataHolder.size(), mh);
			operations.add(fixcore.rewrite(dataHolder));
		}
		return true; // Continue processing
	}
}