XML configuration management : Properties « Development Class « Java






XML configuration management

     
/*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);
    }
  }
}

   
    
    
    
    
  








Related examples in the same category

1.Read a set of properties from the received input stream, strip off any excess white space that exists in those property values,
2.Copy a set of properties from one Property to another.
3.Sorts property list and print out each key=value pair prepended with specific indentation.
4.Sorts a property list and turns the sorted list into a string.
5.A utility class for replacing properties in strings.
6.Property Loader
7.Property access utility methods
8.Represents a persistent set of properties
9.Converts specified map to java.util.Properties
10.A java.util.Properties class that will check a file or URL for changes periodically
11.Encapsulates java.util.Properties to add java primitives and some other java classes
12.Load and save properties to files.
13.Adds new properties to an existing set of properties
14.Extracts a specific property key subset from the known properties
15.Observable Properties
16.Merge Properties Into Map
17.JDOM based XML properties
18.XML Properties
19.Converts Unicode into something that can be embedded in a java properties file
20.Create Properties from String array
21.Task to overwrite Properties
22.Gets strong-type-value property from a standard Properties
23.The properties iterator iterates over a set of enumerated properties.
24.An utility class to ease up using property-file resource bundles.
25.Sorted Properties
26.A subclass of the java.util.Properties class that must be initialized from a file on disk
27.This class contains a collection of static utility methods for creating, retrieving, saving and loading properties.
28.Simplify routine job with properties
29.Property Manager