AbstractSimplifyPlatformStatus.java
/*******************************************************************************
* Copyright (c) 2021, 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;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
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.ExpressionStatement;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
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.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;
import org.sandbox.jdt.internal.corext.util.ImportUtils;
/**
* Shared implementation for semantics-preserving simplification of
* {@link Status} constructor calls.
*
* <p>A factory is preferred when the explicit identity is a side-effect-free
* value that is provably equal to the identity inferred for the calling class,
* and the factory return type fits the surrounding source context. Otherwise
* only a compile-time {@link IStatus#OK} code is removed and every explicit
* constructor value is retained.</p>
*/
public abstract class AbstractSimplifyPlatformStatus {
private static final String BUNDLE_SYMBOLIC_NAME= "Bundle-SymbolicName"; //$NON-NLS-1$
private static final String FRAGMENT_HOST= "Fragment-Host"; //$NON-NLS-1$
private static final String INFO_FACTORY_METHOD= "info"; //$NON-NLS-1$
private final int expectedSeverity;
private final String factoryMethodName;
protected AbstractSimplifyPlatformStatus(int expectedSeverity) {
this(expectedSeverity, null);
}
protected AbstractSimplifyPlatformStatus(int expectedSeverity, String factoryMethodName) {
this.expectedSeverity= expectedSeverity;
this.factoryMethodName= factoryMethodName;
}
/** Adds an import and returns a usable name for a generated type reference. */
protected static Name addImport(String typeName, final CompilationUnitRewrite cuRewrite, AST ast) {
return ImportUtils.addImport(typeName, cuRewrite.getImportRewrite(), ast);
}
/** Returns the compile-time integer value of an expression, or {@code null}. */
protected static Integer constantIntValue(Expression expression) {
Object value= expression.resolveConstantExpressionValue();
return value instanceof Integer integer ? integer : null;
}
/** Tests an expression by compile-time value instead of source spelling. */
protected static boolean hasConstantIntValue(Expression expression, int expected) {
Integer value= constantIntValue(expression);
return value != null && value.intValue() == expected;
}
public abstract String getPreview(boolean afterRefactoring);
/** Finds exact {@link Status} constructors whose code is provably OK. */
public void find(SimplifyPlatformStatusFixCore fixcore, CompilationUnit compilationUnit,
Set<CompilationUnitRewriteOperationWithSourceRange> operations, Set<ASTNode> nodesProcessed)
throws CoreException {
try {
String bundleId= runtimeBundleSymbolicName(compilationUnit);
ReferenceHolder<ASTNode, Object> holder= ReferenceHolder.createForNodes();
HelperVisitorFactory.forClassInstanceCreation(Status.class)
.in(compilationUnit)
.excluding(nodesProcessed)
.processEach(holder, (visited, data) -> {
if (nodesProcessed.contains(visited) || visited.arguments().size() != 5) {
return false;
}
ITypeBinding typeBinding= visited.resolveTypeBinding();
if (typeBinding == null
|| !Status.class.getName().equals(typeBinding.getErasure().getQualifiedName())) {
return false;
}
List<Expression> arguments= visited.arguments();
if (!hasConstantIntValue(arguments.get(0), expectedSeverity)
|| !hasConstantIntValue(arguments.get(2), IStatus.OK)
|| !StatusRewriteSupport.canDiscard(arguments.get(2))) {
return false;
}
Integer factoryArgumentCount= factoryArgumentCount(visited, arguments, bundleId);
if (factoryArgumentCount == null && !StatusRewriteSupport.hasPreservingConstructor(visited)) {
return false;
}
operations.add(fixcore.rewrite(visited, data, factoryArgumentCount));
nodesProcessed.add(visited);
return false;
});
} catch (Exception exception) {
throw new CoreException(Status.error("Problem while finding Status simplifications", exception)); //$NON-NLS-1$
}
}
private Integer factoryArgumentCount(ClassInstanceCreation visited, List<Expression> arguments,
String bundleId) {
if (factoryMethodName == null || bundleId == null || !StatusRewriteSupport.canUseFactory(visited)
|| !hasEquivalentIdentity(arguments.get(1), visited, bundleId)
|| !StatusRewriteSupport.canDiscard(arguments.get(0))
|| !StatusRewriteSupport.canDiscard(arguments.get(1))) {
return null;
}
boolean nullThrowable= ASTNodes.getUnparenthesedExpression(arguments.get(4)) instanceof NullLiteral;
if (nullThrowable && hasCompatibleFactory(visited, 1)) {
return Integer.valueOf(1);
}
return hasCompatibleFactory(visited, 2) ? Integer.valueOf(2) : null;
}
private boolean hasCompatibleFactory(ClassInstanceCreation visited, int argumentCount) {
if (argumentCount == 2 && INFO_FACTORY_METHOD.equals(factoryMethodName)) {
return false;
}
ITypeBinding statusType= visited.resolveTypeBinding();
if (statusType == null) {
return false;
}
ITypeBinding targetType= targetType(visited);
for (IMethodBinding method : statusType.getErasure().getDeclaredMethods()) {
ITypeBinding[] parameters= method.getParameterTypes();
if (!factoryMethodName.equals(method.getName()) || !Modifier.isStatic(method.getModifiers())
|| parameters.length != argumentCount
|| !String.class.getName().equals(parameters[0].getErasure().getQualifiedName())
|| argumentCount == 2
&& !Throwable.class.getName().equals(parameters[1].getErasure().getQualifiedName())) {
continue;
}
ITypeBinding returnType= method.getReturnType();
if (Status.class.getName().equals(returnType.getErasure().getQualifiedName())
|| targetType != null && returnType.isAssignmentCompatible(targetType)
|| targetType == null && isExpressionStatement(visited)) {
return true;
}
}
return false;
}
private static ITypeBinding targetType(Expression expression) {
ASTNode current= expression;
ASTNode parent= current.getParent();
while (parent instanceof ParenthesizedExpression) {
current= parent;
parent= parent.getParent();
}
if (parent instanceof VariableDeclarationFragment fragment && fragment.getInitializer() == current) {
IVariableBinding variableBinding= fragment.resolveBinding();
if (variableBinding != null) {
return variableBinding.getType();
}
}
return ASTNodes.getTargetType((Expression) current);
}
private static boolean hasEquivalentIdentity(Expression identity, ClassInstanceCreation visited,
String bundleId) {
Expression expression= ASTNodes.getUnparenthesedExpression(identity);
Object constantValue= expression.resolveConstantExpressionValue();
if (constantValue instanceof String stringValue) {
return bundleId != null && bundleId.equals(stringValue);
}
if (!(expression instanceof TypeLiteral typeLiteral)) {
return false;
}
ITypeBinding explicitType= typeLiteral.getType().resolveBinding();
ITypeBinding callerType= enclosingTypeBinding(visited);
return explicitType != null && callerType != null
&& explicitType.getErasure().isEqualTo(callerType.getErasure());
}
private static ITypeBinding enclosingTypeBinding(ASTNode node) {
for (ASTNode current= node.getParent(); current != null; current= current.getParent()) {
if (current instanceof AnonymousClassDeclaration anonymousClass) {
return anonymousClass.resolveBinding();
}
if (current instanceof AbstractTypeDeclaration typeDeclaration) {
return typeDeclaration.resolveBinding();
}
}
return null;
}
private static boolean isExpressionStatement(Expression expression) {
ASTNode parent= expression.getParent();
while (parent instanceof ParenthesizedExpression) {
parent= parent.getParent();
}
return parent instanceof ExpressionStatement;
}
private static String runtimeBundleSymbolicName(CompilationUnit compilationUnit) {
IJavaElement javaElement= compilationUnit.getJavaElement();
if (javaElement == null) {
return null;
}
IFile manifestFile= javaElement.getJavaProject().getProject().getFile("META-INF/MANIFEST.MF"); //$NON-NLS-1$
if (!manifestFile.isAccessible()) {
return null;
}
try (InputStream contents= manifestFile.getContents()) {
Attributes attributes= new Manifest(contents).getMainAttributes();
String fragmentHost= attributes.getValue(FRAGMENT_HOST);
String runtimeIdentity= fragmentHost != null ? fragmentHost : attributes.getValue(BUNDLE_SYMBOLIC_NAME);
if (runtimeIdentity == null) {
return null;
}
int directiveStart= runtimeIdentity.indexOf(';');
return (directiveStart < 0 ? runtimeIdentity : runtimeIdentity.substring(0, directiveStart)).trim();
} catch (CoreException | IOException exception) {
return null;
}
}
/** Removes only the redundant OK code and retains every other argument. */
public void rewrite(SimplifyPlatformStatusFixCore cleanup, final ClassInstanceCreation visited,
final CompilationUnitRewrite cuRewrite, TextEditGroup group,
ReferenceHolder<ASTNode, Object> holder) {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
AST ast= cuRewrite.getRoot().getAST();
ClassInstanceCreation simplifiedStatus= ast.newClassInstanceCreation();
Name statusName= addImport(Status.class.getName(), cuRewrite, ast);
simplifiedStatus.setType(ast.newSimpleType(statusName));
List<Expression> originalArguments= visited.arguments();
List<Expression> simplifiedArguments= simplifiedStatus.arguments();
simplifiedArguments.add(ASTNodes.createMoveTarget(rewrite,
ASTNodes.getUnparenthesedExpression(originalArguments.get(0))));
simplifiedArguments.add(ASTNodes.createMoveTarget(rewrite,
ASTNodes.getUnparenthesedExpression(originalArguments.get(1))));
simplifiedArguments.add(ASTNodes.createMoveTarget(rewrite,
ASTNodes.getUnparenthesedExpression(originalArguments.get(3))));
simplifiedArguments.add(ASTNodes.createMoveTarget(rewrite,
ASTNodes.getUnparenthesedExpression(originalArguments.get(4))));
ASTNodes.replaceButKeepComment(rewrite, visited, simplifiedStatus, group);
StatusRewriteSupport.finish(cuRewrite, visited,
List.of(originalArguments.get(0), originalArguments.get(1), originalArguments.get(3), originalArguments.get(4)),
List.of(originalArguments.get(2)), group, Status.class.getName());
}
/** Uses a factory after identity and target-type equivalence have been proven. */
public void rewrite(SimplifyPlatformStatusFixCore cleanup, final ClassInstanceCreation visited,
final CompilationUnitRewrite cuRewrite, TextEditGroup group,
ReferenceHolder<ASTNode, Object> holder, int factoryArgumentCount) {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
AST ast= cuRewrite.getRoot().getAST();
MethodInvocation factoryCall= ast.newMethodInvocation();
factoryCall.setExpression(addImport(Status.class.getName(), cuRewrite, ast));
factoryCall.setName(ast.newSimpleName(factoryMethodName));
List<Expression> originalArguments= visited.arguments();
List<Expression> factoryArguments= factoryCall.arguments();
factoryArguments.add(ASTNodes.createMoveTarget(rewrite,
ASTNodes.getUnparenthesedExpression(originalArguments.get(3))));
if (factoryArgumentCount == 2) {
factoryArguments.add(ASTNodes.createMoveTarget(rewrite,
ASTNodes.getUnparenthesedExpression(originalArguments.get(4))));
}
StatusRewriteSupport.replaceWithFactory(cuRewrite, visited, factoryCall, group);
StatusRewriteSupport.finish(cuRewrite, visited,
factoryArgumentCount == 2 ? List.of(originalArguments.get(3), originalArguments.get(4))
: List.of(originalArguments.get(3)),
List.of(originalArguments.get(0), originalArguments.get(1), originalArguments.get(2)),
group, Status.class.getName());
}
}