| /******************************************************************************* |
| * Copyright (c) 2015, 2017 Rapicorp, Inc 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: |
| * Rapicorp, Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.publisher.eclipse; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import javax.xml.parsers.*; |
| import javax.xml.transform.*; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamResult; |
| import javax.xml.xpath.*; |
| import org.w3c.dom.*; |
| import org.xml.sax.SAXException; |
| |
| public class InfoPListEditor { |
| public static final String MARKER_KEY = "CFBundleExecutable"; //$NON-NLS-1$ |
| public static final String BUNDLE_KEY = "CFBundleName"; //$NON-NLS-1$ |
| public static final String BUNDLE_ID_KEY = "CFBundleIdentifier"; //$NON-NLS-1$ |
| public static final String BUNDLE_DISPLAYNAME_KEY = "CFBundleDisplayName"; //$NON-NLS-1$ |
| public static final String BUNDLE_INFO_KEY = "CFBundleGetInfoString"; //$NON-NLS-1$ |
| public static final String BUNDLE_VERSION_KEY = "CFBundleVersion"; //$NON-NLS-1$ |
| public static final String BUNDLE_SHORT_VERSION_KEY = "CFBundleShortVersionString"; //$NON-NLS-1$ |
| public static final String ICON_KEY = "CFBundleIconFile"; //$NON-NLS-1$ |
| |
| private final Element infoPList; |
| private XPath xPathTool; |
| private final Document document; |
| |
| private InfoPListEditor(Document doc) { |
| document = doc; |
| infoPList = document.getDocumentElement(); |
| } |
| |
| public static InfoPListEditor loadPListEditor(File file) throws IOException { |
| if (!file.exists()) { |
| throw new IOException("No file at " + file.getAbsoluteFile()); //$NON-NLS-1$ |
| } |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| DocumentBuilder builder; |
| Exception exception; |
| try { |
| factory.setValidating(false); |
| factory.setNamespaceAware(true); |
| //These setFeature calls are necessary to disable the validation of the DTD in the info.plist |
| factory.setFeature("http://xml.org/sax/features/namespaces", false); //$NON-NLS-1$ |
| factory.setFeature("http://xml.org/sax/features/validation", false); //$NON-NLS-1$ |
| factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); //$NON-NLS-1$ |
| factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); //$NON-NLS-1$ |
| builder = factory.newDocumentBuilder(); |
| return new InfoPListEditor(builder.parse(file)); |
| } catch (ParserConfigurationException e) { |
| exception = e; |
| } catch (SAXException e) { |
| exception = e; |
| } catch (IOException e) { |
| exception = e; |
| } |
| throw new IOException("Problem parsing " + file.getAbsolutePath(), exception); //$NON-NLS-1$ |
| } |
| |
| public void save(File file) throws TransformerException { |
| final TransformerFactory transformerFactory = TransformerFactory.newInstance(); |
| final Transformer transformer = transformerFactory.newTransformer(); |
| final DOMSource toSerialize = new DOMSource(document); |
| |
| final StreamResult output = new StreamResult(file); |
| transformer.setOutputProperty(OutputKeys.VERSION, "1.0"); //$NON-NLS-1$ |
| transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ |
| transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); //$NON-NLS-1$ |
| |
| transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ |
| transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //$NON-NLS-1$//$NON-NLS-2$ |
| |
| transformer.transform(toSerialize, output); |
| } |
| |
| private Node getNode(Node node, String expression) throws XPathExpressionException { |
| return (Node) getXPathTool().evaluate(expression, node, XPathConstants.NODE); |
| } |
| |
| public void setKey(String key, String value) { |
| if (key == null) |
| throw new IllegalArgumentException("Key can't be null"); //$NON-NLS-1$ |
| if (value == null) |
| throw new IllegalArgumentException("Value can't be null"); //$NON-NLS-1$ |
| |
| String expression = String.format("/plist/dict/key[text() = '%s']/following-sibling::string[1]", key); //$NON-NLS-1$ |
| Node node; |
| try { |
| node = getNode(infoPList, expression); |
| if (node != null) |
| node.getFirstChild().setNodeValue(value); |
| else |
| addKey(key, value); |
| } catch (XPathExpressionException e) { |
| //Can't happen since we craft the expression carefully |
| } |
| } |
| |
| private void addKey(String key, String value) throws DOMException, XPathExpressionException { |
| Element keyNode = document.createElement("key"); //$NON-NLS-1$ |
| Text keyName = document.createTextNode(key); |
| keyNode.appendChild(keyName); |
| |
| Element stringNode = document.createElement("string"); //$NON-NLS-1$ |
| Text stringValue = document.createTextNode(value); |
| stringNode.appendChild(stringValue); |
| getNode(infoPList, "/plist/dict").appendChild(keyNode); //$NON-NLS-1$ |
| getNode(infoPList, "/plist/dict").appendChild(stringNode); //$NON-NLS-1$ |
| } |
| |
| private XPath getXPathTool() { |
| if (xPathTool == null) { |
| xPathTool = XPathFactory.newInstance().newXPath(); |
| } |
| return xPathTool; |
| } |
| |
| public void setEclipseArgument(String key, String value) { |
| if (key == null) |
| throw new IllegalArgumentException("Key can't be null"); //$NON-NLS-1$ |
| |
| String expression = "/plist/dict/key[text() = 'Eclipse']/following-sibling::array[1]/string[text() = -keyring'"; //$NON-NLS-1$ |
| Node node; |
| try { |
| node = getNode(infoPList, expression); |
| if (node != null) |
| node.getFirstChild().setNodeValue(value); |
| else |
| addKey(key, value); |
| } catch (XPathExpressionException e) { |
| //Can't happen since we craft the expression carefully |
| } |
| |
| } |
| |
| private List<String> getValues(Object startingPoint, String expression) throws XPathExpressionException { |
| NodeList nodeList = (NodeList) getXPathTool().evaluate(expression, startingPoint, XPathConstants.NODESET); |
| |
| List<String> result = new ArrayList<>(nodeList.getLength()); |
| for (int ix = 0; ix < nodeList.getLength(); ++ix) { |
| result.add(nodeList.item(ix).getNodeValue()); |
| } |
| return result; |
| } |
| |
| private List<Node> removeNodes(Object startingPoint, String expression) throws XPathExpressionException { |
| NodeList nodeList = (NodeList) getXPathTool().evaluate(expression, startingPoint, XPathConstants.NODESET); |
| |
| List<Node> result = new ArrayList<>(nodeList.getLength()); |
| for (int ix = 0; ix < nodeList.getLength(); ++ix) { |
| result.add(nodeList.item(ix).getParentNode().removeChild(nodeList.item(ix))); |
| } |
| return result; |
| } |
| |
| public List<String> getEclipseArguments() { |
| try { |
| return getValues(infoPList, "/plist/dict/key[text() = 'Eclipse']/following-sibling::array[1]/string/text()"); //$NON-NLS-1$ |
| } catch (XPathExpressionException e) { |
| //Can't happen the expression is carefully crafted |
| return null; |
| } |
| } |
| |
| public void setEclipseArguments(List<String> arguments) { |
| try { |
| removeNodes(infoPList, "/plist/dict/key[text() = 'Eclipse']/following-sibling::array[1]/string"); //$NON-NLS-1$ |
| for (String arg : arguments) { |
| Element stringNode = document.createElement("string"); //$NON-NLS-1$ |
| Text stringName = document.createTextNode(arg); |
| stringNode.appendChild(stringName); |
| Node toAppendTo = getNode(infoPList, "/plist/dict/key[text() = 'Eclipse']/following-sibling::array[1]"); //$NON-NLS-1$ |
| toAppendTo.appendChild(stringNode); |
| } |
| } catch (XPathExpressionException e) { |
| //can't happen |
| } |
| } |
| } |