AbstractSimplifyPlatformStatus.java

/*******************************************************************************
 * Copyright (c) 2021 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.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.MethodInvocation;
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.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
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.HelperVisitor;
import org.sandbox.jdt.internal.common.ReferenceHolder;
import org.sandbox.jdt.internal.corext.fix.SimplifyPlatformStatusFixCore;

/**
 * @param <T> Type found in Visitor
 */
public abstract class AbstractSimplifyPlatformStatus<T extends ASTNode> {
	String methodName;
	String expectedStatusLiteral;

	public AbstractSimplifyPlatformStatus(String methodName, String expectedStatusLiteral) {
		this.methodName= methodName;
		this.expectedStatusLiteral= expectedStatusLiteral;
	}

	/**
	 * Adds an import to the class. This method should be used for every class
	 * reference added to the generated code.
	 *
	 * @param typeName  a fully qualified name of a type
	 * @param cuRewrite CompilationUnitRewrite
	 * @param ast       AST
	 * @return simple name of a class if the import was added and fully qualified
	 *         name if there was a conflict
	 */
	protected static Name addImport(String typeName, final CompilationUnitRewrite cuRewrite, AST ast) {
		String importedName= cuRewrite.getImportRewrite().addImport(typeName);
		return ast.newName(importedName);
	}

	public abstract String getPreview(boolean afterRefactoring);

	public void find(SimplifyPlatformStatusFixCore fixcore, CompilationUnit compilationUnit,
			Set<CompilationUnitRewriteOperationWithSourceRange> operations, Set<ASTNode> nodesprocessed) throws CoreException {
		try {
			ReferenceHolder<ASTNode, Object> dataholder= new ReferenceHolder<>();
			HelperVisitor.callClassInstanceCreationVisitor(Status.class, compilationUnit, dataholder, nodesprocessed, (visited, holder) -> {
				if (nodesprocessed.contains(visited) || (visited.arguments().size() != 5)) {
					return false;
				}
				/**
				 * Expected pattern: new Status(severity, pluginId, IStatus.OK, message, throwable)
				 * Where:
				 *   - severity is IStatus.INFO, IStatus.WARNING, or IStatus.ERROR
				 *   - pluginId is a String
				 *   - code is IStatus.OK (mandatory for this transformation)
				 *   - message is a String
				 *   - throwable is a Throwable or null
				 *
				 * Transforms to: Status.info(message, throwable) / Status.warning(message, throwable) / Status.error(message, throwable)
				 */
				List<Expression> arguments= visited.arguments();
				
				// Safely check if argument at index 2 (code) is IStatus.OK
				Expression codeArg= arguments.get(2);
				if (!(codeArg instanceof QualifiedName)) {
					return false;
				}
				QualifiedName codeQualifiedName= (QualifiedName) codeArg;
				if (!"IStatus.OK".equals(codeQualifiedName.toString())) { //$NON-NLS-1$
					return false;
				}
				
				// Safely check if argument at index 0 (severity) matches expected status literal
				Expression severityArg= arguments.get(0);
				if (!(severityArg instanceof QualifiedName)) {
					return false;
				}
				QualifiedName severityQualifiedName= (QualifiedName) severityArg;
				if (expectedStatusLiteral.equals(severityQualifiedName.toString())) {
					operations.add(fixcore.rewrite(visited, holder));
					nodesprocessed.add(visited);
					return false;
				}
				return true;
			});
		} catch (Exception e) {
			throw new CoreException(Status.error("Problem in find", e)); //$NON-NLS-1$
		}
	}

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

		/**
		 * Create call to Status.warning(), Status.error(), or Status.info()
		 */
		MethodInvocation staticCall= ast.newMethodInvocation();
		staticCall.setExpression(ASTNodeFactory.newName(ast, Status.class.getSimpleName()));
		staticCall.setName(ast.newSimpleName(methodName));
		
		List<ASTNode> arguments= visited.arguments();
		List<ASTNode> staticCallArguments= staticCall.arguments();
		
		// Add message argument (always at position 3 for 5-argument constructor)
		int messagePosition= 3;
		staticCallArguments.add(ASTNodes.createMoveTarget(rewrite,
				ASTNodes.getUnparenthesedExpression(arguments.get(messagePosition))));
		
		// Add throwable argument if present (at position 4) and not null
		ASTNode throwableArg= arguments.get(4);
		ASTNode codeArg= arguments.get(2);
		if (!throwableArg.toString().equals("null") && codeArg.toString().equals("IStatus.OK")) { //$NON-NLS-1$ //$NON-NLS-2$
			staticCallArguments.add(ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression(throwableArg)));
		}
		
		ASTNodes.replaceButKeepComment(rewrite, visited, staticCall, group);
		remover.registerRemovedNode(visited);
		remover.applyRemoves(importRewrite);
	}
}