/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.utility;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* The XML APIs are a bit awkward, and they like to declare
* "checked" exceptions (boo). This utility class simplifies using those APIs.
* In particular, it facilitates the getting and setting of the values
* of the children of a particular node (e.g. when reading and writing
* the attributes of an object from and to an XML document).
*/
public final class XMLTools {
/**The DOM parser factory. */
private static DocumentBuilderFactory documentBuilderFactory;
/**The DOM parser. Just keep one around and synchronize access to it. */
private static DocumentBuilder documentBuilder;
/**The transformer factory. */
private static TransformerFactory transformerFactory;
/**The transformer. Just keep one around and synchronize access to it. */
private static Transformer transformer;
// ********** parsing **********
/**
* @see javax.xml.parsers.DocumentBuilderFactory#newInstance() for
* documentation on how the implementation class is determined
*/
private static synchronized DocumentBuilderFactory documentBuilderFactory() {
if (documentBuilderFactory == null) {
documentBuilderFactory = DocumentBuilderFactory.newInstance();
}
return documentBuilderFactory;
}
private static synchronized DocumentBuilder documentBuilder() {
if (documentBuilder == null) {
try {
documentBuilder = documentBuilderFactory().newDocumentBuilder();
} catch (ParserConfigurationException ex) {
throw new RuntimeException(ex);
}
}
return documentBuilder;
}
/**
* Build and return an XML document based on the contents
* of the specified input source.
* DocumentBuilder#parse(InputSource inputSource) throws RuntimeExceptions
*/
public static synchronized Document parse(InputSource inputSource) {
try {
return documentBuilder().parse(inputSource);
} catch (SAXException ex) {
throw new RuntimeException(ex);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* Build and return an XML document based on the contents
* of the specified reader.
* DocumentBuilder#parse(Reader reader)
*/
public static Document parse(Reader reader) {
Document document = null;
try {
document = parse(new InputSource(reader));
} finally {
try {
reader.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
return document;
}
/**
* Build and return an XML document based on the contents
* of the specified input stream.
* DocumentBuilder#parse(InputStream inputStream) throws RuntimeExceptions
*/
public static Document parse(InputStream inputStream) {
try {
return parse(new InputStreamReader(inputStream, "UTF-8"));
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex);
}
}
/**
* Build and return an XML document based on the contents
* of the specified file.
* DocumentBuilder#parse(File file) throws RuntimeExceptions
*/
public static Document parse(File file) {
InputStream inputStream;
try {
inputStream = new BufferedInputStream(new FileInputStream(file), 8192); // 8KB
} catch (FileNotFoundException ex) {
throw new RuntimeException(ex);
}
Document document = parse(inputStream);
try {
inputStream.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return document;
}
// ********** reading **********
/**
* Return the child element node of the specified parent node with
* the specified name. Return null if the child is not found.
* Node#getChildElement(String childName)
*/
public static Node child(Node parent, String childName) {
for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
if ((child.getNodeType() == Node.ELEMENT_NODE)
&& child.getNodeName().equals(childName)) {
return child;
}
}
return null;
}
/**
* Return all the child element nodes of the specified node.
* Node#getChildElements()
*/
public static Node[] children(Node node) {
NodeList children = node.getChildNodes();
int len = children.getLength();
List result = new ArrayList(len);
for (int i = 0; i < len; i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
result.add(child);
}
}
return (Node[]) result.toArray(new Node[result.size()]);
}
/**
* Return all the child element nodes of the specified node
* with the specified name.
* Node#getChildElements(String childName)
*/
public static Node[] children(Node node, String childName) {
NodeList children = node.getChildNodes();
int len = children.getLength();
List result = new ArrayList(len);
for (int i = 0; i < len; i++) {
Node child = children.item(i);
if ((child.getNodeType() == Node.ELEMENT_NODE)
&& child.getNodeName().equals(childName)) {
result.add(child);
}
}
return (Node[]) result.toArray(new Node[result.size()]);
}
/**
* Return the text content of the specified node.
* Throw an exception if the node is not a "simple" node.
* Node#getTextContent()
*/
public static String textContent(Node node) {
NodeList children = node.getChildNodes();
// <foo></foo> or <foo/>
if (children.getLength() == 0) {
return "";
}
// <foo>bar</foo>
if (children.getLength() == 1) {
Node child = children.item(0);
if (child.getNodeType() == Node.TEXT_NODE) {
return node.getFirstChild().getNodeValue();
}
}
// if this is not a "simple" node, throw an exception
throw new IllegalArgumentException(node.getNodeName());
}
/**
* Return the text content of the specified child node.
* The child node must exist (or you will get a NPE).
*
* For example, given the following XML:
*
* <parent>
* <child>Charlie</child>
* </parent>
*
* XMLTools.childTextContent(parentNode, "child")
* will return "Charlie".
* Node#getChildTextContent(String childName)
*/
public static String childTextContent(Node parent, String childName) {
return textContent(child(parent, childName));
}
/**
* Return the text content of the specified child node.
* If the child node does not exist, return the specified default value.
* Node#getChildTextContent(String childName, String defaultValue)
*/
public static String childTextContent(Node parent, String childName, String defaultValue) {
Node child = child(parent, childName);
if (child == null) {
return defaultValue;
}
return textContent(child);
}
/**
* Return the int content of the specified child node.
* The child node must exist (or you will get a NPE).
* Node#getChildIntContent(String childName)
*/
public static int childIntContent(Node parent, String childName) {
return convertToInt(textContent(child(parent, childName)));
}
/**
* Return the int content of the specified child node.
* If the child node does not exist, return the specified default value.
* Node#getChildIntContent(String childName, int defaultValue)
*/
public static int childIntContent(Node parent, String childName, int defaultValue) {
Node child = child(parent, childName);
if (child == null) {
return defaultValue;
}
return convertToInt(textContent(child));
}
/**
* Convert the specified string to an int.
*/
private static int convertToInt(String string) {
return Integer.parseInt(string);
}
/**
* Return the boolean content of the specified child node.
* The child node must exist (or you will get a NPE).
* Node#getChildBooleanContent(String childName)
*/
public static boolean childBooleanContent(Node parent, String childName) {
return convertToBoolean(textContent(child(parent, childName)));
}
/**
* Return the boolean content of the specified child node.
* If the child node does not exist, return the specified default value.
* Node#getChildBooleanContent(String childName, boolean defaultValue)
*/
public static boolean childBooleanContent(Node parent, String childName, boolean defaultValue) {
Node child = child(parent, childName);
if (child == null) {
return defaultValue;
}
return convertToBoolean(textContent(child));
}
/**
* Convert the specified string to a boolean.
*/
private static boolean convertToBoolean(String string) {
String s = string.toLowerCase();
if (s.equals("t") || s.equals("true") || s.equals("1")) {
return true;
}
if (s.equals("f") || s.equals("false") || s.equals("0")) {
return false;
}
throw new IllegalArgumentException(string);
}
// ********** writing **********
/**
* Build and return a new document. Once the document has been
* built, it can be printed later by calling XMLTools.print(Document, File)
* or XMLTools.print(Document, OutputStream).
*/
public static Document newDocument() {
return documentBuilder().newDocument();
}
/**
* Add a simple text node with the specified name and text
* to the specified parent node.
* Node#addSimpleTextNode(String childName, String text)
*/
public static void addSimpleTextNode(Node parent, String childName, String text) {
Node child = parent.getOwnerDocument().createElement(childName);
Node childTextNode = parent.getOwnerDocument().createTextNode(text);
child.appendChild(childTextNode);
parent.appendChild(child);
}
/**
* Add a simple text node with the specified name and text
* to the specified parent node. If the text equals the default
* value, do not add the simple text node at all.
* Node#addSimpleTextNode(String childName, String text, String defaultValue)
*/
public static void addSimpleTextNode(Node parent, String childName, String text, String defaultValue) {
if ( ! text.equals(defaultValue)) {
addSimpleTextNode(parent, childName, text);
}
}
/**
* Add a simple text node with the specified name and numeric text
* to the specified parent node.
* Node#addSimpleTextNode(String childName, int text)
*/
public static void addSimpleTextNode(Node parent, String childName, int text) {
addSimpleTextNode(parent, childName, String.valueOf(text));
}
/**
* Add a simple text node with the specified name and numeric text
* to the specified parent node. If numeric text equals the default
* value, do not add the simple text node at all.
* Node#addSimpleTextNode(String childName, int text, int defaultValue)
*/
public static void addSimpleTextNode(Node parent, String childName, int text, int defaultValue) {
if (text != defaultValue) {
addSimpleTextNode(parent, childName, text);
}
}
/**
* Add a simple text node with the specified name and boolean text
* to the specified parent node.
* Node#addSimpleTextNode(String childName, boolean text)
*/
public static void addSimpleTextNode(Node parent, String childName, boolean text) {
addSimpleTextNode(parent, childName, String.valueOf(text));
}
/**
* Add a simple text node with the specified name and boolean text
* to the specified parent node. If the boolean text equals the default
* value, do not add the simple text node at all.
* Node#addSimpleTextNode(String childName, boolean text, boolean defaultValue)
*/
public static void addSimpleTextNode(Node parent, String childName, boolean text, boolean defaultValue) {
if (text != defaultValue) {
addSimpleTextNode(parent, childName, text);
}
}
/**
* Add a list of simple text nodes with the specified name and text
* to the specified parent node's children node.
*
* For example, the following call:
* XMLTools.addSimpleTextNodes(parentNode, "children", "child", new String[] {"foo", "bar", "baz"})
* will generate the following XML:
*
* <parent>
* ...
* <children>
* <child>foo</child>
* <child>bar</child>
* <child>baz</child>
* </children>
* </parent>
*
*
* will return a list of three "child" nodes.
* Node#addSimpleTextNodes(String childrenName, String childName, String[] childrenTexts)
*/
public static void addSimpleTextNodes(Node parent, String childrenName, String childName, String[] childrenTexts) {
Node childrenNode = parent.getOwnerDocument().createElement(childrenName);
parent.appendChild(childrenNode);
int len = childrenTexts.length;
for (int i = 0; i < len; i++) {
addSimpleTextNode(childrenNode, childName, childrenTexts[i]);
}
}
/**
* @see javax.xml.transform.TransformerFactory#newInstance() for
* documentation on how the implementation class is determined
*/
private static synchronized TransformerFactory transformerFactory() {
if (transformerFactory == null) {
transformerFactory = TransformerFactory.newInstance();
}
return transformerFactory;
}
private static synchronized Transformer transformer() {
if (transformer == null) {
try {
transformer = transformerFactory().newTransformer();
} catch (TransformerConfigurationException ex) {
throw new RuntimeException(ex);
}
try {
transformer.setOutputProperty("indent", "yes");
transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "3");
} catch (IllegalArgumentException ex) {
// ignore exception - the output will still be valid XML, it just won't be very user-friendly
}
}
return transformer;
}
/**
* Print the specified source to the specified result.
*/
public static synchronized void print(Source source, Result result) {
try {
transformer().transform(source, result);
} catch (TransformerException ex) {
throw new RuntimeException(ex);
}
}
/**
* Print the specified document to the specified output stream.
* Document#print(OutputStream outputStream)
*/
public static void print(Document document, OutputStream outputStream) {
print(new DOMSource(document), new StreamResult(outputStream));
}
/**
* Print the previously built document to the specified file.
* Document#print(File file)
*/
public static void print(Document document, File file) {
OutputStream outputStream;
try {
outputStream = new BufferedOutputStream(new FileOutputStream(file), 8192); // 8KB
} catch (FileNotFoundException ex) {
throw new RuntimeException(ex);
}
print(document, outputStream);
try {
outputStream.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
// ********** constructor **********
/**
* Suppress default constructor, ensuring non-instantiability.
*/
private XMLTools() {
super();
throw new UnsupportedOperationException();
}
}