SignatureStructuredRewriteActions.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer and others.
*
* 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
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.cleanup.actions;
import javax.lang.model.SourceVersion;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.sandbox.jdt.triggerpattern.api.RewriteActionValue;
import org.sandbox.jdt.triggerpattern.api.StructuredRewriteAction;
/** Typed declaration-signature actions used by coordinated migrations. */
final class SignatureStructuredRewriteActions {
private SignatureStructuredRewriteActions() {
}
static void registerAll(StructuredRewriteActionRegistry registry) {
registry.register("renameDeclaration", SignatureStructuredRewriteActions::renameDeclaration); //$NON-NLS-1$
registry.register("replaceFieldType", SignatureStructuredRewriteActions::replaceFieldType); //$NON-NLS-1$
registry.register("addParameter", SignatureStructuredRewriteActions::addParameter); //$NON-NLS-1$
registry.register("removeParameter", SignatureStructuredRewriteActions::removeParameter); //$NON-NLS-1$
registry.register("replaceParameterType", SignatureStructuredRewriteActions::replaceParameterType); //$NON-NLS-1$
}
private static void renameDeclaration(StructuredRewriteAction action,
StructuredRewriteActionContext context) throws CoreException {
ASTNode target= context.resolveAuthorizedNode(action.requiredArgument("target")); //$NON-NLS-1$
String replacement= validIdentifier(
context.resolveString(action.requiredArgument("name")), context); //$NON-NLS-1$
SimpleName name= declarationName(target, context);
if (name.getIdentifier().equals(replacement)) {
throw context.failure("renameDeclaration target already has name " + replacement); //$NON-NLS-1$
}
context.cuRewrite().getASTRewrite().replace(name,
context.ast().newSimpleName(replacement), context.editGroup());
}
private static void replaceFieldType(StructuredRewriteAction action,
StructuredRewriteActionContext context) throws CoreException {
FieldDeclaration field= fieldTarget(context, action.requiredArgument("target")); //$NON-NLS-1$
if (field.fragments().size() != 1) {
throw context.failure("replaceFieldType requires a single-fragment field declaration"); //$NON-NLS-1$
}
Type replacement= context.createType(
context.resolveString(action.requiredArgument("type"))); //$NON-NLS-1$
replaceType(field.getType(), replacement, context);
}
private static void addParameter(StructuredRewriteAction action,
StructuredRewriteActionContext context) throws CoreException {
MethodDeclaration method= methodTarget(context, action.requiredArgument("target")); //$NON-NLS-1$
String name= validIdentifier(context.resolveString(action.requiredArgument("name")), context); //$NON-NLS-1$
if (findParameter(method, name) != null) {
throw context.failure("addParameter would duplicate parameter " + name); //$NON-NLS-1$
}
int index= action.arguments().containsKey("index") //$NON-NLS-1$
? context.resolveInteger(action.arguments().get("index")) : method.parameters().size(); //$NON-NLS-1$
if (index < 0 || index > method.parameters().size()) {
throw context.failure("addParameter index " + index + " is outside 0.." //$NON-NLS-1$ //$NON-NLS-2$
+ method.parameters().size());
}
SingleVariableDeclaration parameter= context.ast().newSingleVariableDeclaration();
parameter.setName(context.ast().newSimpleName(name));
parameter.setType(context.createType(
context.resolveString(action.requiredArgument("type")))); //$NON-NLS-1$
parameters(context, method).insertAt(parameter, index, context.editGroup());
}
private static void removeParameter(StructuredRewriteAction action,
StructuredRewriteActionContext context) throws CoreException {
MethodDeclaration method= methodTarget(context, action.requiredArgument("target")); //$NON-NLS-1$
SingleVariableDeclaration parameter= selectedParameter(action, method, context);
parameters(context, method).remove(parameter, context.editGroup());
context.cuRewrite().getImportRemover().registerRemovedNode(parameter);
context.cuRewrite().getImportRemover().applyRemoves(context.cuRewrite().getImportRewrite());
}
private static void replaceParameterType(StructuredRewriteAction action,
StructuredRewriteActionContext context) throws CoreException {
MethodDeclaration method= methodTarget(context, action.requiredArgument("target")); //$NON-NLS-1$
SingleVariableDeclaration parameter= selectedParameter(action, method, context);
Type replacement= context.createType(
context.resolveString(action.requiredArgument("type"))); //$NON-NLS-1$
replaceType(parameter.getType(), replacement, context);
}
private static SingleVariableDeclaration selectedParameter(StructuredRewriteAction action,
MethodDeclaration method, StructuredRewriteActionContext context) throws CoreException {
boolean hasName= action.arguments().containsKey("name"); //$NON-NLS-1$
boolean hasIndex= action.arguments().containsKey("index"); //$NON-NLS-1$
if (hasName == hasIndex) {
throw context.failure(action.name()
+ " requires exactly one parameter selector: name or index"); //$NON-NLS-1$
}
if (hasName) {
String name= context.resolveString(action.arguments().get("name")); //$NON-NLS-1$
SingleVariableDeclaration parameter= findParameter(method, name);
if (parameter == null) {
throw context.failure(action.name() + " cannot find parameter named " + name); //$NON-NLS-1$
}
return parameter;
}
int index= context.resolveInteger(action.arguments().get("index")); //$NON-NLS-1$
if (index < 0 || index >= method.parameters().size()) {
throw context.failure(action.name() + " index " + index + " is outside 0.." //$NON-NLS-1$ //$NON-NLS-2$
+ Math.max(-1, method.parameters().size() - 1));
}
return (SingleVariableDeclaration) method.parameters().get(index);
}
private static SingleVariableDeclaration findParameter(MethodDeclaration method, String name) {
for (Object parameterObject : method.parameters()) {
SingleVariableDeclaration parameter= (SingleVariableDeclaration) parameterObject;
if (name.equals(parameter.getName().getIdentifier())) {
return parameter;
}
}
return null;
}
private static SimpleName declarationName(ASTNode target,
StructuredRewriteActionContext context) throws CoreException {
if (target instanceof MethodDeclaration method) {
if (method.isConstructor()) {
throw context.failure("renameDeclaration does not rename constructors or their declaring types"); //$NON-NLS-1$
}
return method.getName();
}
if (target instanceof FieldDeclaration field && field.fragments().size() == 1) {
return ((VariableDeclarationFragment) field.fragments().get(0)).getName();
}
if (target instanceof VariableDeclarationFragment fragment
&& fragment.getParent() instanceof FieldDeclaration) {
return fragment.getName();
}
if (target instanceof SimpleName simple && simple.getParent() instanceof MethodDeclaration method
&& method.getName() == simple && !method.isConstructor()) {
return simple;
}
if (target instanceof SimpleName simple
&& simple.getParent() instanceof VariableDeclarationFragment fragment
&& fragment.getName() == simple && fragment.getParent() instanceof FieldDeclaration) {
return simple;
}
throw context.failure("renameDeclaration requires an exact planned method or single field declaration"); //$NON-NLS-1$
}
private static MethodDeclaration methodTarget(StructuredRewriteActionContext context,
RewriteActionValue value) throws CoreException {
ASTNode target= context.resolveAuthorizedNode(value);
if (target instanceof MethodDeclaration method) {
return method;
}
if (target instanceof SimpleName name && name.getParent() instanceof MethodDeclaration method
&& method.getName() == name) {
return method;
}
throw context.failure("Structured signature action target is not an exact method declaration"); //$NON-NLS-1$
}
private static FieldDeclaration fieldTarget(StructuredRewriteActionContext context,
RewriteActionValue value) throws CoreException {
ASTNode target= context.resolveAuthorizedNode(value);
if (target instanceof FieldDeclaration field) {
return field;
}
if (target instanceof VariableDeclarationFragment fragment
&& fragment.getParent() instanceof FieldDeclaration field) {
return field;
}
if (target instanceof SimpleName name && name.getParent() instanceof VariableDeclarationFragment fragment
&& fragment.getName() == name && fragment.getParent() instanceof FieldDeclaration field) {
return field;
}
throw context.failure("Structured field action target is not an exact field declaration"); //$NON-NLS-1$
}
private static ListRewrite parameters(StructuredRewriteActionContext context,
MethodDeclaration method) {
return context.cuRewrite().getASTRewrite()
.getListRewrite(method, MethodDeclaration.PARAMETERS_PROPERTY);
}
private static void replaceType(Type existing, Type replacement,
StructuredRewriteActionContext context) {
ASTRewrite rewrite= context.cuRewrite().getASTRewrite();
rewrite.replace(existing, replacement, context.editGroup());
context.cuRewrite().getImportRemover().registerRemovedNode(existing);
context.cuRewrite().getImportRemover().applyRemoves(context.cuRewrite().getImportRewrite());
}
private static String validIdentifier(String candidate,
StructuredRewriteActionContext context) throws CoreException {
if (!SourceVersion.isIdentifier(candidate) || SourceVersion.isKeyword(candidate)) {
throw context.failure("Structured action name is not a Java identifier: " + candidate); //$NON-NLS-1$
}
return candidate;
}
}