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 }