MultiStatusSimplifyPlatformStatus.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;

import java.util.List;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperationWithSourceRange;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.refactoring.structure.ImportRemover;
import org.eclipse.text.edits.TextEditGroup;
import org.sandbox.jdt.internal.common.HelperVisitorFactory;
import org.sandbox.jdt.internal.common.ReferenceHolder;
import org.sandbox.jdt.internal.corext.fix.SimplifyPlatformStatusFixCore;

/**
 * Simplifies MultiStatus creation patterns to use IStatus.OK as the status code.
 * 
 * Transforms:
 * <pre>
 * MultiStatus status = new MultiStatus(pluginId, someCode, message, exception);
 * </pre>
 * 
 * To:
 * <pre>
 * MultiStatus status = new MultiStatus(pluginId, IStatus.OK, message, exception);
 * </pre>
 * 
 * Note: Unlike Status, MultiStatus doesn't have factory methods like error() or warning().
 * This transformation normalizes the status code to IStatus.OK, which is the recommended
 * practice when the overall status is determined by child statuses.
 */
public class MultiStatusSimplifyPlatformStatus extends AbstractSimplifyPlatformStatus<ClassInstanceCreation> {

	private static final String ISTATUS_OK = "IStatus.OK"; //$NON-NLS-1$
	private static final String ISTATUS_SIMPLE_NAME = "IStatus"; //$NON-NLS-1$
	private static final String OK_SIMPLE_NAME = "OK"; //$NON-NLS-1$

	public MultiStatusSimplifyPlatformStatus() {
		super("", ""); // MultiStatus doesn't use factory method names //$NON-NLS-1$ //$NON-NLS-2$
	}

	@Override
	public String getPreview(boolean afterRefactoring) {
		if (afterRefactoring) {
			return "MultiStatus status = new MultiStatus(pluginId, IStatus.OK, \"message\", null);\n"; //$NON-NLS-1$
		}
		return "MultiStatus status = new MultiStatus(pluginId, someCode, \"message\", null);\n"; //$NON-NLS-1$
	}

	@Override
	public void find(SimplifyPlatformStatusFixCore fixcore, CompilationUnit compilationUnit,
			Set<CompilationUnitRewriteOperationWithSourceRange> operations, Set<ASTNode> nodesprocessed) throws CoreException {
		find(fixcore, compilationUnit, operations, nodesprocessed, false);
	}

	@Override
	public void find(SimplifyPlatformStatusFixCore fixcore, CompilationUnit compilationUnit,
			Set<CompilationUnitRewriteOperationWithSourceRange> operations, Set<ASTNode> nodesprocessed,
			boolean preservePluginId) throws CoreException {
		try {
			ReferenceHolder<ASTNode, Object> dataholder= ReferenceHolder.createForNodes();
			HelperVisitorFactory.forClassInstanceCreation(MultiStatus.class)
				.in(compilationUnit)
				.excluding(nodesprocessed)
				.processEach(dataholder, (visited, holder) -> {
					if (nodesprocessed.contains(visited)) {
						return false;
					}
					
					// MultiStatus has a 4-argument constructor:
					// MultiStatus(String pluginId, int code, String message, Throwable exception)
					if (visited.arguments().size() != 4) {
						return false;
					}
					
					List<Expression> arguments= visited.arguments();
					
					// Check if argument at index 1 (code) is NOT already IStatus.OK
					Expression codeArg= arguments.get(1);
					if (codeArg instanceof QualifiedName) {
						QualifiedName codeQualifiedName= (QualifiedName) codeArg;
						if (ISTATUS_OK.equals(codeQualifiedName.toString())) {
							// Already using IStatus.OK, no transformation needed
							return false;
						}
					}
					
					// Found a MultiStatus that could be simplified to use IStatus.OK
					operations.add(fixcore.rewrite(visited, holder, preservePluginId));
					nodesprocessed.add(visited);
					return false;
				});
		} catch (Exception e) {
			throw new CoreException(Status.error("Problem in find MultiStatus", e)); //$NON-NLS-1$
		}
	}

	@Override
	public void rewrite(SimplifyPlatformStatusFixCore upp, final ClassInstanceCreation visited,
			final CompilationUnitRewrite cuRewrite, TextEditGroup group, ReferenceHolder<ASTNode, Object> holder) {
		rewrite(upp, visited, cuRewrite, group, holder, false);
	}

	@Override
	public void rewrite(SimplifyPlatformStatusFixCore upp, final ClassInstanceCreation visited,
			final CompilationUnitRewrite cuRewrite, TextEditGroup group, ReferenceHolder<ASTNode, Object> holder,
			boolean preservePluginId) {
		ASTRewrite rewrite= cuRewrite.getASTRewrite();
		AST ast= cuRewrite.getRoot().getAST();
		ImportRemover remover= cuRewrite.getImportRemover();

		// Create a new MultiStatus construction with IStatus.OK as the code parameter
		ClassInstanceCreation newMultiStatus= ast.newClassInstanceCreation();
		Name multiStatusName= addImport(MultiStatus.class.getName(), cuRewrite, ast);
		newMultiStatus.setType(ast.newSimpleType(multiStatusName));
		
		// Add import for IStatus to support IStatus.OK reference
		addImport(org.eclipse.core.runtime.IStatus.class.getName(), cuRewrite, ast);
		
		List<Expression> arguments= visited.arguments();
		List<Expression> newArguments= newMultiStatus.arguments();
		
		// Copy pluginId (argument 0)
		newArguments.add(ASTNodes.createMoveTarget(rewrite,
				ASTNodes.getUnparenthesedExpression(arguments.get(0))));
		
		// Replace code with IStatus.OK
		QualifiedName okConstant= ast.newQualifiedName(
				ast.newSimpleName(ISTATUS_SIMPLE_NAME),
				ast.newSimpleName(OK_SIMPLE_NAME));
		newArguments.add(okConstant);
		
		// Copy message (argument 2)
		newArguments.add(ASTNodes.createMoveTarget(rewrite,
				ASTNodes.getUnparenthesedExpression(arguments.get(2))));
		
		// Copy exception (argument 3)
		newArguments.add(ASTNodes.createMoveTarget(rewrite,
				ASTNodes.getUnparenthesedExpression(arguments.get(3))));
		
		ASTNodes.replaceButKeepComment(rewrite, visited, newMultiStatus, group);
		remover.registerRemovedNode(visited);
		// Note: Do NOT call remover.applyRemoves(importRewrite) here
		// The transformation still uses MultiStatus and IStatus classes,
		// so the imports should be preserved.
		// ImportRewrite will automatically manage imports without explicit applyRemoves.
	}
}