Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1/x] Implement joda to java time migration recipe #567

Merged
merged 18 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b0cf791
[1/x] Implement joda to java time migration recipe
amishra-u Oct 4, 2024
5d8c43e
Update src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisito…
sambsnyd Oct 4, 2024
64cc10d
add licence & test fix
amishra-u Oct 4, 2024
89a15ed
license
amishra-u Oct 4, 2024
85d2520
Update src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisito…
sambsnyd Oct 4, 2024
58a51c7
Update src/main/java/org/openrewrite/java/migrate/joda/templates/Date…
sambsnyd Oct 4, 2024
1eaf297
Update src/main/java/org/openrewrite/java/migrate/joda/templates/Date…
sambsnyd Oct 4, 2024
e4a36ea
Update src/main/java/org/openrewrite/java/migrate/joda/templates/Dura…
sambsnyd Oct 4, 2024
c865075
Update src/main/java/org/openrewrite/java/migrate/joda/templates/Date…
sambsnyd Oct 4, 2024
8d546ac
Update src/main/java/org/openrewrite/java/migrate/joda/templates/Meth…
sambsnyd Oct 4, 2024
f0b9475
Update src/main/java/org/openrewrite/java/migrate/joda/templates/Time…
sambsnyd Oct 4, 2024
22e0f25
Update src/main/java/org/openrewrite/java/migrate/joda/templates/Time…
sambsnyd Oct 4, 2024
53a1b9c
Update src/main/java/org/openrewrite/java/migrate/joda/templates/Time…
sambsnyd Oct 4, 2024
3d3cd47
Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisito…
sambsnyd Oct 4, 2024
737ded2
Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisito…
sambsnyd Oct 4, 2024
c642b64
Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisito…
sambsnyd Oct 4, 2024
25dc322
Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisito…
sambsnyd Oct 4, 2024
3d133f8
Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisito…
sambsnyd Oct 4, 2024
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
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
implementation("org.openrewrite.recipe:rewrite-static-analysis:$rewriteVersion")
implementation("org.openrewrite.recipe:rewrite-jenkins:$rewriteVersion")
implementation("org.openrewrite:rewrite-templating:$rewriteVersion")
implementation("org.openrewrite.meta:rewrite-analysis:$rewriteVersion")

runtimeOnly("org.openrewrite:rewrite-java-8")
runtimeOnly("org.openrewrite:rewrite-java-11")
Expand All @@ -48,6 +49,7 @@ dependencies {
testImplementation("org.assertj:assertj-core:latest.release")

testImplementation("com.google.guava:guava:33.0.0-jre")
testImplementation("joda-time:joda-time:2.12.3")

testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr353")
testRuntimeOnly("com.fasterxml.jackson.core:jackson-core")
Expand Down
184 changes: 184 additions & 0 deletions src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.migrate.joda;

import lombok.NonNull;
import org.openrewrite.ExecutionContext;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.JavaTemplate;
sambsnyd marked this conversation as resolved.
Show resolved Hide resolved
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.migrate.joda.templates.DateTimeFormatTemplates;
import org.openrewrite.java.migrate.joda.templates.DateTimeTemplates;
import org.openrewrite.java.migrate.joda.templates.DurationTemplates;
sambsnyd marked this conversation as resolved.
Show resolved Hide resolved
import org.openrewrite.java.migrate.joda.templates.MethodTemplate;
import org.openrewrite.java.migrate.joda.templates.TimeZoneTemplates;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.MethodCall;
import org.openrewrite.java.tree.TypeUtils;

import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;

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


public class JodaTimeVisitor extends JavaVisitor<ExecutionContext> {

private final MethodMatcher anyNewDateTime = new MethodMatcher(JODA_DATE_TIME + "<constructor>(..)");
private final MethodMatcher anyDateTime = new MethodMatcher(JODA_DATE_TIME + " *(..)");
private final MethodMatcher anyBaseDateTime = new MethodMatcher(JODA_BASE_DATE_TIME + " *(..)");
private final MethodMatcher zoneFor = new MethodMatcher(JODA_DATE_TIME_ZONE + " for*(..)");
private final MethodMatcher anyTimeFormatter = new MethodMatcher(JODA_TIME_FORMAT + " *(..)");
private final MethodMatcher anyNewDuration = new MethodMatcher(JODA_DURATION + "<constructor>(..)");
private final MethodMatcher anyDuration = new MethodMatcher(JODA_DURATION + " *(..)");

@Override
public J visitCompilationUnit(@NonNull J.CompilationUnit cu, @NonNull ExecutionContext ctx) {
maybeRemoveImport(JODA_DATE_TIME);
maybeRemoveImport(JODA_DATE_TIME_ZONE);
maybeRemoveImport(JODA_TIME_FORMAT);
maybeRemoveImport(JODA_DURATION);
maybeRemoveImport("java.util.Locale");

maybeAddImport(JAVA_DATE_TIME);
maybeAddImport(JAVA_ZONE_OFFSET);
maybeAddImport(JAVA_ZONE_ID);
maybeAddImport(JAVA_INSTANT);
maybeAddImport(JAVA_TIME_FORMATTER);
maybeAddImport(JAVA_TIME_FORMAT_STYLE);
maybeAddImport(JAVA_DURATION);
maybeAddImport(JAVA_LOCAL_DATE);
maybeAddImport(JAVA_LOCAL_TIME);
maybeAddImport(JAVA_TEMPORAL_ISO_FIELDS);
maybeAddImport(JAVA_CHRONO_FIELD);
return super.visitCompilationUnit(cu, ctx);
}

@Override
public J visitVariable(@NonNull J.VariableDeclarations.NamedVariable variable, @NonNull ExecutionContext ctx) {
// TODO implement logic for safe variable migration
if (variable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return variable;
}
return super.visitVariable(variable, ctx);
}

@Override
public J visitNewClass(@NonNull J.NewClass newClass, @NonNull ExecutionContext ctx) {
MethodCall updated = (MethodCall) super.visitNewClass(newClass, ctx);
if (hasJodaType(updated.getArguments())) {
return newClass;
}
if (anyNewDateTime.matches(newClass)) {
return applyTemplate(newClass, updated, DateTimeTemplates.getTemplates()).orElse(newClass);
}
if (anyNewDuration.matches(newClass)) {
return applyTemplate(newClass, updated, DurationTemplates.getTemplates()).orElse(newClass);
}
if (areArgumentsAssignable(updated)) {
return updated;
}
return newClass;
}


@Override
public J visitMethodInvocation(@NonNull J.MethodInvocation method, @NonNull ExecutionContext ctx) {
J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx);
if (hasJodaType(m.getArguments()) || isJodaVarRef(m.getSelect())) {
return method;
}
if (zoneFor.matches(method)) {
return applyTemplate(method, m, TimeZoneTemplates.getTemplates()).orElse(method);
}
if (anyDateTime.matches(method) || anyBaseDateTime.matches(method)) {
return applyTemplate(method, m, DateTimeTemplates.getTemplates()).orElse(method);
}
if (anyTimeFormatter.matches(method)) {
return applyTemplate(method, m, DateTimeFormatTemplates.getTemplates()).orElse(method);
}
if (anyDuration.matches(method)) {
return applyTemplate(method, m, DurationTemplates.getTemplates()).orElse(method);
}
if (areArgumentsAssignable(m)) {
return m;
}
return method;
}

@Override
public 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) && f.getSimpleName().equals("UTC")) {
return JavaTemplate.builder("ZoneOffset.UTC")
.imports(JAVA_ZONE_OFFSET)
.build()
.apply(updateCursor(f), f.getCoordinates().replace());
}
return f;
}

private boolean hasJodaType(List<Expression> exprs) {
for (Expression expr : exprs) {
JavaType exprType = expr.getType();
if (exprType != null && exprType.isAssignableFrom(Pattern.compile("org.joda.time.*"))) {
return true;
}
}
return false;
}

private Optional<MethodCall> applyTemplate(MethodCall original, MethodCall updated, List<MethodTemplate> templates) {
for (MethodTemplate template : templates) {
if (template.getMatcher().matches(original)) {
Expression[] args = template.getTemplateArgsFunc().apply(updated);
if (args.length == 0) {
return Optional.of(template.getTemplate().apply(updateCursor(updated), updated.getCoordinates().replace()));
}
return Optional.of(template.getTemplate().apply(updateCursor(updated), updated.getCoordinates().replace(), args));
}
}
return Optional.empty(); // unhandled case
}

private boolean areArgumentsAssignable(MethodCall m) {
if (m.getArguments().size() != m.getMethodType().getParameterTypes().size()) {
return false;
}
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 boolean isJodaVarRef(Expression expr) {
if (expr.getType() == null || !expr.getType().isAssignableFrom(JODA_CLASS_PATTERN)) {
return false;
}
if (expr instanceof J.FieldAccess) {
return ((J.FieldAccess) expr).getName().getFieldType() != null;
}
if (expr instanceof J.Identifier) {
return ((J.Identifier) expr).getFieldType() != null;
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.openrewrite.java.migrate.joda.templates;
sambsnyd marked this conversation as resolved.
Show resolved Hide resolved
sambsnyd marked this conversation as resolved.
Show resolved Hide resolved

import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;

import java.util.ArrayList;
import java.util.List;

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

public class DateTimeFormatTemplates {
private final MethodMatcher forPattern = new MethodMatcher(JODA_TIME_FORMAT + " forPattern(String)");
private final MethodMatcher forStyle = new MethodMatcher(JODA_TIME_FORMAT + " forStyle(String)");
private final MethodMatcher patternForStyle = new MethodMatcher(JODA_TIME_FORMAT + " patternForStyle(String, java.util.Locale)");
private final MethodMatcher shortDate = new MethodMatcher(JODA_TIME_FORMAT + " shortDate()");
private final MethodMatcher mediumDate = new MethodMatcher(JODA_TIME_FORMAT + " mediumDate()");
private final MethodMatcher longDate = new MethodMatcher(JODA_TIME_FORMAT + " longDate()");
private final MethodMatcher fullDate = new MethodMatcher(JODA_TIME_FORMAT + " fullDate()");
private final MethodMatcher shortTime = new MethodMatcher(JODA_TIME_FORMAT + " shortTime()");
private final MethodMatcher mediumTime = new MethodMatcher(JODA_TIME_FORMAT + " mediumTime()");
private final MethodMatcher longTime = new MethodMatcher(JODA_TIME_FORMAT + " longTime()");
private final MethodMatcher fullTime = new MethodMatcher(JODA_TIME_FORMAT + " fullTime()");
private final MethodMatcher shortDateTime = new MethodMatcher(JODA_TIME_FORMAT + " shortDateTime()");
private final MethodMatcher mediumDateTime = new MethodMatcher(JODA_TIME_FORMAT + " mediumDateTime()");
private final MethodMatcher longDateTime = new MethodMatcher(JODA_TIME_FORMAT + " longDateTime()");
private final MethodMatcher fullDateTime = new MethodMatcher(JODA_TIME_FORMAT + " fullDateTime()");

private final JavaTemplate ofPatternTemplate = JavaTemplate.builder("DateTimeFormatter.ofPattern(#{any(String)})")
.imports("java.time.format.DateTimeFormatter")
.build();
private final JavaTemplate shortDateTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate mediumDateTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate longDateTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate fullDateTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate shortTimeTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate mediumTimeTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate longTimeTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate fullTimeTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate shortDateTimeTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate mediumDateTimeTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate longDateTimeTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.LONG)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final JavaTemplate fullDateTimeTemplate = JavaTemplate.builder("DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.FULL)")
.imports(JAVA_TIME_FORMATTER, JAVA_TIME_FORMAT_STYLE)
.build();
private final List<MethodTemplate> templates = new ArrayList<MethodTemplate>() {
{
add(new MethodTemplate(forPattern, ofPatternTemplate));
add(new MethodTemplate(shortDate, shortDateTemplate));
add(new MethodTemplate(mediumDate, mediumDateTemplate));
add(new MethodTemplate(longDate, longDateTemplate));
add(new MethodTemplate(fullDate, fullDateTemplate));
add(new MethodTemplate(shortTime, shortTimeTemplate));
add(new MethodTemplate(mediumTime, mediumTimeTemplate));
add(new MethodTemplate(longTime, longTimeTemplate));
add(new MethodTemplate(fullTime, fullTimeTemplate));
add(new MethodTemplate(shortDateTime, shortDateTimeTemplate));
add(new MethodTemplate(mediumDateTime, mediumDateTimeTemplate));
add(new MethodTemplate(longDateTime, longDateTimeTemplate));
add(new MethodTemplate(fullDateTime, fullDateTimeTemplate));
}
};

public static List<MethodTemplate> getTemplates() {
return new DateTimeFormatTemplates().templates;
}
}
Loading
Loading