AbstractExplicitEncoding.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.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
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.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
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.ReferenceHolder;
import org.sandbox.jdt.internal.corext.fix.UseExplicitEncodingFixCore;
import org.sandbox.jdt.internal.corext.util.ImportUtils;
import org.sandbox.jdt.triggerpattern.cleanup.ExceptionCleanupHelper;
/**
* Abstract base class for encoding-related quick fixes. Provides common functionality
* for finding and rewriting code patterns that use implicit or string-based encoding
* specifications.
*
* <p>Subclasses must implement:
* <ul>
* <li>{@link #find} - to locate code patterns that need to be fixed</li>
* <li>{@link #rewrite} - to apply the encoding-related transformation</li>
* <li>{@link #getPreview} - to generate preview text for the fix</li>
* </ul>
*
* @param <T> The type of AST node that this encoding handler processes
*/
public abstract class AbstractExplicitEncoding<T extends ASTNode> {
/** Fully qualified name of java.io.UnsupportedEncodingException. */
private static final String JAVA_IO_UNSUPPORTED_ENCODING_EXCEPTION = "java.io.UnsupportedEncodingException"; //$NON-NLS-1$
/** Simple name of UnsupportedEncodingException for matching in exception types. */
private static final String UNSUPPORTED_ENCODING_EXCEPTION = "UnsupportedEncodingException"; //$NON-NLS-1$
/**
* Immutable map of standard charset names (e.g., "UTF-8") to their corresponding
* StandardCharsets constant names (e.g., "UTF_8").
* <p>
* This mapping covers the six charsets guaranteed to be available on every Java platform.
* </p>
* @since 1.3
*/
public static final Map<String, String> ENCODING_MAP = Map.of(
"UTF-8", "UTF_8", //$NON-NLS-1$ //$NON-NLS-2$
"UTF-16", "UTF_16", //$NON-NLS-1$ //$NON-NLS-2$
"UTF-16BE", "UTF_16BE", //$NON-NLS-1$ //$NON-NLS-2$
"UTF-16LE", "UTF_16LE", //$NON-NLS-1$ //$NON-NLS-2$
"ISO-8859-1", "ISO_8859_1", //$NON-NLS-1$ //$NON-NLS-2$
"US-ASCII", "US_ASCII" //$NON-NLS-1$ //$NON-NLS-2$
);
/**
* Immutable set of supported encoding names that can be converted to StandardCharsets constants.
* @since 1.3
*/
public static final Set<String> ENCODINGS = ENCODING_MAP.keySet();
/**
* Maps standard charset names (e.g., "UTF-8") to their corresponding
* StandardCharsets constant names (e.g., "UTF_8").
* @deprecated Use {@link #ENCODING_MAP} instead. This field is maintained for backward
* compatibility but is immutable and will throw UnsupportedOperationException
* if modification is attempted.
*/
@Deprecated
static final Map<String, String> encodingmap = ENCODING_MAP;
/**
* Set of supported encoding names that can be converted to StandardCharsets constants.
* @deprecated Use {@link #ENCODINGS} instead. This field is maintained for backward
* compatibility but is immutable and will throw UnsupportedOperationException
* if modification is attempted.
*/
@Deprecated
static final Set<String> encodings = ENCODINGS;
/**
* Immutable record to hold node data for encoding transformations.
* Replaces the mutable Nodedata class for better thread safety and immutability.
*
* @param replace Whether to replace an existing encoding parameter (true) or appended (false)
* @param visited The AST node that was visited and needs modification
* @param encoding The encoding constant name (e.g., "UTF_8"), or null for default charset
*/
protected static record NodeData(boolean replace, ASTNode visited, String encoding) {
}
/**
* Thread-safe map to cache charset constant references during aggregation.
* Used to avoid creating duplicate QualifiedName instances.
*/
private static final Map<String, QualifiedName> CHARSET_CONSTANTS = new ConcurrentHashMap<>();
/**
* Returns the charset constants map for use in encoding transformations.
*
* @return thread-safe map of charset constants
*/
protected static Map<String, QualifiedName> getCharsetConstants() {
return CHARSET_CONSTANTS;
}
/** Key used for storing encoding information in data holders. */
protected static final String KEY_ENCODING = "encoding"; //$NON-NLS-1$
/** Key used for storing replace flag in data holders. */
protected static final String KEY_REPLACE = "replace"; //$NON-NLS-1$
/**
* @deprecated Use {@link #KEY_ENCODING} instead. This field will be removed in a future version.
*/
@Deprecated(forRemoval = true)
protected static final String ENCODING = KEY_ENCODING;
/**
* @deprecated Use {@link #KEY_REPLACE} instead. This field will be removed in a future version.
*/
@Deprecated(forRemoval = true)
protected static final String REPLACE = KEY_REPLACE;
/**
* Finds all occurrences of the encoding pattern that this handler processes
* and adds corresponding rewrite operations.
*
* @param fixcore the fix core instance, must not be null
* @param compilationUnit the compilation unit to search in, must not be null
* @param operations the set to add rewrite operations to, must not be null
* @param nodesprocessed the set of already processed nodes (to avoid duplicates), must not be null
* @param cb the change behavior configuration, must not be null
*/
public abstract void find(UseExplicitEncodingFixCore fixcore, CompilationUnit compilationUnit, Set<CompilationUnitRewriteOperation> operations, Set<ASTNode> nodesprocessed, ChangeBehavior cb);
/**
* Rewrites the visited AST node to use explicit encoding.
*
* @param useExplicitEncodingFixCore the fix core instance, must not be null
* @param visited the AST node to rewrite, must not be null
* @param cuRewrite the compilation unit rewrite context, must not be null
* @param group the text edit group for grouping changes, must not be null
* @param cb the change behavior configuration, must not be null
* @param data the reference holder containing node-specific data, must not be null
*/
public abstract void rewrite(UseExplicitEncodingFixCore useExplicitEncodingFixCore, T visited, CompilationUnitRewrite cuRewrite,
TextEditGroup group, ChangeBehavior cb, ReferenceHolder<ASTNode, Object> data);
/**
* 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, must not be null
* @param cuRewrite CompilationUnitRewrite, must not be null
* @param ast AST, must not be null
* @return simple name of a class if the import was added and fully qualified name if there was
* a conflict; never null
*/
protected static Name addImport(String typeName, final CompilationUnitRewrite cuRewrite, AST ast) {
return ImportUtils.addImport(typeName, cuRewrite.getImportRewrite(), ast);
}
/**
* Checks if a string literal contains a known encoding that can be converted
* to a StandardCharsets constant.
*
* @param literal the string literal to check, may be null
* @return true if the literal contains a known encoding, false otherwise
*/
protected static boolean isKnownEncoding(StringLiteral literal) {
if (literal == null) {
return false;
}
return ENCODINGS.contains(literal.getLiteralValue().toUpperCase(Locale.ROOT));
}
/**
* Gets the StandardCharsets constant name for a given encoding string literal.
*
* @param literal the string literal containing the encoding name, may be null
* @return the StandardCharsets constant name (e.g., "UTF_8"), or null if the literal
* is null or contains an unknown encoding
*/
protected static String getEncodingConstantName(StringLiteral literal) {
if (literal == null) {
return null;
}
return ENCODING_MAP.get(literal.getLiteralValue().toUpperCase(Locale.ROOT));
}
/**
* Resolves the encoding value from various AST node types representing a charset argument.
* Handles string literals, variable references, qualified names (e.g., StandardCharsets.UTF_8),
* and field access expressions.
*
* @param encodingArg the AST node representing the charset argument
* @param context the method invocation context for variable resolution
* @return the uppercase encoding string (e.g., "UTF-8"), or null if not determinable
*/
protected static String getEncodingValue(ASTNode encodingArg, MethodInvocation context) {
if (encodingArg instanceof StringLiteral literal) {
return literal.getLiteralValue().toUpperCase(Locale.ROOT);
} else if (encodingArg instanceof SimpleName simpleName) {
return findVariableValue(simpleName, context);
} else if (encodingArg instanceof QualifiedName qualifiedName) {
return extractStandardCharsetName(qualifiedName);
} else if (encodingArg instanceof FieldAccess fieldAccess) {
return extractStandardCharsetName(fieldAccess);
}
return null;
}
/**
* Extracts charset name from QualifiedName like StandardCharsets.UTF_8.
*
* @param qualifiedName the qualified name to extract from
* @return the charset name (e.g., "UTF-8"), or null if not a StandardCharsets reference
*/
protected static String extractStandardCharsetName(QualifiedName qualifiedName) {
String qualifier = qualifiedName.getQualifier().toString();
if ("StandardCharsets".equals(qualifier) || qualifier.endsWith(".StandardCharsets")) { //$NON-NLS-1$ //$NON-NLS-2$
String fieldName = qualifiedName.getName().getIdentifier();
return fieldName.replace('_', '-');
}
return null;
}
/**
* Extracts charset name from FieldAccess like StandardCharsets.UTF_8.
*
* @param fieldAccess the field access to extract from
* @return the charset name (e.g., "UTF-8"), or null if not a StandardCharsets reference
*/
protected static String extractStandardCharsetName(FieldAccess fieldAccess) {
String expression = fieldAccess.getExpression().toString();
if ("StandardCharsets".equals(expression) || expression.endsWith(".StandardCharsets")) { //$NON-NLS-1$ //$NON-NLS-2$
String fieldName = fieldAccess.getName().getIdentifier();
return fieldName.replace('_', '-');
}
return null;
}
/**
* Finds the enclosing MethodDeclaration or TypeDeclaration for a given AST node.
*
* @param node the starting node for the search, may be null
* @return the enclosing MethodDeclaration or TypeDeclaration, or null if not found
*/
private static ASTNode findEnclosingMethodOrType(ASTNode node) {
if (node == null) {
return null;
}
ASTNode methodDecl = ASTNodes.getFirstAncestorOrNull(node, MethodDeclaration.class);
ASTNode typeDecl = ASTNodes.getFirstAncestorOrNull(node, TypeDeclaration.class);
// Return the closest ancestor. In Java, methods are always declared inside types,
// so if a MethodDeclaration exists, it is guaranteed to be closer than any TypeDeclaration.
// getFirstAncestorOrNull returns the nearest ancestor of each type, so we just need to
// prefer the more specific (nested) one.
if (methodDecl != null) {
return methodDecl;
}
return typeDecl;
}
/**
* Extracts the string literal value from a variable declaration fragment if its initializer
* is a string literal.
*
* @param fragment the variable declaration fragment to check, must not be null
* @param variableIdentifier the identifier of the variable to match, must not be null
* @return the uppercase string literal value if found, null otherwise
*/
private static String extractStringLiteralValue(VariableDeclarationFragment fragment, String variableIdentifier) {
if (!fragment.getName().getIdentifier().equals(variableIdentifier)) {
return null;
}
Expression initializer = fragment.getInitializer();
if (initializer instanceof StringLiteral) {
return ((StringLiteral) initializer).getLiteralValue().toUpperCase(Locale.ROOT);
}
return null;
}
/**
* Searches for a variable's string literal value within a list of variable declaration fragments.
*
* @param fragments the list of fragments to search in, must not be null
* @param variableIdentifier the identifier of the variable to find, must not be null
* @return the uppercase string literal value if found, null otherwise
*/
private static String findValueInFragments(List<?> fragments, String variableIdentifier) {
for (Object frag : fragments) {
VariableDeclarationFragment fragment = (VariableDeclarationFragment) frag;
String value = extractStringLiteralValue(fragment, variableIdentifier);
if (value != null) {
return value;
}
}
return null;
}
/**
* Searches for a variable's string literal value within method body statements.
*
* @param method the method declaration to search in, must not be null
* @param variableIdentifier the identifier of the variable to find, must not be null
* @return the uppercase string literal value if found, null otherwise
*/
private static String findVariableValueInMethod(MethodDeclaration method, String variableIdentifier) {
Block body = method.getBody();
if (body == null) {
return null;
}
List<?> statements = body.statements();
for (Object stmt : statements) {
if (stmt instanceof VariableDeclarationStatement) {
VariableDeclarationStatement varDeclStmt = (VariableDeclarationStatement) stmt;
String value = findValueInFragments(varDeclStmt.fragments(), variableIdentifier);
if (value != null) {
return value;
}
}
}
return null;
}
/**
* Searches for a variable's string literal value within type field declarations.
*
* @param type the type declaration to search in, must not be null
* @param variableIdentifier the identifier of the variable to find, must not be null
* @return the uppercase string literal value if found, null otherwise
*/
private static String findVariableValueInType(TypeDeclaration type, String variableIdentifier) {
FieldDeclaration[] fields = type.getFields();
for (FieldDeclaration field : fields) {
String value = findValueInFragments(field.fragments(), variableIdentifier);
if (value != null) {
return value;
}
}
return null;
}
/**
* Finds the value of a variable by searching for its declaration in the enclosing
* method or type. This is used to resolve variable references to their string literal
* initializer values.
*
* @param variable the SimpleName representing the variable reference, may be null
* @param context the AST node providing context for the search, may be null
* @return the uppercase string literal value of the variable's initializer,
* or null if the variable is null, context is null, or the value cannot be found
*/
protected static String findVariableValue(SimpleName variable, ASTNode context) {
if (variable == null || context == null) {
return null;
}
ASTNode enclosing = findEnclosingMethodOrType(context);
if (enclosing == null) {
return null;
}
String variableIdentifier = variable.getIdentifier();
if (enclosing instanceof MethodDeclaration) {
return findVariableValueInMethod((MethodDeclaration) enclosing, variableIdentifier);
} else if (enclosing instanceof TypeDeclaration) {
return findVariableValueInType((TypeDeclaration) enclosing, variableIdentifier);
}
return null;
}
/**
* Generates a preview string showing the code before or after the refactoring.
*
* @param afterRefactoring true to show the code after refactoring, false for before
* @param cb the change behavior configuration
* @return the preview string, never null
*/
public abstract String getPreview(boolean afterRefactoring, ChangeBehavior cb);
/**
* Pattern matching the LAST NLS comment on a line (the one corresponding
* to the last/highest-numbered string literal). When a string literal is
* replaced by a non-string expression, we must remove the last NLS comment
* (not the first) because string literals are numbered left-to-right.
* Uses a negative lookahead to match only the final NLS comment.
* Adapted from {@code org.eclipse.jdt.internal.corext.dom.ASTNodes}.
*/
private static final Pattern LAST_NLS_COMMENT = Pattern.compile("[ ]*\\/\\/\\$NON-NLS-[0-9]+\\$(?!.*\\/\\/\\$NON-NLS-)"); //$NON-NLS-1$
/**
* Replaces a string literal argument with a replacement node and removes the
* associated {@code //$NON-NLS-n$} comment.
*
* <p>When the statement is inside a try body that will be unwrapped by
* {@code removeUnsupportedEncodingException}, this method handles both the
* argument replacement AND the try-catch unwrapping in a single text-based
* operation to avoid conflicts between {@code rewrite.replace()} and
* {@code createMoveTarget()}.
*
* <p>When the statement is NOT inside such a try body, this method replaces
* the enclosing statement with a string placeholder that has the text
* substitution and NLS removal already applied.
*
* @param rewrite the AST rewrite
* @param visited the string literal node to replace
* @param replacement the replacement node (e.g., StandardCharsets.UTF_8)
* @param group the text edit group
* @param cuRewrite the compilation unit rewrite
* @return {@code true} if the enclosing try-catch was already unwrapped by
* this method (caller should skip {@code removeUnsupportedEncodingException}),
* {@code false} otherwise
*/
protected static boolean replaceArgumentAndRemoveNLS(ASTRewrite rewrite, ASTNode visited,
ASTNode replacement, TextEditGroup group, CompilationUnitRewrite cuRewrite) {
ASTNode st = ASTNodes.getFirstAncestorOrNull(visited, Statement.class, FieldDeclaration.class);
if (st != null && isInsideTryBodyWithOnlyUnsupportedEncodingCatch(st)) {
// Statement is in a try body that will be unwrapped.
// Handle BOTH the argument replacement AND the try-catch unwrapping
// in a single text-based operation to avoid conflicts between
// rewrite.replace() and createMoveTarget().
return replaceTryBodyAndUnwrap(rewrite, visited, replacement, st, group, cuRewrite);
}
if (st == null) {
rewrite.replace(visited, replacement, group);
return false;
}
// Safe to use the full statement replacement with NLS removal
// (same approach as ASTNodes.replaceAndRemoveNLS)
try {
String buffer = cuRewrite.getCu().getBuffer().getContents();
CompilationUnit cu = (CompilationUnit) st.getRoot();
int origStart = cu.getExtendedStartPosition(st);
int origLength = cu.getExtendedLength(st);
String original = buffer.substring(origStart, origStart + origLength);
// Remove last NLS comment (the one for the replaced encoding string literal)
original = LAST_NLS_COMMENT.matcher(original).replaceFirst(""); //$NON-NLS-1$
// Remove leading whitespace
original = Pattern.compile("^[ \\t]*").matcher(original).replaceAll(""); //$NON-NLS-1$ //$NON-NLS-2$
original = Pattern.compile("\n[ \\t]*").matcher(original).replaceAll("\n"); //$NON-NLS-1$ //$NON-NLS-2$
// Replace visited text with replacement text
String visitedString = buffer.substring(visited.getStartPosition(),
visited.getStartPosition() + visited.getLength());
String replacementString = replacement.toString().replaceAll(",", ", "); //$NON-NLS-1$ //$NON-NLS-2$
String modified = original.replace(visitedString, replacementString);
ASTNode placeholder = rewrite.createStringPlaceholder(modified, st.getNodeType());
rewrite.replace(st, placeholder, group);
} catch (JavaModelException e) {
// Fall back to simple replacement without NLS removal
rewrite.replace(visited, replacement, group);
}
return false;
}
/**
* Handles the case where a string literal replacement is needed inside a try body
* that will be unwrapped (single catch clause for UnsupportedEncodingException only).
*
* <p>This method creates string placeholders for each statement in the try body
* with the argument replacement already baked in, then replaces the entire try
* statement with these inlined statements. This avoids the conflict between
* {@code rewrite.replace()} on child nodes and {@code createMoveTarget()} on
* parent statements.
*
* @return {@code true} if the try-catch was successfully unwrapped
*/
private static boolean replaceTryBodyAndUnwrap(ASTRewrite rewrite, ASTNode visited,
ASTNode replacement, ASTNode statement, TextEditGroup group, CompilationUnitRewrite cuRewrite) {
Block block = (Block) statement.getParent();
org.eclipse.jdt.core.dom.TryStatement tryStatement = (org.eclipse.jdt.core.dom.TryStatement) block.getParent();
ASTNode tryParent = tryStatement.getParent();
if (!(tryParent instanceof Block parentBlock)) {
// Cannot inline statements if the try's parent is not a block
rewrite.replace(visited, replacement, group);
return false;
}
try {
String buffer = cuRewrite.getCu().getBuffer().getContents();
CompilationUnit cu = (CompilationUnit) statement.getRoot();
String visitedString = buffer.substring(visited.getStartPosition(),
visited.getStartPosition() + visited.getLength());
String replacementString = replacement.toString().replaceAll(",", ", "); //$NON-NLS-1$ //$NON-NLS-2$
// Create string placeholders for each statement in the try body
ListRewrite parentListRewrite = rewrite.getListRewrite(parentBlock, Block.STATEMENTS_PROPERTY);
List<?> tryStatements = block.statements();
for (int i = tryStatements.size() - 1; i >= 0; i--) {
ASTNode stmt = (ASTNode) tryStatements.get(i);
int stmtStart = cu.getExtendedStartPosition(stmt);
int stmtLength = cu.getExtendedLength(stmt);
String stmtSource = buffer.substring(stmtStart, stmtStart + stmtLength);
// Remove leading whitespace
stmtSource = Pattern.compile("^[ \\t]*").matcher(stmtSource).replaceAll(""); //$NON-NLS-1$ //$NON-NLS-2$
stmtSource = Pattern.compile("\n[ \\t]*").matcher(stmtSource).replaceAll("\n"); //$NON-NLS-1$ //$NON-NLS-2$
// Apply the argument replacement if this statement contains the visited node
if (stmt == statement) {
// Remove last NLS comment (the one for the replaced encoding string literal)
stmtSource = LAST_NLS_COMMENT.matcher(stmtSource).replaceFirst(""); //$NON-NLS-1$
stmtSource = stmtSource.replace(visitedString, replacementString);
}
ASTNode placeholder = rewrite.createStringPlaceholder(stmtSource, stmt.getNodeType());
parentListRewrite.insertAfter(placeholder, tryStatement, group);
}
rewrite.remove(tryStatement, group);
return true;
} catch (JavaModelException e) {
// Fall back to simple replacement without NLS removal
rewrite.replace(visited, replacement, group);
return false;
}
}
/**
* Checks whether the given statement is directly inside the body of a try statement
* that will be fully unwrapped by removeUnsupportedEncodingException. This happens when:
* <ul>
* <li>The statement is in the try body (not a catch/finally block)</li>
* <li>The try has no resources (not try-with-resources)</li>
* <li>The try has no finally block</li>
* <li>The try has exactly one catch clause catching only UnsupportedEncodingException</li>
* </ul>
* In this case, simplifyEmptyTryStatement will use createMoveTarget to move
* statements out of the try body, so we must not use statement-level replacement.
*/
private static boolean isInsideTryBodyWithOnlyUnsupportedEncodingCatch(ASTNode statement) {
ASTNode parent = statement.getParent();
if (!(parent instanceof Block block)) {
return false;
}
ASTNode grandParent = block.getParent();
if (!(grandParent instanceof org.eclipse.jdt.core.dom.TryStatement tryStatement)) {
return false;
}
// Check if the block is the try body (not a catch or finally block)
if (tryStatement.getBody() != block) {
return false;
}
// Try-with-resources won't be unwrapped even if catch is removed
if (!tryStatement.resources().isEmpty()) {
return false;
}
if (tryStatement.getFinally() != null) {
return false;
}
@SuppressWarnings("unchecked")
List<org.eclipse.jdt.core.dom.CatchClause> catchClauses = tryStatement.catchClauses();
if (catchClauses.size() != 1) {
return false;
}
org.eclipse.jdt.core.dom.CatchClause catchClause = catchClauses.get(0);
org.eclipse.jdt.core.dom.Type exType = catchClause.getException().getType();
if (exType instanceof org.eclipse.jdt.core.dom.SimpleType simpleType) {
return UNSUPPORTED_ENCODING_EXCEPTION.equals(simpleType.getName().toString());
}
// Union type (e.g., FileNotFoundException | UnsupportedEncodingException):
// After removing UnsupportedEncodingException, another catch type remains,
// so the try won't be unwrapped → no conflict with createMoveTarget.
return false;
}
/**
* Removes UnsupportedEncodingException from the enclosing method's throws clause
* or from catch clauses in a try statement. This is called after converting string-based
* encoding to StandardCharsets, since StandardCharsets methods don't throw
* UnsupportedEncodingException.
*
* <p>Delegates to {@link ExceptionCleanupHelper#removeCheckedException} for the
* actual work.
*
* @param visited the AST node that was modified, must not be null
* @param group the text edit group for tracking changes, must not be null
* @param rewrite the AST rewrite context, must not be null
* @param importRemover the import remover for tracking removed nodes, must not be null
*/
protected void removeUnsupportedEncodingException(final ASTNode visited, TextEditGroup group, ASTRewrite rewrite, ImportRemover importRemover) {
ExceptionCleanupHelper.removeCheckedException(
visited,
JAVA_IO_UNSUPPORTED_ENCODING_EXCEPTION,
UNSUPPORTED_ENCODING_EXCEPTION,
group, rewrite, importRemover);
}
}