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, ExecutionContext> 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
+
+
+
+
+
+
+
+ """
+ )
+ );
+ }
+}