CheckNodeForValidReferences.java
package org.sandbox.jdt.internal.corext.fix.helper;
import java.util.Iterator;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.IBinding;
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.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.internal.corext.dom.AbortSearchException;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.sandbox.ast.api.expr.ASTExpr;
import org.sandbox.ast.api.expr.MethodInvocationExpr;
import org.sandbox.ast.api.expr.SimpleNameExpr;
import org.sandbox.ast.api.jdt.FluentASTVisitor;
import org.sandbox.ast.api.jdt.JDTConverter;
class CheckNodeForValidReferences {
private static final String ITERATOR_NAME= Iterator.class.getCanonicalName();
private final ASTNode fASTNode;
private final boolean fLocalVarsOnly;
public CheckNodeForValidReferences(ASTNode node, boolean localVarsOnly) {
fASTNode= node;
fLocalVarsOnly= localVarsOnly;
}
public boolean isValid() {
FluentASTVisitor visitor= new FluentASTVisitor() {
@Override
public boolean visit(FieldAccess visitedField) {
if (visitedField.resolveFieldBinding() == null) {
throw new AbortSearchException();
}
if (fLocalVarsOnly && visitedField.getLocationInParent() == MethodInvocation.EXPRESSION_PROPERTY) {
MethodInvocation methodInvocation= ASTNodes.getParent(visitedField, MethodInvocation.class);
MethodInvocationExpr miExpr= JDTConverter.convert(methodInvocation);
if (miExpr.returnsType(ITERATOR_NAME)) {
throw new AbortSearchException();
}
}
return true;
}
@Override
public boolean visit(SuperFieldAccess visitedField) {
if (visitedField.resolveFieldBinding() == null) {
throw new AbortSearchException();
}
if (fLocalVarsOnly && visitedField.getLocationInParent() == MethodInvocation.EXPRESSION_PROPERTY) {
MethodInvocation methodInvocation= ASTNodes.getParent(visitedField, MethodInvocation.class);
MethodInvocationExpr miExpr= JDTConverter.convert(methodInvocation);
if (miExpr.returnsType(ITERATOR_NAME)) {
throw new AbortSearchException();
}
}
return true;
}
@Override
protected boolean visitMethodInvocation(MethodInvocationExpr miExpr, MethodInvocation methodInvocation) {
if (fLocalVarsOnly) {
IMethodBinding methodInvocationBinding= methodInvocation.resolveMethodBinding();
if (methodInvocationBinding == null) {
throw new AbortSearchException();
}
ITypeBinding methodTypeBinding= methodInvocationBinding.getReturnType();
if (AbstractTool.isOfType(methodTypeBinding, ITERATOR_NAME)) {
Expression exp= methodInvocation.getExpression();
if (exp instanceof SimpleName simpleName) {
SimpleNameExpr nameExpr= JDTConverter.convert(simpleName);
IBinding binding= simpleName.resolveBinding();
if (binding instanceof IVariableBinding varBinding) {
// Check using fluent API for field and parameter, but use JDT for record component
if (nameExpr.resolveVariable()
.filter(var -> !var.isField() && !var.isParameter())
.filter(var -> var.hasType(ITERATOR_NAME))
.isPresent() && !varBinding.isRecordComponent()) {
return true;
}
}
}
throw new AbortSearchException();
}
}
return true;
}
@Override
public boolean visit(CastExpression castExpression) {
Type castType= castExpression.getType();
ITypeBinding typeBinding= castType.resolveBinding();
if (AbstractTool.isOfType(typeBinding, ITERATOR_NAME)) {
Expression exp= castExpression.getExpression();
if (exp instanceof Name) {
SimpleNameExpr nameExpr= JDTConverter.convertExpression(exp)
.flatMap(ASTExpr::asSimpleName)
.orElse(null);
if (nameExpr != null && nameExpr.isVariable()) {
if (!fLocalVarsOnly) {
// For non-local mode, require field, parameter, or record component
if (nameExpr.isLocalVariable()) {
throw new AbortSearchException();
}
} else {
// For local-only mode, reject field, parameter, or record component
if (!nameExpr.isLocalVariable()) {
throw new AbortSearchException();
}
}
return true;
}
}
throw new AbortSearchException();
}
return true;
}
@Override
protected boolean visitSimpleName(SimpleNameExpr nameExpr, SimpleName simpleName) {
if (!nameExpr.isVariable()) {
return true;
}
return nameExpr.resolveVariable()
.filter(var -> var.hasType(ITERATOR_NAME))
.map(var -> {
// Check if SimpleName is used as receiver for a method invocation
if (simpleName.getLocationInParent() == MethodInvocation.EXPRESSION_PROPERTY) {
MethodInvocation methodInvocation= ASTNodes.getParent(simpleName, MethodInvocation.class);
MethodInvocationExpr miExpr= JDTConverter.convert(methodInvocation);
if (!miExpr.returnsType(ITERATOR_NAME)) {
return true;
}
}
// Check variable kind based on fLocalVarsOnly flag
if (!fLocalVarsOnly) {
// For non-local mode, require field, parameter, or record component
if (nameExpr.isLocalVariable()) {
throw new AbortSearchException();
}
} else {
// For local-only mode, reject field, parameter, or record component
if (!nameExpr.isLocalVariable()) {
throw new AbortSearchException();
}
}
return true;
})
.orElse(true);
}
};
try {
fASTNode.accept(visitor);
return true;
} catch (AbortSearchException e) {
// do nothing and fall through
}
return false;
}
}