diff --git a/build.gradle.kts b/build.gradle.kts index d8d2c82ca1..e1f2080f80 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") 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 be5e5652f3..c99fb43bb1 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java @@ -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); @@ -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); } @@ -168,9 +170,11 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx) if (!isJodaVarRef(ident)) { return super.visitIdentifier(ident, ctx); } - Optional mayBeVar = findVarInScope(ident.getSimpleName()); - if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) { - return ident; + if (this.safeMigration) { + Optional mayBeVar = findVarInScope(ident.getSimpleName()); + if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) { + return ident; + } } JavaType.FullyQualified jodaType = ((JavaType.Class) ident.getType()); diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/AbstractIntervalTemplates.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/AbstractIntervalTemplates.java new file mode 100644 index 0000000000..9028b507ca --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/AbstractIntervalTemplates.java @@ -0,0 +1,64 @@ +/* + * Copyright 2024 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. + */ +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 templates = new ArrayList() { + { + 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)); + } + }; +} 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 dc7e76edc2..4be5bb6530 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 @@ -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 + "(..)"); + private static final MethodMatcher ANY_NEW_INTERVAL = new MethodMatcher(JODA_INTERVAL + "(..)"); + private static final MethodMatcher ANY_ABSTRACT_INTERVAL = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " *(..)"); private static List templates = new ArrayList() { { @@ -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())); } }; diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/IntervalTemplates.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/IntervalTemplates.java new file mode 100644 index 0000000000..38fb212c25 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/IntervalTemplates.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 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. + */ +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 + " (long, long)"); + private final MethodMatcher intervalWithTimeZone = new MethodMatcher(JODA_INTERVAL + " (long, long, " + JODA_DATE_TIME_ZONE + ")"); + private final MethodMatcher intervalWithDateTime = new MethodMatcher(JODA_INTERVAL + " (" + JODA_READABLE_INSTANT + ", " + JODA_READABLE_INSTANT + ")"); + private final MethodMatcher intervalWithDateTimeAndDuration = new MethodMatcher(JODA_INTERVAL + " (" + 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 templates = new ArrayList() { + { + 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)); + } + }; +} 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 0b37602976..7663a8389c 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 @@ -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)); } }; diff --git a/src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassNames.java b/src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassNames.java index 428284174e..e2007c2efa 100644 --- a/src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassNames.java +++ b/src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassNames.java @@ -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"; @@ -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"; @@ -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"; } 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 d555492a33..e6c70ede2a 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 @@ -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; @@ -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); } }; @@ -65,8 +67,9 @@ public static Optional 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 getTemplate(J.Assignment assignment) { diff --git a/src/main/resources/META-INF/rewrite/no-joda-time.yml b/src/main/resources/META-INF/rewrite/no-joda-time.yml new file mode 100644 index 0000000000..d7b2231049 --- /dev/null +++ b/src/main/resources/META-INF/rewrite/no-joda-time.yml @@ -0,0 +1,33 @@ +# +# 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. +# +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.migrate.joda.NoJodaTime +displayName: Prefer the Java standard library instead of Joda-Time +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 Joda-Time types to `java.time` and `threeten-extra` types. +tags: + - joda-time +recipeList: + - org.openrewrite.java.dependencies.AddDependency: + groupId: org.threeten + artifactId: threeten-extra + version: 1.8.0 + onlyIfUsing: org.joda.time.*Interval* + - org.openrewrite.java.migrate.joda.JodaTimeRecipe 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 71118e3a88..bdf930d1cc 100644 --- a/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java +++ b/src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java @@ -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 @@ -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(); } } """ @@ -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)); } } """ diff --git a/src/test/java/org/openrewrite/java/migrate/joda/NoJodaTimeTest.java b/src/test/java/org/openrewrite/java/migrate/joda/NoJodaTimeTest.java new file mode 100644 index 0000000000..f153a00777 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/joda/NoJodaTimeTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2024 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. + */ +package org.openrewrite.java.migrate.joda; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.*; +import static org.openrewrite.maven.Assertions.pomXml; + +class NoJodaTimeTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .recipeFromResource("/META-INF/rewrite/no-joda-time.yml", "org.openrewrite.java.migrate.joda.NoJodaTime") + .parser(JavaParser.fromJavaVersion().classpath("joda-time", "threeten-extra")); + } + + @DocumentExample + @Test + void migrateJodaTime() { + rewriteRun( + mavenProject("foo", + srcMainJava( + // language=java + java( + """ + import org.joda.time.DateTime; + import org.joda.time.Interval; + + class A { + void foo() { + DateTime dt = new DateTime(); + DateTime dt1 = new DateTime().plusDays(1); + Interval i = new Interval(dt, dt1); + System.out.println(i.toDuration()); + } + } + """, + """ + import org.threeten.extra.Interval; + + import java.time.ZonedDateTime; + + class A { + void foo() { + ZonedDateTime dt = ZonedDateTime.now(); + ZonedDateTime dt1 = ZonedDateTime.now().plusDays(1); + Interval i = Interval.of(dt.toInstant(), dt1.toInstant()); + System.out.println(i.toDuration()); + } + } + """ + ), + //language=xml + pomXml( + """ + + 4.0.0 + com.example.foobar + foobar-core + 1.0.0 + + + joda-time + joda-time + 2.12.3 + + + + """, + """ + + 4.0.0 + com.example.foobar + foobar-core + 1.0.0 + + + joda-time + joda-time + 2.12.3 + + + org.threeten + threeten-extra + 1.8.0 + + + + """ + ) + ) + ) + ); + } +}