Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 146 additions & 67 deletions src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import lombok.NonNull;
import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavadocVisitor;
import org.openrewrite.java.migrate.joda.templates.AllTemplates;
import org.openrewrite.java.migrate.joda.templates.MethodTemplate;
Expand All @@ -30,18 +29,14 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.*;

class JodaTimeVisitor extends ScopeAwareVisitor {

private final boolean safeMigration;
private final JodaTimeRecipe.Accumulator acc;

public JodaTimeVisitor(JodaTimeRecipe.Accumulator acc, boolean safeMigration, LinkedList<VariablesInScope> scopes) {
public JodaTimeVisitor(JodaTimeRecipe.Accumulator acc, boolean b, LinkedList<VariablesInScope> scopes) {
super(scopes);
this.acc = acc;
this.safeMigration = safeMigration;
}

@Override
Expand All @@ -65,10 +60,25 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
maybeRemoveImport(JODA_DATE_TIME_ZONE);
maybeRemoveImport(JODA_TIME_FORMAT);
maybeRemoveImport(JODA_DURATION);
maybeRemoveImport(JODA_PERIOD);
maybeRemoveImport(JODA_ABSTRACT_INSTANT);
maybeRemoveImport(JODA_INSTANT);
maybeRemoveImport(JODA_INTERVAL);
maybeRemoveImport("java.util.Locale");
maybeRemoveImport(JODA_TIME_FORMATTER);
maybeRemoveImport(JAVA_UTIL_LOCALE);
maybeRemoveImport(JODA_LOCAL_DATE_TIME);
maybeRemoveImport(JODA_LOCAL_DATE);
maybeRemoveImport(JODA_LOCAL_TIME);
maybeRemoveImport(JODA_SECONDS);
maybeRemoveImport(JODA_MINUTES);
maybeRemoveImport(JODA_HOURS);
maybeRemoveImport(JODA_DAYS);
maybeRemoveImport(JODA_WEEKS);
maybeRemoveImport(JODA_MONTHS);
maybeRemoveImport(JODA_YEARS);
maybeRemoveImport(JODA_DATE_TIME_UTILS);
maybeRemoveImport(JODA_DATE_MIDNIGHT);
maybeRemoveImport(JODA_GEORGIAN_CHRONOLOGY);

maybeAddImport(JAVA_DATE_TIME);
maybeAddImport(JAVA_ZONE_OFFSET);
Expand All @@ -77,13 +87,22 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
maybeAddImport(JAVA_TIME_FORMATTER);
maybeAddImport(JAVA_TIME_FORMAT_STYLE);
maybeAddImport(JAVA_DURATION);
maybeAddImport(JAVA_PERIOD);
maybeAddImport(JAVA_LOCAL_DATE);
maybeAddImport(JAVA_LOCAL_DATE_TIME);
maybeAddImport(JAVA_LOCAL_TIME);
maybeAddImport(JAVA_TEMPORAL_ISO_FIELDS);
maybeAddImport(JAVA_CHRONO_FIELD);
maybeAddImport(JAVA_CHRONO_UNIT);
maybeAddImport(JAVA_UTIL_DATE);
maybeAddImport(JAVA_ISA_CHRONOLOGY);
maybeAddImport(THREE_TEN_EXTRA_INTERVAL);
maybeAddImport(THREE_TEN_EXTRA_DAYS);
maybeAddImport(THREE_TEN_EXTRA_WEEKS);
maybeAddImport(THREE_TEN_EXTRA_MONTHS);
maybeAddImport(THREE_TEN_EXTRA_YEARS);
}

return j;
}

Expand All @@ -93,43 +112,64 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
if (m.getReturnTypeExpression() == null || !m.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return m;
}
if (safeMigration && !acc.getSafeMethodMap().getOrDefault(m.getMethodType(), false)) {
return m;
}

JavaType.Class returnType = TimeClassMap.getJavaTimeType(((JavaType.Class) m.getType()).getFullyQualifiedName());
J.Identifier returnExpr = TypeTree.build(returnType.getClassName()).withType(returnType).withPrefix(Space.format(" "));
return m.withReturnTypeExpression(returnExpr)
.withMethodType(m.getMethodType().withReturnType(returnType));

JavaType.Method methodType = m.getMethodType()
.withReturnType(returnType)
.withParameterTypes(m.getMethodType().getParameterTypes().stream().map(p -> visitTypeParameter(p, ctx)).collect(Collectors.toList()));

return m
.withReturnTypeExpression(returnExpr)
.withMethodType(methodType);
}
JavaType visitTypeParameter(JavaType parameter, @NonNull ExecutionContext ctx){
if (parameter instanceof JavaType.Class) {return visitClassTypeParameter((JavaType.Class)parameter, ctx);}
if (parameter instanceof JavaType.Array) {return ((JavaType.Array)parameter).withElemType(visitClassTypeParameter((JavaType.Class)((JavaType.Array) parameter).getElemType(), ctx));}
return parameter;
}
JavaType visitClassTypeParameter(JavaType.Class classParameter, @NonNull ExecutionContext ctx) {
if (!classParameter.isAssignableFrom(JODA_CLASS_PATTERN)) {
return classParameter;
}
return TimeClassMap.getJavaTimeType(classParameter.getFullyQualifiedName());
}

@Override
public @NonNull J visitVariableDeclarations(@NonNull J.VariableDeclarations multiVariable, @NonNull ExecutionContext ctx) {
if (multiVariable.getTypeExpression() == null || !multiVariable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return super.visitVariableDeclarations(multiVariable, ctx);
J.VariableDeclarations mv = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, ctx);

if (!mv.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return mv;
}
if (multiVariable.getVariables().stream().anyMatch(acc.getUnsafeVars()::contains)) {
return multiVariable;

String fullyQualifiedName = ((JavaType.Class) mv.getType()).getFullyQualifiedName();
JavaType.Class javaTimeType = TimeClassMap.getJavaTimeType(fullyQualifiedName);
String javaTimeShortName = TimeClassMap.getJavaTimeShortName(fullyQualifiedName);

J.Identifier typeExpression = (J.Identifier) mv.getTypeExpression();

if(javaTimeShortName != null){
typeExpression = typeExpression.withSimpleName(javaTimeShortName);
}
J.VariableDeclarations m = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, ctx);
return VarTemplates.getTemplate(multiVariable).<J>map(t -> t.apply(
updateCursor(m),
m.getCoordinates().replace(),
VarTemplates.getTemplateArgs(m))).orElse(multiVariable);

return autoFormat(mv.withTypeExpression(typeExpression.withType(javaTimeType)), ctx);
}

@Override
public @NonNull J visitVariable(@NonNull J.VariableDeclarations.NamedVariable variable, @NonNull ExecutionContext ctx) {
if (!variable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return super.visitVariable(variable, ctx);
J.VariableDeclarations.NamedVariable v = (J.VariableDeclarations.NamedVariable) super.visitVariable(variable, ctx);
if (v.getType() instanceof JavaType.Array && ((JavaType.Array)v.getType()).getElemType().isAssignableFrom(JODA_CLASS_PATTERN)) {
JavaType.Array type = (JavaType.Array) v.getType();
JavaType.Class elemType = (JavaType.Class) type.getElemType();
return v.withType(type.withElemType(TimeClassMap.getJavaTimeType(elemType.getFullyQualifiedName())));
}
if (acc.getUnsafeVars().contains(variable) || !(variable.getType() instanceof JavaType.Class)) {
return variable;
if (v.getType() instanceof JavaType.Class && v.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return v.withType(TimeClassMap.getJavaTimeType(((JavaType.Class) variable.getType()).getFullyQualifiedName()));
}
JavaType.Class jodaType = (JavaType.Class) variable.getType();
return variable
.withType(TimeClassMap.getJavaTimeType(jodaType.getFullyQualifiedName()))
.withInitializer((Expression) visit(variable.getInitializer(), ctx));

return v;
}

@Override
Expand All @@ -143,7 +183,7 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
}
J.Identifier varName = (J.Identifier) a.getVariable();
Optional<NamedVariable> mayBeVar = findVarInScope(varName.getSimpleName());
if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) {
if (!mayBeVar.isPresent()) {
return assignment;
}
return VarTemplates.getTemplate(assignment).<J>map(t -> t.apply(
Expand Down Expand Up @@ -183,34 +223,50 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
@Override
public @NonNull J visitFieldAccess(@NonNull J.FieldAccess fieldAccess, @NonNull ExecutionContext ctx) {
J.FieldAccess f = (J.FieldAccess) super.visitFieldAccess(fieldAccess, ctx);
if (TypeUtils.isOfClassType(f.getType(), JODA_DATE_TIME_ZONE) && "UTC".equals(f.getSimpleName())) {
return JavaTemplate.builder("ZoneOffset.UTC")
.imports(JAVA_ZONE_OFFSET)
.build()
.apply(updateCursor(f), f.getCoordinates().replace());
if (!isJodaVarRef(fieldAccess)) {
return f;
}
return f;

JavaType.Class fieldType = (JavaType.Class) f.getType();
JavaType.Class javaTimeType = TimeClassMap.getJavaTimeType(fieldType.getFullyQualifiedName());

return f.withType(javaTimeType);
}

@Override
public @NonNull J visitIdentifier(@NonNull J.Identifier ident, @NonNull ExecutionContext ctx) {
if (!isJodaVarRef(ident)) {
return super.visitIdentifier(ident, ctx);
}
if (this.safeMigration) {
Optional<NamedVariable> mayBeVar = findVarInScope(ident.getSimpleName());
if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) {
return ident;
}
}
J.Identifier i = (J.Identifier) super.visitIdentifier(ident, ctx);
//i.getType().isAssignableFrom(JODA_CLASS_PATTERN)
if (!isJodaVarRef(i)) {
return i;
}
JavaType type = i.getType();
if(type instanceof JavaType.Array) {return visitArrayIdentifier(i, ctx);}
if(type instanceof JavaType.Class) {return visitClassIdentifier(i, ctx);}

return i;
}
private @NonNull J visitArrayIdentifier(J.Identifier arrayIdentifier, @NonNull ExecutionContext ctx) {
JavaType.Array at = (JavaType.Array)arrayIdentifier.getType();
JavaType.Array javaTimeType = at.withElemType(TimeClassMap.getJavaTimeType(((JavaType.Class)at.getElemType()).getFullyQualifiedName()));

return arrayIdentifier.withType(javaTimeType)
.withFieldType(arrayIdentifier.getFieldType().withType(javaTimeType));
}
private @NonNull J visitClassIdentifier(J.Identifier classIdentifier, @NonNull ExecutionContext ctx) {
JavaType.Class javaTimeType = TimeClassMap.getJavaTimeType(((JavaType.Class)classIdentifier.getType()).getFullyQualifiedName());

return classIdentifier.withType(javaTimeType)
.withFieldType(classIdentifier.getFieldType().withType(javaTimeType));
}

JavaType.FullyQualified jodaType = ((JavaType.Class) ident.getType());
JavaType.FullyQualified fqType = TimeClassMap.getJavaTimeType(jodaType.getFullyQualifiedName());
if (fqType == null) {
return ident;
@Override
public J visitArrayAccess(J.ArrayAccess arrayAccess, @NonNull ExecutionContext ctx){
J.ArrayAccess a = (J.ArrayAccess) super.visitArrayAccess(arrayAccess, ctx);
if (!a.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return a;
}
return ident.withType(fqType)
.withFieldType(ident.getFieldType().withType(fqType));
return a.withType(TimeClassMap.getJavaTimeType(((JavaType.Class)a.getType()).getFullyQualifiedName()));
}

private J migrateMethodCall(MethodCall original, MethodCall updated) {
Expand All @@ -219,38 +275,50 @@ private J migrateMethodCall(MethodCall original, MethodCall updated) {
}
MethodTemplate template = AllTemplates.getTemplate(original);
if (template == null) {
System.out.println("Joda usage is found but mapping is missing: " + original);
return original; // unhandled case
}
if (template.getTemplate().getCode().equals(JODA_MULTIPLE_MAPPING_POSSIBLE)) {
System.out.println(JODA_MULTIPLE_MAPPING_POSSIBLE + ": " + original);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to report anything via stdout.

If anything, for such a cases we place some Marker and attach to the source LST element. I am not fully if that's necessary in this specific case, maybe just refrain from making any change? I am not sure.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to report on missing mapping. It was extremely useful for me during development. And can be used for reporting later.

return original; // usage with no automated mapping
}
if (template.getTemplate().getCode().equals(JODA_NO_AUTOMATIC_MAPPING_POSSIBLE)) {
System.out.println(JODA_NO_AUTOMATIC_MAPPING_POSSIBLE + ": " + original);
return original; // usage with no automated mapping
}
Optional<J> maybeUpdated = applyTemplate(original, updated, template);
if (!maybeUpdated.isPresent()) {
System.out.println("Can not apply template: " + template + " to " + original);
return original; // unhandled case
}
Expression updatedExpr = (Expression) maybeUpdated.get();
if (!safeMigration || !isArgument(original)) {
if (!isArgument(original)) {
return updatedExpr;
}
// this expression is an argument to a method call
MethodCall parentMethod = getCursor().getParentTreeCursor().getValue();
if (parentMethod.getMethodType().getDeclaringType().isAssignableFrom(JODA_CLASS_PATTERN)) {
JavaType.Method parentMethodType = parentMethod.getMethodType();
if (parentMethodType.getDeclaringType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return updatedExpr;
}
int argPos = parentMethod.getArguments().indexOf(original);
JavaType paramType = parentMethod.getMethodType().getParameterTypes().get(argPos);
if (TypeUtils.isAssignableTo(paramType, updatedExpr.getType())) {
return updatedExpr;
}
String paramName = parentMethod.getMethodType().getParameterNames().get(argPos);
NamedVariable var = acc.getVarTable().getVarByName(parentMethod.getMethodType(), paramName);
if (var != null && !acc.getUnsafeVars().contains(var)) {
return updatedExpr;
List<JavaType> parameterTypes = parentMethodType.getParameterTypes();
int parameterTypesSize = parameterTypes.size();

//try to process method with variable arguments
if(argPos > parameterTypesSize)
{
//todo find better way to detect (...) in method arguments
if (parameterTypes.get(parameterTypesSize - 1).toString().endsWith("[]")){
return updatedExpr;
}
return original;
}
return original;

return updatedExpr;
}

private J.MethodInvocation migrateNonJodaMethod(J.MethodInvocation original, J.MethodInvocation updated) {
if (safeMigration && !acc.getSafeMethodMap().getOrDefault(updated.getMethodType(), false)) {
return original;
}
JavaType.Class returnType = (JavaType.Class) updated.getMethodType().getReturnType();
JavaType.Class updatedReturnType = TimeClassMap.getJavaTimeType(returnType.getFullyQualifiedName());
if (updatedReturnType == null) {
Expand Down Expand Up @@ -282,7 +350,18 @@ private Optional<J> applyTemplate(MethodCall original, MethodCall updated, Metho
}

private boolean isJodaVarRef(@Nullable Expression expr) {
if (expr == null || expr.getType() == null || !expr.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
if (expr == null) {
return false;
}
JavaType type = expr.getType();
if (type == null) {
return false;
}
if(type instanceof JavaType.Array){
type = ((JavaType.Array) type).getElemType();
}

if (!type.isAssignableFrom(JODA_CLASS_PATTERN )) {
return false;
}
if (expr instanceof J.FieldAccess) {
Expand Down
Loading