io.codemodder.codemods.CreateConstantForLiteral Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core-codemods Show documentation
Show all versions of core-codemods Show documentation
Codemods for fixing common errors across many Java projects
package io.codemodder.codemods;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.Type;
import io.codemodder.CodemodInvocationContext;
import io.codemodder.sonar.model.Issue;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A class that extends DefineConstantForLiteral and specializes in creating new constants for
* string literals in Java code.
*/
final class CreateConstantForLiteral extends DefineConstantForLiteral {
CreateConstantForLiteral(
final CodemodInvocationContext context,
final CompilationUnit cu,
final StringLiteralExpr stringLiteralExpr,
final Issue issue) {
super(context, cu, stringLiteralExpr, issue);
}
/**
* Retrieves the suggested constant name from the issue's message. The method utilizes a {@link
* ConstantNameStringGenerator} to generate a unique constant name based on various factors such
* as the string literal value, existing names in the CompilationUnit, parent node name, and
* naming conventions.
*/
@Override
protected String getConstantName() {
final List constantFieldDeclarations =
findDeclaredConstantsInClassOrInterface();
final NodeWithSimpleName> nodeWithSimpleName = findAncestorWithSimpleName(stringLiteralExpr);
final String parentNodeName =
nodeWithSimpleName != null ? nodeWithSimpleName.getNameAsString() : null;
return ConstantNameStringGenerator.generateConstantName(
stringLiteralExpr.getValue(),
getNamesInCu(),
parentNodeName,
isUsingSnakeCase(constantFieldDeclarations));
}
/** Retrieves the names of all nodes with simple names in the CompilationUnit. */
private Set getNamesInCu() {
return cu.findAll(Node.class).stream()
.filter(node -> node instanceof NodeWithSimpleName>)
.map(node -> (NodeWithSimpleName>) node)
.map(NodeWithSimpleName::getNameAsString)
.collect(Collectors.toSet());
}
/** Defines a new constant by adding a {@link FieldDeclaration} to the class or interface. */
@Override
protected void defineConstant(final String constantName) {
addConstantFieldToClass(createConstantField(constantName));
}
/**
* Adds a {@link FieldDeclaration} as the last member of the provided {@link
* ClassOrInterfaceDeclaration}. Adding last seems like it would be preferred by users and better for
* JavaParser to match the existing indentation.
*/
private void addConstantFieldToClass(final FieldDeclaration constantField) {
final NodeList> members = classOrInterfaceDeclaration.getMembers();
members.addLast(constantField);
}
/** Creates a {@link FieldDeclaration} of {@link String} type with the constant name provided */
private FieldDeclaration createConstantField(final String constantName) {
final NodeList modifiers =
NodeList.nodeList(
Modifier.privateModifier(), Modifier.staticModifier(), Modifier.finalModifier());
final Type type = new ClassOrInterfaceType(null, "String");
final VariableDeclarator variableDeclarator =
new VariableDeclarator(
type, constantName, new StringLiteralExpr(stringLiteralExpr.getValue()));
return new FieldDeclaration(modifiers, variableDeclarator);
}
/**
* Retrieves the first ancestor node that is a {@link NodeWithSimpleName} of a {@link
* StringLiteralExpr}
*/
private NodeWithSimpleName> findAncestorWithSimpleName(
final StringLiteralExpr stringLiteralExpr) {
Optional parentNodeOptional = stringLiteralExpr.getParentNode();
while (parentNodeOptional.isPresent()
&& !(parentNodeOptional.get() instanceof NodeWithSimpleName)) {
parentNodeOptional = parentNodeOptional.get().getParentNode();
}
return (NodeWithSimpleName>) parentNodeOptional.orElse(null);
}
/**
* This method takes a {@link ClassOrInterfaceDeclaration} as input, filters its members to
* include only {@link FieldDeclaration} nodes, and further filters these FieldDeclarations to
* select those that are declared as static, final, and have a type of String. The resulting list
* represents the declared constants in the given class or interface.
*/
private List findDeclaredConstantsInClassOrInterface() {
return classOrInterfaceDeclaration.getMembers().stream()
.filter(FieldDeclaration.class::isInstance)
.map(FieldDeclaration.class::cast)
.filter(
fieldDeclaration ->
fieldDeclaration.getModifiers().contains(Modifier.staticModifier())
&& fieldDeclaration.getModifiers().contains(Modifier.finalModifier())
&& containsStringType(fieldDeclaration))
.toList();
}
/**
* This method takes a list of {@link FieldDeclaration} objects representing constant fields. It
* checks if the first constant field's name contains an underscore or is entirely in uppercase,
* indicating the use of snake case naming convention. If the list is empty, the method returns
* true as there are no constant fields to assess.
*/
private boolean isUsingSnakeCase(final List constantFieldDeclarations) {
if (constantFieldDeclarations == null || constantFieldDeclarations.isEmpty()) {
return true;
}
final String constantName = constantFieldDeclarations.get(0).getVariable(0).getNameAsString();
return constantName.contains("_") || constantName.equals(constantName.toUpperCase());
}
/**
* Checks if the first constant field's name contains an underscore or is entirely in uppercase,
* indicating the use of snake case naming convention.
*/
private boolean containsStringType(final FieldDeclaration fieldDeclaration) {
for (VariableDeclarator variable : fieldDeclaration.getVariables()) {
final Type fieldType = variable.getType();
if (fieldType instanceof ClassOrInterfaceType classOrInterfaceType
&& classOrInterfaceType.getNameAsString().equals("String")) {
return true;
}
}
return false;
}
}