Java tutorial
/*BEGIN_COPYRIGHT_BLOCK * * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software is Open Source Initiative approved Open Source Software. * Open Source Initative Approved is a trademark of the Open Source Initiative. * * This file is part of DrJava. Download the current version of this project * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/ * * END_COPYRIGHT_BLOCK*/ import org.w3c.dom.*; import org.xml.sax.InputSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.*; import java.util.*; /** * XML configuration management. * <p/> * This class uses DOM paths of a specific form to refer to nodes in the XML document. * Consider this XML structure: * <foo a="foo.a"> * <bar>abc</bar> * <fum fee="xyz">def</fum> * </foo> * The path "foo/bar" refers to the value "abc". * The path "foo/fum" refers to the value "def". * If this form is used, there may be only #text or #comment nodes in the node. All #text nodes will be * concatenated and then stripped of whitespace at the beginning and the end. * The path "foo/fum.fee" refers to the value "xyz". * The path "foo.a" refers to the value "foo.a". * * When using getMultiple, any node or attribute name can be substituted with "*" to get all elements: * The path "foo/*" returns both the value "abc" and "def". * @author Mathias Ricken */ public class XMLConfig { /** Newline string. */ public static final String NL = System.getProperty("line.separator"); /** XML document. */ private Document _document; /** XMLConfig to delegate to, or null. */ private XMLConfig _parent = null; /** Node where this XMLConfig starts if delegation is used, or null. */ private Node _startNode = null; /** Creates an empty configuration. */ public XMLConfig() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); _document = builder.newDocument(); // Create from whole cloth // NOTE: not 1.4 compatible -- _document.setXmlStandalone(true); } catch (ParserConfigurationException e) { e.printStackTrace(); } } /** Creates a configuration from an input stream. * @param is input stream */ public XMLConfig(InputStream is) { init(new InputSource(is)); } /** Creates a configuration from a reader. * @param r reader */ public XMLConfig(Reader r) { init(new InputSource(r)); } /** Creates a configuration that is a part of another configuration, starting at the specified node. * @param parent the configuration that contains this part * @param node the node in the parent configuration where this part starts */ public XMLConfig(XMLConfig parent, Node node) { if ((parent == null) || (node == null)) { throw new XMLConfigException("Error in ctor: parent or node is null"); } _parent = parent; _startNode = node; _document = null; } /** Initialize this XML configuration. * @param is the XML input source */ private void init(InputSource is) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = null; try { builder = factory.newDocumentBuilder(); _document = builder.parse(is); // NOTE: not 1.4 compatible -- _document.setXmlStandalone(true); } catch (Exception e) { throw new XMLConfigException("Error in ctor", e); } _document.normalize(); } /** Creates a configuration from a file. * @param f file */ public XMLConfig(File f) { try { init(new InputSource(new FileInputStream(f))); } catch (FileNotFoundException e) { throw new XMLConfigException("Error in ctor", e); } } /** Creates a configuration from a file name. * @param filename file name */ public XMLConfig(String filename) { try { init(new InputSource(new FileInputStream(filename))); } catch (FileNotFoundException e) { throw new XMLConfigException("Error in ctor", e); } } public boolean isDelegated() { return (_parent != null); } /** Saves configuration to an output stream * @param os output stream */ public void save(OutputStream os) { if (isDelegated()) { _parent.save(os); return; } // Prepare the DOM document for writing Source source = new DOMSource(_document); /* // Prepare the output file Result result = new StreamResult(os); */ // Write the DOM document to the file try { TransformerFactory tf = TransformerFactory.newInstance(); tf.setAttribute("indent-number", Integer.valueOf(2)); Transformer t = tf.newTransformer(); t.setOutputProperty(OutputKeys.INDENT, "yes"); t.transform(source, new StreamResult(new OutputStreamWriter(os, "utf-8"))); /* Transformer xformer = TransformerFactory.newInstance().newTransformer(); xformer.setOutputProperty(OutputKeys.INDENT, "yes"); xformer.transform(source, result); */ } catch (TransformerException e) { throw new XMLConfigException("Error in save", e); } catch (UnsupportedEncodingException e) { throw new XMLConfigException("Error in save", e); } } /** Saves configuration to a file. * @param f file */ public void save(File f) { if (isDelegated()) { _parent.save(f); return; } FileOutputStream fos = null; try { fos = new FileOutputStream(f); save(fos); } catch (FileNotFoundException e) { throw new XMLConfigException("Error in save", e); } finally { try { if (fos != null) fos.close(); } catch (IOException ioe) { /* ignore exception when closing */ } } } /** Saves configuration to a file specified by a file name. * @param filename file name */ public void save(String filename) { save(new File(filename)); } // ----- String ------ /** Returns the value as specified by the DOM path. * @param path DOM path * @return value. */ public String get(String path) { List<String> r = getMultiple(path); if (r.size() != 1) throw new XMLConfigException("Number of results != 1"); return r.get(0); } /** Returns the value as specified by the DOM path. * @param path DOM path * @param root node where the search should start * @return value. */ public String get(String path, Node root) { List<String> r = getMultiple(path, root); if (r.size() != 1) throw new XMLConfigException("Number of results != 1"); return r.get(0); } /** Returns the value as specified by the DOM path, or the default value if the value could not be found. * @param path DOM path * @param defaultVal default value in case value is not in DOM * @return value. */ public String get(String path, String defaultVal) { try { return get(path); } catch (XMLConfigException e) { return defaultVal; } } /** Returns the value as specified by the DOM path, or the default value if the value could not be found. * @param path DOM path * @param root node where the search should start * @param defaultVal default value in case value is not in DOM * @return value. */ public String get(String path, Node root, String defaultVal) { try { return get(path, root); } catch (XMLConfigException e) { return defaultVal; } } // ----- Integer ------ /** Returns the value as specified by the DOM path. * @param path DOM path * @return value. * @throws IllegalArgumentException */ public int getInt(String path) { List<String> r = getMultiple(path); if (r.size() != 1) throw new XMLConfigException("Number of results != 1"); try { return Integer.valueOf(r.get(0)); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("Not an integer value.", nfe); } } /** Returns the value as specified by the DOM path. * @param path DOM path * @param root node where the search should start * @return value. * @throws IllegalArgumentException */ public int getInt(String path, Node root) { List<String> r = getMultiple(path, root); if (r.size() != 1) throw new XMLConfigException("Number of results != 1"); try { return Integer.valueOf(r.get(0)); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("Not an integer value.", nfe); } } /** Returns the value as specified by the DOM path, or the default value if the value could not be found. * @param path DOM path * @param defaultVal default value in case value is not in DOM * @return value. * @throws IllegalArgumentException */ public int getInt(String path, int defaultVal) { try { return getInt(path); } catch (XMLConfigException e) { return defaultVal; } } /** Returns the value as specified by the DOM path, or the default value if the value could not be found. * @param path DOM path * @param root node where the search should start * @param defaultVal default value in case value is not in DOM * @return value. * @throws IllegalArgumentException */ public int getInt(String path, Node root, int defaultVal) { try { return getInt(path, root); } catch (XMLConfigException e) { return defaultVal; } } // ----- Boolean ------ /** Returns the value as specified by the DOM path. * @param path DOM path * @return value. * @throws IllegalArgumentException */ public boolean getBool(String path) { List<String> r = getMultiple(path); if (r.size() != 1) throw new XMLConfigException("Number of results != 1"); String s = r.get(0).toLowerCase().trim(); if ((s.equals("true")) || (s.equals("yes")) || (s.equals("on"))) return true; if ((s.equals("false")) || (s.equals("no")) || (s.equals("off"))) return false; throw new IllegalArgumentException("Not a Boolean vlaue."); } /** Returns the value as specified by the DOM path. * @param path DOM path * @param root node where the search should start * @return value. * @throws IllegalArgumentException */ public boolean getBool(String path, Node root) { List<String> r = getMultiple(path, root); if (r.size() != 1) throw new XMLConfigException("Number of results != 1"); String s = r.get(0).toLowerCase().trim(); if ((s.equals("true")) || (s.equals("yes")) || (s.equals("on"))) return true; if ((s.equals("false")) || (s.equals("no")) || (s.equals("off"))) return false; throw new IllegalArgumentException("Not a Boolean vlaue."); } /** Returns the value as specified by the DOM path, or the default value if the value could not be found. * @param path DOM path * @param defaultVal default value in case value is not in DOM * @return value. * @throws IllegalArgumentException */ public boolean getBool(String path, boolean defaultVal) { try { return getBool(path); } catch (XMLConfigException e) { return defaultVal; } } /** Returns the value as specified by the DOM path, or the default value if the value could not be found. * @param path DOM path * @param root node where the search should start * @param defaultVal default value in case value is not in DOM * @return value. * @throws IllegalArgumentException */ public boolean getBool(String path, Node root, boolean defaultVal) { try { return getBool(path, root); } catch (XMLConfigException e) { return defaultVal; } } // ----- Other ----- /** Returns the value as specified by the DOM path. * @param path DOM path * @return list of values. */ public List<String> getMultiple(String path) { if (isDelegated()) { return getMultiple(path, _startNode); } return getMultiple(path, _document); } /** Returns the value as specified by the DOM path. * @param path DOM path * @param root node where the search should start * @return list of values. */ public List<String> getMultiple(String path, Node root) { List<Node> accum = getNodes(path, root); List<String> strings = new LinkedList<String>(); for (Node n : accum) { if (n instanceof Attr) { strings.add(n.getNodeValue()); } else { Node child; String acc = ""; child = n.getFirstChild(); while (child != null) { if (child.getNodeName().equals("#text")) { acc += " " + child.getNodeValue(); } else if (child.getNodeName().equals("#comment")) { // ignore } else { throw new XMLConfigException("Node " + n.getNodeName() + " contained node " + child.getNodeName() + ", but should only contain #text and #comment."); } child = child.getNextSibling(); } strings.add(acc.trim()); } } return strings; } /** Returns the nodes as specified by the DOM path. * @param path DOM path * @return list of nodes. */ public List<Node> getNodes(String path) { if (isDelegated()) { return getNodes(path, _startNode); } return getNodes(path, _document); } /** Returns the nodes as specified by the DOM path. * @param path DOM path * @param root node where the search should start * @return list of nodes. */ public List<Node> getNodes(String path, Node root) { List<Node> accum = new LinkedList<Node>(); getMultipleHelper(path, root, accum, false); return accum; } /** Returns the value as specified by the DOM path. * @param path DOM path * @param n node where the search begins * @param accum accumulator * @param dotRead whether a dot has been read */ private void getMultipleHelper(String path, Node n, List<Node> accum, boolean dotRead) { int dotPos = path.indexOf('.'); boolean initialDot = (dotPos == 0); if ((path.length() > 0) && (dotPos == -1) && (!path.endsWith("/"))) { path = path + "/"; } int slashPos = path.indexOf('/'); if (dotPos != -1 && path.indexOf('.', dotPos + 1) != -1) throw new XMLConfigException( "An attribute cannot have subparts (foo.bar.fum and foo.bar/fum not allowed)"); if (dotPos != -1 && path.indexOf('/', dotPos + 1) != -1) throw new XMLConfigException( "An attribute cannot have subparts (foo.bar.fum and foo.bar/fum not allowed)"); if (((slashPos > -1) || (dotPos > -1)) && !dotRead || initialDot) { String nodeName; if ((slashPos > -1) && ((dotPos == -1) || (slashPos < dotPos))) { nodeName = path.substring(0, slashPos); path = path.substring(slashPos + 1); } else { if (slashPos > -1) { throw new XMLConfigException( "An attribute cannot have subparts (foo.bar.fum and foo.bar/fum not allowed)"); } if (!initialDot) { nodeName = path.substring(0, dotPos); path = path.substring(dotPos + 1); dotRead = true; } else { path = path.substring(1); getMultipleAddAttributesHelper(path, n, accum); return; } } Node child = n.getFirstChild(); if (nodeName.equals("*")) { while (child != null) { if (!child.getNodeName().equals("#text") && !child.getNodeName().equals("#comment")) { if (dotRead) { getMultipleAddAttributesHelper(path, child, accum); } else { getMultipleHelper(path, child, accum, false); } } child = child.getNextSibling(); } return; } else { while (child != null) { if (child.getNodeName().equals(nodeName)) { // found if (dotRead) { getMultipleAddAttributesHelper(path, child, accum); } else { getMultipleHelper(path, child, accum, false); } } child = child.getNextSibling(); } return; } } else { accum.add(n); } } private void getMultipleAddAttributesHelper(String path, Node n, List<Node> accum) { if ((path.indexOf('.') > -1) || (path.indexOf('/') > -1)) { throw new XMLConfigException( "An attribute cannot have subparts (foo.bar.fum and foo.bar/fum not allowed)"); } NamedNodeMap attrMap = n.getAttributes(); if (path.equals("*")) { for (int i = 0; i < attrMap.getLength(); ++i) { Node attr = attrMap.item(i); accum.add(attr); } } else { Node attr = attrMap.getNamedItem(path); if (attr != null) { accum.add(attr); } } } /** Set the value of the node or attribute specified by the DOM path. * @param path DOM path * @param value node or attribute value * @return the node that was created, or the parent node of the attribute if it was an attribute */ public Node set(String path, String value) { if (isDelegated()) { return set(path, value, _startNode, true); } return set(path, value, _document, true); } /** Set the value of the node or attribute specified by the DOM path. * @param path DOM path * @param value node or attribute value * @param overwrite whether to overwrite (true) or add (false) * @return the node that was created, or the parent node of the attribute if it was an attribute */ public Node set(String path, String value, boolean overwrite) { if (isDelegated()) { return set(path, value, _startNode, overwrite); } return set(path, value, _document, overwrite); } /** Set the value of the node or attribute specified by the DOM path. * @param path DOM path * @param value node or attribute value * @param n node where the search should start * @param overwrite whether to overwrite (true) or add (false) -- only applies for last node! * @return the node that was created, or the parent node of the attribute if it was an attribute */ public Node set(String path, String value, Node n, boolean overwrite) { if (isDelegated()) { return _parent.set(path, value, n, overwrite); } int dotPos = path.lastIndexOf('.'); Node node; if (dotPos == 0) { node = n; } else { node = createNode(path, n, overwrite); } if (dotPos >= 0) { Element e = (Element) node; e.setAttribute(path.substring(dotPos + 1), value); } else { node.appendChild(_document.createTextNode(value)); } return node; } /** Create the node specified by the DOM path. * @param path DOM path * @return the node that was created, or the parent node of the attribute if it was an attribute */ public Node createNode(String path) { if (isDelegated()) { return createNode(path, _startNode, true); } return createNode(path, _document, true); } /** Create the node specified by the DOM path. * @param path DOM path * @param n node where the search should start, or null for the root * @return the node that was created, or the parent node of the attribute if it was an attribute */ public Node createNode(String path, Node n) { return createNode(path, n, true); } /** Create the node specified by the DOM path. * @param path DOM path * @param n node where the search should start, or null for the root * @param overwrite whether to overwrite (true) or add (false) -- only applies for last node! * @return the node that was created, or the parent node of the attribute if it was an attribute */ public Node createNode(String path, Node n, boolean overwrite) { if (isDelegated()) { return _parent.createNode(path, n, overwrite); } if (n == null) { n = _document; } while (path.indexOf('/') > -1) { Node child = null; String nodeName = path.substring(0, path.indexOf('/')); path = path.substring(path.indexOf('/') + 1); child = n.getFirstChild(); while (child != null) { if (child.getNodeName().equals(nodeName)) { // found n = child; break; } child = child.getNextSibling(); } if (child == null) { // not found child = _document.createElement(nodeName); n.appendChild(child); n = child; } } String nodeName; if (path.indexOf('.') > -1) { nodeName = path.substring(0, path.indexOf('.')); } else { if (path.length() == 0) { throw new XMLConfigException("Cannot set node with empty name"); } nodeName = path; } Node child = null; if (nodeName.length() > 0) { if (overwrite) { child = n.getFirstChild(); while (child != null) { if (child.getNodeName().equals(nodeName)) { // found n = child; break; } child = child.getNextSibling(); } if (child == null) { child = _document.createElement(nodeName); n.appendChild(child); n = child; } } else { child = _document.createElement(nodeName); n.appendChild(child); n = child; } } if (path.indexOf('.') > -1) { if (!(n instanceof Element)) { throw new XMLConfigException( "Node " + n.getNodeName() + " should be an element so it can contain attributes"); } return n; } else { if (overwrite) { child = n.getFirstChild(); // remove all children while (child != null) { Node temp = child.getNextSibling(); n.removeChild(child); child = temp; } return n; } else { return child; } } } /** Returns a string representation of the object. * @return a string representation of the object. */ public String toString() { ByteArrayOutputStream os = new ByteArrayOutputStream(); save(os); return os.toString(); } /** Return the path of a node as it is used in XMLConfig. * @param n node * @return path */ public static String getNodePath(Node n) { if (n == null) { return ""; } String path = ""; while (n.getParentNode() != null) { path = n.getNodeName() + "/" + path; n = n.getParentNode(); } return path.substring(0, path.length() - 1); } /** Exception in XMLConfig methods. */ public static class XMLConfigException extends RuntimeException { public XMLConfigException() { super(); } public XMLConfigException(String message) { super(message); } public XMLConfigException(String message, Throwable cause) { super(message, cause); } public XMLConfigException(Throwable cause) { super(cause); } } }