From 602e728ddbd2c883f3957747b27e502912030153 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 17 Jan 2025 13:51:27 +0100 Subject: [PATCH 1/3] Pull up recipes from rewrite-migrate-java --- .../ChangeMethodInvocationReturnType.java | 116 ++++ .../java/ReplaceStringLiteralValue.java | 76 +++ .../ChangeMethodInvocationReturnTypeTest.java | 124 ++++ .../java/ReplaceStringLiteralValueTest.java | 64 ++ ...venCompilerPluginReleaseConfiguration.java | 116 ++++ ...ompilerPluginReleaseConfigurationTest.java | 561 ++++++++++++++++++ 6 files changed, 1057 insertions(+) create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodInvocationReturnType.java create mode 100644 rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralValue.java create mode 100644 rewrite-java/src/test/java/org/openrewrite/java/ChangeMethodInvocationReturnTypeTest.java create mode 100644 rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfiguration.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfigurationTest.java diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodInvocationReturnType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodInvocationReturnType.java new file mode 100644 index 00000000000..6cf60bfc3e8 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeMethodInvocationReturnType.java @@ -0,0 +1,116 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.marker.Markers; + +import static java.util.Collections.emptyList; + +@Value +@EqualsAndHashCode(callSuper = false) +public class ChangeMethodInvocationReturnType extends Recipe { + + @Option(displayName = "Method pattern", + description = "A method pattern that is used to find matching method declarations/invocations.", + example = "org.mockito.Matchers anyVararg()") + String methodPattern; + + @Option(displayName = "New method invocation return type", + description = "The fully qualified new return type of method invocation.", + example = "long") + String newReturnType; + + @Override + public String getDisplayName() { + return "Change method invocation return type"; + } + + @Override + public String getDescription() { + return "Changes the return type of a method invocation."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + private final MethodMatcher methodMatcher = new MethodMatcher(methodPattern, false); + + private boolean methodUpdated; + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + JavaType.Method type = m.getMethodType(); + if (methodMatcher.matches(method) && type != null && !newReturnType.equals(type.getReturnType().toString())) { + type = type.withReturnType(JavaType.buildType(newReturnType)); + m = m.withMethodType(type); + if (m.getName().getType() != null) { + m = m.withName(m.getName().withType(type)); + } + methodUpdated = true; + } + return m; + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + methodUpdated = false; + JavaType.FullyQualified originalType = multiVariable.getTypeAsFullyQualified(); + J.VariableDeclarations mv = super.visitVariableDeclarations(multiVariable, ctx); + + if (methodUpdated) { + JavaType newType = JavaType.buildType(newReturnType); + JavaType.FullyQualified newFieldType = TypeUtils.asFullyQualified(newType); + + maybeAddImport(newFieldType); + maybeRemoveImport(originalType); + + mv = mv.withTypeExpression(mv.getTypeExpression() == null ? + null : + new J.Identifier(mv.getTypeExpression().getId(), + mv.getTypeExpression().getPrefix(), + Markers.EMPTY, + emptyList(), + newReturnType.substring(newReturnType.lastIndexOf('.') + 1), + newType, + null + ) + ); + + mv = mv.withVariables(ListUtils.map(mv.getVariables(), var -> { + JavaType.FullyQualified varType = TypeUtils.asFullyQualified(var.getType()); + if (varType != null && !varType.equals(newType)) { + return var.withType(newType).withName(var.getName().withType(newType)); + } + return var; + })); + } + + return mv; + } + }; + } +} diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralValue.java b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralValue.java new file mode 100644 index 00000000000..64bea17ed38 --- /dev/null +++ b/rewrite-java/src/main/java/org/openrewrite/java/ReplaceStringLiteralValue.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +@Value +@EqualsAndHashCode(callSuper = false) +public class ReplaceStringLiteralValue extends Recipe { + + @Option(displayName = "Old literal `String` value", + description = "The `String` value to replace.", + example = "apple") + String oldLiteralValue; + + @Option(displayName = "New literal `String` value", + description = "The `String` value to replace with.", + example = "orange") + String newLiteralValue; + + @JsonCreator + public ReplaceStringLiteralValue(@JsonProperty("oldStringValue") String oldStringValue, @JsonProperty("newStringValue") String newStringValue) { + this.oldLiteralValue = oldStringValue; + this.newLiteralValue = newStringValue; + } + + @Override + public String getDisplayName() { + return "Replace `String` literal"; + } + + @Override + public String getDescription() { + return "Replace the value of a complete `String` literal."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + @Override + public J.Literal visitLiteral(J.Literal literal, ExecutionContext ctx) { + J.Literal lit = super.visitLiteral(literal, ctx); + if (lit.getType() == JavaType.Primitive.String && + oldLiteralValue.equals(lit.getValue())) { + return lit + .withValue(newLiteralValue) + .withValueSource('"' + newLiteralValue + '"'); + } + return lit; + } + }; + } + +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/ChangeMethodInvocationReturnTypeTest.java b/rewrite-java/src/test/java/org/openrewrite/java/ChangeMethodInvocationReturnTypeTest.java new file mode 100644 index 00000000000..339e1777c28 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/ChangeMethodInvocationReturnTypeTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ChangeMethodInvocationReturnTypeTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ChangeMethodInvocationReturnType("java.lang.Integer parseInt(String)", "long")); + } + + @Test + @DocumentExample + void replaceVariableAssignment() { + rewriteRun( + //language=java + java( + """ + class Foo { + void bar() { + int one = Integer.parseInt("1"); + } + } + """, + """ + class Foo { + void bar() { + long one = Integer.parseInt("1"); + } + } + """ + ) + ); + } + + @Test + void shouldOnlyChangeTargetMethodAssignments() { + rewriteRun( + //language=java + java( + """ + class Foo { + void bar() { + int zero = Integer.valueOf("0"); + int one = Integer.parseInt("1"); + int two = Integer.valueOf("2"); + } + } + """, + """ + class Foo { + void bar() { + int zero = Integer.valueOf("0"); + long one = Integer.parseInt("1"); + int two = Integer.valueOf("2"); + } + } + """ + ) + ); + } + + @Test + void replaceVariableAssignmentFullyQualified() { + rewriteRun( + spec -> spec.recipe(new ChangeMethodInvocationReturnType("bar.Bar bar()", "java.math.BigInteger")) + .parser(JavaParser.fromJavaVersion() + //language=java + .dependsOn( + """ + package bar; + public class Bar { + public static Integer bar() { + return null; + } + } + """ + ) + ), + //language=java + java( + """ + import bar.Bar; + class Foo { + void foo() { + Integer one = Bar.bar(); + } + } + """, + """ + import bar.Bar; + + import java.math.BigInteger; + + class Foo { + void foo() { + BigInteger one = Bar.bar(); + } + } + """ + ) + ); + } +} diff --git a/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java b/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java new file mode 100644 index 00000000000..0a3b664cbb2 --- /dev/null +++ b/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 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; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ReplaceStringLiteralValueTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new ReplaceStringLiteralValue("apple", "orange")); + } + + @DocumentExample + @Test + void replaceAppleWithOrange() { + rewriteRun( + java( + """ + class Test { + String s = "apple"; + } + """, + """ + class Test { + String s = "orange"; + } + """ + ) + ); + } + @Test + void doNotReplacePineapply() { + rewriteRun( + java( + """ + class Test { + // We only match the full String literal value + String s = "pineapple"; + } + """ + ) + ); + } + +} \ No newline at end of file diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfiguration.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfiguration.java new file mode 100644 index 00000000000..c126accf448 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfiguration.java @@ -0,0 +1,116 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.maven; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Optional; + +import static org.openrewrite.xml.AddOrUpdateChild.addOrUpdateChild; +import static org.openrewrite.xml.FilterTagChildrenVisitor.filterTagChildren; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseMavenCompilerPluginReleaseConfiguration extends Recipe { + private static final XPathMatcher PLUGINS_MATCHER = new XPathMatcher("/project/build//plugins"); + + @Option( + displayName = "Release version", + description = "The new value for the release configuration. This recipe prefers ${java.version} if defined.", + example = "11" + ) + Integer releaseVersion; + + @Override + public String getDisplayName() { + return "Use Maven compiler plugin release configuration"; + } + + @Override + public String getDescription() { + return "Replaces any explicit `source` or `target` configuration (if present) on the `maven-compiler-plugin` with " + + "`release`, and updates the `release` value if needed. Will not downgrade the Java version if the current version is higher."; + } + + @Override + public TreeVisitor getVisitor() { + return new MavenIsoVisitor() { + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + if (!PLUGINS_MATCHER.matches(getCursor())) { + return t; + } + Optional maybeCompilerPlugin = t.getChildren().stream() + .filter(plugin -> + "plugin".equals(plugin.getName()) && + "org.apache.maven.plugins".equals(plugin.getChildValue("groupId").orElse("org.apache.maven.plugins")) && + "maven-compiler-plugin".equals(plugin.getChildValue("artifactId").orElse(null))) + .findAny(); + Optional maybeCompilerPluginConfig = maybeCompilerPlugin + .flatMap(it -> it.getChild("configuration")); + if (!maybeCompilerPluginConfig.isPresent()) { + return t; + } + Xml.Tag compilerPluginConfig = maybeCompilerPluginConfig.get(); + Optional source = compilerPluginConfig.getChildValue("source"); + Optional target = compilerPluginConfig.getChildValue("target"); + Optional release = compilerPluginConfig.getChildValue("release"); + if (!source.isPresent() && + !target.isPresent() && + !release.isPresent() || + currentNewerThanProposed(release)) { + return t; + } + Xml.Tag updated = filterTagChildren(t, compilerPluginConfig, + child -> !("source".equals(child.getName()) || "target".equals(child.getName()))); + String releaseVersionValue = hasJavaVersionProperty(getCursor().firstEnclosingOrThrow(Xml.Document.class)) ? + "${java.version}" : releaseVersion.toString(); + updated = addOrUpdateChild(updated, compilerPluginConfig, + Xml.Tag.build("" + releaseVersionValue + ""), getCursor().getParentOrThrow()); + return updated; + } + + }; + } + + private boolean currentNewerThanProposed(@SuppressWarnings("OptionalUsedAsFieldOrParameterType") Optional maybeRelease) { + if (!maybeRelease.isPresent()) { + return false; + } + try { + float currentVersion = Float.parseFloat(maybeRelease.get()); + float proposedVersion = Float.parseFloat(releaseVersion.toString()); + return proposedVersion < currentVersion; + } catch (NumberFormatException e) { + return false; + } + } + + private boolean hasJavaVersionProperty(Xml.Document xml) { + return xml.getMarkers().findFirst(MavenResolutionResult.class) + .map(r -> r.getPom().getProperties().get("java.version") != null) + .orElse(false); + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfigurationTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfigurationTest.java new file mode 100644 index 00000000000..ce23a97daa5 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UseMavenCompilerPluginReleaseConfigurationTest.java @@ -0,0 +1,561 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.maven; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.maven.Assertions.pomXml; + +class UseMavenCompilerPluginReleaseConfigurationTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UseMavenCompilerPluginReleaseConfiguration(11)); + } + + @DocumentExample + @Test + void replacesSourceAndTargetConfig() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 1.8 + 1.8 + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + + + + + + + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/514") + @Test + void replaceSourceAndTargetConfigIfDefault() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + true + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + true + 11 + + + + + + + """ + ) + ); + } + + @Test + void reusesJavaVersionVariableIfAvailable() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + maven-compiler-plugin + 3.8.0 + + ${java.version} + + + + + + + """ + ) + ); + } + + @Test + void upgradesExistingReleaseConfig() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 10 + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + + + + + + + """ + ) + ); + } + + @Test + void prefersJavaVersionIfAvailable() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 10 + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + + + + + + + """ + ) + ); + } + + @Test + void notMisledByUnrelatedProperty() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 10 + ${foobar} + + + + + + + """, + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + ${foobar} + + + + + + + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/169") + @Test + void noVersionDowngrade() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + sample + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 17 + + + + + + + """) + ); + } + + @Test + void reusesJavaVersionVariableIfDefinedInParentPom() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + parent + 1.0.0 + + + 11 + + + pom + + """), + mavenProject( + "sample", + //language=xml + pomXml(""" + + + 4.0.0 + + + org.sample + parent + 1.0.0 + + + sample + 1.0.0 + + + + + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + + + + + + """, + """ + + + 4.0.0 + + + org.sample + parent + 1.0.0 + + + sample + 1.0.0 + + + + + maven-compiler-plugin + 3.8.0 + + ${java.version} + + + + + + """ + ) + ) + ); + } + + @Test + void pluginManagement() { + rewriteRun( + //language=xml + pomXml( + """ + + + 4.0.0 + org.sample + parent + 1.0.0 + + + + + maven-compiler-plugin + 3.8.0 + + 8 + + + + + + + """, + """ + + + 4.0.0 + org.sample + parent + 1.0.0 + + + + + maven-compiler-plugin + 3.8.0 + + 11 + + + + + + + """ + ) + ); + } +} From 8b01350dcd88940f43ba0ef0ee9ca579173784c3 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 17 Jan 2025 14:04:59 +0100 Subject: [PATCH 2/3] Also pull up `UpdateMavenProjectPropertyJavaVersion` --- ...UpdateMavenProjectPropertyJavaVersion.java | 142 ++++++++ ...teMavenProjectPropertyJavaVersionTest.java | 316 ++++++++++++++++++ 2 files changed, 458 insertions(+) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersion.java create mode 100644 rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersionTest.java diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersion.java new file mode 100644 index 00000000000..405ffbb4645 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersion.java @@ -0,0 +1,142 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.maven; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.xml.XPathMatcher; +import org.openrewrite.xml.tree.Xml; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UpdateMavenProjectPropertyJavaVersion extends Recipe { + + private static final List JAVA_VERSION_PROPERTIES = Arrays.asList( + "java.version", + "jdk.version", + "javaVersion", + "jdkVersion", + "maven.compiler.source", + "maven.compiler.target", + "maven.compiler.release", + "release.version"); + + private static final List JAVA_VERSION_XPATH_MATCHERS = + JAVA_VERSION_PROPERTIES.stream() + .map(property -> "/project/properties/" + property) + .map(XPathMatcher::new).collect(Collectors.toList()); + + private static final XPathMatcher PLUGINS_MATCHER = new XPathMatcher("/project/build//plugins"); + + @Option(displayName = "Java version", + description = "The Java version to upgrade to.", + example = "11") + Integer version; + + @Override + public String getDisplayName() { + return "Update Maven Java project properties"; + } + + @Override + public String getDescription() { + //language=markdown + return "The Java version is determined by several project properties, including:\n\n" + + " * `java.version`\n" + + " * `jdk.version`\n" + + " * `javaVersion`\n" + + " * `jdkVersion`\n" + + " * `maven.compiler.source`\n" + + " * `maven.compiler.target`\n" + + " * `maven.compiler.release`\n" + + " * `release.version`\n\n" + + "If none of these properties are in use and the maven compiler plugin is not otherwise configured, adds the `maven.compiler.release` property."; + } + + @Override + public TreeVisitor getVisitor() { + return new MavenIsoVisitor() { + boolean compilerPluginConfiguredExplicitly; + + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + // Update properties already defined in the current pom + Xml.Document d = super.visitDocument(document, ctx); + + // Return early if the parent appears to be within the current repository, as properties defined there will be updated + if (getResolutionResult().parentPomIsProjectPom()) { + return d; + } + + // Otherwise override remote parent's properties locally + Map currentProperties = getResolutionResult().getPom().getProperties(); + boolean foundProperty = false; + for (String property : JAVA_VERSION_PROPERTIES) { + String propertyValue = currentProperties.get(property); + if (propertyValue != null) { + foundProperty = true; + try { + if (Float.parseFloat(propertyValue) < version) { + d = (Xml.Document) new AddProperty(property, String.valueOf(version), null, false) + .getVisitor() + .visitNonNull(d, ctx); + maybeUpdateModel(); + } + } catch (NumberFormatException ex) { + // either an expression or something else, don't touch + } + } + } + + // When none of the relevant properties are explicitly configured Maven defaults to Java 8 + // The release option was added in 9 + // If no properties have yet been updated then set release explicitly + if (!foundProperty && version >= 9 && !compilerPluginConfiguredExplicitly) { + d = (Xml.Document) new AddProperty("maven.compiler.release", String.valueOf(version), null, false) + .getVisitor() + .visitNonNull(d, ctx); + maybeUpdateModel(); + } + + return d; + } + + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag t = super.visitTag(tag, ctx); + if (isPluginTag("org.apache.maven.plugins", "maven-compiler-plugin")) { + t.getChild("configuration").ifPresent(compilerPluginConfig -> { + if (compilerPluginConfig.getChildValue("source").isPresent() || + compilerPluginConfig.getChildValue("target").isPresent() || + compilerPluginConfig.getChildValue("release").isPresent()) { + compilerPluginConfiguredExplicitly = true; + } + }); + } + return t; + } + }; + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersionTest.java new file mode 100644 index 00000000000..6fc6a2fca0b --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpdateMavenProjectPropertyJavaVersionTest.java @@ -0,0 +1,316 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.maven; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.maven.Assertions.pomXml; + +@Deprecated(forRemoval = true) +class UpdateMavenProjectPropertyJavaVersionTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UpdateMavenProjectPropertyJavaVersion(17)); + } + + @DocumentExample + @Test + void basic() { + rewriteRun( + //language=xml + pomXml( + """ + + com.example + foo + 1.0.0 + 4.0 + + 11 + 11 + 11 + 11 + 11 + 11 + 11 + 11 + + + """, + """ + + com.example + foo + 1.0.0 + 4.0 + + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + + + """ + ) + ); + } + + @Test + void basicWithVariables() { + rewriteRun( + //language=xml + pomXml( + """ + + com.example + foo + 1.0.0 + 4.0 + + ${release.version} + 11 + ${release.version} + ${jdk.version} + ${maven.compiler.release} + ${maven.compiler.release} + 11 + 11 + + + """, + """ + + com.example + foo + 1.0.0 + 4.0 + + ${release.version} + 17 + ${release.version} + ${jdk.version} + ${maven.compiler.release} + ${maven.compiler.release} + 17 + 17 + + + """) + ); + } + + @Test + void updateLocalParent() { + rewriteRun( + //language=xml + pomXml( + """ + + com.example + example-parent + 1.0.0 + 4.0 + + 11 + 11 + 11 + 11 + 11 + 11 + 11 + 11 + + + """, + """ + + com.example + example-parent + 1.0.0 + 4.0 + + 17 + 17 + 17 + 17 + 17 + 17 + 17 + 17 + + + """), + mavenProject("example-child", + //language=xml + pomXml( + """ + + + com.example + example-parent + 1.0.0 + + com.example + example-child + 1.0.0 + 4.0 + + """ + ) + ) + ); + } + + @Test + void doNothingForExplicitPluginConfiguration() { + // Use UseMavenCompilerPluginReleaseConfiguration for this case + rewriteRun( + //language=xml + pomXml( + """ + + com.example + example-child + 1.0.0 + 4.0 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + 11 + 11 + + + + + + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/514") + void addReleaseIfNoOtherChangeIsMade() { + rewriteRun( + //language=xml + pomXml( + """ + + com.example + example-child + 1.0.0 + 4.0 + + """, + """ + + com.example + example-child + 1.0.0 + 4.0 + + 17 + + + """ + ) + ); + } + + @Test + void springBoot3ParentToJava17() { + // Spring Boot Starter Parent already enforces Java 17 + rewriteRun( + pomXml( + //language=xml + """ + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + + com.mycompany.app + my-app + 1 + + """ + ) + ); + } + + @Test + void springBoot3ParentToJava21() { + rewriteRun( + spec -> spec.recipe(new UpdateMavenProjectPropertyJavaVersion(21)), + pomXml( + //language=xml + """ + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + + com.mycompany.app + my-app + 1 + + """, + //language=xml + """ + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + + com.mycompany.app + my-app + 1 + + 21 + + + """ + ) + ); + } +} From c71ec4deb06823d72479db8b8ce4491300185006 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 17 Jan 2025 14:05:23 +0100 Subject: [PATCH 3/3] Apply formatter --- .../org/openrewrite/java/ReplaceStringLiteralValueTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java b/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java index 0a3b664cbb2..e40fd0f9f93 100644 --- a/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java +++ b/rewrite-java/src/test/java/org/openrewrite/java/ReplaceStringLiteralValueTest.java @@ -61,4 +61,4 @@ class Test { ); } -} \ No newline at end of file +}