Skip to content

Commit 55f7cf1

Browse files
Move MacHelper.PListWrapper to utils.PListReader
1 parent 02cc8ca commit 55f7cf1

File tree

5 files changed

+426
-84
lines changed

5 files changed

+426
-84
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (c) 2025, 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 jdk.jpackage.internal.util;
26+
27+
import java.io.ByteArrayInputStream;
28+
import java.io.IOException;
29+
import java.util.List;
30+
import java.util.NoSuchElementException;
31+
import java.util.Objects;
32+
import java.util.Optional;
33+
import javax.xml.parsers.ParserConfigurationException;
34+
import javax.xml.xpath.XPathConstants;
35+
import javax.xml.xpath.XPathFactory;
36+
import jdk.jpackage.internal.util.function.ThrowingSupplier;
37+
import org.w3c.dom.Node;
38+
import org.xml.sax.SAXException;
39+
40+
public final class PListReader {
41+
42+
public String queryValue(String keyName) {
43+
final var node = getNode(keyName);
44+
switch (node.getNodeName()) {
45+
case "string" -> {
46+
return node.getTextContent();
47+
}
48+
default -> {
49+
throw new NoSuchElementException();
50+
}
51+
}
52+
}
53+
54+
public boolean queryBoolValue(String keyName) {
55+
final var node = getNode(keyName);
56+
switch (node.getNodeName()) {
57+
case "true" -> {
58+
return true;
59+
}
60+
case "false" -> {
61+
return false;
62+
}
63+
default -> {
64+
throw new NoSuchElementException();
65+
}
66+
}
67+
}
68+
69+
public List<String> queryArrayValue(String keyName) {
70+
final var node = getNode(keyName);
71+
switch (node.getNodeName()) {
72+
case "array" -> {
73+
return XmlUtils.toStream(node.getChildNodes()).filter(n -> {
74+
return n.getNodeName().equals("string");
75+
}).map(Node::getTextContent).toList();
76+
}
77+
default -> {
78+
throw new NoSuchElementException();
79+
}
80+
}
81+
}
82+
83+
public PListReader(Node doc) {
84+
this.root = Objects.requireNonNull(doc);
85+
}
86+
87+
public PListReader(byte[] xmlData) throws ParserConfigurationException, SAXException, IOException {
88+
this(XmlUtils.initDocumentBuilder().parse(new ByteArrayInputStream(xmlData)));
89+
}
90+
91+
private Node getNode(String keyName) {
92+
final var xPath = XPathFactory.newInstance().newXPath();
93+
final var query = String.format("//*[preceding-sibling::key = \"%s\"][1]", keyName);
94+
return Optional.ofNullable(ThrowingSupplier.toSupplier(() -> {
95+
return (Node) xPath.evaluate(query, root, XPathConstants.NODE);
96+
}).get()).orElseThrow(NoSuchElementException::new);
97+
}
98+
99+
private final Node root;
100+
}

src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/XmlUtils.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -30,6 +30,10 @@
3030
import java.nio.file.Files;
3131
import java.nio.file.Path;
3232
import java.util.Collection;
33+
import java.util.Optional;
34+
import java.util.stream.Collectors;
35+
import java.util.stream.IntStream;
36+
import java.util.stream.Stream;
3337
import javax.xml.parsers.DocumentBuilder;
3438
import javax.xml.parsers.DocumentBuilderFactory;
3539
import javax.xml.parsers.ParserConfigurationException;
@@ -41,10 +45,26 @@
4145
import javax.xml.transform.TransformerException;
4246
import javax.xml.transform.TransformerFactory;
4347
import javax.xml.transform.stax.StAXResult;
48+
import javax.xml.xpath.XPath;
49+
import javax.xml.xpath.XPathConstants;
50+
import javax.xml.xpath.XPathExpressionException;
51+
import org.w3c.dom.Element;
52+
import org.w3c.dom.NamedNodeMap;
53+
import org.w3c.dom.Node;
54+
import org.w3c.dom.NodeList;
4455

4556

4657
public final class XmlUtils {
4758

59+
@FunctionalInterface
60+
public interface XmlConsumerNoArg {
61+
void accept() throws IOException, XMLStreamException;
62+
}
63+
64+
public static XmlConsumer toXmlConsumer(XmlConsumerNoArg xmlConsumer) {
65+
return xml -> xmlConsumer.accept();
66+
}
67+
4868
public static void createXml(Path dstFile, XmlConsumer xmlConsumer) throws
4969
IOException {
5070
XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
@@ -101,4 +121,29 @@ public static DocumentBuilderFactory initDocumentBuilderFactory() {
101121
}
102122
return dbf;
103123
}
124+
125+
public static Stream<Node> queryNodes(Node xml, XPath xPath, String xpathExpr) throws XPathExpressionException {
126+
return toStream((NodeList) xPath.evaluate(xpathExpr, xml, XPathConstants.NODESET));
127+
}
128+
129+
public static Stream<Node> toStream(NodeList nodes) {
130+
return Optional.ofNullable(nodes).map(v -> {
131+
return IntStream.range(0, v.getLength()).mapToObj(v::item);
132+
}).orElseGet(Stream::of);
133+
}
134+
135+
public static Stream<Node> toStream(NamedNodeMap nodes) {
136+
return Optional.ofNullable(nodes).map(v -> {
137+
return IntStream.range(0, v.getLength()).mapToObj(v::item);
138+
}).orElseGet(Stream::of);
139+
}
140+
141+
public static String elementValue(Element e, XPath xPath) {
142+
try {
143+
return queryNodes(e, xPath, "text()").map(Node::getNodeValue).collect(Collectors.joining());
144+
} catch (XPathExpressionException ex) {
145+
// Should never happen
146+
throw new RuntimeException(ex);
147+
}
148+
}
104149
}

test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java

Lines changed: 9 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,22 @@
3131
import java.nio.charset.StandardCharsets;
3232
import java.nio.file.Files;
3333
import java.nio.file.Path;
34-
import java.util.ArrayList;
3534
import java.util.List;
3635
import java.util.Objects;
3736
import java.util.Optional;
3837
import java.util.Set;
3938
import java.util.regex.Pattern;
4039
import java.util.stream.Collectors;
4140
import java.util.stream.Stream;
42-
import javax.xml.parsers.DocumentBuilder;
43-
import javax.xml.parsers.DocumentBuilderFactory;
44-
import javax.xml.parsers.ParserConfigurationException;
45-
import javax.xml.xpath.XPath;
4641
import javax.xml.xpath.XPathConstants;
4742
import javax.xml.xpath.XPathFactory;
4843
import jdk.jpackage.internal.RetryExecutor;
44+
import jdk.jpackage.internal.util.PListReader;
4945
import jdk.jpackage.internal.util.PathUtils;
46+
import jdk.jpackage.internal.util.XmlUtils;
5047
import jdk.jpackage.internal.util.function.ThrowingConsumer;
5148
import jdk.jpackage.internal.util.function.ThrowingSupplier;
5249
import jdk.jpackage.test.PackageTest.PackageHandlers;
53-
import org.w3c.dom.NodeList;
54-
import org.xml.sax.SAXException;
5550

5651
public final class MacHelper {
5752

@@ -129,25 +124,25 @@ public static void withExplodedDmg(JPackageCommand cmd,
129124
}
130125
}
131126

132-
public static PListWrapper readPListFromAppImage(Path appImage) {
127+
public static PListReader readPListFromAppImage(Path appImage) {
133128
return readPList(appImage.resolve("Contents/Info.plist"));
134129
}
135130

136-
public static PListWrapper readPList(Path path) {
131+
public static PListReader readPList(Path path) {
137132
TKit.assertReadableFileExists(path);
138133
return ThrowingSupplier.toSupplier(() -> readPList(Files.readAllLines(
139134
path))).get();
140135
}
141136

142-
public static PListWrapper readPList(List<String> lines) {
137+
public static PListReader readPList(List<String> lines) {
143138
return readPList(lines.stream());
144139
}
145140

146-
public static PListWrapper readPList(Stream<String> lines) {
147-
return ThrowingSupplier.toSupplier(() -> new PListWrapper(lines
141+
public static PListReader readPList(Stream<String> lines) {
142+
return ThrowingSupplier.toSupplier(() -> new PListReader(lines
148143
// Skip leading lines before xml declaration
149144
.dropWhile(Pattern.compile("\\s?<\\?xml\\b.+\\?>").asPredicate().negate())
150-
.collect(Collectors.joining()))).get();
145+
.collect(Collectors.joining()).getBytes(StandardCharsets.UTF_8))).get();
151146
}
152147

153148
public static boolean signPredefinedAppImage(JPackageCommand cmd) {
@@ -261,7 +256,7 @@ private static Path unpackPkg(JPackageCommand cmd, Path destinationDir) {
261256
}).forEach(ThrowingConsumer.toConsumer(pkgDir -> {
262257
// Installation root of the package is stored in
263258
// /pkg-info@install-location attribute in $pkgDir/PackageInfo xml file
264-
var doc = createDocumentBuilder().parse(
259+
var doc = XmlUtils.initDocumentBuilder().parse(
265260
new ByteArrayInputStream(Files.readAllBytes(
266261
pkgDir.resolve("PackageInfo"))));
267262
var xPath = XPathFactory.newInstance().newXPath();
@@ -370,73 +365,10 @@ private static String getPackageId(JPackageCommand cmd) {
370365
});
371366
}
372367

373-
public static final class PListWrapper {
374-
public String queryValue(String keyName) {
375-
XPath xPath = XPathFactory.newInstance().newXPath();
376-
// Query for the value of <string> element preceding <key> element
377-
// with value equal to `keyName`
378-
String query = String.format(
379-
"//string[preceding-sibling::key = \"%s\"][1]", keyName);
380-
return ThrowingSupplier.toSupplier(() -> (String) xPath.evaluate(
381-
query, doc, XPathConstants.STRING)).get();
382-
}
383-
384-
public Boolean queryBoolValue(String keyName) {
385-
XPath xPath = XPathFactory.newInstance().newXPath();
386-
// Query boolean element preceding <key> element
387-
// with value equal to `keyName`
388-
String query = String.format(
389-
"name(//*[preceding-sibling::key = \"%s\"])", keyName);
390-
String value = ThrowingSupplier.toSupplier(() -> (String) xPath.evaluate(
391-
query, doc, XPathConstants.STRING)).get();
392-
return Boolean.valueOf(value);
393-
}
394-
395-
public List<String> queryArrayValue(String keyName) {
396-
XPath xPath = XPathFactory.newInstance().newXPath();
397-
// Query string array preceding <key> element with value equal to `keyName`
398-
String query = String.format(
399-
"//array[preceding-sibling::key = \"%s\"]", keyName);
400-
NodeList list = ThrowingSupplier.toSupplier(() -> (NodeList) xPath.evaluate(
401-
query, doc, XPathConstants.NODESET)).get();
402-
if (list.getLength() != 1) {
403-
throw new RuntimeException(
404-
String.format("Unable to find <array> element for key = \"%s\"]",
405-
keyName));
406-
}
407-
408-
NodeList childList = list.item(0).getChildNodes();
409-
List<String> values = new ArrayList<>(childList.getLength());
410-
for (int i = 0; i < childList.getLength(); i++) {
411-
if (childList.item(i).getNodeName().equals("string")) {
412-
values.add(childList.item(i).getTextContent());
413-
}
414-
}
415-
return values;
416-
}
417-
418-
private PListWrapper(String xml) throws ParserConfigurationException,
419-
SAXException, IOException {
420-
doc = createDocumentBuilder().parse(new ByteArrayInputStream(
421-
xml.getBytes(StandardCharsets.UTF_8)));
422-
}
423-
424-
private final org.w3c.dom.Document doc;
425-
}
426-
427368
public static boolean isXcodeDevToolsInstalled() {
428369
return Inner.XCODE_DEV_TOOLS_INSTALLED;
429370
}
430371

431-
private static DocumentBuilder createDocumentBuilder() throws
432-
ParserConfigurationException {
433-
DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance();
434-
dbf.setFeature(
435-
"http://apache.org/xml/features/nonvalidating/load-external-dtd",
436-
false);
437-
return dbf.newDocumentBuilder();
438-
}
439-
440372
private static String getServicePListFileName(String packageName,
441373
String launcherName) {
442374
try {

0 commit comments

Comments
 (0)