1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package job;
 26 
 27 import org.w3c.dom.Attr;
 28 import org.w3c.dom.Document;
 29 import org.w3c.dom.Element;
 30 import org.w3c.dom.Node;
 31 import org.w3c.dom.NodeList;
 32 import org.xml.sax.SAXException;
 33 
 34 import javax.xml.parsers.DocumentBuilderFactory;
 35 import javax.xml.parsers.ParserConfigurationException;
 36 import javax.xml.transform.OutputKeys;
 37 import javax.xml.transform.TransformerFactory;
 38 import javax.xml.transform.dom.DOMSource;
 39 import javax.xml.transform.stream.StreamResult;
 40 import javax.xml.xpath.XPath;
 41 import javax.xml.xpath.XPathConstants;
 42 import javax.xml.xpath.XPathExpression;
 43 import javax.xml.xpath.XPathExpressionException;
 44 import javax.xml.xpath.XPathFactory;
 45 import java.io.File;
 46 import java.io.IOException;
 47 import java.io.InputStream;
 48 import java.io.StringWriter;
 49 import java.net.URI;
 50 import java.net.URL;
 51 import java.nio.file.Files;
 52 import java.nio.file.Path;
 53 import java.util.ArrayList;
 54 import java.util.HashMap;
 55 import java.util.List;
 56 import java.util.Map;
 57 import java.util.Optional;
 58 import java.util.function.BiConsumer;
 59 import java.util.function.Consumer;
 60 import java.util.function.Function;
 61 import java.util.stream.Stream;
 62 
 63 public class XMLNode {
 64     Element element;
 65     List<XMLNode> children = new ArrayList<>();
 66     Map<String, String> attrMap = new HashMap<>();
 67 
 68     public static class AbstractXMLBuilder<T extends AbstractXMLBuilder<T>> {
 69         final public Element element;
 70 
 71         @SuppressWarnings("unchecked")
 72         public T self() {
 73             return (T) this;
 74         }
 75 
 76         public T attr(String name, String value) {
 77             element.setAttribute(name, value);
 78             return self();
 79         }
 80 
 81         public T attr(URI uri, String name, String value) {
 82             element.setAttributeNS(uri.toString(), name, value);
 83             return self();
 84         }
 85 
 86         public T element(String name, Function<Element, T> factory, Consumer<T> xmlBuilderConsumer) {
 87             var node = element.getOwnerDocument().createElement(name);
 88             element.appendChild(node);
 89             var builder = factory.apply(node);
 90             xmlBuilderConsumer.accept(builder);
 91             return self();
 92         }
 93 
 94         public T element(
 95                 URI uri, String name, Function<Element, T> factory, Consumer<T> xmlBuilderConsumer) {
 96             var node = element.getOwnerDocument().createElementNS(uri.toString(), name);
 97             element.appendChild(node);
 98             var builder = factory.apply(node);
 99             xmlBuilderConsumer.accept(builder);
100             return self();
101         }
102 
103         AbstractXMLBuilder(Element element) {
104             this.element = element;
105         }
106 
107         public T text(String thisText) {
108             var node = element.getOwnerDocument().createTextNode(thisText);
109             element.appendChild(node);
110             return self();
111         }
112 
113         public T comment(String thisComment) {
114             var node = element.getOwnerDocument().createComment(thisComment);
115             element.appendChild(node);
116             return self();
117         }
118 
119         <L> T forEach(List<L> list, BiConsumer<T, L> biConsumer) {
120             list.forEach(l -> biConsumer.accept(self(), l));
121             return self();
122         }
123 
124         <L> T forEach(Stream<L> stream, BiConsumer<T, L> biConsumer) {
125             stream.forEach(l -> biConsumer.accept(self(), l));
126             return self();
127         }
128 
129         <L> T forEach(Stream<L> stream, Consumer<L> consumer) {
130             stream.forEach(consumer);
131             return self();
132         }
133 
134         protected T then(Consumer<T> xmlBuilderConsumer) {
135             xmlBuilderConsumer.accept(self());
136             return self();
137         }
138     }
139 
140     public static class PomXmlBuilder extends AbstractXMLBuilder<PomXmlBuilder> {
141         PomXmlBuilder(Element element) {
142             super(element);
143         }
144 
145         public PomXmlBuilder element(String name, Consumer<PomXmlBuilder> xmlBuilderConsumer) {
146             return element(name, PomXmlBuilder::new, xmlBuilderConsumer);
147         }
148 
149         public PomXmlBuilder element(URI uri, String name, Consumer<PomXmlBuilder> xmlBuilderConsumer) {
150             return element(uri, name, PomXmlBuilder::new, xmlBuilderConsumer);
151         }
152 
153         public PomXmlBuilder modelVersion(String s) {
154             return element("modelVersion", $ -> $.text(s));
155         }
156 
157         public PomXmlBuilder pom(String groupId, String artifactId, String version) {
158             return modelVersion("4.0.0").packaging("pom").ref(groupId, artifactId, version);
159         }
160 
161         public PomXmlBuilder jar(String groupId, String artifactId, String version) {
162             return modelVersion("4.0.0").packaging("jar").ref(groupId, artifactId, version);
163         }
164 
165         public PomXmlBuilder groupId(String s) {
166             return element("groupId", $ -> $.text(s));
167         }
168 
169         public PomXmlBuilder artifactId(String s) {
170             return element("artifactId", $ -> $.text(s));
171         }
172 
173         public PomXmlBuilder packaging(String s) {
174             return element("packaging", $ -> $.text(s));
175         }
176 
177         public PomXmlBuilder version(String s) {
178             return element("version", $ -> $.text(s));
179         }
180 
181         public PomXmlBuilder build(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
182             return element("build", pomXmlBuilderConsumer);
183         }
184 
185         public PomXmlBuilder plugins(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
186             return element("plugins", pomXmlBuilderConsumer);
187         }
188 
189         public PomXmlBuilder plugin(
190                 String groupId,
191                 String artifactId,
192                 String version,
193                 Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
194             return element(
195                     "plugin", $ -> $.ref(groupId, artifactId, version).then(pomXmlBuilderConsumer));
196         }
197 
198         public PomXmlBuilder antPlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
199             return plugin(
200                     "org.apache.maven.plugins",
201                     "maven-antrun-plugin",
202                     "1.8",
203                     pomXmlBuilderConsumer);
204         }
205 
206         public PomXmlBuilder surefirePlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
207             return plugin(
208                     "org.apache.maven.plugins",
209                     "maven-surefire-plugin",
210                     "3.1.2",
211                     pomXmlBuilderConsumer);
212         }
213 
214         public PomXmlBuilder compilerPlugin(
215                 Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
216             return plugin(
217                     "org.apache.maven.plugins",
218                     "maven-compiler-plugin",
219                     "3.11.0", pomXmlBuilderConsumer
220             );
221         }
222 
223         public PomXmlBuilder execPlugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
224             return plugin("org.codehaus.mojo", "exec-maven-plugin", "3.1.0", pomXmlBuilderConsumer);
225         }
226 
227 
228         public PomXmlBuilder plugin(
229                 String groupId, String artifactId, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
230             return element("plugin", $ -> $.groupIdArtifactId(groupId, artifactId).then(pomXmlBuilderConsumer));
231         }
232 
233         public PomXmlBuilder plugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
234             return element("plugin", pomXmlBuilderConsumer);
235         }
236 
237         public PomXmlBuilder parent(String groupId, String artifactId, String version) {
238             return parent(parent -> parent.ref(groupId, artifactId, version));
239         }
240 
241         public PomXmlBuilder parent(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
242             return element("parent", pomXmlBuilderConsumer);
243         }
244 
245         public PomXmlBuilder pluginManagement(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
246             return element("pluginManagement", pomXmlBuilderConsumer);
247         }
248 
249         public PomXmlBuilder file(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
250             return element("file", pomXmlBuilderConsumer);
251         }
252 
253         public PomXmlBuilder activation(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
254             return element("activation", pomXmlBuilderConsumer);
255         }
256 
257         public PomXmlBuilder profiles(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
258             return element("profiles", pomXmlBuilderConsumer);
259         }
260 
261         public PomXmlBuilder profile(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
262             return element("profile", pomXmlBuilderConsumer);
263         }
264 
265         public PomXmlBuilder arguments(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
266             return element("arguments", pomXmlBuilderConsumer);
267         }
268 
269         public PomXmlBuilder executions(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
270             return element("executions", pomXmlBuilderConsumer);
271         }
272 
273         public PomXmlBuilder execution(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
274             return element("execution", pomXmlBuilderConsumer);
275         }
276 
277         public PomXmlBuilder execIdPhaseConf(
278                 String id, String phase, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
279             return execution(execution -> execution.id(id).phase(phase).goals(gs -> gs.goal("exec")).configuration(pomXmlBuilderConsumer));
280         }
281 
282         public PomXmlBuilder exec(
283                 String phase, String executable, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
284             return execIdPhaseConf(
285                     executable + "-" + phase,
286                     phase,
287                     conf -> conf.executable(executable).arguments(pomXmlBuilderConsumer));
288         }
289 
290         public PomXmlBuilder cmake(
291                 String id, String phase, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
292             return execIdPhaseConf(
293                     id, phase, conf -> conf.executable("cmake").arguments(pomXmlBuilderConsumer));
294         }
295 
296         public PomXmlBuilder cmake(String id, String phase, String... args) {
297             return execIdPhaseConf(
298                     id,
299                     phase,
300                     conf ->
301                             conf.executable("cmake")
302                                     .arguments(arguments -> arguments.forEach(Stream.of(args), arguments::argument)));
303         }
304 
305         public PomXmlBuilder jextract(String id, String phase, String... args) {
306             return execIdPhaseConf(
307                     id,
308                     phase,
309                     conf ->
310                             conf.executable("jextract")
311                                     .arguments(arguments -> arguments.forEach(Stream.of(args), arguments::argument)));
312         }
313 
314         public PomXmlBuilder ant(
315                 String id, String phase, String goal, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
316             return execution(execution -> execution
317                     .id(id)
318                     .phase(phase)
319                     .goals(gs -> gs.goal(goal))
320                     .configuration(configuration -> configuration.target(pomXmlBuilderConsumer)));
321         }
322 
323         public PomXmlBuilder goals(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
324             return element("goals", pomXmlBuilderConsumer);
325         }
326 
327         public PomXmlBuilder target(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
328             return element("target", pomXmlBuilderConsumer);
329         }
330 
331         public PomXmlBuilder configuration(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
332             return element("configuration", pomXmlBuilderConsumer);
333         }
334 
335         public PomXmlBuilder compilerArgs(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
336             return element("compilerArgs", pomXmlBuilderConsumer);
337         }
338 
339         public PomXmlBuilder compilerArgs(String... args) {
340             return element("compilerArgs", $ -> $.forEach(Stream.of(args), $::arg));
341         }
342 
343         public PomXmlBuilder properties(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
344             return element("properties", pomXmlBuilderConsumer);
345         }
346 
347         public PomXmlBuilder dependencies(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
348             return element("dependencies", pomXmlBuilderConsumer);
349         }
350 
351         public PomXmlBuilder dependsOn(String groupId, String artifactId, String version) {
352             return element("dependencies", $ -> $.dependency(groupId, artifactId, version));
353         }
354 
355         public PomXmlBuilder dependsOn(String groupId, String artifactId, String version, String phase) {
356             return element("dependencies", $ -> $.dependency(groupId, artifactId, version, phase));
357         }
358 
359         public PomXmlBuilder dependency(String groupId, String artifactId, String version) {
360             return dependency($ -> $.ref(groupId, artifactId, version));
361         }
362 
363         public PomXmlBuilder dependency(
364                 String groupId, String artifactId, String version, String scope) {
365             return dependency($ -> $.ref(groupId, artifactId, version).scope(scope));
366         }
367 
368         public PomXmlBuilder dependency(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
369             return element("dependency", pomXmlBuilderConsumer);
370         }
371 
372         public PomXmlBuilder modules(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
373             return element("modules", pomXmlBuilderConsumer);
374         }
375 
376         public PomXmlBuilder modules(List<String> modules) {
377             return element("modules", $ -> $.forEach(modules.stream(), $::module));
378         }
379 
380         public PomXmlBuilder modules(String... modules) {
381             return modules(List.of(modules));
382         }
383 
384         public PomXmlBuilder module(String name) {
385             return element("module", $ -> $.text(name));
386         }
387 
388         public PomXmlBuilder property(String name, String value) {
389             return element(name, $ -> $.text(value));
390         }
391 
392         public PomXmlBuilder antproperty(String name, String value) {
393             return element("property", $ -> $.attr("name", name).attr("value", value));
394         }
395 
396         public PomXmlBuilder scope(String s) {
397             return element("scope", $ -> $.text(s));
398         }
399 
400         public PomXmlBuilder phase(String s) {
401             return element("phase", $ -> $.text(s));
402         }
403 
404         public PomXmlBuilder argument(String s) {
405             return element("argument", $ -> $.text(s));
406         }
407 
408         public PomXmlBuilder goal(String s) {
409             return element("goal", $ -> $.text(s));
410         }
411 
412         public PomXmlBuilder copy(String file, String toDir) {
413             return element("copy", $ -> $.attr("file", file).attr("toDir", toDir));
414         }
415 
416         public PomXmlBuilder antjar(String basedir, String include, String destfile) {
417             return element("jar", $ -> $.attr("basedir", basedir).attr("includes", include + "/**").attr("destfile", destfile));
418         }
419 
420         public PomXmlBuilder echo(String message) {
421             return element("echo", $ -> $.attr("message", message));
422         }
423 
424         public PomXmlBuilder echo(String filename, String message) {
425             return element("echo", $ -> $.attr("message", message).attr("file", filename));
426         }
427 
428         public PomXmlBuilder mkdir(String dirName) {
429             return element("mkdir", $ -> $.attr("dir", dirName));
430         }
431 
432         public PomXmlBuilder groupIdArtifactId(String groupId, String artifactId) {
433             return groupId(groupId).artifactId(artifactId);
434         }
435 
436         public PomXmlBuilder ref(String groupId, String artifactId, String version) {
437             return groupIdArtifactId(groupId, artifactId).version(version);
438         }
439 
440         public PomXmlBuilder skip(String string) {
441             return element("skip", $ -> $.text(string));
442         }
443 
444         public PomXmlBuilder id(String s) {
445             return element("id", $ -> $.text(s));
446         }
447 
448         public PomXmlBuilder arg(String s) {
449             return element("arg", $ -> $.text(s));
450         }
451 
452         public PomXmlBuilder argLine(String s) {
453             return element("argLine", $ -> $.text(s));
454         }
455 
456         public PomXmlBuilder source(String s) {
457             return element("source", $ -> $.text(s));
458         }
459 
460         public PomXmlBuilder target(String s) {
461             return element("target", $ -> $.text(s));
462         }
463 
464         public PomXmlBuilder showWarnings(String s) {
465             return element("showWarnings", $ -> $.text(s));
466         }
467 
468         public PomXmlBuilder showDeprecation(String s) {
469             return element("showDeprecation", $ -> $.text(s));
470         }
471 
472         public PomXmlBuilder failOnError(String s) {
473             return element("failOnError", $ -> $.text(s));
474         }
475 
476         public PomXmlBuilder exists(String s) {
477             return element("exists", $ -> $.text(s));
478         }
479 
480         public PomXmlBuilder activeByDefault(String s) {
481             return element("activeByDefault", $ -> $.text(s));
482         }
483 
484         public PomXmlBuilder executable(String s) {
485             return element("executable", $ -> $.text(s));
486         }
487 
488         public PomXmlBuilder workingDirectory(String s) {
489             return element("workingDirectory", $ -> $.text(s));
490         }
491     }
492 
493     public static class ImlBuilder extends AbstractXMLBuilder<ImlBuilder> {
494 
495         ImlBuilder(Element element) {
496             super(element);
497         }
498 
499         public ImlBuilder element(String name, Consumer<ImlBuilder> xmlBuilderConsumer) {
500             return element(name, ImlBuilder::new, xmlBuilderConsumer);
501         }
502 
503         public ImlBuilder element(URI uri, String name, Consumer<ImlBuilder> xmlBuilderConsumer) {
504             return element(uri, name, ImlBuilder::new, xmlBuilderConsumer);
505         }
506 
507         public ImlBuilder modelVersion(String s) {
508             return element("modelVersion", $ -> $.text(s));
509         }
510 
511         public ImlBuilder groupId(String s) {
512             return element("groupId", $ -> $.text(s));
513         }
514 
515         public ImlBuilder artifactId(String s) {
516             return element("artifactId", $ -> $.text(s));
517         }
518 
519         public ImlBuilder packaging(String s) {
520             return element("packaging", $ -> $.text(s));
521         }
522 
523         public ImlBuilder version(String s) {
524             return element("version", $ -> $.text(s));
525         }
526 
527         public ImlBuilder build(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
528             return element("build", pomXmlBuilderConsumer);
529         }
530 
531         public ImlBuilder plugins(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
532             return element("plugins", pomXmlBuilderConsumer);
533         }
534 
535         public ImlBuilder plugin(
536                 String groupId,
537                 String artifactId,
538                 String version,
539                 Consumer<ImlBuilder> pomXmlBuilderConsumer) {
540             return element(
541                     "plugin",
542                     $ ->
543                             $.groupIdArtifactIdVersion(groupId, artifactId, version).then(pomXmlBuilderConsumer));
544         }
545 
546         public ImlBuilder plugin(
547                 String groupId, String artifactId, Consumer<ImlBuilder> pomXmlBuilderConsumer) {
548             return element(
549                     "plugin", $ -> $.groupIdArtifactId(groupId, artifactId).then(pomXmlBuilderConsumer));
550         }
551 
552         public ImlBuilder plugin(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
553             return element("plugin", pomXmlBuilderConsumer);
554         }
555 
556         public ImlBuilder parent(String groupId, String artifactId, String version) {
557             return parent(parent -> parent.groupIdArtifactIdVersion(groupId, artifactId, version));
558         }
559 
560         public ImlBuilder parent(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
561             return element("parent", pomXmlBuilderConsumer);
562         }
563 
564         public ImlBuilder pluginManagement(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
565             return element("pluginManagement", pomXmlBuilderConsumer);
566         }
567 
568         public ImlBuilder file(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
569             return element("file", pomXmlBuilderConsumer);
570         }
571 
572         public ImlBuilder activation(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
573             return element("activation", pomXmlBuilderConsumer);
574         }
575 
576         public ImlBuilder profiles(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
577             return element("profiles", pomXmlBuilderConsumer);
578         }
579 
580         public ImlBuilder profile(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
581             return element("profile", pomXmlBuilderConsumer);
582         }
583 
584         public ImlBuilder arguments(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
585             return element("arguments", pomXmlBuilderConsumer);
586         }
587 
588         public ImlBuilder executions(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
589             return element("executions", pomXmlBuilderConsumer);
590         }
591 
592         public ImlBuilder execution(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
593             return element("execution", pomXmlBuilderConsumer);
594         }
595 
596         public ImlBuilder goals(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
597             return element("goals", pomXmlBuilderConsumer);
598         }
599 
600         public ImlBuilder target(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
601             return element("target", pomXmlBuilderConsumer);
602         }
603 
604         public ImlBuilder configuration(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
605             return element("configuration", pomXmlBuilderConsumer);
606         }
607 
608         public ImlBuilder compilerArgs(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
609             return element("compilerArgs", pomXmlBuilderConsumer);
610         }
611 
612         public ImlBuilder properties(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
613             return element("properties", pomXmlBuilderConsumer);
614         }
615 
616         public ImlBuilder dependencies(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
617             return element("dependencies", pomXmlBuilderConsumer);
618         }
619 
620         public ImlBuilder dependency(String groupId, String artifactId, String version) {
621             return dependency($ -> $.groupIdArtifactIdVersion(groupId, artifactId, version));
622         }
623 
624         public ImlBuilder dependency(String groupId, String artifactId, String version, String scope) {
625             return dependency($ -> $.groupIdArtifactIdVersion(groupId, artifactId, version).scope(scope));
626         }
627 
628         public ImlBuilder dependency(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
629             return element("dependency", pomXmlBuilderConsumer);
630         }
631 
632         public ImlBuilder modules(Consumer<ImlBuilder> pomXmlBuilderConsumer) {
633             return element("modules", pomXmlBuilderConsumer);
634         }
635 
636         public ImlBuilder module(String name) {
637             return element("module", $ -> $.text(name));
638         }
639 
640         public ImlBuilder property(String name, String value) {
641             return element(name, $ -> $.text(value));
642         }
643 
644         public ImlBuilder scope(String s) {
645             return element("scope", $ -> $.text(s));
646         }
647 
648         public ImlBuilder phase(String s) {
649             return element("phase", $ -> $.text(s));
650         }
651 
652         public ImlBuilder argument(String s) {
653             return element("argument", $ -> $.text(s));
654         }
655 
656         public ImlBuilder goal(String s) {
657             return element("goal", $ -> $.text(s));
658         }
659 
660         public ImlBuilder copy(String file, String toDir) {
661             return element("copy", $ -> $.attr("file", file).attr("toDir", toDir));
662         }
663 
664         public ImlBuilder groupIdArtifactId(String groupId, String artifactId) {
665             return groupId(groupId).artifactId(artifactId);
666         }
667 
668         public ImlBuilder groupIdArtifactIdVersion(String groupId, String artifactId, String version) {
669             return groupIdArtifactId(groupId, artifactId).version(version);
670         }
671 
672         public ImlBuilder skip(String string) {
673             return element("skip", $ -> $.text(string));
674         }
675 
676         public ImlBuilder id(String s) {
677             return element("id", $ -> $.text(s));
678         }
679 
680         public ImlBuilder arg(String s) {
681             return element("arg", $ -> $.text(s));
682         }
683 
684         public ImlBuilder argLine(String s) {
685             return element("argLine", $ -> $.text(s));
686         }
687 
688         public ImlBuilder source(String s) {
689             return element("source", $ -> $.text(s));
690         }
691 
692         public ImlBuilder target(String s) {
693             return element("target", $ -> $.text(s));
694         }
695 
696         public ImlBuilder showWarnings(String s) {
697             return element("showWarnings", $ -> $.text(s));
698         }
699 
700         public ImlBuilder showDeprecation(String s) {
701             return element("showDeprecation", $ -> $.text(s));
702         }
703 
704         public ImlBuilder failOnError(String s) {
705             return element("failOnError", $ -> $.text(s));
706         }
707 
708         public ImlBuilder exists(String s) {
709             return element("exists", $ -> $.text(s));
710         }
711 
712         public ImlBuilder activeByDefault(String s) {
713             return element("activeByDefault", $ -> $.text(s));
714         }
715 
716         public ImlBuilder executable(String s) {
717             return element("executable", $ -> $.text(s));
718         }
719     }
720 
721     public static class XMLBuilder extends AbstractXMLBuilder<XMLBuilder> {
722         XMLBuilder(Element element) {
723             super(element);
724         }
725 
726         public XMLBuilder element(String name, Consumer<XMLBuilder> xmlBuilderConsumer) {
727             return element(name, XMLBuilder::new, xmlBuilderConsumer);
728         }
729 
730         public XMLBuilder element(URI uri, String name, Consumer<XMLBuilder> xmlBuilderConsumer) {
731             return element(uri, name, XMLBuilder::new, xmlBuilderConsumer);
732         }
733     }
734 
735     static XMLNode create(String nodeName, Consumer<XMLBuilder> xmlBuilderConsumer) {
736 
737         try {
738             var doc =
739                     DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
740             var element = doc.createElement(nodeName);
741             doc.appendChild(element);
742             XMLBuilder xmlBuilder = new XMLBuilder(element);
743             xmlBuilderConsumer.accept(xmlBuilder);
744             return new XMLNode(element);
745         } catch (ParserConfigurationException e) {
746             throw new RuntimeException(e);
747         }
748     }
749 
750     static XMLNode createIml(String commentText, Consumer<ImlBuilder> imlBuilderConsumer) {
751         try {
752             var doc =
753                     DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
754             var uri1 = URI.create("http://maven.apache.org/POM/4.0.0");
755             var uri2 = URI.create("http://www.w3.org/2001/XMLSchema-instance");
756             var uri3 = URI.create("http://maven.apache.org/xsd/maven-4.0.0.xsd");
757             var comment = doc.createComment(commentText);
758             doc.appendChild(comment);
759             var element = doc.createElementNS(uri1.toString(), "project");
760             doc.appendChild(element);
761             element.setAttributeNS(uri2.toString(), "xsi:schemaLocation", uri1 + " " + uri3);
762             ImlBuilder imlBuilder = new ImlBuilder(element);
763             imlBuilderConsumer.accept(imlBuilder);
764             return new XMLNode(element);
765         } catch (ParserConfigurationException e) {
766             throw new RuntimeException(e);
767         }
768     }
769 
770     public static XMLNode createPom(
771             String commentText, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
772         try {
773             var doc =
774                     DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
775 
776             var uri1 = URI.create("http://maven.apache.org/POM/4.0.0");
777             var uri2 = URI.create("http://www.w3.org/2001/XMLSchema-instance");
778             var uri3 = URI.create("http://maven.apache.org/xsd/maven-4.0.0.xsd");
779             var comment = doc.createComment(commentText);
780             doc.appendChild(comment);
781             var element = doc.createElementNS(uri1.toString(), "project");
782             doc.appendChild(element);
783             element.setAttributeNS(uri2.toString(), "xsi:schemaLocation", uri1 + " " + uri3);
784             PomXmlBuilder pomXmlBuilder = new PomXmlBuilder(element);
785             pomXmlBuilderConsumer.accept(pomXmlBuilder);
786             return new XMLNode(element);
787         } catch (ParserConfigurationException e) {
788             throw new RuntimeException(e);
789         }
790     }
791 
792     static XMLNode create(URI uri, String nodeName, Consumer<XMLBuilder> xmlBuilderConsumer) {
793         try {
794             var doc =
795                     DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
796             var element = doc.createElementNS(uri.toString(), nodeName);
797             doc.appendChild(element);
798             XMLBuilder xmlBuilder = new XMLBuilder(element);
799             xmlBuilderConsumer.accept(xmlBuilder);
800             return new XMLNode(element);
801         } catch (ParserConfigurationException e) {
802             throw new RuntimeException(e);
803         }
804     }
805 
806     XMLNode(Element element) {
807         this.element = element;
808         this.element.normalize();
809         NodeList nodeList = element.getChildNodes();
810         for (int i = 0; i < nodeList.getLength(); i++) {
811             if (nodeList.item(i) instanceof Element e) {
812                 this.children.add(new XMLNode(e));
813             }
814         }
815         for (int i = 0; i < element.getAttributes().getLength(); i++) {
816             if (element.getAttributes().item(i) instanceof Attr attr) {
817                 this.attrMap.put(attr.getName(), attr.getValue());
818             }
819         }
820     }
821 
822     public boolean hasAttr(String name) {
823         return attrMap.containsKey(name);
824     }
825 
826     public String attr(String name) {
827         return attrMap.get(name);
828     }
829 
830     static Document parse(InputStream is) {
831         try {
832             return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
833         } catch (ParserConfigurationException | SAXException | IOException e) {
834             throw new RuntimeException(e);
835         }
836     }
837 
838     static Document parse(Path path) {
839         try {
840             return parse(Files.newInputStream(path));
841         } catch (IOException e) {
842             throw new RuntimeException(e);
843         }
844     }
845 
846     XMLNode(Path path) {
847         this(parse(path).getDocumentElement());
848     }
849 
850     XMLNode(File file) {
851         this(parse(file.toPath()).getDocumentElement());
852     }
853 
854     XMLNode(URL url) throws Throwable {
855         this(parse(url.openStream()).getDocumentElement());
856     }
857 
858     void write(StreamResult streamResult) throws Throwable {
859         var transformer = TransformerFactory.newInstance().newTransformer();
860         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
861         transformer.setOutputProperty(OutputKeys.METHOD, "xml");
862         transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
863         transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
864         transformer.transform(new DOMSource(element.getOwnerDocument()), streamResult);
865     }
866 
867     void write(File file) {
868         try {
869             write(new StreamResult(file));
870         } catch (Throwable t) {
871             throw new RuntimeException(t);
872         }
873     }
874 
875     public void write(Path xmlFile) {
876         try {
877             write(new StreamResult(xmlFile.toFile()));
878         } catch (Throwable t) {
879             throw new RuntimeException(t);
880         }
881     }
882 
883     @Override
884     public String toString() {
885         var stringWriter = new StringWriter();
886         try {
887             var transformer = TransformerFactory.newInstance().newTransformer();
888             transformer.setOutputProperty(OutputKeys.INDENT, "yes");
889             transformer.setOutputProperty(OutputKeys.METHOD, "xml");
890             transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
891             transformer.transform(new DOMSource(element), new StreamResult(stringWriter));
892             return stringWriter.toString();
893         } catch (Throwable e) {
894             throw new RuntimeException(e);
895         }
896     }
897 
898     XPathExpression xpath(String expression) {
899         XPath xpath = XPathFactory.newInstance().newXPath();
900         try {
901             return xpath.compile(expression);
902         } catch (XPathExpressionException e) {
903             throw new RuntimeException(e);
904         }
905     }
906 
907     Node node(XPathExpression xPathExpression) {
908         try {
909             return (Node) xPathExpression.evaluate(this.element, XPathConstants.NODE);
910         } catch (XPathExpressionException e) {
911             throw new RuntimeException(e);
912         }
913     }
914 
915     Optional<Node> optionalNode(XPathExpression xPathExpression) {
916         var nodes = nodes(xPathExpression).toList();
917         return switch (nodes.size()) {
918             case 0 -> Optional.empty();
919             case 1 -> Optional.of(nodes.getFirst());
920             default -> throw new IllegalStateException("Expected 0 or 1 but got more");
921         };
922     }
923 
924     String str(XPathExpression xPathExpression) {
925         try {
926             return (String) xPathExpression.evaluate(this.element, XPathConstants.STRING);
927         } catch (XPathExpressionException e) {
928             throw new RuntimeException(e);
929         }
930     }
931 
932     String xpathQueryString(String xpathString) {
933         try {
934             return (String) xpath(xpathString).evaluate(this.element, XPathConstants.STRING);
935         } catch (XPathExpressionException e) {
936             throw new RuntimeException(e);
937         }
938     }
939 
940     NodeList nodeList(XPathExpression xPathExpression) {
941         try {
942             return (NodeList) xPathExpression.evaluate(this.element, XPathConstants.NODESET);
943         } catch (XPathExpressionException e) {
944             throw new RuntimeException(e);
945         }
946     }
947 
948     Stream<Node> nodes(XPathExpression xPathExpression) {
949         var nodeList = nodeList(xPathExpression);
950         List<Node> nodes = new ArrayList<>();
951         for (int i = 0; i < nodeList.getLength(); i++) {
952             nodes.add(nodeList.item(i));
953         }
954         return nodes.stream();
955     }
956 
957     Stream<Element> elements(XPathExpression xPathExpression) {
958         return nodes(xPathExpression)
959                 .filter(n -> n instanceof Element)
960                 .map(n -> (Element) n);
961     }
962 
963     Stream<XMLNode> xmlNodes(XPathExpression xPathExpression) {
964         return elements(xPathExpression).map(e -> new XMLNode(e));
965     }
966 }