diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/TYCHO192sourceBundles/Tycho192SourceBundleTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/TYCHO192sourceBundles/Tycho192SourceBundleTest.java index c3de9c2af5..fcc1ad713a 100644 --- a/tycho-its/src/test/java/org/eclipse/tycho/test/TYCHO192sourceBundles/Tycho192SourceBundleTest.java +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/TYCHO192sourceBundles/Tycho192SourceBundleTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010, 2021 SAP AG and others. + * Copyright (c) 2010, 2023 SAP AG and others. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -25,37 +25,19 @@ import java.util.jar.JarFile; import java.util.zip.ZipEntry; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; import org.apache.maven.it.Verifier; import org.eclipse.tycho.TychoConstants; import org.eclipse.tycho.test.AbstractTychoIntegrationTest; +import org.eclipse.tycho.test.util.XMLTool; import org.junit.Test; import org.osgi.framework.Constants; import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.xml.sax.SAXException; public class Tycho192SourceBundleTest extends AbstractTychoIntegrationTest { - private final DocumentBuilder docBuilder = createDocBuilder(); - private final XPath xpath = XPathFactory.newInstance().newXPath(); - - private DocumentBuilder createDocBuilder() { - try { - return DocumentBuilderFactory.newInstance().newDocumentBuilder(); - } catch (ParserConfigurationException e) { - throw new RuntimeException(e); - } - } - @Test public void testDefaultSourceBundleSuffix() throws Exception { Verifier verifier = getVerifier("/TYCHO192sourceBundles", false); @@ -69,27 +51,25 @@ public void testDefaultSourceBundleSuffix() throws Exception { private void checkP2ContentXml(File p2Content) throws Exception { assertTrue(p2Content.isFile()); - Document p2ContentDOM = docBuilder.parse(p2Content); - XPathExpression sourceBundleUnitExpression = xpath.compile("/units/unit[@id = 'helloworld.source']"); - Element sourceBundleUnitNode = (Element) sourceBundleUnitExpression.evaluate(p2ContentDOM.getDocumentElement(), - XPathConstants.NODE); + Document p2ContentDOM = XMLTool.parseXMLDocument(p2Content); + Element sourceBundleUnitNode = (Element) XMLTool.getFirstMatchingNode(p2ContentDOM, + "/units/unit[@id = 'helloworld.source']"); assertNotNull("unit with id 'helloworld.source' not found", sourceBundleUnitNode); assertHasMavenClassifierProperty(sourceBundleUnitNode); } private void assertHasMavenClassifierProperty(Element node) throws XPathExpressionException { - XPathExpression classifierNodeExpression = xpath.compile("properties/property[@name = 'maven-classifier']"); - Element classifierNode = (Element) classifierNodeExpression.evaluate(node, XPathConstants.NODE); + Element classifierNode = (Element) XMLTool.getFirstMatchingNode(node, + "properties/property[@name = 'maven-classifier']"); assertNotNull("property node with name 'maven-classifier' not found", classifierNode); assertEquals("sources", classifierNode.getAttribute("value")); } - private void checkP2ArtifactsXml(File p2Artifacts) throws SAXException, IOException, XPathExpressionException { + private void checkP2ArtifactsXml(File p2Artifacts) throws Exception { assertTrue(p2Artifacts.isFile()); - Document p2ArtifactsDOM = docBuilder.parse(p2Artifacts); - XPathExpression sourceBundleNodeExpression = xpath.compile("/artifacts/artifact[@id = 'helloworld.source']"); - Element sourceBundleArtifactNode = (Element) sourceBundleNodeExpression - .evaluate(p2ArtifactsDOM.getDocumentElement(), XPathConstants.NODE); + Document p2ArtifactsDOM = XMLTool.parseXMLDocument(p2Artifacts); + Element sourceBundleArtifactNode = (Element) XMLTool.getFirstMatchingNode(p2ArtifactsDOM, + "/artifacts/artifact[@id = 'helloworld.source']"); assertNotNull("artifact with id 'helloworld.source' not found", sourceBundleArtifactNode); assertHasMavenClassifierProperty(sourceBundleArtifactNode); } diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/TYCHO279HttpProxy/ProxySupportTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/TYCHO279HttpProxy/ProxySupportTest.java index 5ca9a61e81..c2e0958465 100644 --- a/tycho-its/src/test/java/org/eclipse/tycho/test/TYCHO279HttpProxy/ProxySupportTest.java +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/TYCHO279HttpProxy/ProxySupportTest.java @@ -23,15 +23,10 @@ import java.util.Map; import java.util.Properties; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathFactory; import org.apache.maven.it.Verifier; import org.eclipse.jetty.server.NetworkTrafficServerConnector; @@ -40,6 +35,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.tycho.test.AbstractTychoIntegrationTest; +import org.eclipse.tycho.test.util.XMLTool; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -47,6 +43,7 @@ import org.sonatype.jettytestsuite.proxy.MonitorableProxyServlet; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; public class ProxySupportTest extends AbstractTychoIntegrationTest { @@ -182,28 +179,22 @@ private void replaceSettingsArg(Verifier verifier) throws IOException { } private void configureProxyInSettingsXml(boolean isProxyActive, String user, String password) throws Exception { - Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(settings); - XPath xpath = XPathFactory.newInstance().newXPath(); - XPathExpression proxyExpr = xpath.compile("/settings/proxies/proxy"); - Element proxyNode = (Element) proxyExpr.evaluate(dom.getDocumentElement(), XPathConstants.NODE); + Document dom = XMLTool.parseXMLDocument(settings); + Element proxyNode = (Element) XMLTool.getFirstMatchingNode(dom, "/settings/proxies/proxy"); { - XPathExpression portExpr = xpath.compile("/settings/proxies/proxy/port"); - Element node = (Element) portExpr.evaluate(dom.getDocumentElement(), XPathConstants.NODE); + Element node = (Element) XMLTool.getFirstMatchingNode(dom, "/settings/proxies/proxy/port"); node.setTextContent(String.valueOf(proxyPort)); } { - XPathExpression activeExpr = xpath.compile("/settings/proxies/proxy/active"); - Element activeNode = (Element) activeExpr.evaluate(dom.getDocumentElement(), XPathConstants.NODE); + Element activeNode = (Element) XMLTool.getFirstMatchingNode(dom, "/settings/proxies/proxy/active"); activeNode.setTextContent(String.valueOf(isProxyActive)); } { - XPathExpression userExpr = xpath.compile("/settings/proxies/proxy/username"); - Element userNode = (Element) userExpr.evaluate(dom.getDocumentElement(), XPathConstants.NODE); + Element userNode = (Element) XMLTool.getFirstMatchingNode(dom, "/settings/proxies/proxy/username"); updateNodeValue("username", userNode, user, dom, proxyNode); } { - XPathExpression passwordExpr = xpath.compile("/settings/proxies/proxy/password"); - Element passwordNode = (Element) passwordExpr.evaluate(dom.getDocumentElement(), XPathConstants.NODE); + Element passwordNode = (Element) XMLTool.getFirstMatchingNode(dom, "/settings/proxies/proxy/password"); updateNodeValue("password", passwordNode, password, dom, proxyNode); } Transformer xslTransformer = TransformerFactory.newInstance().newTransformer(); diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/jarsigning/JarSigningTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/jarsigning/JarSigningTest.java index 34c38238b8..d9aaee5127 100644 --- a/tycho-its/src/test/java/org/eclipse/tycho/test/jarsigning/JarSigningTest.java +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/jarsigning/JarSigningTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011, 2021 SAP AG + * Copyright (c) 2011, 2023 SAP AG * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -12,24 +12,18 @@ *******************************************************************************/ package org.eclipse.tycho.test.jarsigning; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathFactory; +import java.util.List; import org.apache.maven.it.Verifier; import org.eclipse.tycho.test.AbstractTychoIntegrationTest; +import org.eclipse.tycho.test.util.XMLTool; import org.junit.Test; import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; +import org.w3c.dom.Node; public class JarSigningTest extends AbstractTychoIntegrationTest { @@ -57,30 +51,12 @@ private void checkSha256SumsArePresent(Verifier verifier) throws Exception { File repoDir = new File(verifier.getBasedir(), "rcp/target/repository"); File artifacts = new File(repoDir, "artifacts.jar"); assertTrue(artifacts.isFile()); - DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document document = null; - try (ZipFile artifactsJar = new ZipFile(artifacts)) { - ZipEntry artifactsXmlEntry = artifactsJar.getEntry("artifacts.xml"); - document = parser.parse(artifactsJar.getInputStream(artifactsXmlEntry)); - } - Element repository = document.getDocumentElement(); - XPath xpath = XPathFactory.newInstance().newXPath(); - NodeList nodeList = (NodeList) xpath.evaluate("/repository/artifacts/artifact", repository, - XPathConstants.NODESET); - for (int i = 0; i < nodeList.getLength(); i++) { - Element artifactNode = (Element) nodeList.item(i); - NodeList properties = (NodeList) xpath.evaluate("properties/property", artifactNode, - XPathConstants.NODESET); - boolean hasSha256 = false; - for (int j = 0; j < properties.getLength(); j++) { - Element property = (Element) properties.item(j); - String propName = property.getAttribute("name"); - if ("download.checksum.sha-256".equals(propName)) { - hasSha256 = true; - break; - } - } - assertTrue("artifact does not have a 'download.checksum.sha-256' attribute", hasSha256); + Document document = XMLTool.parseXMLDocumentFromJar(artifacts, "artifacts.xml"); + List artifactNodes = XMLTool.getMatchingNodes(document, "/repository/artifacts/artifact"); + for (Node artifact : artifactNodes) { + List checksumProperties = XMLTool.getMatchingNodes(artifact, + "properties/property[@name='download.checksum.sha-256']"); + assertFalse("artifact does not have a 'download.checksum.sha-256' attribute", checksumProperties.isEmpty()); } } } diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/surefire/ParallelTestExecutionTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/surefire/ParallelTestExecutionTest.java index 2e6c790c4e..c59f29d244 100644 --- a/tycho-its/src/test/java/org/eclipse/tycho/test/surefire/ParallelTestExecutionTest.java +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/surefire/ParallelTestExecutionTest.java @@ -18,24 +18,18 @@ import static org.junit.Assert.assertTrue; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FilenameFilter; -import java.io.IOException; import java.util.HashSet; +import java.util.List; import java.util.Set; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; - import org.apache.maven.it.Verifier; import org.eclipse.tycho.test.AbstractTychoIntegrationTest; +import org.eclipse.tycho.test.util.XMLTool; import org.junit.Test; +import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; +import org.w3c.dom.Node; public class ParallelTestExecutionTest extends AbstractTychoIntegrationTest { @@ -56,20 +50,15 @@ public void testParallelExecution() throws Exception { assertEquals(expectedTests, actualTests); } - private Set extractExecutedTests(File[] xmlReports) - throws FileNotFoundException, XPathExpressionException, IOException { - XPath xpath = XPathFactory.newInstance().newXPath(); + private Set extractExecutedTests(File[] xmlReports) throws Exception { Set actualTests = new HashSet<>(); for (File xmlReportFile : xmlReports) { - NodeList testCaseNodes; - try (FileInputStream xmlStream = new FileInputStream(xmlReportFile)) { - testCaseNodes = (NodeList) xpath.evaluate("/testsuite/testcase", new InputSource(xmlStream), - XPathConstants.NODESET); - } - for (int i = 0; i < testCaseNodes.getLength(); i++) { - Element node = (Element) testCaseNodes.item(i); - String testClassName = node.getAttribute("classname"); - String method = node.getAttribute("name"); + Document document = XMLTool.parseXMLDocument(xmlReportFile); + List matchingNodes = XMLTool.getMatchingNodes(document, "/testsuite/testcase"); + for (Node node : matchingNodes) { + Element element = (Element) node; + String testClassName = element.getAttribute("classname"); + String method = element.getAttribute("name"); actualTests.add(testClassName + "#" + method); } } diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/util/SurefireUtil.java b/tycho-its/src/test/java/org/eclipse/tycho/test/util/SurefireUtil.java index 2d4fa07106..eb24a44037 100644 --- a/tycho-its/src/test/java/org/eclipse/tycho/test/util/SurefireUtil.java +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/util/SurefireUtil.java @@ -16,15 +16,10 @@ import static org.junit.Assert.assertTrue; import java.io.File; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathFactory; +import java.util.List; import org.w3c.dom.Document; -import org.w3c.dom.NodeList; +import org.w3c.dom.Node; public class SurefireUtil { @@ -42,23 +37,22 @@ public static void assertTestMethodWasSuccessfullyExecuted(String baseDir, Strin int iterations) throws Exception { File resultFile = getTestResultFile(baseDir, className); Document document = readDocument(resultFile); - XPath xpath = XPathFactory.newInstance().newXPath(); // surefire-test-report XML schema: // https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd String testCaseXPath = String.format("/testsuite/testcase[@classname='%s' and @name='%s']", className, methodName); - NodeList testCaseNodes = (NodeList) xpath.evaluate(testCaseXPath, document, XPathConstants.NODESET); + List testCaseNodes2 = XMLTool.getMatchingNodes(document, testCaseXPath); assertEquals(resultFile.getAbsolutePath() + " with xpath " + testCaseXPath - + " does not match the number of iterations", iterations, testCaseNodes.getLength()); + + " does not match the number of iterations", iterations, testCaseNodes2.size()); - NodeList failureNodes = (NodeList) xpath.evaluate(testCaseXPath + "/failure", document, XPathConstants.NODESET); - assertEquals(0, failureNodes.getLength()); + List failureNodes = XMLTool.getMatchingNodes(document, testCaseXPath + "/failure"); + assertEquals(0, failureNodes.size()); - NodeList errorNodes = (NodeList) xpath.evaluate(testCaseXPath + "/error", document, XPathConstants.NODESET); - assertEquals(0, errorNodes.getLength()); + List errorNodes = XMLTool.getMatchingNodes(document, testCaseXPath + "/error"); + assertEquals(0, errorNodes.size()); - NodeList skippedNodes = (NodeList) xpath.evaluate(testCaseXPath + "/skipped", document, XPathConstants.NODESET); - assertEquals(0, skippedNodes.getLength()); + List skippedNodes = XMLTool.getMatchingNodes(document, testCaseXPath + "/skipped"); + assertEquals(0, skippedNodes.size()); } public static void assertTestMethodWasSuccessfullyExecuted(String baseDir, String className, String methodName) @@ -88,22 +82,14 @@ public static void assertNumberOfSkippedTests(String baseDir, String className, private static int extractNumericAttribute(String baseDir, String className, String attributeXPath) throws Exception { - Document document = readDocument(baseDir, className); - XPath xpath = XPathFactory.newInstance().newXPath(); - String numberOfTests = (String) xpath.evaluate(attributeXPath, document, XPathConstants.STRING); - return Integer.parseInt(numberOfTests); - } - - private static Document readDocument(String baseDir, String className) throws Exception { - return readDocument(getTestResultFile(baseDir, className)); + Document document = readDocument(getTestResultFile(baseDir, className)); + Node numberOfTests = XMLTool.getFirstMatchingNode(document, attributeXPath); + return Integer.parseInt(numberOfTests.getNodeValue()); } private static Document readDocument(File sureFireTestReport) throws Exception { assertTrue(sureFireTestReport.isFile()); - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); - Document document = db.parse(sureFireTestReport); - return document; + return XMLTool.parseXMLDocument(sureFireTestReport); } private static File getTestResultFile(String baseDir, String className) { diff --git a/tycho-testing-harness/src/main/java/org/eclipse/tycho/test/util/P2RepositoryTool.java b/tycho-testing-harness/src/main/java/org/eclipse/tycho/test/util/P2RepositoryTool.java index d4ecba057d..b3151eec01 100644 --- a/tycho-testing-harness/src/main/java/org/eclipse/tycho/test/util/P2RepositoryTool.java +++ b/tycho-testing-harness/src/main/java/org/eclipse/tycho/test/util/P2RepositoryTool.java @@ -11,25 +11,17 @@ import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; import org.junit.Assert; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Node; -import org.w3c.dom.NodeList; public class P2RepositoryTool { - private static final ThreadLocal XPATH_TOOL = ThreadLocal - .withInitial(() -> XPathFactory.newInstance().newXPath()); private static final Pattern strictVersionRangePattern = Pattern.compile("\\[([^,]*),\\1\\]"); private final File repoLocation; private final File metadataFile; @@ -229,36 +221,25 @@ public List getAllRepositoryReferences() throws Exception { } private void loadMetadata() throws Exception { - if (contentXml != null) + if (contentXml != null) { return; - if (metadataFile.getName().endsWith("jar")) - throw new UnsupportedOperationException("Can't read compressed p2 repositories yet"); - - contentXml = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(metadataFile); - } - - private static XPath getXPathTool() { - return XPATH_TOOL.get(); + } + contentXml = metadataFile.getName().endsWith("jar") + ? XMLTool.parseXMLDocumentFromJar(metadataFile, "content.xml") + : XMLTool.parseXMLDocument(metadataFile); } static List getNodes(Object startingPoint, String expression) throws XPathExpressionException { - NodeList nodeList = (NodeList) getXPathTool().evaluate(expression, startingPoint, XPathConstants.NODESET); - - return IntStream.range(0, nodeList.getLength()).mapToObj(nodeList::item).toList(); + return XMLTool.getMatchingNodes(startingPoint, expression); } static List getValues(Object startingPoint, String expression) throws XPathExpressionException { - return getNodes(startingPoint, expression).stream().map(Node::getNodeValue).toList(); + return XMLTool.getMatchingNodesValue(startingPoint, expression); } static String getAttribute(Node node, String expression) throws XPathExpressionException { - Attr attribute = (Attr) getXPathTool().evaluate(expression, node, XPathConstants.NODE); - - if (attribute == null) { - return null; - } else { - return attribute.getValue(); - } + Attr attribute = (Attr) XMLTool.getFirstMatchingNode(node, expression); + return attribute != null ? attribute.getValue() : null; } static boolean isStrictRange(String range) { @@ -279,13 +260,7 @@ static String getLowerBound(String range) { return range.substring(begin, end); } - public static final class IU { - - private final Node unitElement; - - IU(Node unitElement) { - this.unitElement = unitElement; - } + public static final record IU(Node unitElement) { public String getVersion() throws Exception { return getAttribute(unitElement, "@version"); diff --git a/tycho-testing-harness/src/main/java/org/eclipse/tycho/test/util/XMLTool.java b/tycho-testing-harness/src/main/java/org/eclipse/tycho/test/util/XMLTool.java new file mode 100644 index 0000000000..a8c9e20fda --- /dev/null +++ b/tycho-testing-harness/src/main/java/org/eclipse/tycho/test/util/XMLTool.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2023, 2023 Hannes Wellmann and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Hannes Wellmann - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.test.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.jar.JarFile; +import java.util.stream.IntStream; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +public class XMLTool { + private XMLTool() { // static use only + } + + private static final DocumentBuilderFactory FACTORY; + static { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // completely disable external entities declarations: + try { + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } + FACTORY = factory; + } + + public static Document parseXMLDocument(File file) throws SAXException, IOException, ParserConfigurationException { + return FACTORY.newDocumentBuilder().parse(file); + } + + public static Document parseXMLDocumentFromJar(File jarFile, String entryPath) + throws SAXException, IOException, ParserConfigurationException { + try (JarFile jar = new JarFile(jarFile); // + InputStream stream = jar.getInputStream(jar.getEntry(entryPath));) { + return FACTORY.newDocumentBuilder().parse(stream); + } + } + + private static final ThreadLocal XPATH_TOOL = ThreadLocal + .withInitial(() -> XPathFactory.newInstance().newXPath()); + + private static Object evaluateXPath(Object startingPoint, String xpathExpression, QName returnType) + throws XPathExpressionException { + return XPATH_TOOL.get().evaluate(xpathExpression, startingPoint, returnType); + } + + public static Node getFirstMatchingNode(Object startingPoint, String xpathExpression) + throws XPathExpressionException { + return (Node) evaluateXPath(startingPoint, xpathExpression, XPathConstants.NODE); + } + + public static List getMatchingNodes(Object startingPoint, String xpathExpression) + throws XPathExpressionException { + NodeList nodeList = (NodeList) evaluateXPath(startingPoint, xpathExpression, XPathConstants.NODESET); + return IntStream.range(0, nodeList.getLength()).mapToObj(nodeList::item).toList(); + } + + public static List getMatchingNodesValue(Object startingPoint, String xpathExpression) + throws XPathExpressionException { + return getMatchingNodes(startingPoint, xpathExpression).stream().map(Node::getNodeValue).toList(); + } + +}