org.openrewrite.java.ReplaceStringLiteralWithConstant Maven / Gradle / Ivy
Show all versions of rewrite-java Show documentation
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.experimental.NonFinal;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.TypeUtils;
import java.lang.reflect.Field;
import java.util.Objects;
import static org.openrewrite.Validated.invalid;
@Value
@EqualsAndHashCode(callSuper = false)
public class ReplaceStringLiteralWithConstant extends Recipe {
private static final String CONSTANT_FQN_PARAM = "fullyQualifiedConstantName";
@Option(displayName = "String literal value to replace",
description = "The literal that is to be replaced. If not configured, the value of the specified constant will be used by default.",
example = "application/json",
required = false)
@Nullable
@NonFinal
String literalValue;
@Option(displayName = "Fully qualified name of the constant to use in place of String literal", example = "org.springframework.http.MediaType.APPLICATION_JSON_VALUE")
String fullyQualifiedConstantName;
public ReplaceStringLiteralWithConstant(String fullyQualifiedConstantName) {
this.literalValue = null;
this.fullyQualifiedConstantName = fullyQualifiedConstantName;
}
@JsonCreator
public ReplaceStringLiteralWithConstant(String literalValue, String fullyQualifiedConstantName) {
this.literalValue = literalValue;
this.fullyQualifiedConstantName = fullyQualifiedConstantName;
}
@Override
public String getDisplayName() {
return "Replace String literal with constant";
}
@Override
public String getDescription() {
return "Replace String literal with constant, adding import on class if needed.";
}
public @Nullable String getLiteralValue() {
if (this.literalValue == null && this.fullyQualifiedConstantName != null) {
try {
this.literalValue = (String) getConstantValueByFullyQualifiedName(this.fullyQualifiedConstantName);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
// Failed to retrieve unspecified value; field might be missing in this version of the class
return null;
}
}
return this.literalValue;
}
@Override
public Validated validate() {
Validated result = super.validate();
if (StringUtils.isBlank(fullyQualifiedConstantName)) {
return result.and(invalid(CONSTANT_FQN_PARAM, fullyQualifiedConstantName, "The constant's fully qualified name may not be empty or blank."));
}
try {
Object constantValue = getConstantValueByFullyQualifiedName(fullyQualifiedConstantName);
if (constantValue == null) {
return result.and(invalid(CONSTANT_FQN_PARAM, fullyQualifiedConstantName, "Provided constant should not be null."));
}
if (!(constantValue instanceof String)) {
// currently, we only support string literals, also see visitor implementation
return result.and(invalid(CONSTANT_FQN_PARAM, fullyQualifiedConstantName, "Unsupported type of constant provided. Only literals can be replaced."));
}
return result;
} catch (ClassNotFoundException e) {
return result.and(invalid(CONSTANT_FQN_PARAM, fullyQualifiedConstantName, "No class for specified name was found."));
} catch (NoSuchFieldException e) {
return result.and(invalid(CONSTANT_FQN_PARAM, fullyQualifiedConstantName, "No field with specified name was found."));
} catch (IllegalAccessException e) {
return result.and(invalid(CONSTANT_FQN_PARAM, fullyQualifiedConstantName, "Unable to access specified field."));
}
}
@Override
public TreeVisitor, ExecutionContext> getVisitor() {
String value = getLiteralValue();
return value == null ? TreeVisitor.noop() : new ReplaceStringLiteralVisitor(value, fullyQualifiedConstantName);
}
private static class ReplaceStringLiteralVisitor extends JavaVisitor {
private final String literalValue;
private final String owningType;
private final String template;
public ReplaceStringLiteralVisitor(String literalValue, String fullyQualifiedConstantName) {
this.literalValue = literalValue;
this.owningType = fullyQualifiedConstantName.substring(0, fullyQualifiedConstantName.lastIndexOf('.'));
this.template = fullyQualifiedConstantName.substring(owningType.lastIndexOf('.') + 1);
}
@Override
public J visitLiteral(J.Literal literal, ExecutionContext ctx) {
// Only handle String literals
if (!TypeUtils.isString(literal.getType()) ||
!Objects.equals(literalValue, literal.getValue())) {
return super.visitLiteral(literal, ctx);
}
// Prevent changing constant definition
J.ClassDeclaration classDeclaration = getCursor().firstEnclosing(J.ClassDeclaration.class);
if (classDeclaration != null &&
classDeclaration.getType() != null &&
owningType.equals(classDeclaration.getType().getFullyQualifiedName())) {
return super.visitLiteral(literal, ctx);
}
maybeAddImport(owningType, false);
return JavaTemplate.builder(template)
.contextSensitive()
.imports(owningType)
.build()
.apply(getCursor(), literal.getCoordinates().replace())
.withPrefix(literal.getPrefix());
}
}
private static @Nullable Object getConstantValueByFullyQualifiedName(String fullyQualifiedConstantName) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
String owningType = fullyQualifiedConstantName.substring(0, fullyQualifiedConstantName.lastIndexOf('.'));
String constantName = fullyQualifiedConstantName.substring(fullyQualifiedConstantName.lastIndexOf('.') + 1);
Field constantField = Class.forName(owningType).getField(constantName);
return constantField.get(null);
}
}