MethodCallReplacer.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 java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;

/**
 * Method Call Replacer - Generates replacement code for inline sequences
 * 
 * This class creates a method invocation to replace an inline code sequence,
 * mapping the inline variables to method parameters based on the variable mapping.
 */
public class MethodCallReplacer {
	
	/**
	 * Create a method invocation that replaces an inline code sequence
	 * 
	 * @param ast The AST to use for creating new nodes
	 * @param targetMethod The method to call
	 * @param variableMapping The mapping from method parameters to inline expressions
	 * @return A method invocation node
	 */
	@SuppressWarnings("unchecked")
	public static MethodInvocation createMethodCall(AST ast, MethodDeclaration targetMethod, VariableMapping variableMapping) {
		if (ast == null || targetMethod == null || variableMapping == null) {
			return null;
		}
		
		MethodInvocation methodCall = ast.newMethodInvocation();
		
		// Set method name
		SimpleName methodName = ast.newSimpleName(targetMethod.getName().getIdentifier());
		methodCall.setName(methodName);
		
		// Create arguments based on parameter mapping
		List<Expression> arguments = createArguments(ast, targetMethod, variableMapping);
		methodCall.arguments().addAll(arguments);
		
		return methodCall;
	}
	
	/**
	 * Create the argument list for the method call
	 */
	@SuppressWarnings("unchecked")
	private static List<Expression> createArguments(AST ast, MethodDeclaration targetMethod, VariableMapping variableMapping) {
		List<Expression> arguments = new ArrayList<>();
		Map<String, String> nameMappings = variableMapping.getMappings();
		Map<String, org.eclipse.jdt.core.dom.Expression> exprMappings = variableMapping.getExpressionMappings();
		
		// For each parameter in the target method, find the corresponding inline expression
		List<SingleVariableDeclaration> parameters = targetMethod.parameters();
		for (SingleVariableDeclaration param : parameters) {
			String paramName = param.getName().getIdentifier();
			
			// First check if there's an expression mapping (for complex expressions)
			if (variableMapping.hasExpressionMapping(paramName)) {
				org.eclipse.jdt.core.dom.Expression candidateExpr = exprMappings.get(paramName);
				if (candidateExpr != null) {
					// Copy the expression from the candidate code
					Expression argExpr = (Expression) org.eclipse.jdt.core.dom.ASTNode.copySubtree(ast, candidateExpr);
					arguments.add(argExpr);
					continue;
				} else if (exprMappings != null && exprMappings.containsKey(paramName)) {
					// An expression mapping exists but the mapped expression is null - treat as mapping error
					SimpleName arg = ast.newSimpleName("/* mapping error */");
					arguments.add(arg);
					continue;
				}
			}
			
			// Fall back to simple name mapping
			String inlineName = nameMappings.get(paramName);
			if (inlineName != null) {
				// Create a simple name reference for the inline variable
				SimpleName arg = ast.newSimpleName(inlineName);
				arguments.add(arg);
			} else {
				// This shouldn't happen if matching worked correctly
				// but we handle it gracefully
				SimpleName arg = ast.newSimpleName("/* mapping error */");
				arguments.add(arg);
			}
		}
		
		return arguments;
	}
	
	/**
	 * Replace a sequence of statements with a method call using AST rewrite
	 * 
	 * @param rewrite The AST rewrite to use
	 * @param methodCall The method invocation to insert
	 * @param statementsToReplace The statements to replace
	 * @return true if replacement was successful
	 */
	public static boolean replaceWithMethodCall(ASTRewrite rewrite, MethodInvocation methodCall, List<org.eclipse.jdt.core.dom.Statement> statementsToReplace) {
		if (rewrite == null || methodCall == null || statementsToReplace == null || statementsToReplace.isEmpty()) {
			return false;
		}
		
		// Get the parent block and create a list rewrite for it
		org.eclipse.jdt.core.dom.Statement firstStatement = statementsToReplace.get(0);
		org.eclipse.jdt.core.dom.ASTNode parent = firstStatement.getParent();
		
		if (!(parent instanceof org.eclipse.jdt.core.dom.Block)) {
			return false;
		}
		
		ListRewrite listRewrite = rewrite.getListRewrite(parent, org.eclipse.jdt.core.dom.Block.STATEMENTS_PROPERTY);
		
		// Create an expression statement wrapping the method call
		AST ast = rewrite.getAST();
		org.eclipse.jdt.core.dom.ExpressionStatement expressionStatement = ast.newExpressionStatement(methodCall);
		
		// Replace first statement with the method call
		listRewrite.replace(firstStatement, expressionStatement, null);
		
		// Remove remaining statements
		for (int i = 1; i < statementsToReplace.size(); i++) {
			listRewrite.remove(statementsToReplace.get(i), null);
		}
		
		return true;
	}
	
	/**
	 * Check if a method call can be safely created for the given mapping
	 * 
	 * @param targetMethod The target method
	 * @param variableMapping The variable mapping
	 * @return true if all parameters can be mapped
	 */
	@SuppressWarnings("unchecked")
	public static boolean canCreateMethodCall(MethodDeclaration targetMethod, VariableMapping variableMapping) {
		if (targetMethod == null || variableMapping == null) {
			return false;
		}
		
		Map<String, String> mappings = variableMapping.getMappings();
		List<SingleVariableDeclaration> parameters = targetMethod.parameters();
		
		// Check that all parameters have a mapping
		for (SingleVariableDeclaration param : parameters) {
			String paramName = param.getName().getIdentifier();
			if (!mappings.containsKey(paramName)) {
				return false;
			}
		}
		
		return true;
	}
}