NamingUtils.java
/*******************************************************************************
* Copyright (c) 2025 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.util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
/**
* Utility class for naming and string operations.
* Provides methods for class name generation, type name extraction,
* and string transformations needed during JUnit migration.
*/
public final class NamingUtils {
/** Length in hexadecimal characters of the checksum used for generated nested class names to ensure uniqueness */
private static final int GENERATED_CLASS_NAME_CHECKSUM_LENGTH = 5;
private NamingUtils() {
// Utility class - prevent instantiation
}
/**
* Capitalizes the first letter of a string.
*
* @param input the string to capitalize
* @return the string with first letter capitalized, or the original string if null or empty
*/
public static String capitalizeFirstLetter(String input) {
if (input == null || input.isEmpty()) {
return input;
}
return Character.toUpperCase(input.charAt(0)) + input.substring(1);
}
/**
* Generates a unique nested class name based on the anonymous class content and field name.
* Uses a checksum of the class code to ensure uniqueness.
*
* @param anonymousClass the anonymous class declaration
* @param baseName the base name from the field
* @return a unique class name combining capitalized base name and checksum
*/
public static String generateUniqueNestedClassName(AnonymousClassDeclaration anonymousClass, String baseName) {
// Convert anonymous class to string for checksum generation to ensure unique naming
String anonymousCode = anonymousClass.toString();
String checksum = generateChecksum(anonymousCode);
// Capitalize field name for class naming convention
String capitalizedBaseName = capitalizeFirstLetter(baseName);
return capitalizedBaseName + "_" + checksum; //$NON-NLS-1$
}
/**
* Generates a short SHA-256 checksum for the given input.
*
* @param input the string to hash
* @return a 5-character hexadecimal checksum
* @throws RuntimeException if SHA-256 algorithm is not available (should never happen in standard JVM environments)
*/
public static String generateChecksum(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256"); //$NON-NLS-1$
byte[] hashBytes = md.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString().substring(0, GENERATED_CLASS_NAME_CHECKSUM_LENGTH);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 algorithm not found", e); //$NON-NLS-1$
}
}
/**
* Extracts the class name from a field declaration's initializer.
*
* @param field the field declaration to extract from
* @return the class name, or null if not found
*/
public static String extractClassNameFromField(FieldDeclaration field) {
for (Object fragmentObj : field.fragments()) {
if (fragmentObj instanceof VariableDeclarationFragment) {
VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragmentObj;
if (fragment.getInitializer() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation) {
return extractTypeName(
((org.eclipse.jdt.core.dom.ClassInstanceCreation) fragment.getInitializer()).getType());
}
}
}
return null;
}
/**
* Extracts the field name from a field declaration.
*
* @param fieldDeclaration the field declaration
* @return the field name, or "UnnamedField" if not found
*/
public static String extractFieldName(FieldDeclaration fieldDeclaration) {
return (String) fieldDeclaration.fragments().stream()
.filter(VariableDeclarationFragment.class::isInstance)
.map(fragment -> ((VariableDeclarationFragment) fragment).getName().getIdentifier())
.findFirst()
.orElse("UnnamedField"); //$NON-NLS-1$
}
/**
* Extracts the fully qualified type name from a QualifiedType AST node.
*
* @param qualifiedType the qualified type to extract from
* @return the fully qualified class name
*/
public static String extractQualifiedTypeName(QualifiedType qualifiedType) {
StringBuilder fullClassName = new StringBuilder();
Type currentType = qualifiedType;
while (currentType instanceof QualifiedType) {
QualifiedType currentQualified = (QualifiedType) currentType;
if (fullClassName.length() > 0) {
fullClassName.insert(0, "."); //$NON-NLS-1$
}
fullClassName.insert(0, currentQualified.getName().getFullyQualifiedName());
currentType = currentQualified.getQualifier();
}
return fullClassName.toString();
}
/**
* General method to extract a type's fully qualified name.
*
* @param type the type to extract from
* @return the type name, or null if not a recognized type
*/
public static String extractTypeName(Type type) {
if (type instanceof QualifiedType) {
return extractQualifiedTypeName((QualifiedType) type);
} else if (type instanceof SimpleType) {
return ((SimpleType) type).getName().getFullyQualifiedName();
}
return null;
}
}