From d6ecd54ec8a93bc33111a4bf12917d8884ca93bc Mon Sep 17 00:00:00 2001 From: amishra-u <119983081+amishra-u@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:05:22 -0800 Subject: [PATCH] Joda-Time to Java time: Add support for Method Parameter Migration (#605) * Joda time to Java time: Add support for Method Parameter Migration * Formatting * Remove unused import in JodaTimeScannerTest * Update Co-authored-by: Tim te Beek * Apply formatter * Minimize visibility & reduce overloaded constructors * Null mark the packages to reduce warnings --------- Co-authored-by: Tim te Beek --- .../java/migrate/joda/JodaTimeFlowSpec.java | 8 +- .../java/migrate/joda/JodaTimeRecipe.java | 50 +++++-- .../java/migrate/joda/JodaTimeScanner.java | 109 +++++++++++----- .../java/migrate/joda/JodaTimeVisitor.java | 123 +++++++++--------- .../java/migrate/joda/ScopeAwareVisitor.java | 2 +- .../java/migrate/joda/package-info.java | 21 +++ .../templates/AbstractDurationTemplates.java | 3 +- .../migrate/joda/templates/AllTemplates.java | 11 +- .../joda/templates/DateTimeTemplates.java | 7 +- .../joda/templates/DurationTemplates.java | 2 +- .../migrate/joda/templates/TimeClassMap.java | 2 +- .../migrate/joda/templates/VarTemplates.java | 2 +- .../migrate/joda/templates/package-info.java | 21 +++ .../migrate/joda/JodaTimeFlowSpecTest.java | 28 ++-- .../java/migrate/joda/JodaTimeRecipeTest.java | 94 ++++++++++++- .../migrate/joda/JodaTimeScannerTest.java | 69 +++++++--- .../migrate/joda/JodaTimeVisitorTest.java | 62 ++++----- 17 files changed, 430 insertions(+), 184 deletions(-) create mode 100644 src/main/java/org/openrewrite/java/migrate/joda/package-info.java create mode 100644 src/main/java/org/openrewrite/java/migrate/joda/templates/package-info.java diff --git a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeFlowSpec.java b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeFlowSpec.java index 3639b708a..135a9c2b4 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeFlowSpec.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeFlowSpec.java @@ -23,7 +23,7 @@ import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.JODA_CLASS_PATTERN; -public class JodaTimeFlowSpec extends DataFlowSpec { +class JodaTimeFlowSpec extends DataFlowSpec { @Override public boolean isSource(@NonNull DataFlowNode srcNode) { @@ -36,6 +36,12 @@ public boolean isSource(@NonNull DataFlowNode srcNode) { if (value instanceof J.VariableDeclarations.NamedVariable) { return isJodaType(((J.VariableDeclarations.NamedVariable) value).getType()); } + + if (value instanceof J.VariableDeclarations) { + if (srcNode.getCursor().getParentTreeCursor().getParentTreeCursor().getValue() instanceof J.MethodDeclaration) { + return isJodaType(((J.VariableDeclarations) value).getType()); + } + } return false; } diff --git a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeRecipe.java b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeRecipe.java index faf34af26..c9634697f 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeRecipe.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeRecipe.java @@ -15,17 +15,20 @@ */ package org.openrewrite.java.migrate.joda; +import lombok.Getter; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.ScanningRecipe; +import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.VariableDeclarations.NamedVariable; +import org.openrewrite.java.tree.JavaType; -import java.util.HashSet; -import java.util.Set; +import java.util.*; -public class JodaTimeRecipe extends ScanningRecipe> { +public class JodaTimeRecipe extends ScanningRecipe { @Override public String getDisplayName() { - return "Migrate Joda Time to Java Time"; + return "Migrate Joda-Time to Java time"; } @Override @@ -34,17 +37,46 @@ public String getDescription() { } @Override - public Set getInitialValue(ExecutionContext ctx) { - return new HashSet<>(); + public Accumulator getInitialValue(ExecutionContext ctx) { + return new Accumulator(); } @Override - public JodaTimeScanner getScanner(Set acc) { + public JodaTimeScanner getScanner(Accumulator acc) { return new JodaTimeScanner(acc); } @Override - public JodaTimeVisitor getVisitor(Set acc) { - return new JodaTimeVisitor(acc); + public JodaTimeVisitor getVisitor(Accumulator acc) { + return new JodaTimeVisitor(acc, true, new LinkedList<>()); + } + + @Getter + public static class Accumulator { + private final Set unsafeVars = new HashSet<>(); + private final VarTable varTable = new VarTable(); + } + + static class VarTable { + private final Map> vars = new HashMap<>(); + + public void addVars(J.MethodDeclaration methodDeclaration) { + JavaType type = methodDeclaration.getMethodType(); + assert type != null; + methodDeclaration.getParameters().forEach(p -> { + if (!(p instanceof J.VariableDeclarations)) { + return; + } + J.VariableDeclarations.NamedVariable namedVariable = ((J.VariableDeclarations) p).getVariables().get(0); + vars.computeIfAbsent(type, k -> new ArrayList<>()).add(namedVariable); + }); + } + + public @Nullable NamedVariable getVarByName(@Nullable JavaType declaringType, String varName) { + return vars.getOrDefault(declaringType, Collections.emptyList()).stream() + .filter(v -> v.getSimpleName().equals(varName)) + .findFirst() // there should be only one variable with the same name + .orElse(null); + } } } diff --git a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeScanner.java b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeScanner.java index 2559e3fe5..a46a741bf 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeScanner.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeScanner.java @@ -19,6 +19,7 @@ import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.analysis.dataflow.Dataflow; @@ -28,36 +29,34 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.VariableDeclarations.NamedVariable; import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.MethodCall; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.JODA_CLASS_PATTERN; -public class JodaTimeScanner extends ScopeAwareVisitor { +class JodaTimeScanner extends ScopeAwareVisitor { @Getter - private final Set unsafeVars; + private final JodaTimeRecipe.Accumulator acc; private final Map> varDependencies = new HashMap<>(); + private final Map> unsafeVarsByType = new HashMap<>(); - public JodaTimeScanner(Set unsafeVars, LinkedList scopes) { - super(scopes); - this.unsafeVars = unsafeVars; - } - - public JodaTimeScanner(Set unsafeVars) { - this(unsafeVars, new LinkedList<>()); + public JodaTimeScanner(JodaTimeRecipe.Accumulator acc) { + super(new LinkedList<>()); + this.acc = acc; } @Override public J visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { super.visitCompilationUnit(cu, ctx); Set allReachable = new HashSet<>(); - for (NamedVariable var : unsafeVars) { + for (NamedVariable var : acc.getUnsafeVars()) { dfs(var, allReachable); } - unsafeVars.addAll(allReachable); + acc.getUnsafeVars().addAll(allReachable); return cu; } @@ -66,21 +65,32 @@ public NamedVariable visitVariable(NamedVariable variable, ExecutionContext ctx) if (!variable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { return variable; } - // TODO: handle class variables && method parameters - if (!isLocalVar(variable)) { - unsafeVars.add(variable); + // TODO: handle class variables + if (isClassVar(variable)) { + acc.getUnsafeVars().add(variable); return variable; } variable = (NamedVariable) super.visitVariable(variable, ctx); - if (!variable.getType().isAssignableFrom(JODA_CLASS_PATTERN) || variable.getInitializer() == null) { + if (!variable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { + return variable; + } + boolean isMethodParam = getCursor().getParentTreeCursor() // VariableDeclaration + .getParentTreeCursor() // MethodDeclaration + .getValue() instanceof J.MethodDeclaration; + Cursor cursor = null; + if (isMethodParam) { + cursor = getCursor(); + } else if (variable.getInitializer() != null) { + cursor = new Cursor(getCursor(), variable.getInitializer()); + } + if (cursor == null) { return variable; } - List sinks = findSinks(variable.getInitializer()); + List sinks = findSinks(cursor); Cursor currentScope = getCurrentScope(); - J.Block block = currentScope.getValue(); - new AddSafeCheckMarker(sinks).visit(block, ctx, currentScope.getParent()); + new AddSafeCheckMarker(sinks).visit(currentScope.getValue(), ctx, currentScope.getParentOrThrow()); processMarkersOnExpression(sinks, variable); return variable; } @@ -99,12 +109,24 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ct } NamedVariable variable = mayBeVar.get(); Cursor varScope = findScope(variable); - List sinks = findSinks(assignment.getAssignment()); - new AddSafeCheckMarker(sinks).visit(varScope.getValue(), ctx, varScope.getParent()); + List sinks = findSinks(new Cursor(getCursor(), assignment.getAssignment())); + new AddSafeCheckMarker(sinks).visit(varScope.getValue(), ctx, varScope.getParentOrThrow()); processMarkersOnExpression(sinks, variable); return assignment; } + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + acc.getVarTable().addVars(method); + unsafeVarsByType.getOrDefault(method.getMethodType(), Collections.emptySet()).forEach(varName -> { + NamedVariable var = acc.getVarTable().getVarByName(method.getMethodType(), varName); + if (var != null) { // var can only be null if method is not correctly type attributed + acc.getUnsafeVars().add(var); + } + }); + return (J.MethodDeclaration) super.visitMethodDeclaration(method, ctx); + } + private void processMarkersOnExpression(List expressions, NamedVariable var) { for (Expression expr : expressions) { Optional mayBeMarker = expr.getMarkers().findFirst(SafeCheckMarker.class); @@ -113,7 +135,7 @@ private void processMarkersOnExpression(List expressions, NamedVaria } SafeCheckMarker marker = mayBeMarker.get(); if (!marker.isSafe()) { - unsafeVars.add(var); + acc.getUnsafeVars().add(var); } if (!marker.getReferences().isEmpty()) { varDependencies.compute(var, (k, v) -> v == null ? new HashSet<>() : v).addAll(marker.getReferences()); @@ -128,8 +150,7 @@ private boolean isJodaExpr(Expression expression) { return expression.getType() != null && expression.getType().isAssignableFrom(JODA_CLASS_PATTERN); } - private List findSinks(Expression expr) { - Cursor cursor = new Cursor(getCursor(), expr); + private List findSinks(Cursor cursor) { Option mayBeSinks = Dataflow.startingAt(cursor).findSinks(new JodaTimeFlowSpec()); if (mayBeSinks.isNone()) { return Collections.emptyList(); @@ -137,12 +158,8 @@ private List findSinks(Expression expr) { return mayBeSinks.some().getExpressionSinks(); } - private boolean isLocalVar(NamedVariable variable) { - if (!(variable.getVariableType().getOwner() instanceof JavaType.Method)) { - return false; - } - J j = getCursor().dropParentUntil(t -> t instanceof J.Block || t instanceof J.MethodDeclaration).getValue(); - return j instanceof J.Block; + private boolean isClassVar(NamedVariable variable) { + return variable.getVariableType().getOwner() instanceof JavaType.Class; } private void dfs(NamedVariable root, Set visited) { @@ -167,7 +184,17 @@ public Expression visitExpression(Expression expression, ExecutionContext ctx) { if (index == -1) { return super.visitExpression(expression, ctx); } - Expression withMarker = expression.withMarkers(expression.getMarkers().addIfAbsent(getMarker(expression, ctx))); + SafeCheckMarker marker = getMarker(expression, ctx); + if (!marker.isSafe()) { + Optional mayBeArgCursor = findArgumentExprCursor(); + if (mayBeArgCursor.isPresent()) { + MethodCall parentMethod = mayBeArgCursor.get().getParentTreeCursor().getValue(); + int argPos = parentMethod.getArguments().indexOf(mayBeArgCursor.get().getValue()); + String paramName = parentMethod.getMethodType().getParameterNames().get(argPos); + unsafeVarsByType.computeIfAbsent(parentMethod.getMethodType(), k -> new HashSet<>()).add(paramName); + } + } + Expression withMarker = expression.withMarkers(expression.getMarkers().addIfAbsent(marker)); expressions.set(index, withMarker); return withMarker; } @@ -185,8 +212,9 @@ private SafeCheckMarker getMarker(Expression expr, ExecutionContext ctx) { isSafe = false; } Expression boundaryExpr = boundary.getValue(); - J j = new JodaTimeVisitor(new HashSet<>(), scopes).visit(boundaryExpr, ctx, boundary.getParentTreeCursor()); - Set referencedVars = new HashSet<>(); + J j = new JodaTimeVisitor(new JodaTimeRecipe.Accumulator(), false, scopes) + .visit(boundaryExpr, ctx, boundary.getParentTreeCursor()); + Set<@Nullable NamedVariable> referencedVars = new HashSet<>(); new FindVarReferences().visit(expr, referencedVars, getCursor().getParentTreeCursor()); AtomicBoolean hasJodaType = new AtomicBoolean(); new HasJodaType().visit(j, hasJodaType); @@ -211,12 +239,25 @@ private Cursor findBoundaryCursorForJodaExpr() { } return cursor; } + + private Optional findArgumentExprCursor() { + Cursor cursor = getCursor(); + while (cursor.getValue() instanceof Expression && isJodaExpr(cursor.getValue())) { + Cursor parentCursor = cursor.getParentTreeCursor(); + if (parentCursor.getValue() instanceof MethodCall && + ((MethodCall) parentCursor.getValue()).getArguments().contains(cursor.getValue())) { + return Optional.of(cursor); + } + cursor = parentCursor; + } + return Optional.empty(); + } } - private class FindVarReferences extends JavaIsoVisitor> { + private class FindVarReferences extends JavaIsoVisitor> { @Override - public J.Identifier visitIdentifier(J.Identifier ident, Set vars) { + public J.Identifier visitIdentifier(J.Identifier ident, Set<@Nullable NamedVariable> vars) { if (!isJodaExpr(ident) || ident.getFieldType() == null) { return ident; } diff --git a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java index 84bf39e95..647dac37b 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java @@ -19,30 +19,28 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.migrate.joda.templates.*; +import org.openrewrite.java.migrate.joda.templates.AllTemplates; +import org.openrewrite.java.migrate.joda.templates.MethodTemplate; +import org.openrewrite.java.migrate.joda.templates.TimeClassMap; +import org.openrewrite.java.migrate.joda.templates.VarTemplates; import org.openrewrite.java.tree.*; import org.openrewrite.java.tree.J.VariableDeclarations.NamedVariable; -import org.openrewrite.java.tree.MethodCall; -import java.util.*; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.*; -public class JodaTimeVisitor extends ScopeAwareVisitor { +class JodaTimeVisitor extends ScopeAwareVisitor { - private final Set unsafeVars; + private final boolean safeMigration; + private final JodaTimeRecipe.Accumulator acc; - public JodaTimeVisitor(Set unsafeVars, LinkedList scopes) { + public JodaTimeVisitor(JodaTimeRecipe.Accumulator acc, boolean safeMigration, LinkedList scopes) { super(scopes); - this.unsafeVars = unsafeVars; - } - - public JodaTimeVisitor(Set unsafeVars) { - this(unsafeVars, new LinkedList<>()); - } - - public JodaTimeVisitor() { - this(new HashSet<>()); + this.acc = acc; + this.safeMigration = safeMigration; } @Override @@ -75,7 +73,7 @@ public JodaTimeVisitor() { if (!multiVariable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { return super.visitVariableDeclarations(multiVariable, ctx); } - if (multiVariable.getVariables().stream().anyMatch(unsafeVars::contains)) { + if (multiVariable.getVariables().stream().anyMatch(acc.getUnsafeVars()::contains)) { return multiVariable; } multiVariable = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, ctx); @@ -90,14 +88,14 @@ public JodaTimeVisitor() { if (!variable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { return super.visitVariable(variable, ctx); } - if (unsafeVars.contains(variable) || ! (variable.getType() instanceof JavaType.Class)) { + if (acc.getUnsafeVars().contains(variable) || !(variable.getType() instanceof JavaType.Class)) { return variable; } JavaType.Class jodaType = (JavaType.Class) variable.getType(); return variable .withType(TimeClassMap.getJavaTimeType(jodaType.getFullyQualifiedName())) .withInitializer((Expression) visit(variable.getInitializer(), ctx)); - } + } @Override public @NonNull J visitAssignment(@NonNull J.Assignment assignment, @NonNull ExecutionContext ctx) { @@ -110,7 +108,7 @@ public JodaTimeVisitor() { } J.Identifier varName = (J.Identifier) a.getVariable(); Optional mayBeVar = findVarInScope(varName.getSimpleName()); - if (!mayBeVar.isPresent() || unsafeVars.contains(mayBeVar.get())) { + if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) { return assignment; } return VarTemplates.getTemplate(a).apply( @@ -126,14 +124,7 @@ public JodaTimeVisitor() { if (hasJodaType(updated.getArguments())) { return newClass; } - MethodTemplate template = AllTemplates.getTemplate(newClass); - if (template != null) { - return applyTemplate(newClass, updated, template).orElse(newClass); - } - if (areArgumentsAssignable(updated)) { - return updated; - } - return newClass; + return migrateMethodCall(newClass, updated); } @@ -143,17 +134,7 @@ public JodaTimeVisitor() { if (hasJodaType(m.getArguments()) || isJodaVarRef(m.getSelect())) { return method; } - MethodTemplate template = AllTemplates.getTemplate(method); - if (template != null) { - return applyTemplate(method, m, template).orElse(method); - } - if (method.getMethodType().getDeclaringType().isAssignableFrom(JODA_CLASS_PATTERN)) { - return method; // unhandled case - } - if (areArgumentsAssignable(m)) { - return m; - } - return method; + return migrateMethodCall(method, m); } @Override @@ -174,7 +155,7 @@ public JodaTimeVisitor() { return super.visitIdentifier(ident, ctx); } Optional mayBeVar = findVarInScope(ident.getSimpleName()); - if (!mayBeVar.isPresent() || unsafeVars.contains(mayBeVar.get())) { + if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) { return ident; } @@ -185,6 +166,40 @@ public JodaTimeVisitor() { .withFieldType(ident.getFieldType().withType(fqType)); } + private J migrateMethodCall(MethodCall original, MethodCall updated) { + if (!original.getMethodType().getDeclaringType().isAssignableFrom(JODA_CLASS_PATTERN)) { + return updated; // not a joda type, no need to migrate + } + MethodTemplate template = AllTemplates.getTemplate(original); + if (template == null) { + return original; // unhandled case + } + Optional maybeUpdated = applyTemplate(original, updated, template); + if (!maybeUpdated.isPresent()) { + return original; // unhandled case + } + Expression updatedExpr = (Expression) maybeUpdated.get(); + if (!safeMigration || !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)) { + 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; + } + return original; + } + private boolean hasJodaType(List exprs) { for (Expression expr : exprs) { JavaType exprType = expr.getType(); @@ -206,28 +221,6 @@ private Optional applyTemplate(MethodCall original, MethodCall updated, Metho return Optional.empty(); // unhandled case } - private boolean areArgumentsAssignable(MethodCall m) { - if (m.getMethodType() == null || getArgumentsCount(m) != m.getMethodType().getParameterTypes().size()) { - return false; - } - if (getArgumentsCount(m) == 0) { - return true; - } - for (int i = 0; i < m.getArguments().size(); i++) { - if (!TypeUtils.isAssignableTo(m.getMethodType().getParameterTypes().get(i), m.getArguments().get(i).getType())) { - return false; - } - } - return true; - } - - private int getArgumentsCount(MethodCall m) { - if (m.getArguments().size() == 1 && m.getArguments().get(0) instanceof J.Empty) { - return 0; - } - return m.getArguments().size(); - } - private boolean isJodaVarRef(@Nullable Expression expr) { if (expr == null || expr.getType() == null || !expr.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { return false; @@ -240,4 +233,12 @@ private boolean isJodaVarRef(@Nullable Expression expr) { } return false; } + + private boolean isArgument(J expr) { + if (!(getCursor().getParentTreeCursor().getValue() instanceof MethodCall)) { + return false; + } + MethodCall methodCall = getCursor().getParentTreeCursor().getValue(); + return methodCall.getArguments().contains(expr); + } } diff --git a/src/main/java/org/openrewrite/java/migrate/joda/ScopeAwareVisitor.java b/src/main/java/org/openrewrite/java/migrate/joda/ScopeAwareVisitor.java index 257e90ea0..b9c7ca9d9 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/ScopeAwareVisitor.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/ScopeAwareVisitor.java @@ -29,7 +29,7 @@ import java.util.Set; @AllArgsConstructor -public class ScopeAwareVisitor extends JavaVisitor { +class ScopeAwareVisitor extends JavaVisitor { protected final LinkedList scopes; @Override diff --git a/src/main/java/org/openrewrite/java/migrate/joda/package-info.java b/src/main/java/org/openrewrite/java/migrate/joda/package-info.java new file mode 100644 index 000000000..8cc4e1811 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/joda/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.java.migrate.joda; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/AbstractDurationTemplates.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/AbstractDurationTemplates.java index edd48b7a2..3de626102 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/templates/AbstractDurationTemplates.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/AbstractDurationTemplates.java @@ -12,7 +12,8 @@ * 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.migrate.joda.templates; + */ +package org.openrewrite.java.migrate.joda.templates; import lombok.Getter; import org.openrewrite.java.JavaTemplate; diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/AllTemplates.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/AllTemplates.java index ffa7584e6..dc7e76edc 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/templates/AllTemplates.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/AllTemplates.java @@ -17,7 +17,6 @@ import lombok.Value; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.MethodCall; import java.util.ArrayList; @@ -39,7 +38,7 @@ public class AllTemplates { private static final MethodMatcher ANY_ABSTRACT_DATE_TIME = new MethodMatcher(JODA_ABSTRACT_DATE_TIME + " *(..)"); private static final MethodMatcher ANY_ABSTRACT_DURATION = new MethodMatcher(JODA_ABSTRACT_DURATION + " *(..)"); private static final MethodMatcher ANY_INSTANT = new MethodMatcher(JODA_INSTANT + " *(..)"); - private static final MethodMatcher ANY_NEW_INSTANT = new MethodMatcher(JODA_INSTANT + "(..)"); + private static final MethodMatcher ANY_NEW_INSTANT = new MethodMatcher(JODA_INSTANT + "(..)"); private static List templates = new ArrayList() { { @@ -59,18 +58,12 @@ public class AllTemplates { } }; - public static MethodTemplate getTemplate(J.MethodInvocation method) { + public static MethodTemplate getTemplate(MethodCall method) { return getTemplateGroup(method).flatMap(templates -> templates.getTemplates().stream() .filter(template -> template.getMatcher().matches(method) && templates.matchesMethodCall(method, template)) .findFirst()).orElse(null); } - public static MethodTemplate getTemplate(J.NewClass newClass) { - return getTemplateGroup(newClass).flatMap(templates -> templates.getTemplates().stream() - .filter(template -> template.getMatcher().matches(newClass) && templates.matchesMethodCall(newClass, template)) - .findFirst()).orElse(null); - } - private static Optional getTemplateGroup(MethodCall method) { for (MatcherAndTemplates matcherAndTemplates : templates) { if (matcherAndTemplates.getMatcher().matches(method)) { diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/DateTimeTemplates.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/DateTimeTemplates.java index f1af14455..913cf6684 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/templates/DateTimeTemplates.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/DateTimeTemplates.java @@ -180,7 +180,7 @@ public class DateTimeTemplates implements Templates { .build(); private final JavaTemplate withTimeTemplate = JavaTemplate.builder("#{any(java.time.ZonedDateTime)}.withHour(#{any(int)}).withMinute(#{any(int)}).withSecond(#{any(int)}).withNano(#{any(int)} * 1_000_000)") .build(); - private final JavaTemplate withTimeAtStartOfDayTemplate = JavaTemplate.builder("#{any(java.time.ZonedDateTime)}.atStartOfDay(#{any(java.time.ZonedDateTime)}.getZone())") + private final JavaTemplate withTimeAtStartOfDayTemplate = JavaTemplate.builder("#{any(java.time.ZonedDateTime)}.toLocalDate().atStartOfDay(#{any(java.time.ZonedDateTime)}.getZone())") .build(); private final JavaTemplate withDurationAddedTemplate = JavaTemplate.builder("#{any(java.time.ZonedDateTime)}.plus(Duration.ofMillis(#{any(long)}).multipliedBy(#{any(int)}))") .imports(JAVA_DURATION) @@ -301,7 +301,10 @@ public class DateTimeTemplates implements Templates { add(new MethodTemplate(withDateLocalDate, withTemporalAdjusterTemplate)); add(new MethodTemplate(withTime, withTimeTemplate)); add(new MethodTemplate(withTimeLocalTime, withTemporalAdjusterTemplate)); - add(new MethodTemplate(withTimeAtStartOfDay, withTimeAtStartOfDayTemplate)); + add(new MethodTemplate(withTimeAtStartOfDay, withTimeAtStartOfDayTemplate, m -> { + J.MethodInvocation mi = (J.MethodInvocation) m; + return new Expression[]{mi.getSelect(), mi.getSelect()}; + })); add(new MethodTemplate(withDurationAdded, withDurationAddedTemplate)); add(new MethodTemplate(plusLong, plusMillisTemplate)); add(new MethodTemplate(plusReadableDuration, plusReadableDurationTemplate)); diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/DurationTemplates.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/DurationTemplates.java index ea642137e..ec48f586b 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/templates/DurationTemplates.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/DurationTemplates.java @@ -27,7 +27,7 @@ import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.*; @NoArgsConstructor -public class DurationTemplates implements Templates{ +public class DurationTemplates implements Templates { private final MethodMatcher parse = new MethodMatcher(JODA_DURATION + " parse(String)"); private final MethodMatcher standardDays = new MethodMatcher(JODA_DURATION + " standardDays(long)"); private final MethodMatcher standardHours = new MethodMatcher(JODA_DURATION + " standardHours(long)"); diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassMap.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassMap.java index 209f39985..0b3760297 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassMap.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassMap.java @@ -49,7 +49,7 @@ private static JavaType.Class javaTypeClass(String fqn, JavaType.Class superType null, null, null, null, null); } - public static JavaType.@Nullable Class getJavaTimeType(String typeFqn) { + public static JavaType.@Nullable Class getJavaTimeType(String typeFqn) { return new TimeClassMap().jodaToJavaTimeMap.get(typeFqn); } diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/VarTemplates.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/VarTemplates.java index 2d0259b06..c85b30293 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/templates/VarTemplates.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/VarTemplates.java @@ -70,7 +70,7 @@ public static JavaTemplate getTemplate(J.Assignment assignment) { JavaType.Class varType = (JavaType.Class) assignment.getVariable().getType(); String typeName = JodaToJavaTimeType.get(type.getFullyQualifiedName()); String varTypeName = JodaToJavaTimeType.get(varType.getFullyQualifiedName()); - String template = "#{any(" + varTypeName + ")} = #{any(" + typeName +")}"; + String template = "#{any(" + varTypeName + ")} = #{any(" + typeName + ")}"; return JavaTemplate.builder(template) .build(); } diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/package-info.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/package-info.java new file mode 100644 index 000000000..f2541036e --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.java.migrate.joda.templates; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeFlowSpecTest.java b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeFlowSpecTest.java index a54e65d46..924b165f6 100644 --- a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeFlowSpecTest.java +++ b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeFlowSpecTest.java @@ -46,10 +46,12 @@ public void defaults(RecipeSpec spec) { @Override public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, ExecutionContext ctx) { - if (variable.getInitializer() == null) { - return super.visitVariable(variable, ctx); + if (atMethodParam(getCursor())) { + updateSinks(getCursor(), variable.getName()); + } + if (variable.getInitializer() != null) { + updateSinks(new Cursor(getCursor(), variable.getInitializer()), variable.getName()); } - updateSinks(variable.getInitializer(), variable.getName()); return super.visitVariable(variable, ctx); } @@ -58,7 +60,8 @@ public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ct if (!(assignment.getVariable() instanceof J.Identifier)) { return super.visitAssignment(assignment, ctx); } - updateSinks(assignment.getAssignment(), (J.Identifier) assignment.getVariable()); + Cursor cursor = new Cursor(getCursor(), assignment.getAssignment()); + updateSinks(cursor, (J.Identifier) assignment.getVariable()); return super.visitAssignment(assignment, ctx); } @@ -72,8 +75,7 @@ public Expression visitExpression(Expression expression, ExecutionContext ctx) { return SearchResult.found(expression, desc); } - private void updateSinks(Expression expr, J.Identifier identifier) { - Cursor cursor = new Cursor(getCursor(), expr); + private void updateSinks(Cursor cursor, J.Identifier identifier) { Dataflow.startingAt(cursor).findSinks(new JodaTimeFlowSpec()) .foreachDoEffect(sinkFlow -> { for (Expression sink : sinkFlow.getExpressionSinks()) { @@ -81,6 +83,10 @@ private void updateSinks(Expression expr, J.Identifier identifier) { } }); } + + private boolean atMethodParam(Cursor cursor) { + return cursor.getParentTreeCursor().getParentTreeCursor().getValue() instanceof J.MethodDeclaration; + } })) .parser(JavaParser.fromJavaVersion().classpath("joda-time")); } @@ -94,7 +100,7 @@ void jodaTimeUsageWithVarBindings() { """ import org.joda.time.DateTime; import org.joda.time.Interval; - + class A { public void foo() { DateTime dateTime = new DateTime(), _dateTime = DateTime.now(); @@ -104,6 +110,9 @@ public void foo() { dateTime = dateTime.minusDays(1); _dateTime = dateTime; Interval interval = new Interval(_dateTime, dateTimePlus2); + print(interval); + } + private void print(Interval interval) { System.out.println(interval); } } @@ -111,7 +120,7 @@ public void foo() { """ import org.joda.time.DateTime; import org.joda.time.Interval; - + class A { public void foo() { DateTime dateTime = /*~~(dateTime)~~>*/new DateTime(), _dateTime = /*~~(_dateTime)~~>*/DateTime.now(); @@ -121,6 +130,9 @@ public void foo() { dateTime = /*~~(dateTime)~~>*//*~~(dateTime)~~>*/dateTime.minusDays(1); _dateTime = /*~~(dateTime, _dateTime)~~>*/dateTime; Interval interval = /*~~(interval)~~>*/new Interval(/*~~(dateTime, _dateTime)~~>*/_dateTime, /*~~(dateTimePlus2)~~>*/dateTimePlus2); + print(/*~~(interval)~~>*/interval); + } + private void print(Interval interval) { System.out.println(/*~~(interval)~~>*/interval); } } diff --git a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeRecipeTest.java b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeRecipeTest.java index 27418ff1f..927ce2d3d 100644 --- a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeRecipeTest.java +++ b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeRecipeTest.java @@ -39,7 +39,7 @@ void migrateSafeVariable() { java( """ import org.joda.time.DateTime; - + class A { public void foo() { DateTime dt = new DateTime(); @@ -69,7 +69,7 @@ void dontChangeClassVariable() { java( """ import org.joda.time.DateTime; - + class A { public void foo() { DateTime dt = new DateTime(); @@ -85,7 +85,7 @@ public static class B { import org.joda.time.DateTime; import java.time.ZonedDateTime; - + class A { public void foo() { ZonedDateTime dt = ZonedDateTime.now(); @@ -102,7 +102,7 @@ public static class B { } @Test - void dontChangeIncompatibleType() { + void safeMethodParamMigrationAcrossClassBoundary() { //language=java rewriteRun( java( @@ -111,8 +111,8 @@ void dontChangeIncompatibleType() { class A { public void foo() { - new B().print(new DateTime()); // print is public method accepting DateTime, not handled yet - System.out.println(new B().dateTime); + new B().print(new DateTime()); + System.out.println(new B().dateTime); // dateTime is class variable, not handled yet } } @@ -122,6 +122,25 @@ public void print(DateTime dateTime) { System.out.println(dateTime); } } + """, + """ + import org.joda.time.DateTime; + + import java.time.ZonedDateTime; + + class A { + public void foo() { + new B().print(ZonedDateTime.now()); + System.out.println(new B().dateTime); // dateTime is class variable, not handled yet + } + } + + class B { + DateTime dateTime = new DateTime(); + public void print(ZonedDateTime dateTime) { + System.out.println(dateTime); + } + } """ ) ); @@ -203,4 +222,67 @@ public DateTime foo(String city) { ) ); } + + @Test + void migrateSafeMethodParam() { + //language=java + rewriteRun( + java( + """ + import org.joda.time.DateTime; + + class A { + public void foo() { + new Bar().bar(new DateTime()); + } + + private static class Bar { + public void bar(DateTime dt) { + dt.getMillis(); + } + } + } + """, + """ + import java.time.ZonedDateTime; + + class A { + public void foo() { + new Bar().bar(ZonedDateTime.now()); + } + + private static class Bar { + public void bar(ZonedDateTime dt) { + dt.toInstant().toEpochMilli(); + } + } + } + """ + ) + ); + } + + @Test + void doNotMigrateUnsafeMethodParam() { + //language=java + rewriteRun( + java( + """ + import org.joda.time.DateTime; + + class A { + public void foo() { + new Bar().bar(new DateTime()); + } + + private static class Bar { + public void bar(DateTime dt) { + dt.toDateMidnight(); // template doesn't exist for toDateMidnight + } + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeScannerTest.java b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeScannerTest.java index 093469353..ca7204814 100644 --- a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeScannerTest.java +++ b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeScannerTest.java @@ -21,8 +21,6 @@ import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; -import java.util.HashSet; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openrewrite.java.Assertions.java; @@ -36,7 +34,7 @@ public void defaults(RecipeSpec spec) { @Test void noUnsafeVar() { - JodaTimeScanner scanner = new JodaTimeScanner(new HashSet<>()); + JodaTimeScanner scanner = new JodaTimeScanner(new JodaTimeRecipe.Accumulator()); // language=java rewriteRun( spec -> spec.recipe(toRecipe(() -> scanner)), @@ -64,12 +62,12 @@ private void print(Date date) { """ ) ); - assertTrue(scanner.getUnsafeVars().isEmpty()); + assertTrue(scanner.getAcc().getUnsafeVars().isEmpty()); } @Test void hasUnsafeVars() { - JodaTimeScanner scanner = new JodaTimeScanner(new HashSet<>()); + JodaTimeScanner scanner = new JodaTimeScanner(new JodaTimeRecipe.Accumulator()); // language=java rewriteRun( spec -> spec.recipe(toRecipe(() -> scanner)), @@ -79,7 +77,7 @@ void hasUnsafeVars() { import org.joda.time.DateTimeZone; class A { - DateTime dateTime; + DateTime dateTime; // class Variable not handled yet public void foo(String city) { DateTimeZone dtz; if ("london".equals(city)) { @@ -87,20 +85,20 @@ public void foo(String city) { } else { dtz = DateTimeZone.forID("America/New_York"); } - DateTime dt = new DateTime(dtz); - print(dt.toDateTime()); + dateTime = new DateTime(dtz); + print(dateTime.toDateTime()); } - private void print(DateTime dateTime) { // method parameter not handled yet - System.out.println(dateTime); + private void print(DateTime dt) { + System.out.println(dt); } } """ ) ); - // The variable dtz is unsafe due to dt. The dt variable is unsafe because its associated expression - // is passed as argument to method, and migration of method parameters has not been implemented yet. - assertEquals(4, scanner.getUnsafeVars().size()); - for (J.VariableDeclarations.NamedVariable var : scanner.getUnsafeVars()) { + // The variable 'dtz' is unsafe due to the class variable 'dateTime'. + // The parameter 'dt' in the 'print' method is also unsafe because one of its method calls is unsafe. + assertEquals(3, scanner.getAcc().getUnsafeVars().size()); + for (J.VariableDeclarations.NamedVariable var : scanner.getAcc().getUnsafeVars()) { assertTrue(var.getSimpleName().equals("dtz") || var.getSimpleName().equals("dt") || var.getSimpleName().equals("dateTime") @@ -110,7 +108,7 @@ private void print(DateTime dateTime) { // method parameter not handled yet @Test void localVarReferencingClassVar() { // not supported yet - JodaTimeScanner scanner = new JodaTimeScanner(new HashSet<>()); + JodaTimeScanner scanner = new JodaTimeScanner(new JodaTimeRecipe.Accumulator()); // language=java rewriteRun( spec -> spec.recipe(toRecipe(() -> scanner)), @@ -136,15 +134,15 @@ public void foo(String city) { ) ); // The local variable dt is unsafe due to class var datetime. - assertEquals(2, scanner.getUnsafeVars().size()); - for (J.VariableDeclarations.NamedVariable var : scanner.getUnsafeVars()) { + assertEquals(2, scanner.getAcc().getUnsafeVars().size()); + for (J.VariableDeclarations.NamedVariable var : scanner.getAcc().getUnsafeVars()) { assertTrue(var.getSimpleName().equals("dateTime") || var.getSimpleName().equals("dt")); } } @Test void localVarUsedReferencedInReturnStatement() { // not supported yet - JodaTimeScanner scanner = new JodaTimeScanner(new HashSet<>()); + JodaTimeScanner scanner = new JodaTimeScanner(new JodaTimeRecipe.Accumulator()); // language=java rewriteRun( spec -> spec.recipe(toRecipe(() -> scanner)), @@ -169,9 +167,40 @@ public DateTime foo(String city) { ) ); // The local variable dt used in return statement. - assertEquals(2, scanner.getUnsafeVars().size()); - for (J.VariableDeclarations.NamedVariable var : scanner.getUnsafeVars()) { + assertEquals(2, scanner.getAcc().getUnsafeVars().size()); + for (J.VariableDeclarations.NamedVariable var : scanner.getAcc().getUnsafeVars()) { assertTrue(var.getSimpleName().equals("dtz") || var.getSimpleName().equals("dt")); } } + + @Test + void unsafeMethodParam() { + JodaTimeScanner scanner = new JodaTimeScanner(new JodaTimeRecipe.Accumulator()); + // language=java + rewriteRun( + spec -> spec.recipe(toRecipe(() -> scanner)), + java( + """ + import org.joda.time.DateTime; + + class A { + public void foo() { + new Bar().bar(new DateTime()); + } + + private static class Bar { + public void bar(DateTime dt) { + dt.toDateMidnight(); + } + } + } + """ + ) + ); + // The bar method parameter dt is unsafe because migration of toDateMidnight() is not yet implemented. + assertEquals(1, scanner.getAcc().getUnsafeVars().size()); + for (J.VariableDeclarations.NamedVariable var : scanner.getAcc().getUnsafeVars()) { + assertEquals("dt", var.getSimpleName()); + } + } } diff --git a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java index 1f132d7d4..86fecef8a 100644 --- a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java +++ b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java @@ -22,6 +22,8 @@ import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import java.util.LinkedList; + import static org.openrewrite.java.Assertions.java; import static org.openrewrite.test.RewriteTest.toRecipe; @@ -29,7 +31,7 @@ class JodaTimeVisitorTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec - .recipe(toRecipe(() -> new JodaTimeVisitor())) + .recipe(toRecipe(() -> new JodaTimeVisitor(new JodaTimeRecipe.Accumulator(), true, new LinkedList<>()))) .parser(JavaParser.fromJavaVersion().classpath("joda-time")); } @@ -43,7 +45,7 @@ void migrateNewDateTime() { import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import java.util.TimeZone; - + class A { public void foo() { System.out.println(new DateTime()); @@ -65,7 +67,7 @@ public void foo() { import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.TimeZone; - + class A { public void foo() { System.out.println(ZonedDateTime.now()); @@ -95,7 +97,7 @@ void migrateDateTimeStaticCalls() { import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import java.util.TimeZone; - + class A { public void foo() { System.out.println(DateTime.now()); @@ -110,7 +112,7 @@ public void foo() { import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.TimeZone; - + class A { public void foo() { System.out.println(ZonedDateTime.now()); @@ -134,7 +136,7 @@ void migrateDateTimeInstanceCalls() { import org.joda.time.DateTimeZone; import org.joda.time.Duration; import java.util.TimeZone; - + class A { public void foo() { System.out.println(new DateTime().toDateTime()); @@ -182,6 +184,7 @@ public void foo() { System.out.println(new DateTime().withSecondOfMinute(57)); System.out.println(new DateTime().withMillisOfSecond(550)); System.out.println(new DateTime().withMillisOfDay(123456)); + System.out.println(new DateTime().withTimeAtStartOfDay()); } } """, @@ -193,7 +196,7 @@ public void foo() { import java.time.temporal.ChronoField; import java.time.temporal.IsoFields; import java.util.TimeZone; - + class A { public void foo() { System.out.println(ZonedDateTime.now()); @@ -241,6 +244,7 @@ public void foo() { System.out.println(ZonedDateTime.now().withSecond(57)); System.out.println(ZonedDateTime.now().withNano(550 * 1_000_000)); System.out.println(ZonedDateTime.now().with(ChronoField.MILLI_OF_DAY, 123456)); + System.out.println(ZonedDateTime.now().toLocalDate().atStartOfDay(ZonedDateTime.now().getZone())); } } """ @@ -256,7 +260,7 @@ void migrateDateTimeZone() { """ import org.joda.time.DateTimeZone; import java.util.TimeZone; - + class A { public void foo() { System.out.println(DateTimeZone.UTC); @@ -267,11 +271,11 @@ public void foo() { } } """, - """ + """ import java.time.ZoneId; import java.time.ZoneOffset; import java.util.TimeZone; - + class A { public void foo() { System.out.println(ZoneOffset.UTC); @@ -293,7 +297,7 @@ void migrateDateTimeFormat() { java( """ import org.joda.time.format.DateTimeFormat; - + class A { public void foo() { System.out.println(DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); @@ -317,7 +321,7 @@ public void foo() { """ import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; - + class A { public void foo() { System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); @@ -349,7 +353,7 @@ void migrateJodaDuration() { java( """ import org.joda.time.Duration; - + class A { public void foo() { System.out.println(Duration.standardDays(1L)); @@ -381,7 +385,7 @@ public void foo() { """ import java.time.Duration; import java.time.Instant; - + class A { public void foo() { System.out.println(Duration.ofDays(1L)); @@ -423,7 +427,7 @@ void migrateAbstractInstant() { import org.joda.time.DateTime; import org.joda.time.Duration;import org.joda.time.Instant; import org.joda.time.format.DateTimeFormat; - + class A { public void foo() { new DateTime().equals(DateTime.now()); @@ -453,7 +457,7 @@ public void foo() { import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; - + class A { public void foo() { ZonedDateTime.now().equals(ZonedDateTime.now()); @@ -488,7 +492,7 @@ void migrateAbstractDateTime() { java( """ import org.joda.time.DateTime; - + class A { public void foo() { new DateTime().getDayOfMonth(); @@ -508,7 +512,7 @@ public void foo() { """ import java.time.ZonedDateTime; import java.time.temporal.ChronoField; - + class A { public void foo() { ZonedDateTime.now().getDayOfMonth(); @@ -538,7 +542,7 @@ void migrateDateTimeFormatter() { import org.joda.time.format.DateTimeFormat; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; - + class A { public void foo() { DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss").parseDateTime("2024-10-25T15:45:00"); @@ -556,7 +560,7 @@ public void foo() { import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; - + class A { public void foo() { ZonedDateTime.parse("2024-10-25T15:45:00", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")); @@ -580,7 +584,7 @@ void migrateInstant() { """ import org.joda.time.Instant; import org.joda.time.Duration; - + class A { public void foo() { System.out.println(new Instant()); @@ -595,7 +599,7 @@ public void foo() { """ import java.time.Duration; import java.time.Instant; - + class A { public void foo() { System.out.println(Instant.now()); @@ -625,7 +629,7 @@ public void foo() { """, """ import java.time.format.DateTimeFormatter; - + class A { public void foo() { System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); @@ -643,7 +647,7 @@ void migrateJodaTypeExpressionReferencingNonJodaTypeVar() { java( """ import org.joda.time.DateTime; - + class A { public void foo() { long millis = DateTime.now().getMillis(); @@ -653,7 +657,7 @@ public void foo() { """, """ import java.time.ZonedDateTime; - + class A { public void foo() { long millis = ZonedDateTime.now().toInstant().toEpochMilli(); @@ -673,7 +677,7 @@ void dontChangeMethodsWithUnhandledArguments() { """ import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; - + class A { public void foo() { // DateTimeFormat.forStyle is unhandled so parent method should not be changed @@ -692,7 +696,7 @@ void unhandledCases() { java( """ import org.joda.time.Interval; - + class A { public void foo() { new Interval(100, 50); @@ -710,7 +714,7 @@ void methodInvocationWithStaticImport() { java( """ import static org.joda.time.DateTime.now; - + class A { public void foo() { now(); @@ -719,7 +723,7 @@ public void foo() { """, """ import java.time.ZonedDateTime; - + class A { public void foo() { ZonedDateTime.now();