1 package bldr;
  2 
  3 import java.io.File;
  4 import java.io.StringWriter;
  5 import java.net.URI;
  6 import java.net.URL;
  7 import java.nio.file.Path;
  8 import java.util.ArrayList;
  9 import java.util.HashMap;
 10 import java.util.List;
 11 import java.util.Map;
 12 import java.util.Optional;
 13 import java.util.function.BiConsumer;
 14 import java.util.function.Consumer;
 15 import java.util.function.Function;
 16 import java.util.stream.Stream;
 17 import javax.xml.parsers.ParserConfigurationException;
 18 import javax.xml.transform.OutputKeys;
 19 import javax.xml.transform.TransformerFactory;
 20 import javax.xml.transform.dom.DOMSource;
 21 import javax.xml.transform.stream.StreamResult;
 22 import javax.xml.xpath.XPath;
 23 import javax.xml.xpath.XPathConstants;
 24 import javax.xml.xpath.XPathExpression;
 25 import javax.xml.xpath.XPathExpressionException;
 26 import javax.xml.xpath.XPathFactory;
 27 import org.w3c.dom.Element;
 28 import org.w3c.dom.Node;
 29 import org.w3c.dom.NodeList;
 30 
 31 public class XMLNode {
 32   org.w3c.dom.Element element;
 33   List<XMLNode> children = new ArrayList<>();
 34   Map<String, String> attrMap = new HashMap<>();
 35 
 36   public static class AbstractXMLBuilder<T extends AbstractXMLBuilder<T>>{
 37     public org.w3c.dom.Element element;
 38     public T self(){
 39       return (T)this;
 40     }
 41     public T attr(String name, String value) {
 42        // var att = element.getOwnerDocument().createAttribute(name);
 43         //att.setValue(value);
 44         element.setAttribute(name, value);
 45        // element.appendChild(att);
 46       return self();
 47     }
 48     public T attr(URI uri,String name, String value) {
 49       // var att = element.getOwnerDocument().createAttribute(name);
 50       //att.setValue(value);
 51       element.setAttributeNS(uri.toString(),name, value);
 52       // element.appendChild(att);
 53       return self();
 54     }
 55     public T element(String name, Function<Element,T> factory, Consumer<T> xmlBuilderConsumer) {
 56       var node = element.getOwnerDocument().createElement(name);
 57       element.appendChild(node);
 58       var builder = factory.apply(node);
 59       xmlBuilderConsumer.accept(builder);
 60       return self();
 61     }
 62     public T element(URI uri, String name,  Function<Element,T> factory,Consumer<T> xmlBuilderConsumer) {
 63       var node = element.getOwnerDocument().createElementNS(uri.toString(), name);
 64       element.appendChild(node);
 65       var builder = factory.apply(node);
 66       xmlBuilderConsumer.accept(builder);
 67       return self();
 68     }
 69 
 70     AbstractXMLBuilder(org.w3c.dom.Element element) {this.element=element;}
 71 
 72     public T text(String thisText) {
 73       var node = element.getOwnerDocument().createTextNode(thisText);
 74       element.appendChild(node);
 75       return self();
 76     }
 77 
 78     public T comment(String thisComment) {
 79       var node = element.getOwnerDocument().createComment(thisComment);
 80       element.appendChild(node);
 81       return self();
 82     }
 83     public T oracleComment () {
 84       return comment("""
 85                         Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
 86                         DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 87 
 88                         This code is free software; you can redistribute it and/or modify it
 89                         under the terms of the GNU General Public License version 2 only, as
 90                         published by the Free Software Foundation.  Oracle designates this
 91                         particular file as subject to the "Classpath" exception as provided
 92                         by Oracle in the LICENSE file that accompanied this code.
 93 
 94                         This code is distributed in the hope that it will be useful, but WITHOUT
 95                         ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 96                         FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 97                         version 2 for more details (a copy is included in the LICENSE file that
 98                         accompanied this code).
 99 
100                         You should have received a copy of the GNU General Public License version
101                         2 along with this work; if not, write to the Free Software Foundation,
102                         Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
103 
104                         Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
105                         or visit www.oracle.com if you need additional information or have any
106                         questions.
107                         """);
108     }
109     <L> T forEach(List<L> list, BiConsumer<T,L> biConsumer){
110       list.forEach(l->biConsumer.accept(self(),l));
111       return self();
112     }
113 
114     <L> T forEach(Stream<L> stream, BiConsumer<T,L> biConsumer){
115       stream.forEach(l->biConsumer.accept(self(),l));
116       return self();
117     }
118 
119     protected T then(Consumer<T> xmlBuilderConsumer) {
120       xmlBuilderConsumer.accept(self());
121       return self();
122     }
123   }
124   public static class PomXmlBuilder extends AbstractXMLBuilder<PomXmlBuilder>{
125     PomXmlBuilder(Element element) {
126       super(element);
127     }
128     public PomXmlBuilder element(String name, Consumer<PomXmlBuilder> xmlBuilderConsumer) {
129       return element(name, PomXmlBuilder::new, xmlBuilderConsumer);
130     }
131     public PomXmlBuilder element(URI uri, String name, Consumer<PomXmlBuilder> xmlBuilderConsumer) {
132       return element(uri, name, PomXmlBuilder::new, xmlBuilderConsumer);
133     }
134 
135     public PomXmlBuilder modelVersion(String s) {
136       return element("modelVersion", $->$.text(s));
137     }
138     public PomXmlBuilder groupId(String s) {
139       return element("groupId", $->$.text(s));
140     }
141     public PomXmlBuilder artifactId(String s) {
142       return element("artifactId", $->$.text(s));
143     }
144     public PomXmlBuilder packaging(String s) {
145       return element("packaging", $->$.text(s));
146     }
147     public PomXmlBuilder version(String s) {
148       return element("version", $->$.text(s));
149     }
150     public PomXmlBuilder build(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
151       return element("build", pomXmlBuilderConsumer);
152     }
153     public PomXmlBuilder plugins(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
154       return element("plugins", pomXmlBuilderConsumer);
155     }
156     public PomXmlBuilder plugin(String groupId, String artifactId, String version, Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
157       return element("plugin", $->$
158               .groupIdArtifactIdVersion(groupId, artifactId, version).then(pomXmlBuilderConsumer)
159       );
160     }
161     public PomXmlBuilder plugin(String groupId, String artifactId,  Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
162       return element("plugin", $->$
163               .groupIdArtifactId(groupId, artifactId).then(pomXmlBuilderConsumer)
164       );
165     }
166   //  public PomXmlBuilder plugin(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
167    //   return element("plugin", pomXmlBuilderConsumer);
168   //  }
169     public PomXmlBuilder parent(String groupId, String artifactId, String version){
170       return parent(parent -> parent.groupIdArtifactIdVersion(groupId, artifactId, version));
171     }
172     public PomXmlBuilder parent(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
173       return element("parent", pomXmlBuilderConsumer);
174     }
175     public PomXmlBuilder profiles(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
176        return element("profiles", pomXmlBuilderConsumer);
177     }
178     public PomXmlBuilder profile(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
179       return element("profile", pomXmlBuilderConsumer);
180     }
181     public PomXmlBuilder arguments(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
182       return element("arguments", pomXmlBuilderConsumer);
183     }
184     public PomXmlBuilder executions(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
185       return element("executions", pomXmlBuilderConsumer);
186     }
187     public PomXmlBuilder execution(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
188       return element("execution", pomXmlBuilderConsumer);
189     }
190     public PomXmlBuilder goals(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
191       return element("goals", pomXmlBuilderConsumer);
192     }
193     public PomXmlBuilder target(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
194       return element("target", pomXmlBuilderConsumer);
195     }
196     public PomXmlBuilder configuration(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
197       return element("configuration", pomXmlBuilderConsumer);
198     }
199     public PomXmlBuilder properties(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
200       return element("properties", pomXmlBuilderConsumer);
201     }
202     public PomXmlBuilder dependencies(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
203       return element("dependencies", pomXmlBuilderConsumer);
204     }
205     public    PomXmlBuilder dependency(String groupId, String artifactId, String version) {
206       return dependency($->$.groupIdArtifactIdVersion(groupId, artifactId, version));
207     }
208     public PomXmlBuilder dependency(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
209       return element("dependency", pomXmlBuilderConsumer);
210     }
211 
212     public PomXmlBuilder modules(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
213       return element("modules", pomXmlBuilderConsumer);
214     }
215     public PomXmlBuilder module(String name) {
216       return element("module", $->$.text(name));
217     }
218 
219     public PomXmlBuilder property(String name, String value) {
220       return element(name,$->$.text(value));
221     }
222 
223     public PomXmlBuilder phase(String s) {
224       return element("phase", $->$.text(s));
225     }
226     public PomXmlBuilder argument(String s) {
227       return element("argument", $->$.text(s));
228     }
229 
230     public PomXmlBuilder goal(String s) {
231       return element("goal", $->$.text(s));
232     }
233 
234     public PomXmlBuilder copy(String file, String toDir) {
235       return element("copy", $->$.attr("file", file).attr("toDir", toDir));
236     }
237 
238     public PomXmlBuilder groupIdArtifactId(String groupId, String artifactId) {
239         return groupId(groupId).artifactId(artifactId);
240     }
241     public PomXmlBuilder groupIdArtifactIdVersion(String groupId, String artifactId, String version) {
242       return groupIdArtifactId(groupId,artifactId).version(version);
243     }
244 
245     public PomXmlBuilder skip(String string) {
246       return element("skip", $->$.text(string));
247     }
248 
249     public PomXmlBuilder id(String s) {
250       return element("id", $->$.text(s));
251     }
252 
253     public PomXmlBuilder executable(String s) {
254       return element("executable", $->$.text(s));
255     }
256   }
257   public static class XMLBuilder extends AbstractXMLBuilder<XMLBuilder>{
258 
259     XMLBuilder(Element element) {
260       super(element);
261     }
262     public XMLBuilder element(String name, Consumer<XMLBuilder> xmlBuilderConsumer) {
263       return element(name, XMLBuilder::new, xmlBuilderConsumer);
264     }
265     public XMLBuilder element(URI uri, String name, Consumer<XMLBuilder> xmlBuilderConsumer) {
266       return element(uri, name, XMLBuilder::new, xmlBuilderConsumer);
267     }
268   }
269   static XMLNode create( String nodeName, Consumer<XMLBuilder> xmlBuilderConsumer) {
270 
271       try {
272           var doc  = javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
273           //var nl = doc.createTextNode("\n");
274           //doc.appendChild(nl);
275           var element = doc.createElement(nodeName);
276           doc.appendChild(element);
277         XMLBuilder xmlBuilder = new XMLBuilder(element);
278         xmlBuilderConsumer.accept(xmlBuilder);
279         return new XMLNode(element);
280       } catch (ParserConfigurationException e) {
281           throw new RuntimeException(e);
282       }
283 
284 
285 
286     }
287   static XMLNode createPom(Consumer<PomXmlBuilder> pomXmlBuilderConsumer) {
288     try {
289       var doc  = javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
290       //var nl = doc.createTextNode("\n");
291       //doc.appendChild(nl);
292       var uri1 = URI.create("http://maven.apache.org/POM/4.0.0");
293       var uri2 = URI.create("http://www.w3.org/2001/XMLSchema-instance");
294       var uri3 = URI.create("http://maven.apache.org/xsd/maven-4.0.0.xsd");
295       var element = doc.createElementNS(uri1.toString(),"project");
296       doc.appendChild(element);
297       element.setAttributeNS(uri2.toString(), "xsi:schemaLocation",uri1+" "+ uri3);
298       PomXmlBuilder pomXmlBuilder = new PomXmlBuilder(element);
299       pomXmlBuilderConsumer.accept(pomXmlBuilder);
300       return new XMLNode(element);
301     } catch (ParserConfigurationException e) {
302       throw new RuntimeException(e);
303     }
304   }
305 
306   static XMLNode create(URI uri, String nodeName, Consumer<XMLBuilder> xmlBuilderConsumer) {
307 
308     try {
309       var doc  = javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
310       //var nl = doc.createTextNode("\n");
311       //doc.appendChild(nl);
312       var element = doc.createElementNS(uri.toString(),nodeName);
313       doc.appendChild(element);
314       XMLBuilder xmlBuilder = new XMLBuilder(element);
315       xmlBuilderConsumer.accept(xmlBuilder);
316       return new XMLNode(element);
317     } catch (ParserConfigurationException e) {
318       throw new RuntimeException(e);
319     }
320 
321 
322 
323   }
324 
325 
326   XMLNode(org.w3c.dom.Element element) {
327     this.element = element;
328     this.element.normalize();
329     NodeList nodeList = element.getChildNodes();
330     for (int i = 0; i < nodeList.getLength(); i++) {
331       if (nodeList.item(i) instanceof org.w3c.dom.Element e) {
332         this.children.add(new XMLNode(e));
333       }
334     }
335     for (int i = 0; i < element.getAttributes().getLength(); i++) {
336       if (element.getAttributes().item(i) instanceof org.w3c.dom.Attr attr) {
337         this.attrMap.put(attr.getName(), attr.getValue());
338       }
339     }
340   }
341 
342   public boolean hasAttr(String name) {
343     return attrMap.containsKey(name);
344   }
345 
346   public String attr(String name) {
347     return attrMap.get(name);
348   }
349 
350   XMLNode(Path path) throws Throwable {
351     this(
352         javax.xml.parsers.DocumentBuilderFactory.newInstance()
353             .newDocumentBuilder()
354             .parse(path.toFile())
355             .getDocumentElement());
356   }
357 
358   XMLNode(File file) throws Throwable {
359     this(
360         javax.xml.parsers.DocumentBuilderFactory.newInstance()
361             .newDocumentBuilder()
362             .parse(file)
363             .getDocumentElement());
364   }
365 
366   XMLNode(URL url) throws Throwable {
367     this(
368         javax.xml.parsers.DocumentBuilderFactory.newInstance()
369             .newDocumentBuilder()
370             .parse(url.openStream())
371             .getDocumentElement());
372   }
373 
374   void write(StreamResult streamResult) throws Throwable {
375     var transformer = TransformerFactory.newInstance().newTransformer();
376     transformer.setOutputProperty(OutputKeys.INDENT, "yes");
377     transformer.setOutputProperty(OutputKeys.METHOD, "xml");
378     transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
379     transformer.transform(new DOMSource(element.getOwnerDocument()), streamResult);
380   }
381 
382   void write(File file) {
383     try {
384       write(new StreamResult(file));
385     }catch (Throwable t){
386       throw new RuntimeException(t);
387     }
388   }
389   void write(Bldr.XMLFile xmlFile) {
390     try {
391       write(new StreamResult(xmlFile.path().toFile()));
392     }catch (Throwable t){
393       throw new RuntimeException(t);
394     }
395   }
396 
397   @Override
398   public String toString() {
399     var stringWriter = new StringWriter();
400     try {
401       var transformer = TransformerFactory.newInstance().newTransformer();
402       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
403       transformer.setOutputProperty(OutputKeys.METHOD, "xml");
404       transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
405       transformer.transform(new DOMSource(element), new StreamResult(stringWriter));
406       return stringWriter.toString();
407     } catch (Throwable e) {
408       throw new RuntimeException(e);
409     }
410   }
411 
412   XPathExpression xpath(String expression) {
413     XPath xpath = XPathFactory.newInstance().newXPath();
414     try {
415       return xpath.compile(expression);
416     } catch (XPathExpressionException e) {
417       throw new RuntimeException(e);
418     }
419   }
420 
421   Node node(XPathExpression xPathExpression) {
422     try {
423       return (Node) xPathExpression.evaluate(this.element, XPathConstants.NODE);
424     } catch (XPathExpressionException e) {
425       throw new RuntimeException(e);
426     }
427   }
428 
429   Optional<Node> optionalNode(XPathExpression xPathExpression) {
430     var nodes = nodes(xPathExpression).toList();
431     return switch (nodes.size()) {
432       case 0 -> Optional.empty();
433       case 1 -> Optional.of(nodes.getFirst());
434       default -> throw new IllegalStateException("Expected 0 or 1 but got more");
435     };
436   }
437 
438   String str(XPathExpression xPathExpression) {
439     try {
440       return (String) xPathExpression.evaluate(this.element, XPathConstants.STRING);
441     } catch (XPathExpressionException e) {
442       throw new RuntimeException(e);
443     }
444   }
445 
446   String xpathQueryString(String xpathString) {
447     try {
448       return (String) xpath(xpathString).evaluate(this.element, XPathConstants.STRING);
449     } catch (XPathExpressionException e) {
450       throw new RuntimeException(e);
451     }
452   }
453 
454   NodeList nodeList(XPathExpression xPathExpression) {
455     try {
456       return (NodeList) xPathExpression.evaluate(this.element, XPathConstants.NODESET);
457     } catch (XPathExpressionException e) {
458       throw new RuntimeException(e);
459     }
460   }
461 
462   Stream<Node> nodes(XPathExpression xPathExpression) {
463     var nodeList = nodeList(xPathExpression);
464     List<Node> nodes = new ArrayList<>();
465     for (int i = 0; i < nodeList.getLength(); i++) {
466       nodes.add(nodeList.item(i));
467     }
468     return nodes.stream();
469   }
470 
471   Stream<org.w3c.dom.Element> elements(XPathExpression xPathExpression) {
472     return nodes(xPathExpression)
473         .filter(n -> n instanceof org.w3c.dom.Element)
474         .map(n -> (Element) n);
475   }
476 
477   Stream<XMLNode> xmlNodes(XPathExpression xPathExpression) {
478     return elements(xPathExpression).map(e -> new XMLNode(e));
479   }
480 }