diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddAnnotationProcessor.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddAnnotationProcessor.java new file mode 100644 index 0000000000..fbc2644eb3 --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddAnnotationProcessor.java @@ -0,0 +1,115 @@ +/* + * 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.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.internal.ListUtils; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; +import org.openrewrite.xml.XmlIsoVisitor; +import org.openrewrite.xml.tree.Xml; + +import java.util.List; + +import static org.openrewrite.maven.trait.Traits.mavenPlugin; + +@Value +@EqualsAndHashCode(callSuper = false) +public class AddAnnotationProcessor extends Recipe { + private static final String MAVEN_COMPILER_PLUGIN_GROUP_ID = "org.apache.maven.plugins"; + private static final String MAVEN_COMPILER_PLUGIN_ARTIFACT_ID = "maven-compiler-plugin"; + + @Option(displayName = "Group", + description = "The first part of the coordinate 'org.projectlombok:lombok-mapstruct-binding:0.2.0' of the processor to add.", + example = "org.projectlombok") + String groupId; + + @Option(displayName = "Artifact", + description = "The second part of a coordinate 'org.projectlombok:lombok-mapstruct-binding:0.2.0' of the processor to add.", + example = "lombok-mapstruct-binding") + String artifactId; + + @Option(displayName = "Version", + description = "The third part of a coordinate 'org.projectlombok:lombok-mapstruct-binding:0.2.0' of the processor to add. " + + "Note that an exact version is expected", + example = "0.2.0") + String version; + + @Override + public String getDisplayName() { + return "Add an annotation processor to `maven-compiler-plugin`"; + } + + @Override + public String getDescription() { + return "Add an annotation processor to the maven compiler plugin. Will not do anything if it already exists. " + + "Also doesn't add anything when no other annotation processors are defined yet. " + + "(Perhaps `ChangePluginConfiguration` can be used)."; + } + + @Override + public TreeVisitor getVisitor() { + return new MavenVisitor() { + @Override + public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag plugins = (Xml.Tag) super.visitTag(tag, ctx); + plugins = (Xml.Tag) mavenPlugin().asVisitor(plugin -> { + if (MAVEN_COMPILER_PLUGIN_GROUP_ID.equals(plugin.getGroupId()) && + MAVEN_COMPILER_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId())) { + return new XmlIsoVisitor() { + @Override + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { + Xml.Tag tg = super.visitTag(tag, ctx); + if ("annotationProcessorPaths".equals(tg.getName())) { + for (int i = 0; i < tg.getChildren().size(); i++) { + Xml.Tag child = tg.getChildren().get(i); + if (groupId.equals(child.getChildValue("groupId").orElse(null)) && + artifactId.equals(child.getChildValue("artifactId").orElse(null))) { + if (!version.equals(child.getChildValue("version").orElse(null))) { + String oldVersion = child.getChildValue("version").orElse(""); + VersionComparator comparator = Semver.validate(oldVersion, null).getValue(); + if (comparator.compare(version, oldVersion) > 0) { + List tags = tg.getChildren(); + tags.set(i, child.withChildValue("version", version)); + return tg.withContent(tags); + } + } + return tg; + } + } + return tg.withContent(ListUtils.concat(tg.getChildren(), Xml.Tag.build(String.format( + "\n%s\n%s\n%s\n", + groupId, artifactId, version)))); + } + return tg; + } + }.visitTag(plugin.getTree(), ctx); + } + return plugin.getTree(); + }).visitNonNull(plugins, 0); + if (plugins != tag) { + plugins = autoFormat(plugins, ctx); + } + return plugins; + } + }; + } +} diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenPlugin.java b/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenPlugin.java index c4be15ef41..7b618cd750 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenPlugin.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenPlugin.java @@ -18,6 +18,7 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; +import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.trait.Trait; import org.openrewrite.xml.XPathMatcher; import org.openrewrite.xml.tree.Xml; @@ -29,6 +30,7 @@ public class MavenPlugin implements Trait { static final XPathMatcher PLUGIN_MATCHER = new XPathMatcher("//plugins/plugin"); Cursor cursor; + String groupId; @Nullable @@ -59,10 +61,11 @@ public static class Matcher extends MavenTraitMatcher { private Optional getProperty(Cursor cursor, String property) { Xml.Tag tag = cursor.getValue(); - if (getResolutionResult(cursor).getPom().getProperties() != null) { + MavenResolutionResult resolutionResult = getResolutionResult(cursor); + if (resolutionResult != null && resolutionResult.getPom().getProperties() != null) { if (tag.getChildValue(property).isPresent() && tag.getChildValue(property).get().trim().startsWith("${")) { String propertyKey = tag.getChildValue(property).get().trim(); - return Optional.ofNullable(getResolutionResult(cursor).getPom().getValue(propertyKey)); + return Optional.ofNullable(resolutionResult.getPom().getValue(propertyKey)); } } return tag.getChildValue(property); diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenTraitMatcher.java b/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenTraitMatcher.java index 09dce8228d..ff91e91b56 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenTraitMatcher.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/trait/MavenTraitMatcher.java @@ -15,6 +15,7 @@ */ package org.openrewrite.maven.trait; +import org.jspecify.annotations.Nullable; import org.openrewrite.Cursor; import org.openrewrite.SourceFile; import org.openrewrite.maven.MavenVisitor; @@ -27,7 +28,7 @@ public abstract class MavenTraitMatcher> extends SimpleTraitMatcher { - protected MavenResolutionResult getResolutionResult(Cursor cursor) { + protected @Nullable MavenResolutionResult getResolutionResult(Cursor cursor) { AtomicReference mrr = new AtomicReference<>(); new MavenVisitor() { @Override diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/trait/Traits.java b/rewrite-maven/src/main/java/org/openrewrite/maven/trait/Traits.java similarity index 100% rename from rewrite-maven/src/test/java/org/openrewrite/maven/trait/Traits.java rename to rewrite-maven/src/main/java/org/openrewrite/maven/trait/Traits.java diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java new file mode 100644 index 0000000000..b164cb28f9 --- /dev/null +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddAnnotationProcessorTest.java @@ -0,0 +1,181 @@ +/* + * 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.maven; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.maven.Assertions.pomXml; + +class AddAnnotationProcessorTest implements RewriteTest { + + @DocumentExample + @Test + void addAnnotationProcessor() { + rewriteRun( + spec -> spec.recipe(new AddAnnotationProcessor( + "org.projectlombok", + "lombok-mapstruct-binding", + "0.2.0" + )), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + + + org.projectlombok + lombok + + + + + + + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + + + org.projectlombok + lombok + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + + + """ + ) + ); + } + + @Test + void shouldUpdateProcessorVersionAlreadyPresent() { + rewriteRun( + spec -> spec.recipe(new AddAnnotationProcessor( + "org.projectlombok", + "lombok-mapstruct-binding", + "0.2.0" + )), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + + + org.projectlombok + lombok + + + org.projectlombok + lombok-mapstruct-binding + 0.1.0 + + + + + + + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + + + org.projectlombok + lombok + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + + + """ + ) + ); + } +}