EncodingDslRemovedCatchImportCleanup.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer and others.
*
* This program and the accompanying materials are made available under the
* Eclipse Public License 2.0: https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.sandbox.jdt.internal.corext.fix.helper;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalModelCore;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
/** Tracks catches removed by atomic encoding DSL rewrites for import cleanup. */
public final class EncodingDslRemovedCatchImportCleanup extends CompilationUnitRewriteOperation {
private static final String EXCEPTION = "UnsupportedEncodingException"; //$NON-NLS-1$
private static final Set<String> ENCODINGS = Set.of(
"UTF-8", "UTF-16", "UTF-16BE", "UTF-16LE", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
"US-ASCII", "ISO-8859-1"); //$NON-NLS-1$ //$NON-NLS-2$
private final Set<CatchClause> removedCatches;
private EncodingDslRemovedCatchImportCleanup(Set<CatchClause> removedCatches) {
this.removedCatches = removedCatches;
}
public static CompilationUnitRewriteOperation create(Set<ASTNode> processedNodes) {
Set<CatchClause> catches = Collections.newSetFromMap(new IdentityHashMap<>());
for (ASTNode node : processedNodes) {
if (containsEncoding(node)) {
CatchClause catchClause = removedCatch(node);
if (catchClause != null) {
catches.add(catchClause);
}
}
}
if (catches.isEmpty() || hasExternalTypeUse(catches.iterator().next().getRoot(), catches)) {
return null;
}
return new EncodingDslRemovedCatchImportCleanup(catches);
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore linkedModel) {
for (CatchClause catchClause : removedCatches) {
cuRewrite.getImportRemover().registerRemovedNode(catchClause);
}
}
private static boolean containsEncoding(ASTNode node) {
boolean[] found = { false };
node.accept(new ASTVisitor() {
@Override
public boolean visit(StringLiteral literal) {
found[0] = ENCODINGS.contains(literal.getLiteralValue().toUpperCase(Locale.ROOT));
return !found[0];
}
});
return found[0];
}
private static boolean hasExternalTypeUse(ASTNode root, Set<CatchClause> catches) {
boolean[] found = { false };
root.accept(new ASTVisitor() {
@Override
public boolean visit(SimpleType type) {
if (EXCEPTION.equals(type.getName().toString()) && !inside(type, catches)) {
found[0] = true;
}
return !found[0];
}
});
return found[0];
}
private static boolean inside(ASTNode node, Set<CatchClause> catches) {
for (ASTNode current = node; current != null; current = current.getParent()) {
if (catches.contains(current)) {
return true;
}
}
return false;
}
private static CatchClause removedCatch(ASTNode node) {
Statement statement = ASTNodes.getFirstAncestorOrNull(node, Statement.class);
if (statement == null || !(statement.getParent() instanceof Block body)
|| !(body.getParent() instanceof TryStatement tryStatement)
|| tryStatement.getBody() != body || !(tryStatement.getParent() instanceof Block)
|| !tryStatement.resources().isEmpty() || tryStatement.getFinally() != null) {
return null;
}
@SuppressWarnings("unchecked")
List<CatchClause> catches = tryStatement.catchClauses();
if (catches.size() != 1) {
return null;
}
CatchClause catchClause = catches.get(0);
Type type = catchClause.getException().getType();
return type instanceof SimpleType simpleType && EXCEPTION.equals(simpleType.getName().toString())
? catchClause : null;
}
}