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

Joda-Time to Java time: Add templates for Joda Interval to Threeten-extra Interval #617

Merged
merged 5 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies {

testImplementation("com.google.guava:guava:33.0.0-jre")
testImplementation("joda-time:joda-time:2.12.3")
testImplementation("org.threeten:threeten-extra:1.8.0")

testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr353")
testRuntimeOnly("com.fasterxml.jackson.core:jackson-core")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
maybeRemoveImport(JODA_DURATION);
maybeRemoveImport(JODA_ABSTRACT_INSTANT);
maybeRemoveImport(JODA_INSTANT);
maybeRemoveImport(JODA_INTERVAL);
maybeRemoveImport("java.util.Locale");

maybeAddImport(JAVA_DATE_TIME);
Expand All @@ -79,6 +80,7 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
maybeAddImport(JAVA_TEMPORAL_ISO_FIELDS);
maybeAddImport(JAVA_CHRONO_FIELD);
maybeAddImport(JAVA_UTIL_DATE);
maybeAddImport(THREE_TEN_EXTRA_INTERVAL);
return super.visitCompilationUnit(cu, ctx);
}

Expand Down Expand Up @@ -168,9 +170,11 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
if (!isJodaVarRef(ident)) {
return super.visitIdentifier(ident, ctx);
}
Optional<NamedVariable> mayBeVar = findVarInScope(ident.getSimpleName());
if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) {
return ident;
if (this.safeMigration) {
Optional<NamedVariable> mayBeVar = findVarInScope(ident.getSimpleName());
if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) {
return ident;
}
}

JavaType.FullyQualified jodaType = ((JavaType.Class) ident.getType());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 lombok.Getter;
import org.openrewrite.java.JavaParser;
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 AbstractIntervalTemplates implements Templates {
private final MethodMatcher getStart = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " getStart()");
private final MethodMatcher getEnd = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " getEnd()");
private final MethodMatcher toDuration = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " toDuration()");
private final MethodMatcher toDurationMillis = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " toDurationMillis()");
private final MethodMatcher contains = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " contains(long)");

private final JavaTemplate getStartTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.getStart().atZone(ZoneId.systemDefault())")
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
.imports(JAVA_ZONE_ID)
.build();
private final JavaTemplate getEndTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.getEnd().atZone(ZoneId.systemDefault())")
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
.imports(JAVA_ZONE_ID)
.build();
private final JavaTemplate toDurationTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.toDuration()")
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
.build();
private final JavaTemplate toDurationMillisTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.toDuration().toMillis()")
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
.build();
private final JavaTemplate containsTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.contains(Instant.ofEpochMilli(#{any(long)}))")
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
.imports(JAVA_INSTANT)
.build();

@Getter
private final List<MethodTemplate> templates = new ArrayList<MethodTemplate>() {
{
add(new MethodTemplate(getStart, getStartTemplate));
add(new MethodTemplate(getEnd, getEndTemplate));
add(new MethodTemplate(toDuration, toDurationTemplate));
add(new MethodTemplate(toDurationMillis, toDurationMillisTemplate));
add(new MethodTemplate(contains, containsTemplate));
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class AllTemplates {
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 + "<constructor>(..)");
private static final MethodMatcher ANY_NEW_INTERVAL = new MethodMatcher(JODA_INTERVAL + "<constructor>(..)");
private static final MethodMatcher ANY_ABSTRACT_INTERVAL = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " *(..)");

private static List<MatcherAndTemplates> templates = new ArrayList<MatcherAndTemplates>() {
{
Expand All @@ -55,6 +57,8 @@ public class AllTemplates {
add(new MatcherAndTemplates(ANY_DATE_TIMEZONE, new TimeZoneTemplates()));
add(new MatcherAndTemplates(ANY_INSTANT, new InstantTemplates()));
add(new MatcherAndTemplates(ANY_NEW_INSTANT, new InstantTemplates()));
add(new MatcherAndTemplates(ANY_NEW_INTERVAL, new IntervalTemplates()));
add(new MatcherAndTemplates(ANY_ABSTRACT_INTERVAL, new AbstractIntervalTemplates()));
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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 lombok.Getter;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.tree.Expression;

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

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

public class IntervalTemplates implements Templates {
private final MethodMatcher interval = new MethodMatcher(JODA_INTERVAL + " <constructor>(long, long)");
private final MethodMatcher intervalWithTimeZone = new MethodMatcher(JODA_INTERVAL + " <constructor>(long, long, " + JODA_DATE_TIME_ZONE + ")");
private final MethodMatcher intervalWithDateTime = new MethodMatcher(JODA_INTERVAL + " <constructor>(" + JODA_READABLE_INSTANT + ", " + JODA_READABLE_INSTANT + ")");
private final MethodMatcher intervalWithDateTimeAndDuration = new MethodMatcher(JODA_INTERVAL + " <constructor>(" + JODA_READABLE_INSTANT + ", " + JODA_READABLE_DURATION + ")");

private final JavaTemplate intervalTemplate = JavaTemplate.builder("Interval.of(Instant.ofEpochMilli(#{any(long)}), Instant.ofEpochMilli(#{any(long)}))")
.javaParser(JavaParser.fromJavaVersion().classpath("threeten"))
.imports(JAVA_INSTANT, THREE_TEN_EXTRA_INTERVAL)
.build();
private final JavaTemplate intervalWithDateTimeTemplate = JavaTemplate.builder("Interval.of(#{any(" + JAVA_DATE_TIME + ")}.toInstant(), #{any(" + JAVA_DATE_TIME + ")}.toInstant())")
.javaParser(JavaParser.fromJavaVersion().classpath("threeten"))
.imports(THREE_TEN_EXTRA_INTERVAL)
.build();
private final JavaTemplate intervalWithDateTimeAndDurationTemplate = JavaTemplate.builder("Interval.of(#{any(" + JAVA_DATE_TIME + ")}.toInstant(), #{any(" + JAVA_DURATION + ")})")
.javaParser(JavaParser.fromJavaVersion().classpath("threeten"))
.imports(THREE_TEN_EXTRA_INTERVAL)
.build();

@Getter
private final List<MethodTemplate> templates = new ArrayList<MethodTemplate>() {
{
add(new MethodTemplate(interval, intervalTemplate));
add(new MethodTemplate(intervalWithTimeZone, intervalTemplate,
m -> new Expression[]{m.getArguments().get(0), m.getArguments().get(1)}));
add(new MethodTemplate(intervalWithDateTime, intervalWithDateTimeTemplate));
add(new MethodTemplate(intervalWithDateTimeAndDuration, intervalWithDateTimeAndDurationTemplate));
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class TimeClassMap {
put(JODA_TIME_FORMATTER, javaTypeClass(JAVA_TIME_FORMATTER, object));
put(JODA_DURATION, javaTypeClass(JAVA_DURATION, object));
put(JODA_READABLE_DURATION, javaTypeClass(JAVA_DURATION, object));
put(JODA_INTERVAL, javaTypeClass(THREE_TEN_EXTRA_INTERVAL, object));
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class TimeClassNames {
public static final String JODA_TIME_PKG = "org.joda.time";
public static final String JODA_ABSTRACT_DATE_TIME = JODA_TIME_PKG + ".base.AbstractDateTime";
public static final String JODA_ABSTRACT_DURATION = JODA_TIME_PKG + ".base.AbstractDuration";
public static final String JODA_ABSTRACT_INTERVAL = JODA_TIME_PKG + ".base.AbstractInterval";
public static final String JODA_BASE_DATE_TIME = JODA_TIME_PKG + ".base.BaseDateTime";
public static final String JODA_DATE_TIME = JODA_TIME_PKG + ".DateTime";
public static final String JODA_DATE_TIME_ZONE = JODA_TIME_PKG + ".DateTimeZone";
Expand All @@ -39,7 +40,9 @@ public class TimeClassNames {
public static final String JODA_DURATION = JODA_TIME_PKG + ".Duration";
public static final String JODA_READABLE_DURATION = JODA_TIME_PKG + ".ReadableDuration";
public static final String JODA_ABSTRACT_INSTANT = JODA_TIME_PKG + ".base.AbstractInstant";
public static final String JODA_READABLE_INSTANT = JODA_TIME_PKG + ".ReadableInstant";
public static final String JODA_INSTANT = JODA_TIME_PKG + ".Instant";
public static final String JODA_INTERVAL = JODA_TIME_PKG + ".Interval";

// Java Time classes
public static final String JAVA_TIME_PKG = "java.time";
Expand All @@ -55,4 +58,8 @@ public class TimeClassNames {
public static final String JAVA_LOCAL_TIME = JAVA_TIME_PKG + ".LocalTime";
public static final String JAVA_TEMPORAL_ISO_FIELDS = JAVA_TIME_PKG + ".temporal.IsoFields";
public static final String JAVA_CHRONO_FIELD = JAVA_TIME_PKG + ".temporal.ChronoField";

// ThreeTen-Extra classes
public static final String THREE_TEN_EXTRA_PKG = "org.threeten.extra";
public static final String THREE_TEN_EXTRA_INTERVAL = THREE_TEN_EXTRA_PKG + ".Interval";
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.openrewrite.java.migrate.joda.templates;

import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
Expand All @@ -35,6 +36,7 @@ public class VarTemplates {
put(JODA_LOCAL_TIME, JAVA_LOCAL_TIME);
put(JODA_DATE_TIME_ZONE, JAVA_ZONE_ID);
put(JODA_DURATION, JAVA_DURATION);
put(JODA_INTERVAL, THREE_TEN_EXTRA_INTERVAL);
}
};

Expand Down Expand Up @@ -65,8 +67,9 @@ public static Optional<JavaTemplate> getTemplate(J.VariableDeclarations variable
}
}
return Optional.of(JavaTemplate.builder(template.toString())
.imports(typeName)
.build());
.imports(typeName)
.javaParser(JavaParser.fromJavaVersion().classpath("threeten"))
.build());
}

public static Optional<JavaTemplate> getTemplate(J.Assignment assignment) {
Expand Down
33 changes: 33 additions & 0 deletions src/main/resources/META-INF/rewrite/no-joda-time.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#
# Copyright 2021 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.
#
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.migrate.joda.NoJodaTime
displayName: Prefer the Java standard library instead of JodaTime
description: >-
Before Java 8, Java lacked a robust date and time library, leading to the widespread use of Joda-Time to fill this
gap. With the release of Java 8, the java.time package was introduced, incorporating most of Joda-Time's concepts.
Features deemed too specialized or bulky for java.time were included in the ThreeTen-Extra library. This recipe
migrates JodaTime types to java.time and threeten-extra types.
tags:
- joda-time
recipeList:
- org.openrewrite.java.migrate.joda.JodaTimeRecipe
- org.openrewrite.java.dependencies.AddDependency:
groupId: org.threeten
artifactId: threeten-extra
version: 1.8.0
onlyIfUsing: org.joda.time.*Interval*
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class JodaTimeVisitorTest implements RewriteTest {
public void defaults(RecipeSpec spec) {
spec
.recipe(toRecipe(() -> new JodaTimeVisitor(new JodaTimeRecipe.Accumulator(), true, new LinkedList<>())))
.parser(JavaParser.fromJavaVersion().classpath("joda-time"));
.parser(JavaParser.fromJavaVersion().classpath("joda-time", "threeten-extra"));
}

@DocumentExample
Expand Down Expand Up @@ -695,11 +695,11 @@ void unhandledCases() {
rewriteRun(
java(
"""
import org.joda.time.Interval;
import org.joda.time.PeriodType;

class A {
public void foo() {
new Interval(100, 50);
PeriodType.standard();
}
}
"""
Expand Down Expand Up @@ -782,11 +782,90 @@ void unhandledVarDeclaration() {
rewriteRun(
java(
"""
import org.joda.time.PeriodType;

class A {
public void foo(PeriodType periodType) {
periodType = PeriodType.days();
}
}
"""
)
);
}

@Test
void migrateInterval() {
// language=java
rewriteRun(
java(
"""
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Interval;
import org.joda.time.DateTimeZone;

class A {
public void foo() {
System.out.println(new Interval(50, 100));
System.out.println(new Interval(50, 100, DateTimeZone.UTC));
System.out.println(new Interval(DateTime.now(), DateTime.now().plusDays(1)));
System.out.println(new Interval(DateTime.now(), Duration.standardDays(1)));
}
}
""",
"""
import org.threeten.extra.Interval;

import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;

class A {
public void foo(Interval interval) {
interval = new Interval(100, 50);
public void foo() {
System.out.println(Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)));
System.out.println(Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)));
System.out.println(Interval.of(ZonedDateTime.now().toInstant(), ZonedDateTime.now().plusDays(1).toInstant()));
System.out.println(Interval.of(ZonedDateTime.now().toInstant(), Duration.ofDays(1)));
}
}
"""
)
);
}

@Test
void migrateAbstractInterval() {
// language=java
rewriteRun(
java(
"""
import org.joda.time.DateTime;
import org.joda.time.Interval;

class A {
public void foo() {
new Interval(50, 100).getStart();
new Interval(50, 100).getEnd();
new Interval(50, 100).toDuration();
new Interval(50, 100).toDurationMillis();
new Interval(50, 100).contains(75);
}
}
""",
"""
import org.threeten.extra.Interval;

import java.time.Instant;
import java.time.ZoneId;

class A {
public void foo() {
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).getStart().atZone(ZoneId.systemDefault());
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).getEnd().atZone(ZoneId.systemDefault());
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).toDuration();
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).toDuration().toMillis();
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).contains(Instant.ofEpochMilli(75));
}
}
"""
Expand Down
Loading
Loading