From 1ab9aab71de69a5569d5ab8f27a999a90bccca4e Mon Sep 17 00:00:00 2001 From: Didier Loiseau Date: Sat, 28 Sep 2024 22:48:13 +0200 Subject: [PATCH] Expose AddOrUpdateChild as a recipe using XPath --- .../openrewrite/xml/AddOrUpdateChildTag.java | 80 ++++ .../xml/AddOrUpdateChildTagTest.java | 404 ++++++++++++++++++ 2 files changed, 484 insertions(+) create mode 100644 rewrite-xml/src/main/java/org/openrewrite/xml/AddOrUpdateChildTag.java create mode 100644 rewrite-xml/src/test/java/org/openrewrite/xml/AddOrUpdateChildTagTest.java diff --git a/rewrite-xml/src/main/java/org/openrewrite/xml/AddOrUpdateChildTag.java b/rewrite-xml/src/main/java/org/openrewrite/xml/AddOrUpdateChildTag.java new file mode 100644 index 00000000000..425772cfd7b --- /dev/null +++ b/rewrite-xml/src/main/java/org/openrewrite/xml/AddOrUpdateChildTag.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 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.xml; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.intellij.lang.annotations.Language; +import org.openrewrite.*; +import org.openrewrite.xml.tree.Xml; + +@Value +@EqualsAndHashCode(callSuper = false) +public class AddOrUpdateChildTag extends Recipe { + + @Option(displayName = "Parent XPath", + description = "XPath identifying the parent to which a child tag must be added", + example = "/project//plugin//configuration") + @Language("xpath") + String parentXPath; + + @Option(displayName = "New child tag", + description = "The XML of the new child to add or update on the parent tag.", + example = "true") + @Language("xml") + String newChildTag; + + @Override + public String getDisplayName() { + return "Add or update child tag"; + } + + @Override + public String getDescription() { + return "Adds or updates a child element below the parent(s) matching the provided `parentXPath` expression. " + + "If a child with the same name exists, it will be replaced, otherwise a new child will be added. " + + "This ensures idempotent behaviour."; + } + + @Override + public Validated validate() { + Validated validated = super.validate() + .and(Validated.notBlank("parentXPath", parentXPath)) + .and(Validated.notBlank("newChildTag", newChildTag)); + try { + Xml.Tag.build(newChildTag); + } catch (Exception e) { + validated = validated.and(Validated.invalid("newChildTag", newChildTag, "Invalid XML for child tag", e)); + } + return validated; + } + + @Override + public TreeVisitor getVisitor() { + return new XmlVisitor() { + private final XPathMatcher xPathMatcher = new XPathMatcher(parentXPath); + + @Override + public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { + if (xPathMatcher.matches(getCursor())) { + Xml.Tag newChild = Xml.Tag.build(newChildTag); + return AddOrUpdateChild.addOrUpdateChild(tag, newChild, getCursor().getParentOrThrow()); + } + return super.visitTag(tag, ctx); + } + }; + } +} diff --git a/rewrite-xml/src/test/java/org/openrewrite/xml/AddOrUpdateChildTagTest.java b/rewrite-xml/src/test/java/org/openrewrite/xml/AddOrUpdateChildTagTest.java new file mode 100644 index 00000000000..230b91e2c2e --- /dev/null +++ b/rewrite-xml/src/test/java/org/openrewrite/xml/AddOrUpdateChildTagTest.java @@ -0,0 +1,404 @@ +/* + * 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.xml; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.xml.Assertions.xml; + +class AddOrUpdateChildTagTest implements RewriteTest { + @Test + void addsTagEverywhereWhenAbsent() { + rewriteRun(spec -> spec.recipe(new AddOrUpdateChildTag( + "/project//plugin[groupId='org.apache.maven.plugins' and artifactId='maven-resources-plugin']" + + "//configuration", + "true")), + xml( + """ + + + 4.0.0 + com.example + my-project + 1.0 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + + UTF-8 + + + + + + + + + profile1 + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + + UTF-8 + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + + + + + + + + """, + """ + + + 4.0.0 + com.example + my-project + 1.0 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + true + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + + UTF-8 + true + + + + + + + + + profile1 + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + + UTF-8 + true + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + true + + + + + + + + """ + ) + ); + } + + @Test + void updateTagEverywhere() { + rewriteRun(spec -> spec.recipe(new AddOrUpdateChildTag( + "/project//plugin[groupId='org.apache.maven.plugins' and artifactId='maven-resources-plugin']" + + "//configuration", + "true")), + xml( + """ + + + 4.0.0 + com.example + my-project + 1.0 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + false + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + false + + + + + + + profile1 + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + false + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + false + + + + + + + + """, + """ + + + 4.0.0 + com.example + my-project + 1.0 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + true + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + true + + + + + + + profile1 + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + true + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + true + + + + + + + + """ + ) + ); + } + + @Test + void dontTouchAnythingElse() { + rewriteRun(spec -> spec.recipe(new AddOrUpdateChildTag( + "/project//plugin[groupId='org.apache.maven.plugins' and artifactId='maven-resources-plugin']" + + "//configuration", + "true")), + xml( + """ + + + 4.0.0 + com.example + my-project + 1.0 + + + + + + org.apache.maven.plugins + maven-other-plugin + 3.3.1 + + UTF-8 + false + + + + + + + org.apache.other.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + false + + + + + + + profile1 + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + + + + org.apache.maven.plugins + maven-other-plugin + 3.3.1 + + UTF-8 + false + + + + + + + + """ + ) + ); + } +}