Skip to content

Commit

Permalink
[1/x] Implement joda to java time migration recipe (#567)
Browse files Browse the repository at this point in the history
* [1/x] Implement joda to java time migration recipe

* Update src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* add licence & test fix

* license

* Update src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/main/java/org/openrewrite/java/migrate/joda/templates/DateTimeFormatTemplates.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/main/java/org/openrewrite/java/migrate/joda/templates/DateTimeFormatTemplates.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/main/java/org/openrewrite/java/migrate/joda/templates/DurationTemplates.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/main/java/org/openrewrite/java/migrate/joda/templates/DateTimeTemplates.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/main/java/org/openrewrite/java/migrate/joda/templates/MethodTemplate.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassNames.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/main/java/org/openrewrite/java/migrate/joda/templates/TimeZoneTemplates.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/main/java/org/openrewrite/java/migrate/joda/templates/TimeZoneTemplates.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: Sam Snyder <sam@moderne.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 4, 2024
1 parent ae8faf5 commit 631f5dc
Show file tree
Hide file tree
Showing 9 changed files with 1,652 additions and 0 deletions.
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
180 changes: 180 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,180 @@
/*
* 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.migrate.joda.templates.*;
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,133 @@
/*
* 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.
*/
/*
* 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.
*/
/*
* 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.templates;

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

0 comments on commit 631f5dc

Please sign in to comment.